import 'dart:async'; import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; import 'glass_logic.dart'; class ShaderPainter extends CustomPainter { ShaderPainter(this.shader); final ui.FragmentShader shader; @override void paint(Canvas canvas, Size size) { final paint = Paint()..shader = shader; canvas.drawRect(Offset.zero & size, paint); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => true; } class LiquidGlassContainer extends StatefulWidget { final Widget child; final GlobalKey backgroundKey; final GlassShader shader; final double distortion; final double blur; final double dispersion; final BorderRadius? borderRadius; const LiquidGlassContainer({ super.key, required this.child, required this.backgroundKey, required this.shader, this.distortion = 1.0, this.blur = 5.0, this.dispersion = 1.0, this.borderRadius, }); @override State createState() => _LiquidGlassContainerState(); } class _LiquidGlassContainerState extends State with SingleTickerProviderStateMixin { ui.Image? _capturedImage; late Ticker _ticker; @override void initState() { super.initState(); _ticker = createTicker((_) => _captureBackground()); _ticker.start(); } @override void dispose() { _ticker.dispose(); super.dispose(); } Future _captureBackground() async { final boundaryContext = widget.backgroundKey.currentContext; if (boundaryContext == null) return; final renderObject = boundaryContext.findRenderObject(); if (renderObject is! RenderRepaintBoundary) return; try { final image = await renderObject.toImage(pixelRatio: 1.0); if (mounted) { setState(() { _capturedImage = image; }); } } catch (e) { // Ignored } } @override Widget build(BuildContext context) { Offset offsetFromBackground = Offset.zero; Size bgSize = Size.zero; final RenderBox? box = context.findRenderObject() as RenderBox?; final RenderBox? backgroundBox = widget.backgroundKey.currentContext?.findRenderObject() as RenderBox?; if (box != null && backgroundBox != null) { final absolutePos = box.localToGlobal(Offset.zero); final backgroundPos = backgroundBox.localToGlobal(Offset.zero); offsetFromBackground = absolutePos - backgroundPos; bgSize = backgroundBox.size; } return ClipRRect( borderRadius: widget.borderRadius ?? BorderRadius.zero, child: Stack( children: [ if (_capturedImage != null && widget.shader.shader != null && backgroundBox != null) Positioned.fill( child: CustomPaint( painter: ShaderPainter(widget.shader.shader!), ), ), Builder(builder: (ctx) { if (_capturedImage != null && widget.shader.shader != null && backgroundBox != null) { widget.shader.updateUniforms( widgetSize: (box?.size ?? Size.zero), textureSize: bgSize, widgetOffset: offsetFromBackground, distortion: widget.distortion, blur: widget.blur, dispersion: widget.dispersion, texture: _capturedImage!, ); } return const SizedBox.shrink(); }), widget.child, ], ), ); } }