Line data Source code
1 : import 'dart:math';
2 : import 'dart:ui';
3 :
4 : import 'package:flutter/animation.dart';
5 :
6 : import '../../extensions.dart';
7 : import '../components/component.dart';
8 : import '../components/particle_component.dart';
9 : import '../timer.dart';
10 : import 'accelerated_particle.dart';
11 : import 'composed_particle.dart';
12 : import 'moving_particle.dart';
13 : import 'rotating_particle.dart';
14 : import 'scaled_particle.dart';
15 : import 'translated_particle.dart';
16 :
17 : /// A function which returns a [Particle] when called.
18 : typedef ParticleGenerator = Particle Function(int);
19 :
20 : /// Base class implementing common behavior for all the particles.
21 : ///
22 : /// Intention is to follow the same "Extreme Composability" style as seen across
23 : /// the whole Flutter framework. Each type of particle implements some
24 : /// particular behavior which then could be nested and combined to create
25 : /// the experience you are looking for.
26 : abstract class Particle {
27 : /// Generates a given amount of particles and then combining them into one
28 : /// single [ComposedParticle].
29 : ///
30 : /// Useful for procedural particle generation.
31 0 : static Particle generate({
32 : int count = 10,
33 : required ParticleGenerator generator,
34 : double? lifespan,
35 : }) {
36 0 : return ComposedParticle(
37 : lifespan: lifespan,
38 0 : children: List<Particle>.generate(count, generator),
39 : );
40 : }
41 :
42 : /// Internal timer defining how long this [Particle] will live.
43 : ///
44 : /// [Particle] will be marked for removal when this timer is over.
45 : Timer? _timer;
46 :
47 : /// Stores desired lifespan of the particle in seconds.
48 : late double _lifespan;
49 :
50 : /// Will be set to true by [update] when this [Particle] reaches the end of
51 : /// its lifespan.
52 : bool _shouldBeRemoved = false;
53 :
54 : /// Construct a new [Particle].
55 : ///
56 : /// The [lifespan] is how long this [Particle] will live in seconds, with
57 : /// microsceond precision.
58 0 : Particle({
59 : double? lifespan,
60 : }) {
61 0 : setLifespan(lifespan ?? .5);
62 : }
63 :
64 : /// This method will return true as soon as the particle reaches the end of
65 : /// its lifespan.
66 : ///
67 : /// It will then be ready to be removed by a wrapping container.
68 0 : bool get shouldRemove => _shouldBeRemoved;
69 :
70 : /// Getter which should be used by subclasses to get overall progress.
71 : ///
72 : /// Also allows to substitute progress with other values, for example adding
73 : /// easing as in CurvedParticle.
74 0 : double get progress => _timer?.progress ?? 0.0;
75 :
76 : /// Should render this [Particle] to given [Canvas].
77 : ///
78 : /// Default behavior is empty, so that it's not required to override this in
79 : /// a [Particle] that renders nothing and serve as a behavior container.
80 0 : void render(Canvas canvas) {}
81 :
82 : /// Updates the [_timer] of this [Particle].
83 0 : void update(double dt) {
84 0 : _timer?.update(dt);
85 : }
86 :
87 : /// A control method allowing a parent of this [Particle] to pass down it's
88 : /// lifespan.
89 : ///
90 : /// Allows to only specify desired lifespan once, at the very top of the
91 : /// [Particle] tree which then will be propagated down using this method.
92 : ///
93 : /// See `SingleChildParticle` or [ComposedParticle] for details.
94 0 : void setLifespan(double lifespan) {
95 : // TODO: Maybe make it into a setter/getter?
96 0 : _lifespan = lifespan;
97 0 : _timer?.stop();
98 0 : _timer = Timer(lifespan, callback: () => _shouldBeRemoved = true)..start();
99 : }
100 :
101 : /// Wraps this particle with a [TranslatedParticle].
102 : ///
103 : /// Statically repositioning it for the time of the lifespan.
104 0 : Particle translated(Vector2 offset) {
105 0 : return TranslatedParticle(
106 : offset: offset,
107 : child: this,
108 0 : lifespan: _lifespan,
109 : );
110 : }
111 :
112 : /// Wraps this particle with a [MovingParticle].
113 : ///
114 : /// Allowing it to move from one [Vector2] to another one.
115 0 : Particle moving({
116 : Vector2? from,
117 : required Vector2 to,
118 : Curve curve = Curves.linear,
119 : }) {
120 0 : return MovingParticle(
121 0 : from: from ?? Vector2.zero(),
122 : to: to,
123 : curve: curve,
124 : child: this,
125 0 : lifespan: _lifespan,
126 : );
127 : }
128 :
129 : /// Wraps this particle with a [AcceleratedParticle].
130 : ///
131 : /// Allowing to specify desired position speed and acceleration and leave
132 : /// the basic physics do the rest.
133 0 : Particle accelerated({
134 : required Vector2 acceleration,
135 : Vector2? position,
136 : Vector2? speed,
137 : }) {
138 0 : return AcceleratedParticle(
139 0 : position: position ?? Vector2.zero(),
140 0 : speed: speed ?? Vector2.zero(),
141 : acceleration: acceleration,
142 : child: this,
143 0 : lifespan: _lifespan,
144 : );
145 : }
146 :
147 : /// Rotates this particle to a fixed angle in radians using [RotatingParticle].
148 0 : Particle rotated(double angle) {
149 0 : return RotatingParticle(
150 : child: this,
151 0 : lifespan: _lifespan,
152 : from: angle,
153 : to: angle,
154 : );
155 : }
156 :
157 : /// Rotates this particle from a given angle to another one in radians
158 : /// using [RotatingParticle].
159 0 : Particle rotating({
160 : double from = 0,
161 : double to = pi,
162 : }) {
163 0 : return RotatingParticle(
164 : child: this,
165 0 : lifespan: _lifespan,
166 : from: from,
167 : to: to,
168 : );
169 : }
170 :
171 : /// Wraps this particle with a [ScaledParticle].
172 : ///
173 : /// Allows for chainging the size of this particle and/or its children.
174 0 : Particle scaled(double scale) {
175 0 : return ScaledParticle(scale: scale, child: this, lifespan: _lifespan);
176 : }
177 :
178 : /// Wraps this particle with a [ParticleComponent].
179 : ///
180 : /// Should be used with the FCS.
181 0 : Component asComponent() => ParticleComponent(particle: this);
182 : }
|