Line data Source code
1 : import 'package:flutter/material.dart';
2 :
3 : import '../components/base_component.dart';
4 : import '../components/position_component.dart';
5 : import '../extensions/vector2.dart';
6 :
7 : export './color_effect.dart';
8 : export './move_effect.dart';
9 : export './opacity_effect.dart';
10 : export './rotate_effect.dart';
11 : export './sequence_effect.dart';
12 : export './size_effect.dart';
13 :
14 : abstract class ComponentEffect<T extends BaseComponent> {
15 : T? component;
16 : Function()? onComplete;
17 :
18 : bool _isDisposed = false;
19 14 : bool get isDisposed => _isDisposed;
20 :
21 : bool _isPaused = false;
22 14 : bool get isPaused => _isPaused;
23 0 : void resume() => _isPaused = false;
24 0 : void pause() => _isPaused = true;
25 :
26 : /// If the animation should first follow the initial curve and then follow the
27 : /// curve backwards
28 : bool isInfinite;
29 : bool isAlternating;
30 : final bool isRelative;
31 : final bool _initialIsInfinite;
32 : final bool _initialIsAlternating;
33 : double? percentage;
34 : double curveProgress = 0.0;
35 : double peakTime = 0.0;
36 : double currentTime = 0.0;
37 : double driftTime = 0.0;
38 : int curveDirection = 1;
39 : Curve curve;
40 :
41 : /// If this is set to true the effect will not be set to its original state
42 : /// once it is done.
43 : bool skipEffectReset = false;
44 :
45 24 : double get iterationTime => peakTime * (isAlternating ? 2 : 1);
46 :
47 7 : ComponentEffect(
48 : this._initialIsInfinite,
49 : this._initialIsAlternating, {
50 : this.isRelative = false,
51 : Curve? curve,
52 : this.onComplete,
53 : }) : isInfinite = _initialIsInfinite,
54 : isAlternating = _initialIsAlternating,
55 : curve = curve ?? Curves.linear;
56 :
57 7 : @mustCallSuper
58 : void update(double dt) {
59 7 : if (isAlternating) {
60 28 : curveDirection = isMax() ? -1 : (isMin() ? 1 : curveDirection);
61 : }
62 7 : if (isInfinite) {
63 24 : if ((!isAlternating && isMax()) || (isAlternating && isMin())) {
64 6 : reset();
65 : }
66 : }
67 7 : if (!hasCompleted()) {
68 42 : currentTime += (dt + driftTime) * curveDirection;
69 42 : percentage = (currentTime / peakTime).clamp(0.0, 1.0).toDouble();
70 28 : curveProgress = curve.transform(percentage!);
71 7 : _updateDriftTime();
72 35 : currentTime = currentTime.clamp(0.0, peakTime).toDouble();
73 : }
74 : }
75 :
76 7 : @mustCallSuper
77 : void initialize(T component) {
78 7 : this.component = component;
79 : }
80 :
81 0 : void dispose() => _isDisposed = true;
82 :
83 : /// Whether the effect has completed or not.
84 7 : bool hasCompleted() {
85 21 : return (!isInfinite && !isAlternating && isMax()) ||
86 20 : (!isInfinite && isAlternating && isMin()) ||
87 7 : isDisposed;
88 : }
89 :
90 28 : bool isMax() => percentage == null ? false : percentage == 1.0;
91 28 : bool isMin() => percentage == null ? false : percentage == 0.0;
92 30 : bool isRootEffect() => component?.effects.contains(this) == true;
93 :
94 : /// Resets the effect and the component which the effect was added to.
95 6 : void reset() {
96 6 : resetEffect();
97 6 : setComponentToOriginalState();
98 : }
99 :
100 : /// Resets the effect to its original state so that it can be re-run.
101 6 : void resetEffect() {
102 6 : _isDisposed = false;
103 6 : percentage = null;
104 6 : currentTime = 0.0;
105 6 : curveDirection = 1;
106 12 : isInfinite = _initialIsInfinite;
107 12 : isAlternating = _initialIsAlternating;
108 : }
109 :
110 : // When the time overshoots the max and min it needs to add that time to
111 : // whatever is going to happen next, for example an alternation or
112 : // following effect in a SequenceEffect.
113 7 : void _updateDriftTime() {
114 7 : if (isMax()) {
115 24 : driftTime = currentTime - peakTime;
116 7 : } else if (isMin()) {
117 18 : driftTime = currentTime.abs();
118 : } else {
119 7 : driftTime = 0;
120 : }
121 : }
122 :
123 : /// Called when the effect is removed from the component.
124 : /// Calls the [onComplete] callback if it is defined and sets the effect back
125 : /// to its original state so that it can be re-added.
126 6 : void onRemove() {
127 12 : onComplete?.call();
128 6 : if (!skipEffectReset) {
129 0 : resetEffect();
130 : }
131 : }
132 :
133 : void setComponentToOriginalState();
134 : void setComponentToEndState();
135 : }
136 :
137 : abstract class PositionComponentEffect
138 : extends ComponentEffect<PositionComponent> {
139 : /// Used to be able to determine the start state of the component
140 : Vector2? originalPosition;
141 : double? originalAngle;
142 : Vector2? originalSize;
143 : Vector2? originalScale;
144 :
145 : /// Used to be able to determine the end state of a sequence of effects
146 : Vector2? endPosition;
147 : double? endAngle;
148 : Vector2? endSize;
149 : Vector2? endScale;
150 :
151 : /// Whether the state of a certain field was modified by the effect
152 : final bool modifiesPosition;
153 : final bool modifiesAngle;
154 : final bool modifiesSize;
155 : final bool modifiesScale;
156 :
157 6 : PositionComponentEffect(
158 : bool initialIsInfinite,
159 : bool initialIsAlternating, {
160 : bool isRelative = false,
161 : Curve? curve,
162 : this.modifiesPosition = false,
163 : this.modifiesAngle = false,
164 : this.modifiesSize = false,
165 : this.modifiesScale = false,
166 : VoidCallback? onComplete,
167 6 : }) : super(
168 : initialIsInfinite,
169 : initialIsAlternating,
170 : isRelative: isRelative,
171 : curve: curve,
172 : onComplete: onComplete,
173 : );
174 :
175 6 : @mustCallSuper
176 : @override
177 : void initialize(PositionComponent component) {
178 6 : super.initialize(component);
179 6 : this.component = component;
180 18 : originalPosition = component.position.clone();
181 12 : originalAngle = component.angle;
182 18 : originalSize = component.size.clone();
183 18 : originalScale = component.scale.clone();
184 :
185 : /// If these aren't modified by the extending effect it is assumed that the
186 : /// effect didn't bring the component to another state than the one it
187 : /// started in
188 18 : endPosition = component.position.clone();
189 12 : endAngle = component.angle;
190 18 : endSize = component.size.clone();
191 18 : endScale = component.scale.clone();
192 : }
193 :
194 : /// Only change the parts of the component that is affected by the
195 : /// effect, and only set the state if it is the root effect (not part of
196 : /// another effect, like children of a CombinedEffect or SequenceEffect).
197 6 : void _setComponentState(
198 : Vector2? position,
199 : double? angle,
200 : Vector2? size,
201 : Vector2? scale,
202 : ) {
203 6 : if (isRootEffect()) {
204 6 : if (modifiesPosition) {
205 : assert(
206 0 : position != null,
207 : '`position` must not be `null` for an effect which modifies `position`',
208 : );
209 9 : component?.position.setFrom(position!);
210 : }
211 6 : if (modifiesAngle) {
212 : assert(
213 0 : angle != null,
214 : '`angle` must not be `null` for an effect which modifies `angle`',
215 : );
216 6 : component?.angle = angle!;
217 : }
218 6 : if (modifiesSize) {
219 : assert(
220 0 : size != null,
221 : '`size` must not be `null` for an effect which modifies `size`',
222 : );
223 9 : component?.size.setFrom(size!);
224 : }
225 6 : if (modifiesScale) {
226 : assert(
227 0 : scale != null,
228 : '`scale` must not be `null` for an effect which modifies `scale`',
229 : );
230 3 : component?.scale.setFrom(scale!);
231 : }
232 : }
233 : }
234 :
235 6 : @override
236 : void setComponentToOriginalState() {
237 6 : _setComponentState(
238 6 : originalPosition,
239 6 : originalAngle,
240 6 : originalSize,
241 6 : originalScale,
242 : );
243 : }
244 :
245 6 : @override
246 : void setComponentToEndState() {
247 30 : _setComponentState(endPosition, endAngle, endSize, endScale);
248 : }
249 : }
250 :
251 : abstract class SimplePositionComponentEffect extends PositionComponentEffect {
252 : double? duration;
253 : double? speed;
254 :
255 6 : SimplePositionComponentEffect(
256 : bool initialIsInfinite,
257 : bool initialIsAlternating, {
258 : this.duration,
259 : this.speed,
260 : Curve? curve,
261 : bool isRelative = false,
262 : bool modifiesPosition = false,
263 : bool modifiesAngle = false,
264 : bool modifiesSize = false,
265 : bool modifiesScale = false,
266 : VoidCallback? onComplete,
267 : }) : assert(
268 6 : (duration != null) ^ (speed != null),
269 : 'Either speed or duration necessary',
270 : ),
271 6 : super(
272 : initialIsInfinite,
273 : initialIsAlternating,
274 : isRelative: isRelative,
275 : curve: curve,
276 : modifiesPosition: modifiesPosition,
277 : modifiesAngle: modifiesAngle,
278 : modifiesSize: modifiesSize,
279 : modifiesScale: modifiesScale,
280 : onComplete: onComplete,
281 : );
282 : }
|