Files
iroute/packages/idraw/lib/p19/s02.dart
2023-12-22 10:01:10 +08:00

473 lines
10 KiB
Dart

import 'dart:convert';
import 'dart:math';
import 'dart:ui';
// import 'dart:ui' as ui;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
/// create by 张风捷特烈 on 2020/11/5
/// contact me by email 1981462002@qq.com
/// 说明:
class Paper extends StatelessWidget {
const Paper({super.key});
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
alignment: Alignment.center,
child: Screen(),
);
}
}
final easingDelayDuration = Duration(seconds: 15);
class BgFx extends ClockFx {
BgFx({required Size size, required DateTime time})
: super(
size: size,
time: time,
numParticles: 120,
);
@override
void tick(Duration duration) {
var secFrac = 1 - (DateTime.now().millisecond / 1000);
var vecSpeed = duration.compareTo(easingDelayDuration) > 0
? max(.2, Curves.easeInOutSine.transform(secFrac))
: 1;
particles.forEach((p) {
p.y -= p.vy * vecSpeed;
if (p.y > height || p.y < 0 || p.life == 0) {
_activateParticle(p);
}
});
super.tick(duration);
}
void _activateParticle(Particle p) {
var xRnd = Rnd.getDouble(0, width / 5);
p.x = Rnd.getBool() ? width - xRnd : 0 + xRnd;
p.y = Rnd.getDouble(0, height);
p.a = Rnd.ratio > .95 ? Rnd.getDouble(.6, .8) : Rnd.getDouble(.08, .4);
p.isFilled = Rnd.getBool();
p.size = Rnd.getDouble(height / 20, height / 5);
p.life = 1;
p.vx = 0;
p.vy = Rnd.getDouble(-3, 3);
double v = Rnd.getDouble(.1, .5);
p.vx = 0;
p.vy *= v;
}
}
class ClockBgParticlePainter extends CustomPainter {
ClockFx fx;
ClockBgParticlePainter({required this.fx}) : super(repaint: fx);
@override
void paint(Canvas canvas, Size size) {
canvas.clipRect(Offset.zero&size);
fx.particles.forEach((p) {
var pos = Offset(p.x, p.y);
var paint = Paint()
..color = p.color.withAlpha((255 * p.a).floor())
..strokeWidth = p.size * .2
..style = p.isFilled ? PaintingStyle.fill : PaintingStyle.stroke;
if (p.isFilled) {
var rect = Rect.fromCenter(
center: pos,
width: p.size,
height: p.size,
);
canvas.drawRect(rect, paint);
} else {
canvas.drawCircle(pos, p.size / 1.2, paint);
}
});
}
@override
bool shouldRepaint(_) => false;
}
final Color transparent = Color.fromARGB(0, 0, 0, 0);
abstract class ClockFx with ChangeNotifier {
/// 可用画板宽度
double width = 0;
/// 可用画板盖度
double height = 0;
/// 宽高最小值
double sizeMin = 0;
/// 画板中心
Offset center = Offset.zero;
/// 产生新粒子的区域
Rect spawnArea = Rect.zero;
/// 绘制时的颜色集
Palette? palette;
/// 粒子集合
late List<Particle> particles;
/// 最大粒子数
int numParticles;
/// Date and time used for rendering time-specific effects.
DateTime time;
ClockFx({
required Size size,
required this.time,
this.numParticles = 5000,
}) {
this.time = time;
particles = List.filled(numParticles, Particle());
// growableList.length = 3;
palette = Palette(components: [transparent, transparent]);
setSize(size);
}
/// Initializes the particle effect by resetting all the particles and assigning a random color from the palette.
void init() {
if (palette == null) return;
for (int i = 0; i < numParticles; i++) {
var color = Rnd.getItem(palette!.components);
particles[i] = Particle(color: color);
resetParticle(i);
}
}
/// Sets the palette used for coloring.
void setPalette(Palette palette) {
this.palette = palette;
var colors = palette.components.sublist(1);
var accentColor = colors[colors.length - 1];
particles.where((p) => p != null).forEach((p) => p.color =
p.type == ParticleType.noise ? Rnd.getItem(colors) : accentColor);
}
/// Sets the time used for time-specific effects.
void setTime(DateTime time) {
this.time = time;
}
/// Sets the canvas size and updates dependent values.
void setSize(Size size) {
width = size.width;
height = size.height;
sizeMin = min(width, height);
center = Offset(width / 2, height / 2);
spawnArea = Rect.fromLTRB(
center.dx - sizeMin / 100,
center.dy - sizeMin / 100,
center.dx + sizeMin / 100,
center.dy + sizeMin / 100,
);
init();
}
/// Resets a particle's values.
Particle resetParticle(int i) {
Particle p = particles[i];
p.size = p.a = p.vx = p.vy = p.life = p.lifeLeft = 0;
p.x = center.dx;
p.y = center.dy;
return p;
}
void tick(Duration duration) {
notifyListeners();
}
}
/// Holds a list of colors.
class Palette {
/// The palette's color members. All unique.
List<Color> components;
Palette({required this.components});
/// Creates a new palette from JSON.
factory Palette.fromJson(List<dynamic> json) {
var components = json.map((c) => Color(int.tryParse(c)!)).toList();
return Palette(components: components);
}
}
class Screen extends StatefulWidget {
@override
_ScreenState createState() => _ScreenState();
}
class _ScreenState extends State<Screen> with SingleTickerProviderStateMixin{
late BgFx _bgFx;
late Ticker _ticker;
Palette? _palette;
@override
void initState() {
_bgFx = BgFx(
size: Size(300, 200),
time: DateTime.now(),
);
_init();
super.initState();
}
@override
void dispose() {
_ticker.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(child: Container(
color: Color(0xff98D9B6).withAlpha(22),
width: 300,
height: 200,
child: _buildBgBlurFx()));
}
void _init() async{
List<Palette> palettes = await _loadPalettes();
_palette = palettes[4];
if(_palette==null) return;
_bgFx.setPalette(_palette!);
_ticker = createTicker(_tick)..start();
setState(() {
});
}
Future<List<Palette>> _loadPalettes() async {
String data = await DefaultAssetBundle.of(context).loadString("assets/palettes.json");
var palettes = json.decode(data) as List;
return palettes.map((p) => Palette.fromJson(p)).toList();
}
Widget _buildBgBlurFx() {
return
RepaintBoundary(
child: Stack(
children: <Widget>[
CustomPaint(
painter: ClockBgParticlePainter(
fx: _bgFx,
),
child: Container(),
),
BackdropFilter(
filter: ImageFilter.blur(
sigmaX: 300 * .05,
sigmaY: 0,
),
// filter: ImageFilter.blur(sigmaX: 0, sigmaY: 0),
child: Container(child: Text(" ")),
),
],
),
);
}
void _tick(Duration duration) {
if (DateTime.now().second % 5 == 0) _bgFx.tick(duration);
}
}
class Particle {
/// 粒子 x 位置
double x;
/// 粒子 y 位置
double y;
/// 粒子角度(弧度制)
double a;
/// 粒子水平速度
double vx;
/// 粒子垂直速度
double vy;
/// 距画布中心距离
double dist;
/// 到画布中心距离的百分比 (0-1).
double distFrac;
/// 粒子大小
double size;
/// 粒子生命长度 (0-1).
double life;
/// 粒子右侧粒子存货时间 (0-1).
double lifeLeft;
/// I粒子是否填充.
bool isFilled;
/// 粒子是否需要 "speed marks".
bool isFlowing;
/// 粒子颜色.
Color color;
/// 粒子描述
int distribution;
/// 粒子类型
ParticleType type;
Particle({
this.x = 0,
this.y = 0,
this.a = 0,
this.vx = 0,
this.vy = 0,
this.dist = 0,
this.distFrac = 0,
this.size = 0,
this.life = 0,
this.lifeLeft = 0,
this.isFilled = false,
this.isFlowing = false,
this.color = Colors.black,
this.distribution = 0,
this.type = ParticleType.noise,
});
}
enum ParticleType {
hour,
minute,
noise,
}
class Rnd {
static int _seed = DateTime.now().millisecondsSinceEpoch;
static Random random = Random(_seed);
static set seed(int val) => random = Random(_seed = val);
static int get seed => _seed;
/// Gets the next double.
static get ratio => random.nextDouble();
/// Gets a random int between [min] and [max].
static int getInt(int min, int max) {
return min + random.nextInt(max - min);
}
/// Gets a random double between [min] and [max].
static double getDouble(double min, double max) {
return min + random.nextDouble() * (max - min);
}
/// Gets a random boolean with chance [chance].
static bool getBool([double chance = 0.5]) {
return random.nextDouble() < chance;
}
/// Randomize the positions of items in a list.
static List shuffle(List list) {
for (int i = 0, l = list.length; i < l; i++) {
int j = random.nextInt(l);
if (j == i) {
continue;
}
dynamic item = list[j];
list[j] = list[i];
list[i] = item;
}
return list;
}
/// Randomly selects an item from a list.
static dynamic getItem(List list) {
return list[random.nextInt(list.length)];
}
/// Gets a random palette from a list of palettes and sorts its' colors by luminance.
///
/// Given if [dark] or not, this method makes sure the luminance of the background color is valid.
static Palette getPalette(List<Palette?> palettes, bool dark) {
Palette? result;
while (result == null) {
Palette palette = Rnd.getItem(palettes);
List<Color> colors = Rnd.shuffle(palette.components).map<Color>((e) => e).toList();
var luminance = colors[0].computeLuminance();
if (dark ? luminance <= .1 : luminance >= .1) {
var lumDiff = colors
.sublist(1)
.asMap()
.map(
(i, color) => MapEntry(
i,
[i, (luminance - color.computeLuminance()).abs()],
),
)
.values
.toList();
lumDiff.sort((List<num> a, List<num> b) {
return a[1].compareTo(b[1]);
});
List<Color> sortedColors =
lumDiff.map((d) => colors[(d[0] + 1).toInt()]).toList();
result = Palette(
components: [colors[0]] + sortedColors,
);
}
}
return result;
}
}