@effindomv2/fui-as 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/LICENSE.md +7 -0
  2. package/browser/src/common-harness/host-imports.ts +430 -0
  3. package/browser/src/common-harness/interop.ts +39 -0
  4. package/browser/src/common-harness/managed-harness-bitmap-host.ts +92 -0
  5. package/browser/src/common-harness/managed-harness-fetch-host.ts +201 -0
  6. package/browser/src/common-harness/managed-harness-file-host.ts +1101 -0
  7. package/browser/src/common-harness/managed-harness-file-payloads.ts +143 -0
  8. package/browser/src/common-harness/managed-harness-file-types.ts +106 -0
  9. package/browser/src/common-harness/managed-harness-session.ts +15 -0
  10. package/browser/src/common-harness/managed-harness.ts +1323 -0
  11. package/browser/src/common-harness/managed-history.ts +168 -0
  12. package/browser/src/common-harness/persisted-restore-policy.ts +50 -0
  13. package/browser/src/common-harness/persisted-ui-state-controller.ts +309 -0
  14. package/browser/src/common-harness/text-session-bridge.ts +452 -0
  15. package/browser/src/common-harness/types.ts +205 -0
  16. package/browser/src/common-harness/ui-chrome.ts +191 -0
  17. package/browser/src/common-harness/ui-imports.ts +529 -0
  18. package/browser/src/common-harness/wasm-module-cache.ts +47 -0
  19. package/browser/src/common-harness.ts +27 -0
  20. package/browser/src/file-processing-worker.ts +89 -0
  21. package/browser/src/host-events.ts +97 -0
  22. package/browser/src/host-services.ts +203 -0
  23. package/browser/src/index.ts +62 -0
  24. package/browser/src/persisted-ui-state.ts +206 -0
  25. package/browser/src/routed-harness.ts +198 -0
  26. package/browser/src/worker-bootstrap.ts +483 -0
  27. package/browser/src/worker-manager.ts +230 -0
  28. package/browser/src/worker-types.ts +50 -0
  29. package/package.json +89 -0
  30. package/scripts/build-demo-as.sh +91 -0
  31. package/scripts/build.sh +325 -0
  32. package/scripts/generate-host-events.ts +175 -0
  33. package/scripts/generate-host-services.ts +157 -0
  34. package/src/Fui.ts +205 -0
  35. package/src/FuiExports.ts +55 -0
  36. package/src/FuiPrimitives.ts +15 -0
  37. package/src/FuiWorker.ts +3 -0
  38. package/src/FuiWorkerExports.ts +6 -0
  39. package/src/bindings/ui.ts +531 -0
  40. package/src/color.ts +86 -0
  41. package/src/controls/AntiSelectionArea.ts +23 -0
  42. package/src/controls/Button.ts +750 -0
  43. package/src/controls/Checkbox.ts +181 -0
  44. package/src/controls/ContextMenu.ts +885 -0
  45. package/src/controls/ControlTemplateSet.ts +37 -0
  46. package/src/controls/Dialog.ts +355 -0
  47. package/src/controls/Dropdown.ts +856 -0
  48. package/src/controls/Form.ts +110 -0
  49. package/src/controls/NavLink.ts +211 -0
  50. package/src/controls/Popup.ts +129 -0
  51. package/src/controls/ProgressBar.ts +180 -0
  52. package/src/controls/RadioButton.ts +135 -0
  53. package/src/controls/RadioGroup.ts +244 -0
  54. package/src/controls/SelectionArea.ts +75 -0
  55. package/src/controls/Slider.ts +471 -0
  56. package/src/controls/Switch.ts +132 -0
  57. package/src/controls/TextArea.ts +20 -0
  58. package/src/controls/TextInput.ts +7 -0
  59. package/src/controls/index.ts +18 -0
  60. package/src/controls/internal/ButtonPresenter.ts +95 -0
  61. package/src/controls/internal/CheckboxIndicatorPresenter.ts +93 -0
  62. package/src/controls/internal/DropdownChevronPresenter.ts +67 -0
  63. package/src/controls/internal/DropdownFieldPresenter.ts +110 -0
  64. package/src/controls/internal/DropdownOptionRowPresenter.ts +82 -0
  65. package/src/controls/internal/PopupPresenter.ts +198 -0
  66. package/src/controls/internal/PressableIndicatorPresenter.ts +32 -0
  67. package/src/controls/internal/PressableLabeledControl.ts +221 -0
  68. package/src/controls/internal/RadioIndicatorPresenter.ts +73 -0
  69. package/src/controls/internal/SliderPresenter.ts +157 -0
  70. package/src/controls/internal/SwitchIndicatorPresenter.ts +72 -0
  71. package/src/controls/internal/TextInputCore.ts +695 -0
  72. package/src/controls/internal/TextInputPresenter.ts +72 -0
  73. package/src/controls/templating.ts +54 -0
  74. package/src/core/Action.ts +94 -0
  75. package/src/core/Actions.ts +37 -0
  76. package/src/core/Animation.ts +412 -0
  77. package/src/core/Application.ts +328 -0
  78. package/src/core/Assets.ts +264 -0
  79. package/src/core/AttachedProperties.ts +32 -0
  80. package/src/core/Bitmap.ts +70 -0
  81. package/src/core/BoundCallback.ts +104 -0
  82. package/src/core/Callbacks.ts +17 -0
  83. package/src/core/ContextMenuManager.ts +466 -0
  84. package/src/core/DebugApi.ts +30 -0
  85. package/src/core/Disposable.ts +10 -0
  86. package/src/core/DragDropManager.ts +179 -0
  87. package/src/core/DragGesture.ts +184 -0
  88. package/src/core/DynamicAssetIds.ts +24 -0
  89. package/src/core/Errors.ts +48 -0
  90. package/src/core/EventRouter.ts +408 -0
  91. package/src/core/ExternalDropManager.ts +122 -0
  92. package/src/core/Fetch.ts +264 -0
  93. package/src/core/FetchFfi.ts +15 -0
  94. package/src/core/File.ts +1002 -0
  95. package/src/core/FocusAdornerManager.ts +263 -0
  96. package/src/core/FocusVisibility.ts +36 -0
  97. package/src/core/FrameScheduler.ts +28 -0
  98. package/src/core/KeyboardScroll.ts +161 -0
  99. package/src/core/KeyboardScrollTracker.ts +386 -0
  100. package/src/core/Logger.ts +80 -0
  101. package/src/core/Navigation.ts +13 -0
  102. package/src/core/Node.ts +1708 -0
  103. package/src/core/PersistedState.ts +102 -0
  104. package/src/core/PersistedUiState.ts +142 -0
  105. package/src/core/Platform.ts +219 -0
  106. package/src/core/Signal.ts +89 -0
  107. package/src/core/Theme.ts +365 -0
  108. package/src/core/Timers.ts +129 -0
  109. package/src/core/ToolTip.ts +122 -0
  110. package/src/core/ToolTipManager.ts +459 -0
  111. package/src/core/Transitions.ts +34 -0
  112. package/src/core/Typography.ts +204 -0
  113. package/src/core/Worker.ts +196 -0
  114. package/src/core/bind.ts +37 -0
  115. package/src/core/event_exports.ts +596 -0
  116. package/src/core/ffi.ts +728 -0
  117. package/src/host-services/runtime.ts +25 -0
  118. package/src/nodes/FlexBox.ts +789 -0
  119. package/src/nodes/GradientStop.ts +9 -0
  120. package/src/nodes/Grid.ts +183 -0
  121. package/src/nodes/Image.ts +189 -0
  122. package/src/nodes/Portal.ts +14 -0
  123. package/src/nodes/RichText.ts +312 -0
  124. package/src/nodes/ScrollBar.ts +570 -0
  125. package/src/nodes/ScrollBox.ts +415 -0
  126. package/src/nodes/ScrollState.ts +10 -0
  127. package/src/nodes/ScrollView.ts +511 -0
  128. package/src/nodes/Svg.ts +142 -0
  129. package/src/nodes/Text.ts +145 -0
  130. package/src/nodes/TextCore.ts +558 -0
  131. package/src/nodes/VirtualList.ts +431 -0
  132. package/src/nodes/helpers.ts +25 -0
  133. package/src/nodes/index.ts +14 -0
  134. package/src/tsconfig.json +7 -0
  135. package/src/worker/Worker.ts +169 -0
  136. package/src/worker/WorkerJob.ts +65 -0
  137. package/src/worker/ffi.ts +23 -0
