didvan-app/lib/views/widgets/glass/liquid_glass_container.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,
],
),
);
}
}