120 lines
3.1 KiB
Dart
120 lines
3.1 KiB
Dart
import 'dart:math' as math;
|
||
|
||
import 'package:flutter/material.dart';
|
||
|
||
/// بارشِ کاغذرنگی (کانفتی) سبک و بدونِ وابستگی — برای صفحهی بردِ بازی.
|
||
class Confetti extends StatefulWidget {
|
||
final int count;
|
||
const Confetti({super.key, this.count = 60});
|
||
|
||
@override
|
||
State<Confetti> createState() => _ConfettiState();
|
||
}
|
||
|
||
class _ConfettiState extends State<Confetti> with SingleTickerProviderStateMixin {
|
||
late final AnimationController _c;
|
||
late final List<_Piece> _pieces;
|
||
|
||
static const _colors = [
|
||
Color(0xFFE9B949), // طلایی
|
||
Color(0xFF1E5FA8), // لاجوردی
|
||
Color(0xFFB81D2A), // شنگرف
|
||
Color(0xFF3FA34D), // سبز
|
||
Color(0xFFFFFFFF),
|
||
];
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
final r = math.Random();
|
||
_pieces = List.generate(widget.count, (_) {
|
||
return _Piece(
|
||
x: r.nextDouble(),
|
||
delay: r.nextDouble() * 0.5,
|
||
speed: 0.7 + r.nextDouble() * 0.6,
|
||
drift: (r.nextDouble() - 0.5) * 0.4,
|
||
size: 6 + r.nextDouble() * 7,
|
||
color: _colors[r.nextInt(_colors.length)],
|
||
rot: r.nextDouble() * math.pi,
|
||
rotSpeed: (r.nextDouble() - 0.5) * 12,
|
||
round: r.nextBool(),
|
||
);
|
||
});
|
||
_c = AnimationController(
|
||
vsync: this,
|
||
duration: const Duration(milliseconds: 2600),
|
||
)..forward();
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_c.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return IgnorePointer(
|
||
child: AnimatedBuilder(
|
||
animation: _c,
|
||
builder: (_, __) => CustomPaint(
|
||
size: Size.infinite,
|
||
painter: _ConfettiPainter(_pieces, _c.value),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
class _Piece {
|
||
final double x, delay, speed, drift, size, rot, rotSpeed;
|
||
final Color color;
|
||
final bool round;
|
||
_Piece({
|
||
required this.x,
|
||
required this.delay,
|
||
required this.speed,
|
||
required this.drift,
|
||
required this.size,
|
||
required this.color,
|
||
required this.rot,
|
||
required this.rotSpeed,
|
||
required this.round,
|
||
});
|
||
}
|
||
|
||
class _ConfettiPainter extends CustomPainter {
|
||
final List<_Piece> pieces;
|
||
final double t; // ۰..۱
|
||
_ConfettiPainter(this.pieces, this.t);
|
||
|
||
@override
|
||
void paint(Canvas canvas, Size size) {
|
||
for (final p in pieces) {
|
||
final local = ((t - p.delay) * p.speed).clamp(0.0, 1.0);
|
||
if (local <= 0) continue;
|
||
final y = -0.1 + local * 1.25;
|
||
final x = (p.x + p.drift * local) * size.width;
|
||
final py = y * size.height;
|
||
final opacity = (1.0 - local).clamp(0.0, 1.0);
|
||
final paint = Paint()..color = p.color.withValues(alpha: opacity);
|
||
|
||
canvas.save();
|
||
canvas.translate(x, py);
|
||
canvas.rotate(p.rot + p.rotSpeed * local);
|
||
if (p.round) {
|
||
canvas.drawCircle(Offset.zero, p.size / 2, paint);
|
||
} else {
|
||
canvas.drawRect(
|
||
Rect.fromCenter(center: Offset.zero, width: p.size, height: p.size * 0.6),
|
||
paint,
|
||
);
|
||
}
|
||
canvas.restore();
|
||
}
|
||
}
|
||
|
||
@override
|
||
bool shouldRepaint(covariant _ConfettiPainter old) => old.t != t;
|
||
}
|