@@ -0,0 +1,72 @@
1
+ import { BorderStyle, CursorStyle, Unit } from "../../core/ffi";
2
+ import { Node } from "../../core/Node";
3
+ import { Theme } from "../../core/Theme";
4
+ import { FlexBox } from "../../nodes";
5
+
6
+ export class TextInputVisualState {
7
+ constructor(
8
+ readonly multiline: bool,
9
+ readonly enabled: bool,
10
+ readonly wrapping: bool,
11
+ ) {}
12
+ }
13
+
14
+ export abstract class TextInputPresenter {
15
+ private hostValue: FlexBox | null = null;
16
+ private editorHostValue: Node | null = null;
17
+ private placeholderHostValue: FlexBox | null = null;
18
+
19
+ bind(host: FlexBox, editorHost: Node, placeholderHost: FlexBox): this {
20
+ this.hostValue = host;
21
+ this.editorHostValue = editorHost;
22
+ this.placeholderHostValue = placeholderHost;
23
+ return this;
24
+ }
25
+
26
+ protected get host(): FlexBox {
27
+ return changetype<FlexBox>(this.hostValue);
28
+ }
29
+
30
+ protected get editorHost(): Node {
31
+ return changetype<Node>(this.editorHostValue);
32
+ }
33
+
34
+ protected get placeholderHost(): FlexBox {
35
+ return changetype<FlexBox>(this.placeholderHostValue);
36
+ }
37
+
38
+ abstract apply(theme: Theme, state: TextInputVisualState): void;
39
+ }
40
+
41
+ export abstract class TextInputTemplate {
42
+ abstract create(): TextInputPresenter;
43
+ }
44
+
45
+ class DefaultTextInputPresenter extends TextInputPresenter {
46
+ apply(theme: Theme, state: TextInputVisualState): void {
47
+ const horizontalPadding = theme.spacing.md;
48
+ const verticalPadding = theme.spacing.sm;
49
+ const editableCursor = state.enabled ? CursorStyle.Text : CursorStyle.Default;
50
+ const shellCursor = !state.multiline && state.enabled ? CursorStyle.Text : CursorStyle.Default;
51
+ this.host
52
+ .bgColor(theme.colors.surface)
53
+ .cornerRadius(theme.spacing.sm)
54
+ .border(1.0, theme.colors.border, BorderStyle.Solid)
55
+ .padding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding)
56
+ .cursor(shellCursor);
57
+ this.host.opacity(state.enabled ? 1.0 : 0.6);
58
+ this.editorHost.cursor(editableCursor);
59
+ this.placeholderHost
60
+ .position(horizontalPadding, verticalPadding)
61
+ .width(100.0, Unit.Percent)
62
+ .cursor(editableCursor);
63
+ }
64
+ }
65
+
66
+ class DefaultTextInputTemplate extends TextInputTemplate {
67
+ create(): TextInputPresenter {
68
+ return new DefaultTextInputPresenter();
69
+ }
70
+ }
71
+
72
+ export const defaultTextInputTemplate = new DefaultTextInputTemplate();
@@ -0,0 +1,54 @@
1
+ export {
2
+ ButtonPresenter,
3
+ ButtonTemplate,
4
+ ButtonVisualState,
5
+ } from "./internal/ButtonPresenter";
6
+ export {
7
+ ControlTemplateSet,
8
+ clearControlTemplates,
9
+ getControlTemplates,
10
+ useControlTemplates,
11
+ } from "./ControlTemplateSet";
12
+ export { PressableIndicatorMetrics } from "./internal/PressableIndicatorPresenter";
13
+ export {
14
+ CheckboxIndicatorPresenter,
15
+ CheckboxIndicatorTemplate,
16
+ CheckboxIndicatorVisualState,
17
+ } from "./internal/CheckboxIndicatorPresenter";
18
+ export {
19
+ RadioIndicatorPresenter,
20
+ RadioIndicatorTemplate,
21
+ RadioIndicatorVisualState,
22
+ } from "./internal/RadioIndicatorPresenter";
23
+ export {
24
+ SwitchIndicatorPresenter,
25
+ SwitchIndicatorTemplate,
26
+ SwitchIndicatorVisualState,
27
+ } from "./internal/SwitchIndicatorPresenter";
28
+ export {
29
+ SliderPresenter,
30
+ SliderPresenterMetrics,
31
+ SliderTemplate,
32
+ SliderVisualState,
33
+ } from "./internal/SliderPresenter";
34
+ export {
35
+ DropdownFieldPresenter,
36
+ DropdownFieldTemplate,
37
+ DropdownFieldVisualState,
38
+ } from "./internal/DropdownFieldPresenter";
39
+ export {
40
+ DropdownChevronPresenter,
41
+ DropdownChevronTemplate,
42
+ DropdownChevronVisualState,
43
+ } from "./internal/DropdownChevronPresenter";
44
+ export {
45
+ DropdownOptionRowMetrics,
46
+ DropdownOptionRowPresenter,
47
+ DropdownOptionRowTemplate,
48
+ DropdownOptionRowVisualState,
49
+ } from "./internal/DropdownOptionRowPresenter";
50
+ export {
51
+ TextInputPresenter,
52
+ TextInputTemplate,
53
+ TextInputVisualState,
54
+ } from "./internal/TextInputPresenter";
@@ -0,0 +1,94 @@
1
+ import { Node } from "./Node";
2
+ import { Disposable } from "./Disposable";
3
+ import { Signal } from "./Signal";
4
+
5
+ export type SignalHandler<Owner, T> = (owner: Owner, value: T) => void;
6
+
7
+ export abstract class Action<T> implements Disposable {
8
+ next: Action<T> | null = null;
9
+ private signal: Signal<T> | null = null;
10
+
11
+ abstract invoke(value: T): void;
12
+
13
+ debugName(): string {
14
+ return "Action";
15
+ }
16
+
17
+ dispose(): void {
18
+ const signal = this.signal;
19
+ if (signal !== null) {
20
+ signal.removeAction(this);
21
+ }
22
+ }
23
+
24
+ _attach(signal: Signal<T>): void {
25
+ this.signal = signal;
26
+ }
27
+
28
+ _detach(): void {
29
+ this.signal = null;
30
+ this.next = null;
31
+ }
32
+
33
+ _isAttachedTo(signal: Signal<T>): bool {
34
+ return this.signal === signal;
35
+ }
36
+
37
+ detach(): void {
38
+ this.dispose();
39
+ }
40
+ }
41
+
42
+ export class CallbackAction<T> extends Action<T> {
43
+ private readonly callback: () => void;
44
+
45
+ constructor(callback: () => void) {
46
+ super();
47
+ this.callback = callback;
48
+ }
49
+
50
+ invoke(_value: T): void {
51
+ this.callback();
52
+ }
53
+
54
+ debugName(): string {
55
+ return "CallbackAction";
56
+ }
57
+ }
58
+
59
+ export class HandlerAction<Owner, T> extends Action<T> {
60
+ private readonly owner: Owner;
61
+ private readonly handler: SignalHandler<Owner, T>;
62
+
63
+ constructor(owner: Owner, handler: SignalHandler<Owner, T>) {
64
+ super();
65
+ this.owner = owner;
66
+ this.handler = handler;
67
+ }
68
+
69
+ invoke(value: T): void {
70
+ this.handler(this.owner, value);
71
+ }
72
+
73
+ debugName(): string {
74
+ return "HandlerAction";
75
+ }
76
+ }
77
+
78
+
79
+ export abstract class NodeAction<T> extends Action<T> {
80
+ protected readonly node: Node;
81
+
82
+ constructor(node: Node) {
83
+ super();
84
+ this.node = node;
85
+ }
86
+
87
+ protected get handle(): u64 {
88
+ return this.node.builtHandle;
89
+ }
90
+
91
+ debugName(): string {
92
+ return "NodeAction";
93
+ }
94
+ }
@@ -0,0 +1,37 @@
1
+ import { NodeAction } from "./Action";
2
+ import { FlexBox } from "../nodes/FlexBox";
3
+ import { Text } from "../nodes/Text";
4
+
5
+ export class SetTextAction extends NodeAction<string> {
6
+ private readonly textNode: Text;
7
+
8
+ constructor(node: Text) {
9
+ super(node);
10
+ this.textNode = node;
11
+ }
12
+
13
+ invoke(value: string): void {
14
+ this.textNode.text(value);
15
+ }
16
+
17
+ debugName(): string {
18
+ return "SetTextAction";
19
+ }
20
+ }
21
+
22
+ export class SetBackgroundAction extends NodeAction<u32> {
23
+ private readonly boxNode: FlexBox;
24
+
25
+ constructor(node: FlexBox) {
26
+ super(node);
27
+ this.boxNode = node;
28
+ }
29
+
30
+ invoke(value: u32): void {
31
+ this.boxNode.bgColor(value);
32
+ }
33
+
34
+ debugName(): string {
35
+ return "SetBackgroundAction";
36
+ }
37
+ }
@@ -0,0 +1,412 @@
1
+ import { mixColor } from "../color";
2
+ import { Handler1 } from "./BoundCallback";
3
+ import { Disposable } from "./Disposable";
4
+ import { markNeedsCommit } from "./FrameScheduler";
5
+
6
+ const MAX_FRAME_DELTA_MS: f64 = 100.0;
7
+
8
+ function clampUnit(value: f32): f32 {
9
+ if (value < 0.0) {
10
+ return 0.0;
11
+ }
12
+ if (value > 1.0) {
13
+ return 1.0;
14
+ }
15
+ return value;
16
+ }
17
+
18
+ function clampFrameDelta(deltaMs: f64): f64 {
19
+ if (deltaMs < 0.0) {
20
+ return 0.0;
21
+ }
22
+ if (deltaMs > MAX_FRAME_DELTA_MS) {
23
+ return MAX_FRAME_DELTA_MS;
24
+ }
25
+ return deltaMs;
26
+ }
27
+
28
+ function mixFloat(from: f32, to: f32, amount: f32): f32 {
29
+ return from + ((to - from) * clampUnit(amount));
30
+ }
31
+
32
+ export abstract class Easing {
33
+ abstract sample(progress: f32): f32;
34
+ }
35
+
36
+ export class LinearEasing extends Easing {
37
+ sample(progress: f32): f32 {
38
+ return clampUnit(progress);
39
+ }
40
+ }
41
+
42
+ export class CubicInEasing extends Easing {
43
+ sample(progress: f32): f32 {
44
+ const t = clampUnit(progress);
45
+ return t * t * t;
46
+ }
47
+ }
48
+
49
+ export class CubicOutEasing extends Easing {
50
+ sample(progress: f32): f32 {
51
+ const t = clampUnit(progress) - 1.0;
52
+ return (t * t * t) + 1.0;
53
+ }
54
+ }
55
+
56
+ export class CubicInOutEasing extends Easing {
57
+ sample(progress: f32): f32 {
58
+ const t = clampUnit(progress);
59
+ if (t < 0.5) {
60
+ return 4.0 * t * t * t;
61
+ }
62
+ const offset = (-2.0 * t) + 2.0;
63
+ return <f32>(1.0 - ((offset * offset * offset) * 0.5));
64
+ }
65
+ }
66
+
67
+ export class QuadOutEasing extends Easing {
68
+ sample(progress: f32): f32 {
69
+ const t = 1.0 - clampUnit(progress);
70
+ return <f32>(1.0 - (t * t));
71
+ }
72
+ }
73
+
74
+ const LINEAR_EASING: Easing = new LinearEasing();
75
+ const CUBIC_IN_EASING: Easing = new CubicInEasing();
76
+ const CUBIC_OUT_EASING: Easing = new CubicOutEasing();
77
+ const CUBIC_IN_OUT_EASING: Easing = new CubicInOutEasing();
78
+ const QUAD_OUT_EASING: Easing = new QuadOutEasing();
79
+
80
+ export class Easings {
81
+ static readonly linear: Easing = LINEAR_EASING;
82
+ static readonly cubicIn: Easing = CUBIC_IN_EASING;
83
+ static readonly cubicOut: Easing = CUBIC_OUT_EASING;
84
+ static readonly cubicInOut: Easing = CUBIC_IN_OUT_EASING;
85
+ static readonly quadOut: Easing = QUAD_OUT_EASING;
86
+ }
87
+
88
+ export class AnimationTiming {
89
+ readonly durationMs: f64;
90
+ readonly easing: Easing;
91
+
92
+ constructor(durationMs: f64, easing: Easing = LINEAR_EASING) {
93
+ this.durationMs = durationMs > 0.0 ? durationMs : 0.0;
94
+ this.easing = easing;
95
+ }
96
+ }
97
+
98
+ export abstract class Animation implements Disposable {
99
+ private managerValue: AnimationManager | null = null;
100
+ private runningValue: bool = false;
101
+ private startedValue: bool = false;
102
+ private lastTimestampMs: f64 = 0.0;
103
+ private elapsedMs: f64 = 0.0;
104
+
105
+ protected constructor(readonly timing: AnimationTiming) {}
106
+
107
+ get isRunning(): bool {
108
+ return this.runningValue;
109
+ }
110
+
111
+ cancel(): void {
112
+ const manager = this.managerValue;
113
+ if (manager !== null) {
114
+ manager.cancel(this);
115
+ return;
116
+ }
117
+ this._cancel();
118
+ }
119
+
120
+ finish(): void {
121
+ const manager = this.managerValue;
122
+ if (manager !== null) {
123
+ manager.finish(this);
124
+ return;
125
+ }
126
+ this._finish();
127
+ }
128
+
129
+ dispose(): void {
130
+ this.cancel();
131
+ }
132
+
133
+ protected onStart(_timestampMs: f64): void {}
134
+
135
+ protected abstract onSample(easedProgress: f32, linearProgress: f32): void;
136
+
137
+ protected onStop(_finished: bool): void {}
138
+
139
+ _attach(manager: AnimationManager, knownTimestampMs: f64, hasKnownTimestamp: bool): void {
140
+ this.managerValue = manager;
141
+ this.runningValue = true;
142
+ this.startedValue = false;
143
+ this.lastTimestampMs = 0.0;
144
+ this.elapsedMs = 0.0;
145
+ if (hasKnownTimestamp) {
146
+ this.startInternal(knownTimestampMs);
147
+ }
148
+ }
149
+
150
+ _tick(timestampMs: f64): void {
151
+ if (!this.runningValue) {
152
+ return;
153
+ }
154
+ if (!this.startedValue) {
155
+ this.startInternal(timestampMs);
156
+ return;
157
+ }
158
+ const durationMs = this.timing.durationMs;
159
+ const deltaMs = clampFrameDelta(timestampMs - this.lastTimestampMs);
160
+ this.lastTimestampMs = timestampMs;
161
+ this.elapsedMs += deltaMs;
162
+ const linearProgress = durationMs <= 0.0
163
+ ? <f32>1.0
164
+ : clampUnit(<f32>(this.elapsedMs / durationMs));
165
+ this.onSample(this.timing.easing.sample(linearProgress), linearProgress);
166
+ if (linearProgress >= 1.0) {
167
+ this.stop(true);
168
+ }
169
+ }
170
+
171
+ _cancel(): void {
172
+ if (!this.runningValue) {
173
+ return;
174
+ }
175
+ this.stop(false);
176
+ }
177
+
178
+ _finish(): void {
179
+ if (!this.runningValue) {
180
+ return;
181
+ }
182
+ if (!this.startedValue) {
183
+ this.startedValue = true;
184
+ this.lastTimestampMs = 0.0;
185
+ this.elapsedMs = this.timing.durationMs;
186
+ this.onStart(0.0);
187
+ }
188
+ this.onSample(1.0, 1.0);
189
+ this.stop(true);
190
+ }
191
+
192
+ private startInternal(timestampMs: f64): void {
193
+ this.startedValue = true;
194
+ this.lastTimestampMs = timestampMs;
195
+ this.elapsedMs = 0.0;
196
+ this.onStart(timestampMs);
197
+ if (this.timing.durationMs <= 0.0) {
198
+ this.onSample(1.0, 1.0);
199
+ this.stop(true);
200
+ return;
201
+ }
202
+ this.onSample(this.timing.easing.sample(0.0), 0.0);
203
+ }
204
+
205
+ private stop(finished: bool): void {
206
+ this.runningValue = false;
207
+ this.managerValue = null;
208
+ this.onStop(finished);
209
+ }
210
+ }
211
+
212
+ export class AnimationManager {
213
+ private readonly activeAnimations: Array<Animation> = new Array<Animation>();
214
+ private lastTimestampMs: f64 = 0.0;
215
+ private hasLastTimestamp: bool = false;
216
+
217
+ start(animation: Animation): Animation {
218
+ this.cancel(animation);
219
+ animation._attach(this, this.lastTimestampMs, this.hasLastTimestamp);
220
+ if (animation.isRunning) {
221
+ this.activeAnimations.push(animation);
222
+ }
223
+ markNeedsCommit();
224
+ return animation;
225
+ }
226
+
227
+ cancel(animation: Animation): void {
228
+ const index = this.indexOf(animation);
229
+ if (index < 0) {
230
+ return;
231
+ }
232
+ this.removeAt(index);
233
+ animation._cancel();
234
+ }
235
+
236
+ finish(animation: Animation): void {
237
+ const index = this.indexOf(animation);
238
+ if (index < 0) {
239
+ return;
240
+ }
241
+ this.removeAt(index);
242
+ animation._finish();
243
+ }
244
+
245
+ tick(timestampMs: f64): void {
246
+ this.lastTimestampMs = timestampMs;
247
+ this.hasLastTimestamp = true;
248
+ let hasActive = false;
249
+ for (let index = this.activeAnimations.length - 1; index >= 0; --index) {
250
+ const animation = unchecked(this.activeAnimations[index]);
251
+ animation._tick(timestampMs);
252
+ if (!animation.isRunning) {
253
+ this.removeAt(index);
254
+ continue;
255
+ }
256
+ hasActive = true;
257
+ }
258
+ if (hasActive) {
259
+ markNeedsCommit();
260
+ }
261
+ }
262
+
263
+ hasActiveAnimations(): bool {
264
+ return this.activeAnimations.length > 0;
265
+ }
266
+
267
+ reset(): void {
268
+ for (let index = this.activeAnimations.length - 1; index >= 0; --index) {
269
+ unchecked(this.activeAnimations[index])._cancel();
270
+ }
271
+ this.activeAnimations.length = 0;
272
+ this.lastTimestampMs = 0.0;
273
+ this.hasLastTimestamp = false;
274
+ }
275
+
276
+ private indexOf(target: Animation): i32 {
277
+ for (let index = 0; index < this.activeAnimations.length; index += 1) {
278
+ if (unchecked(this.activeAnimations[index]) === target) {
279
+ return index;
280
+ }
281
+ }
282
+ return -1;
283
+ }
284
+
285
+ private removeAt(index: i32): void {
286
+ const lastIndex = this.activeAnimations.length - 1;
287
+ if (index < 0 || index > lastIndex) {
288
+ return;
289
+ }
290
+ if (index != lastIndex) {
291
+ unchecked(this.activeAnimations[index] = this.activeAnimations[lastIndex]);
292
+ }
293
+ this.activeAnimations.pop();
294
+ }
295
+ }
296
+
297
+ export type FloatAnimationHandler = (value: f32) => void;
298
+ export type ColorAnimationHandler = (value: u32) => void;
299
+
300
+ class CallbackFloatAnimation extends Animation {
301
+ constructor(
302
+ private readonly fromValue: f32,
303
+ private readonly toValue: f32,
304
+ timing: AnimationTiming,
305
+ private readonly handler: FloatAnimationHandler,
306
+ ) {
307
+ super(timing);
308
+ }
309
+
310
+ protected onSample(easedProgress: f32, _linearProgress: f32): void {
311
+ this.handler(mixFloat(this.fromValue, this.toValue, easedProgress));
312
+ }
313
+ }
314
+
315
+ class OwnerFloatAnimation<Owner> extends Animation {
316
+ constructor(
317
+ private readonly owner: Owner,
318
+ private readonly fromValue: f32,
319
+ private readonly toValue: f32,
320
+ timing: AnimationTiming,
321
+ private readonly handler: Handler1<Owner, f32>,
322
+ ) {
323
+ super(timing);
324
+ }
325
+
326
+ protected onSample(easedProgress: f32, _linearProgress: f32): void {
327
+ this.handler(this.owner, mixFloat(this.fromValue, this.toValue, easedProgress));
328
+ }
329
+ }
330
+
331
+ class CallbackColorAnimation extends Animation {
332
+ constructor(
333
+ private readonly fromValue: u32,
334
+ private readonly toValue: u32,
335
+ timing: AnimationTiming,
336
+ private readonly handler: ColorAnimationHandler,
337
+ ) {
338
+ super(timing);
339
+ }
340
+
341
+ protected onSample(easedProgress: f32, _linearProgress: f32): void {
342
+ this.handler(mixColor(this.fromValue, this.toValue, easedProgress));
343
+ }
344
+ }
345
+
346
+ class OwnerColorAnimation<Owner> extends Animation {
347
+ constructor(
348
+ private readonly owner: Owner,
349
+ private readonly fromValue: u32,
350
+ private readonly toValue: u32,
351
+ timing: AnimationTiming,
352
+ private readonly handler: Handler1<Owner, u32>,
353
+ ) {
354
+ super(timing);
355
+ }
356
+
357
+ protected onSample(easedProgress: f32, _linearProgress: f32): void {
358
+ this.handler(this.owner, mixColor(this.fromValue, this.toValue, easedProgress));
359
+ }
360
+ }
361
+
362
+ const sharedAnimationManager = new AnimationManager();
363
+
364
+ export function getAnimationManager(): AnimationManager {
365
+ return sharedAnimationManager;
366
+ }
367
+
368
+ export function tickAnimations(timestampMs: f64): void {
369
+ sharedAnimationManager.tick(timestampMs);
370
+ }
371
+
372
+ export function resetAnimations(): void {
373
+ sharedAnimationManager.reset();
374
+ }
375
+
376
+ export function animateFloat(
377
+ fromValue: f32,
378
+ toValue: f32,
379
+ timing: AnimationTiming,
380
+ handler: FloatAnimationHandler,
381
+ ): Animation {
382
+ return sharedAnimationManager.start(new CallbackFloatAnimation(fromValue, toValue, timing, handler));
383
+ }
384
+
385
+ export function animateFloatWith<Owner>(
386
+ owner: Owner,
387
+ fromValue: f32,
388
+ toValue: f32,
389
+ timing: AnimationTiming,
390
+ handler: Handler1<Owner, f32>,
391
+ ): Animation {
392
+ return sharedAnimationManager.start(new OwnerFloatAnimation<Owner>(owner, fromValue, toValue, timing, handler));
393
+ }
394
+
395
+ export function animateColor(
396
+ fromValue: u32,
397
+ toValue: u32,
398
+ timing: AnimationTiming,
399
+ handler: ColorAnimationHandler,
400
+ ): Animation {
401
+ return sharedAnimationManager.start(new CallbackColorAnimation(fromValue, toValue, timing, handler));
402
+ }
403
+
404
+ export function animateColorWith<Owner>(
405
+ owner: Owner,
406
+ fromValue: u32,
407
+ toValue: u32,
408
+ timing: AnimationTiming,
409
+ handler: Handler1<Owner, u32>,
410
+ ): Animation {
411
+ return sharedAnimationManager.start(new OwnerColorAnimation<Owner>(owner, fromValue, toValue, timing, handler));
412
+ }