132 lines
3.6 KiB
Dart
132 lines
3.6 KiB
Dart
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<LiquidGlassContainer> createState() => _LiquidGlassContainerState();
|
|
}
|
|
|
|
class _LiquidGlassContainerState extends State<LiquidGlassContainer>
|
|
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<void> _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,
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|