Last active
October 12, 2025 19:26
-
-
Save PlugFox/87948692684838afc9efd839e83d0903 to your computer and use it in GitHub Desktop.
Revisions
-
PlugFox revised this gist
Jun 24, 2025 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,5 +1,5 @@ ``` project └───packages └───ui ├───lib -
PlugFox revised this gist
Jun 24, 2025 . 1 changed file with 3 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -5,7 +5,8 @@ ├───lib │ ├───shaders │ │ └─── shimmer.frag │ ├───src │ │ └─── shimmer.dart │ └───ui.dart └─── pubspec.yaml ``` -
PlugFox revised this gist
Jun 24, 2025 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,3 +1,4 @@ ``` . └───packages └───ui @@ -7,3 +8,4 @@ │ └───src │ └─── shimmer.dart └─── pubspec.yaml ``` -
PlugFox created this gist
Jun 24, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,9 @@ . └───packages └───ui ├───lib │ ├───shaders │ │ └─── shimmer.frag │ └───src │ └─── shimmer.dart └─── pubspec.yaml This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,15 @@ name: ui resolution: workspace environment: sdk: ">=3.7.0 <4.0.0" flutter: ">=3.29.3" dependencies: flutter: sdk: flutter flutter: shaders: - packages/ui/shaders/shimmer.frag This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,308 @@ import 'dart:developer' as developer; import 'dart:ui' as ui; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/widgets.dart'; /// {@template shimmer} /// A widget that creates a shimmering effect similar /// to a moving highlight or reflection. /// This is commonly used as a placeholder or loading indicator. /// {@endtemplate} /// {@category shaders} class Shimmer extends LeafRenderObjectWidget { /// Creates a shimmer effect with the specified [highlight], [speed], [size], and [radius]. /// {@macro stepper} const Shimmer({ this.highlight = const ui.Color(0xFFEEEEEE), this.background = const Color(0xFFFFFFFF), this.speed = 1.0, this.size, this.radius, this.stripe, super.key, }); /// The color of the shimmer effect. /// Defaults to a light color, slightly off-white. /// This color is used to create the shimmering highlight. final Color highlight; /// The background color of the shimmer effect. /// Better to use a background color of the parent widget. final Color background; /// The speed of the shimmer effect, where 1.0 is the default speed. final double speed; /// The size of the shimmer effect. /// If null, it will use the size of the parent widget. final Size? size; /// The radius for rounded corners of the shimmer effect. /// If null, it will not apply any rounded corners. final Radius? radius; /// Size of the stripe in the shimmer effect. /// One of the best choice is between 0.5 .. 1.0 final double? stripe; @override RenderObject createRenderObject(BuildContext context) => ShimmerRenderObject( highlight: highlight, // Shimmer (primary) highlight color for the shimmer effect background: background, // Background (secondary) color for the shimmer effect speed: speed, // Speed of the shimmer effect multiplier size: size, // Size of the shimmer effect radius: radius, // Radius for rounded corners stripe: stripe, // Size of the stripe in the shimmer effect ); @override void updateRenderObject(BuildContext context, covariant ShimmerRenderObject renderObject) => renderObject.update( highlight: highlight, // Update shimmer (primary) highlight color for the shimmer effect background: background, // Update background (secondary) color for the shimmer effect speed: speed, // Speed of the shimmer effect multiplier size: size, // Size of the shimmer effect radius: radius, // Radius for rounded corners stripe: stripe, // Size of the stripe in the shimmer effect ); } class ShimmerRenderObject extends RenderBox with WidgetsBindingObserver { ShimmerRenderObject({ required Color highlight, required Color background, required double speed, required Size? size, required Radius? radius, required double? stripe, }) : _highlight = highlight, _background = background, _speed = speed, _size = size, _radius = radius, _stripe = stripe, _paint = Paint() { _paint ..color = background ..style = PaintingStyle.fill ..blendMode = BlendMode.srcOver ..filterQuality = FilterQuality.low ..isAntiAlias = true; } Color _highlight; Color _background; double _speed; Size? _size; Radius? _radius; double? _stripe; final Paint _paint; /// Animation vsync ticker for the shimmer effect. Ticker? _animationTicker; void update({ required Color highlight, required Color background, required double speed, required Size? size, required Radius? radius, required double? stripe, }) { if (size != _size) { markNeedsLayout(); } _highlight = highlight; _background = background; _speed = speed; _size = size; _radius = radius; _stripe = stripe; _paint.color = background; markNeedsPaint(); } Size _$size = Size.zero; @override Size get size => _$size; @override set size(Size value) { final prev = super.hasSize ? super.size : null; super.size = value; if (prev == value) return; _$size = value; } int _activeFlag = 0; @override bool get isRepaintBoundary => false; @override bool get alwaysNeedsCompositing => false; @override bool get sizedByParent => false; @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); const lifecycleFlag = 1 << 0; // Flag to indicate lifecycle changes if (state == AppLifecycleState.resumed) { _activeFlag &= ~lifecycleFlag; // Clear the active flag when the app is resumed } else { _activeFlag |= lifecycleFlag; // Set the active flag for other states } } @override void attach(PipelineOwner owner) { super.attach(owner); _activeFlag &= ~(1 << 1); // Clear the active flag when attached WidgetsBinding.instance.addObserver(this); // Load the shader if it hasn't been loaded yet _ShimmerShaderManager.setShader(_paint); _animationTicker = Ticker(_onTick)..start(); } Duration _elapsed = Duration.zero; void _onTick(Duration elapsed) { _elapsed = elapsed; if (_activeFlag != 0) return; // Only update if the active flag is set // Perform the shimmer effect update logic here markNeedsPaint(); } @override @protected void detach() { super.detach(); _activeFlag |= (1 << 1); // Set the active flag when detached _animationTicker?.dispose(); WidgetsBinding.instance.removeObserver(this); } @override bool hitTestSelf(Offset position) => false; @override bool hitTestChildren(BoxHitTestResult result, {required Offset position}) => false; @override bool hitTest(BoxHitTestResult result, {required Offset position}) => false; @override Size computeDryLayout(BoxConstraints constraints) => switch (_size) { Size size => constraints.constrain(size), _ => constraints.biggest, }; @override void performLayout() { size = computeDryLayout(constraints); } @override void performResize() { size = computeDryLayout(constraints); } @override void paint(PaintingContext context, Offset offset) { final size = this.size; if (size.isEmpty) return; // No need to paint if size is empty final canvas = context.canvas ..save() ..translate(offset.dx, offset.dy); // Clip the canvas to the size and radius if provided if (_radius case Radius radius when radius != Radius.zero) { canvas.clipRRect(RRect.fromRectAndRadius(Offset.zero & size, _radius ?? Radius.zero)); } else { canvas.clipRect(Offset.zero & size); } if (_paint.shader case ui.FragmentShader shader) { // If the shader is available, apply it to the paint final seed = _elapsed.inMicroseconds * _speed / 200000; _paint.shader = shader ..setFloat(0, size.width) ..setFloat(1, size.height) ..setFloat(2, seed) ..setFloat(3, _highlight.r) ..setFloat(4, _highlight.g) ..setFloat(5, _highlight.b) ..setFloat(6, _highlight.a) ..setFloat(7, _background.r) ..setFloat(8, _background.g) ..setFloat(9, _background.b) ..setFloat(10, _background.a) ..setFloat(11, _stripe ?? 0.75); canvas.drawRect(Offset.zero & size, _paint); } else { // If the shader is not available, draw a solid color canvas.drawRect(Offset.zero & size, _paint); } canvas.restore(); } } abstract final class _ShimmerShaderManager { static ui.FragmentProgram? _$fragmentProgram; static final Future<ui.FragmentProgram?> _$loadfragmentProgramOnce = _$loadfragmentProgram(); static Future<ui.FragmentProgram?> _$loadfragmentProgram() async { const asset = 'packages/ui/shaders/shimmer.frag'; try { final program = _$fragmentProgram = await ui.FragmentProgram.fromAsset(asset).timeout(const Duration(seconds: 5)); return program; } on UnsupportedError { return null; // Thats fine for HTML Renderer and unsupported platforms. } catch (e, s) { developer.log('Failed to load shader: $e', error: e, stackTrace: s, name: 'ui', level: 700); FlutterError.reportError( FlutterErrorDetails( exception: e, stack: s, library: 'ui', context: ErrorDescription('Failed to load shimmer shader'), ), ); return null; } } /// The shader to be used for the shimmer effect to be applied to the paint. static void setShader(Paint paint) { if (_$fragmentProgram case ui.FragmentProgram program) { paint ..shader = program.fragmentShader() ..blendMode = BlendMode.src ..filterQuality = FilterQuality.low ..isAntiAlias = false; } else { _$loadfragmentProgramOnce.then((program) { if (program == null) return; paint ..shader = program.fragmentShader() ..blendMode = BlendMode.src ..filterQuality = FilterQuality.low ..isAntiAlias = false; }).ignore(); } } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,29 @@ #version 460 core #define SHOW_GRID #include <flutter/runtime_effect.glsl> uniform vec2 u_size; // size of the shape uniform float u_seed; // shader playback time (in seconds) uniform vec4 u_color_highlight; // line color of the shape uniform vec4 u_color_background; // background color of the shape uniform float u_stripe; // width of the stripes out vec4 fragColor; void main() { // Direction vector for 30 degrees angle (values are precalculated) vec2 direction = vec2(0.866, 0.5); // Calculate normalized coordinates vec2 normalizedCoords = gl_FragCoord.xy / u_size; // Generate a smooth moving wave based on time and coordinates float waveRaw = 0.5 * (1.0 + sin(u_seed - dot(normalizedCoords, direction) * u_stripe * 3.1415)); float wave = smoothstep(0.0, 1.0, waveRaw); // Use the wave to interpolate between the background color and line color vec4 color = mix(u_color_background, u_color_highlight, wave); fragColor = color; }