@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.
- package/LICENSE.md +7 -0
- package/browser/src/common-harness/host-imports.ts +430 -0
- package/browser/src/common-harness/interop.ts +39 -0
- package/browser/src/common-harness/managed-harness-bitmap-host.ts +92 -0
- package/browser/src/common-harness/managed-harness-fetch-host.ts +201 -0
- package/browser/src/common-harness/managed-harness-file-host.ts +1101 -0
- package/browser/src/common-harness/managed-harness-file-payloads.ts +143 -0
- package/browser/src/common-harness/managed-harness-file-types.ts +106 -0
- package/browser/src/common-harness/managed-harness-session.ts +15 -0
- package/browser/src/common-harness/managed-harness.ts +1323 -0
- package/browser/src/common-harness/managed-history.ts +168 -0
- package/browser/src/common-harness/persisted-restore-policy.ts +50 -0
- package/browser/src/common-harness/persisted-ui-state-controller.ts +309 -0
- package/browser/src/common-harness/text-session-bridge.ts +452 -0
- package/browser/src/common-harness/types.ts +205 -0
- package/browser/src/common-harness/ui-chrome.ts +191 -0
- package/browser/src/common-harness/ui-imports.ts +529 -0
- package/browser/src/common-harness/wasm-module-cache.ts +47 -0
- package/browser/src/common-harness.ts +27 -0
- package/browser/src/file-processing-worker.ts +89 -0
- package/browser/src/host-events.ts +97 -0
- package/browser/src/host-services.ts +203 -0
- package/browser/src/index.ts +62 -0
- package/browser/src/persisted-ui-state.ts +206 -0
- package/browser/src/routed-harness.ts +198 -0
- package/browser/src/worker-bootstrap.ts +483 -0
- package/browser/src/worker-manager.ts +230 -0
- package/browser/src/worker-types.ts +50 -0
- package/package.json +89 -0
- package/scripts/build-demo-as.sh +91 -0
- package/scripts/build.sh +325 -0
- package/scripts/generate-host-events.ts +175 -0
- package/scripts/generate-host-services.ts +157 -0
- package/src/Fui.ts +205 -0
- package/src/FuiExports.ts +55 -0
- package/src/FuiPrimitives.ts +15 -0
- package/src/FuiWorker.ts +3 -0
- package/src/FuiWorkerExports.ts +6 -0
- package/src/bindings/ui.ts +531 -0
- package/src/color.ts +86 -0
- package/src/controls/AntiSelectionArea.ts +23 -0
- package/src/controls/Button.ts +750 -0
- package/src/controls/Checkbox.ts +181 -0
- package/src/controls/ContextMenu.ts +885 -0
- package/src/controls/ControlTemplateSet.ts +37 -0
- package/src/controls/Dialog.ts +355 -0
- package/src/controls/Dropdown.ts +856 -0
- package/src/controls/Form.ts +110 -0
- package/src/controls/NavLink.ts +211 -0
- package/src/controls/Popup.ts +129 -0
- package/src/controls/ProgressBar.ts +180 -0
- package/src/controls/RadioButton.ts +135 -0
- package/src/controls/RadioGroup.ts +244 -0
- package/src/controls/SelectionArea.ts +75 -0
- package/src/controls/Slider.ts +471 -0
- package/src/controls/Switch.ts +132 -0
- package/src/controls/TextArea.ts +20 -0
- package/src/controls/TextInput.ts +7 -0
- package/src/controls/index.ts +18 -0
- package/src/controls/internal/ButtonPresenter.ts +95 -0
- package/src/controls/internal/CheckboxIndicatorPresenter.ts +93 -0
- package/src/controls/internal/DropdownChevronPresenter.ts +67 -0
- package/src/controls/internal/DropdownFieldPresenter.ts +110 -0
- package/src/controls/internal/DropdownOptionRowPresenter.ts +82 -0
- package/src/controls/internal/PopupPresenter.ts +198 -0
- package/src/controls/internal/PressableIndicatorPresenter.ts +32 -0
- package/src/controls/internal/PressableLabeledControl.ts +221 -0
- package/src/controls/internal/RadioIndicatorPresenter.ts +73 -0
- package/src/controls/internal/SliderPresenter.ts +157 -0
- package/src/controls/internal/SwitchIndicatorPresenter.ts +72 -0
- package/src/controls/internal/TextInputCore.ts +695 -0
- package/src/controls/internal/TextInputPresenter.ts +72 -0
- package/src/controls/templating.ts +54 -0
- package/src/core/Action.ts +94 -0
- package/src/core/Actions.ts +37 -0
- package/src/core/Animation.ts +412 -0
- package/src/core/Application.ts +328 -0
- package/src/core/Assets.ts +264 -0
- package/src/core/AttachedProperties.ts +32 -0
- package/src/core/Bitmap.ts +70 -0
- package/src/core/BoundCallback.ts +104 -0
- package/src/core/Callbacks.ts +17 -0
- package/src/core/ContextMenuManager.ts +466 -0
- package/src/core/DebugApi.ts +30 -0
- package/src/core/Disposable.ts +10 -0
- package/src/core/DragDropManager.ts +179 -0
- package/src/core/DragGesture.ts +184 -0
- package/src/core/DynamicAssetIds.ts +24 -0
- package/src/core/Errors.ts +48 -0
- package/src/core/EventRouter.ts +408 -0
- package/src/core/ExternalDropManager.ts +122 -0
- package/src/core/Fetch.ts +264 -0
- package/src/core/FetchFfi.ts +15 -0
- package/src/core/File.ts +1002 -0
- package/src/core/FocusAdornerManager.ts +263 -0
- package/src/core/FocusVisibility.ts +36 -0
- package/src/core/FrameScheduler.ts +28 -0
- package/src/core/KeyboardScroll.ts +161 -0
- package/src/core/KeyboardScrollTracker.ts +386 -0
- package/src/core/Logger.ts +80 -0
- package/src/core/Navigation.ts +13 -0
- package/src/core/Node.ts +1708 -0
- package/src/core/PersistedState.ts +102 -0
- package/src/core/PersistedUiState.ts +142 -0
- package/src/core/Platform.ts +219 -0
- package/src/core/Signal.ts +89 -0
- package/src/core/Theme.ts +365 -0
- package/src/core/Timers.ts +129 -0
- package/src/core/ToolTip.ts +122 -0
- package/src/core/ToolTipManager.ts +459 -0
- package/src/core/Transitions.ts +34 -0
- package/src/core/Typography.ts +204 -0
- package/src/core/Worker.ts +196 -0
- package/src/core/bind.ts +37 -0
- package/src/core/event_exports.ts +596 -0
- package/src/core/ffi.ts +728 -0
- package/src/host-services/runtime.ts +25 -0
- package/src/nodes/FlexBox.ts +789 -0
- package/src/nodes/GradientStop.ts +9 -0
- package/src/nodes/Grid.ts +183 -0
- package/src/nodes/Image.ts +189 -0
- package/src/nodes/Portal.ts +14 -0
- package/src/nodes/RichText.ts +312 -0
- package/src/nodes/ScrollBar.ts +570 -0
- package/src/nodes/ScrollBox.ts +415 -0
- package/src/nodes/ScrollState.ts +10 -0
- package/src/nodes/ScrollView.ts +511 -0
- package/src/nodes/Svg.ts +142 -0
- package/src/nodes/Text.ts +145 -0
- package/src/nodes/TextCore.ts +558 -0
- package/src/nodes/VirtualList.ts +431 -0
- package/src/nodes/helpers.ts +25 -0
- package/src/nodes/index.ts +14 -0
- package/src/tsconfig.json +7 -0
- package/src/worker/Worker.ts +169 -0
- package/src/worker/WorkerJob.ts +65 -0
- package/src/worker/ffi.ts +23 -0
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
import * as ui from "../bindings/ui";
|
|
2
|
+
import { Animation, AnimationTiming, getAnimationManager } from "../core/Animation";
|
|
3
|
+
import { Node } from "../core/Node";
|
|
4
|
+
import { throwNullArgument } from "../core/Errors";
|
|
5
|
+
import { NodeTransitions } from "../core/Transitions";
|
|
6
|
+
import {
|
|
7
|
+
canRestorePersistedScrollOffset,
|
|
8
|
+
clampPersistedScrollOffset,
|
|
9
|
+
storePersistedScrollOffset,
|
|
10
|
+
tryLoadPersistedScrollOffset,
|
|
11
|
+
} from "../core/PersistedUiState";
|
|
12
|
+
import { HandleValue, NodeType, Unit } from "../core/ffi";
|
|
13
|
+
import { ScrollState } from "./ScrollState";
|
|
14
|
+
|
|
15
|
+
const PROGRAMMATIC_SCROLL_ACK_TOLERANCE: f32 = 0.5;
|
|
16
|
+
|
|
17
|
+
class ScrollOffsetTransitionAnimation extends Animation {
|
|
18
|
+
constructor(
|
|
19
|
+
private readonly owner: ScrollView,
|
|
20
|
+
private readonly fromX: f32,
|
|
21
|
+
private readonly fromY: f32,
|
|
22
|
+
private readonly toX: f32,
|
|
23
|
+
private readonly toY: f32,
|
|
24
|
+
timing: AnimationTiming,
|
|
25
|
+
) {
|
|
26
|
+
super(timing);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
protected onSample(easedProgress: f32, _linearProgress: f32): void {
|
|
30
|
+
this.owner._applyAnimatedScrollOffset(
|
|
31
|
+
this.fromX + ((this.toX - this.fromX) * easedProgress),
|
|
32
|
+
this.fromY + ((this.toY - this.fromY) * easedProgress),
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class ScrollView extends Node {
|
|
38
|
+
private widthValue: f32 = 0.0;
|
|
39
|
+
private widthUnit: Unit = Unit.Pixel;
|
|
40
|
+
private hasWidth: bool = false;
|
|
41
|
+
private heightValue: f32 = 0.0;
|
|
42
|
+
private heightUnit: Unit = Unit.Pixel;
|
|
43
|
+
private hasHeight: bool = false;
|
|
44
|
+
private flexBasisValue: f32 = 0.0;
|
|
45
|
+
private hasFlexBasis: bool = false;
|
|
46
|
+
private flexGrowValue: f32 = 0.0;
|
|
47
|
+
private hasFlexGrow: bool = false;
|
|
48
|
+
private enableScrollX: bool = true;
|
|
49
|
+
private enableScrollY: bool = true;
|
|
50
|
+
private showScrollbarsValue: bool = true;
|
|
51
|
+
private frictionValue: f32 = 0.0;
|
|
52
|
+
private scrollOffsetX: f32 = 0.0;
|
|
53
|
+
private scrollOffsetY: f32 = 0.0;
|
|
54
|
+
private hasScrollOffset: bool = false;
|
|
55
|
+
private explicitContentWidth: f32 = -1.0;
|
|
56
|
+
private explicitContentHeight: f32 = -1.0;
|
|
57
|
+
private hasExplicitScrollContentSize: bool = false;
|
|
58
|
+
private hasPendingProgrammaticScroll: bool = false;
|
|
59
|
+
private pendingProgrammaticOffsetX: f32 = 0.0;
|
|
60
|
+
private pendingProgrammaticOffsetY: f32 = 0.0;
|
|
61
|
+
private hasFriction: bool = false;
|
|
62
|
+
private _scrollState: ScrollState = new ScrollState();
|
|
63
|
+
private persistScrollValue: bool = false;
|
|
64
|
+
private persistedScrollRestorePending: bool = false;
|
|
65
|
+
private transitionsValue: NodeTransitions | null = null;
|
|
66
|
+
private scrollOffsetTransitionAnimation: Animation | null = null;
|
|
67
|
+
|
|
68
|
+
get scrollState(): ScrollState {
|
|
69
|
+
return this._scrollState;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get isVerticalScrollEnabled(): bool {
|
|
73
|
+
return this.enableScrollY;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get isHorizontalScrollEnabled(): bool {
|
|
77
|
+
return this.enableScrollX;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
constructor() {
|
|
81
|
+
super();
|
|
82
|
+
// Make the viewport hit-testable so blank interior/padding starts resolve to this
|
|
83
|
+
// scroll surface instead of only working over interactive descendants.
|
|
84
|
+
this.onPointerDown((_x: f32, _y: f32): void => {});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
bindScrollState(state: ScrollState): this {
|
|
88
|
+
this._scrollState = state;
|
|
89
|
+
this.persistedScrollRestorePending = false;
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
nodeId(id: string): this {
|
|
94
|
+
super.nodeId(id);
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
width(value: f32, unit: Unit = Unit.Pixel): this {
|
|
99
|
+
this.widthValue = value;
|
|
100
|
+
this.widthUnit = unit;
|
|
101
|
+
this.hasWidth = true;
|
|
102
|
+
if (unit == Unit.Pixel) {
|
|
103
|
+
this._scrollState.viewportWidth.value = value;
|
|
104
|
+
}
|
|
105
|
+
if (this.hasBuiltHandle()) {
|
|
106
|
+
ui.setWidth(this.handle, value, <u32>unit);
|
|
107
|
+
this.notifyRetainedLayoutMutation();
|
|
108
|
+
}
|
|
109
|
+
return this;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
height(value: f32, unit: Unit = Unit.Pixel): this {
|
|
113
|
+
this.heightValue = value;
|
|
114
|
+
this.heightUnit = unit;
|
|
115
|
+
this.hasHeight = true;
|
|
116
|
+
if (unit == Unit.Pixel) {
|
|
117
|
+
this._scrollState.viewportHeight.value = value;
|
|
118
|
+
}
|
|
119
|
+
if (this.hasBuiltHandle()) {
|
|
120
|
+
ui.setHeight(this.handle, value, <u32>unit);
|
|
121
|
+
this.notifyRetainedLayoutMutation();
|
|
122
|
+
}
|
|
123
|
+
return this;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
fillWidth(): this {
|
|
127
|
+
this.width(100.0, Unit.Percent);
|
|
128
|
+
return this;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
fillHeight(): this {
|
|
132
|
+
this.height(100.0, Unit.Percent);
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
fillSize(): this {
|
|
137
|
+
this.fillWidth();
|
|
138
|
+
this.fillHeight();
|
|
139
|
+
return this;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
flexBasis(value: f32): this {
|
|
143
|
+
this.flexBasisValue = value;
|
|
144
|
+
this.hasFlexBasis = true;
|
|
145
|
+
if (this.hasBuiltHandle()) {
|
|
146
|
+
ui.setFlexBasis(this.handle, value);
|
|
147
|
+
this.notifyRetainedLayoutMutation();
|
|
148
|
+
}
|
|
149
|
+
return this;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
flexGrow(value: f32): this {
|
|
153
|
+
this.flexGrowValue = value;
|
|
154
|
+
this.hasFlexGrow = true;
|
|
155
|
+
if (this.hasBuiltHandle()) {
|
|
156
|
+
ui.setFlexGrow(this.handle, value);
|
|
157
|
+
this.notifyRetainedLayoutMutation();
|
|
158
|
+
}
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
grow(value: f32 = 1.0): this {
|
|
163
|
+
this.flexGrow(value);
|
|
164
|
+
return this;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
scrollEnabledX(flag: bool): this {
|
|
168
|
+
this.enableScrollX = flag;
|
|
169
|
+
if (this.hasBuiltHandle()) {
|
|
170
|
+
ui.setScrollEnabled(this.handle, this.enableScrollX, this.enableScrollY);
|
|
171
|
+
this.notifyRetainedMutation();
|
|
172
|
+
}
|
|
173
|
+
return this;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
scrollEnabledY(flag: bool): this {
|
|
177
|
+
this.enableScrollY = flag;
|
|
178
|
+
if (this.hasBuiltHandle()) {
|
|
179
|
+
ui.setScrollEnabled(this.handle, this.enableScrollX, this.enableScrollY);
|
|
180
|
+
this.notifyRetainedMutation();
|
|
181
|
+
}
|
|
182
|
+
return this;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
showScrollbars(flag: bool): this {
|
|
186
|
+
this.showScrollbarsValue = flag;
|
|
187
|
+
if (this.hasBuiltHandle()) {
|
|
188
|
+
ui.setShowScrollbars(this.handle, flag);
|
|
189
|
+
this.notifyRetainedMutation();
|
|
190
|
+
}
|
|
191
|
+
return this;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
friction(value: f32): this {
|
|
195
|
+
this.frictionValue = value;
|
|
196
|
+
this.hasFriction = true;
|
|
197
|
+
if (this.hasBuiltHandle()) {
|
|
198
|
+
ui.setScrollFriction(this.handle, value);
|
|
199
|
+
this.notifyRetainedMutation();
|
|
200
|
+
}
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
persistScroll(flag: bool = true): this {
|
|
205
|
+
this.persistScrollValue = flag;
|
|
206
|
+
if (!flag) {
|
|
207
|
+
this.persistedScrollRestorePending = false;
|
|
208
|
+
}
|
|
209
|
+
return this;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
transitions(transitions: NodeTransitions | null): this {
|
|
213
|
+
this.transitionsValue = transitions;
|
|
214
|
+
return this;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
scrollTo(x: f32, y: f32): this {
|
|
218
|
+
this.cancelScrollOffsetTransition();
|
|
219
|
+
if (this.hasBuiltHandle() && (this.scrollOffsetX != x || this.scrollOffsetY != y)) {
|
|
220
|
+
this.takeProgrammaticScrollOwnership();
|
|
221
|
+
}
|
|
222
|
+
this._applyAnimatedScrollOffset(x, y);
|
|
223
|
+
return this;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
scrollToAnimated(x: f32, y: f32, timing: AnimationTiming): this {
|
|
227
|
+
this.cancelScrollOffsetTransition();
|
|
228
|
+
if (!this.hasBuiltHandle() || (this.scrollOffsetX == x && this.scrollOffsetY == y)) {
|
|
229
|
+
this._applyAnimatedScrollOffset(x, y);
|
|
230
|
+
return this;
|
|
231
|
+
}
|
|
232
|
+
this.takeProgrammaticScrollOwnership();
|
|
233
|
+
this.startScrollOffsetAnimation(x, y, timing);
|
|
234
|
+
return this;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
scrollOffset(x: f32, y: f32): this {
|
|
238
|
+
this.cancelScrollOffsetTransition();
|
|
239
|
+
if (this.shouldAnimateScrollOffset(x, y)) {
|
|
240
|
+
const transitions = changetype<NodeTransitions>(this.transitionsValue);
|
|
241
|
+
const timing = changetype<AnimationTiming>(transitions.scrollOffsetTiming);
|
|
242
|
+
this.takeProgrammaticScrollOwnership();
|
|
243
|
+
this.startScrollOffsetAnimation(x, y, timing);
|
|
244
|
+
return this;
|
|
245
|
+
}
|
|
246
|
+
if (this.hasBuiltHandle() && (this.scrollOffsetX != x || this.scrollOffsetY != y)) {
|
|
247
|
+
this.takeProgrammaticScrollOwnership();
|
|
248
|
+
}
|
|
249
|
+
this._applyAnimatedScrollOffset(x, y);
|
|
250
|
+
return this;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
scrollContentSize(contentWidth: f32 = -1.0, contentHeight: f32 = -1.0): this {
|
|
254
|
+
this.explicitContentWidth = contentWidth;
|
|
255
|
+
this.explicitContentHeight = contentHeight;
|
|
256
|
+
this.hasExplicitScrollContentSize = true;
|
|
257
|
+
if (contentWidth >= 0.0) {
|
|
258
|
+
this._scrollState.contentWidth.value = contentWidth;
|
|
259
|
+
}
|
|
260
|
+
if (contentHeight >= 0.0) {
|
|
261
|
+
this._scrollState.contentHeight.value = contentHeight;
|
|
262
|
+
}
|
|
263
|
+
if (this.hasBuiltHandle()) {
|
|
264
|
+
ui.setScrollContentSize(this.handle, contentWidth, contentHeight);
|
|
265
|
+
this.notifyRetainedMutation();
|
|
266
|
+
}
|
|
267
|
+
return this;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
setRuntimeScrollOffset(x: f32, y: f32): void {
|
|
271
|
+
this.cancelScrollOffsetTransition();
|
|
272
|
+
this._applyAnimatedScrollOffset(x, y);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
onClick(cb: () => void): this {
|
|
276
|
+
super.onClick(cb);
|
|
277
|
+
return this;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
onPointerEnter(cb: () => void): this {
|
|
281
|
+
super.onPointerEnter(cb);
|
|
282
|
+
return this;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
onPointerLeave(cb: () => void): this {
|
|
286
|
+
super.onPointerLeave(cb);
|
|
287
|
+
return this;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
child(node: Node): this {
|
|
291
|
+
if (node == null) {
|
|
292
|
+
throwNullArgument("ScrollView.child", "node");
|
|
293
|
+
}
|
|
294
|
+
this.appendChild(node);
|
|
295
|
+
return this;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
children(nodes: Array<Node>): this {
|
|
299
|
+
this.replaceChildren(nodes);
|
|
300
|
+
return this;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
build(): u64 {
|
|
304
|
+
if (this.hasBuiltHandle()) {
|
|
305
|
+
return this.handle;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
this.handle = ui.createNode(<u32>NodeType.ScrollView);
|
|
309
|
+
this.applyNodeMetadata();
|
|
310
|
+
this.finishBuild();
|
|
311
|
+
ui.setScrollEnabled(this.handle, this.enableScrollX, this.enableScrollY);
|
|
312
|
+
ui.setShowScrollbars(this.handle, this.showScrollbarsValue);
|
|
313
|
+
if (this.hasFriction) {
|
|
314
|
+
ui.setScrollFriction(this.handle, this.frictionValue);
|
|
315
|
+
}
|
|
316
|
+
if (this.hasExplicitScrollContentSize) {
|
|
317
|
+
ui.setScrollContentSize(this.handle, this.explicitContentWidth, this.explicitContentHeight);
|
|
318
|
+
}
|
|
319
|
+
if (this.hasWidth) {
|
|
320
|
+
ui.setWidth(this.handle, this.widthValue, <u32>this.widthUnit);
|
|
321
|
+
}
|
|
322
|
+
if (this.hasHeight) {
|
|
323
|
+
ui.setHeight(this.handle, this.heightValue, <u32>this.heightUnit);
|
|
324
|
+
}
|
|
325
|
+
if (this.hasFlexGrow) {
|
|
326
|
+
ui.setFlexGrow(this.handle, this.flexGrowValue);
|
|
327
|
+
}
|
|
328
|
+
if (this.hasFlexBasis) {
|
|
329
|
+
ui.setFlexBasis(this.handle, this.flexBasisValue);
|
|
330
|
+
}
|
|
331
|
+
if (this.hasScrollOffset) {
|
|
332
|
+
this.prepareProgrammaticScroll(this.scrollOffsetX, this.scrollOffsetY);
|
|
333
|
+
ui.setScrollOffset(this.handle, this.scrollOffsetX, this.scrollOffsetY);
|
|
334
|
+
}
|
|
335
|
+
this.buildChildren();
|
|
336
|
+
return this.handle;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
dispose(): void {
|
|
340
|
+
this.cancelScrollOffsetTransition();
|
|
341
|
+
this.disposeTree();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
_debugMainAxisPercentValue(isHorizontal: bool): f32 {
|
|
345
|
+
if (isHorizontal) {
|
|
346
|
+
return this.hasWidth && this.widthUnit == Unit.Percent ? this.widthValue : -1.0;
|
|
347
|
+
}
|
|
348
|
+
return this.hasHeight && this.heightUnit == Unit.Percent ? this.heightValue : -1.0;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
_handleScroll(
|
|
352
|
+
offsetX: f32,
|
|
353
|
+
offsetY: f32,
|
|
354
|
+
contentWidth: f32,
|
|
355
|
+
contentHeight: f32,
|
|
356
|
+
viewportWidth: f32,
|
|
357
|
+
viewportHeight: f32,
|
|
358
|
+
): void {
|
|
359
|
+
this.syncScrollMetrics(contentWidth, contentHeight, viewportWidth, viewportHeight);
|
|
360
|
+
if (this.tryRestorePersistedScrollOffset()) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (this.hasPendingProgrammaticScroll) {
|
|
364
|
+
this.hasPendingProgrammaticScroll = false;
|
|
365
|
+
if (this.matchesPendingProgrammaticScroll(offsetX, offsetY)) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
this.cancelScrollOffsetTransition();
|
|
370
|
+
this.updateRetainedScrollOffset(offsetX, offsetY);
|
|
371
|
+
this._scrollState.offsetX.value = offsetX;
|
|
372
|
+
this._scrollState.offsetY.value = offsetY;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
protected capturePersistedState(): void {
|
|
376
|
+
super.capturePersistedState();
|
|
377
|
+
if (!this.persistScrollValue) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
this.storePersistedScrollOffset(this.scrollOffsetX, this.scrollOffsetY);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
protected restorePersistedState(): void {
|
|
384
|
+
super.restorePersistedState();
|
|
385
|
+
if (!this.persistScrollValue) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
this.persistedScrollRestorePending = true;
|
|
389
|
+
this.tryRestorePersistedScrollOffset();
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
private syncScrollMetrics(
|
|
393
|
+
contentWidth: f32,
|
|
394
|
+
contentHeight: f32,
|
|
395
|
+
viewportWidth: f32,
|
|
396
|
+
viewportHeight: f32,
|
|
397
|
+
): void {
|
|
398
|
+
this._scrollState.contentWidth.value = contentWidth;
|
|
399
|
+
this._scrollState.contentHeight.value = contentHeight;
|
|
400
|
+
this._scrollState.viewportWidth.value = viewportWidth;
|
|
401
|
+
this._scrollState.viewportHeight.value = viewportHeight;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
private updateRetainedScrollOffset(x: f32, y: f32): void {
|
|
405
|
+
this.scrollOffsetX = x;
|
|
406
|
+
this.scrollOffsetY = y;
|
|
407
|
+
this.hasScrollOffset = true;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
_applyAnimatedScrollOffset(x: f32, y: f32): void {
|
|
411
|
+
this.updateRetainedScrollOffset(x, y);
|
|
412
|
+
this._scrollState.offsetX.value = x;
|
|
413
|
+
this._scrollState.offsetY.value = y;
|
|
414
|
+
if (this.hasBuiltHandle()) {
|
|
415
|
+
this.prepareProgrammaticScroll(x, y);
|
|
416
|
+
ui.setScrollOffset(this.handle, x, y);
|
|
417
|
+
this.notifyRetainedMutation();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
private shouldAnimateScrollOffset(x: f32, y: f32): bool {
|
|
422
|
+
const transitions = this.transitionsValue;
|
|
423
|
+
if (transitions === null || transitions.scrollOffsetTiming === null) {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
if (!this.hasBuiltHandle()) {
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
return this.scrollOffsetX != x || this.scrollOffsetY != y;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
private cancelScrollOffsetTransition(): void {
|
|
433
|
+
const animation = this.scrollOffsetTransitionAnimation;
|
|
434
|
+
if (animation !== null) {
|
|
435
|
+
animation.cancel();
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
private startScrollOffsetAnimation(x: f32, y: f32, timing: AnimationTiming): void {
|
|
440
|
+
this.scrollOffsetTransitionAnimation = getAnimationManager().start(
|
|
441
|
+
new ScrollOffsetTransitionAnimation(
|
|
442
|
+
this,
|
|
443
|
+
this.scrollOffsetX,
|
|
444
|
+
this.scrollOffsetY,
|
|
445
|
+
x,
|
|
446
|
+
y,
|
|
447
|
+
timing,
|
|
448
|
+
),
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private takeProgrammaticScrollOwnership(): void {
|
|
453
|
+
ui.clearMomentumScroll();
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
private prepareProgrammaticScroll(x: f32, y: f32): void {
|
|
457
|
+
this.hasPendingProgrammaticScroll = true;
|
|
458
|
+
this.pendingProgrammaticOffsetX = x;
|
|
459
|
+
this.pendingProgrammaticOffsetY = y;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
private matchesPendingProgrammaticScroll(offsetX: f32, offsetY: f32): bool {
|
|
463
|
+
let deltaX = offsetX - this.pendingProgrammaticOffsetX;
|
|
464
|
+
if (deltaX < 0.0) {
|
|
465
|
+
deltaX = -deltaX;
|
|
466
|
+
}
|
|
467
|
+
if (deltaX > PROGRAMMATIC_SCROLL_ACK_TOLERANCE) {
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
let deltaY = offsetY - this.pendingProgrammaticOffsetY;
|
|
472
|
+
if (deltaY < 0.0) {
|
|
473
|
+
deltaY = -deltaY;
|
|
474
|
+
}
|
|
475
|
+
return deltaY <= PROGRAMMATIC_SCROLL_ACK_TOLERANCE;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
private tryRestorePersistedScrollOffset(): bool {
|
|
479
|
+
if (!this.persistedScrollRestorePending || !this.persistScrollValue) {
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
const nodeId = this.getNodeId();
|
|
483
|
+
if (nodeId === null || nodeId.length == 0) {
|
|
484
|
+
this.persistedScrollRestorePending = false;
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
const restored = tryLoadPersistedScrollOffset(nodeId);
|
|
488
|
+
if (restored === null) {
|
|
489
|
+
this.persistedScrollRestorePending = false;
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
if (!canRestorePersistedScrollOffset(this._scrollState, restored, this.enableScrollX, this.enableScrollY)) {
|
|
493
|
+
return false;
|
|
494
|
+
}
|
|
495
|
+
this.persistedScrollRestorePending = false;
|
|
496
|
+
const clamped = clampPersistedScrollOffset(this._scrollState, restored, this.enableScrollX, this.enableScrollY);
|
|
497
|
+
this.setRuntimeScrollOffset(clamped.x, clamped.y);
|
|
498
|
+
return true;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
private storePersistedScrollOffset(x: f32, y: f32): void {
|
|
502
|
+
if (!this.persistScrollValue) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
const nodeId = this.getNodeId();
|
|
506
|
+
if (nodeId === null || nodeId.length == 0) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
storePersistedScrollOffset(nodeId, x, y);
|
|
510
|
+
}
|
|
511
|
+
}
|
package/src/nodes/Svg.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
acquireSvgAsset,
|
|
3
|
+
AssetLoadState,
|
|
4
|
+
getSvgAssetError,
|
|
5
|
+
getSvgAssetHeight,
|
|
6
|
+
getSvgAssetState,
|
|
7
|
+
getSvgAssetUrl,
|
|
8
|
+
getSvgAssetWidth,
|
|
9
|
+
releaseSvgAsset,
|
|
10
|
+
} from "../core/Assets";
|
|
11
|
+
import * as ui from "../bindings/ui";
|
|
12
|
+
import { HandleValue, NodeType, SemanticRole } from "../core/ffi";
|
|
13
|
+
import { Signal } from "../core/Signal";
|
|
14
|
+
import { FlexBox, FlexBoxProps } from "./FlexBox";
|
|
15
|
+
|
|
16
|
+
export class Svg extends FlexBox {
|
|
17
|
+
private svgIdValue: u32;
|
|
18
|
+
private sourceUrlValue: string = "";
|
|
19
|
+
private tintColorValue: u32 = 0;
|
|
20
|
+
private ownedSvgAssetId: u32 = 0;
|
|
21
|
+
|
|
22
|
+
constructor(svgId: u32 = 0, tintColor: u32 = 0) {
|
|
23
|
+
super();
|
|
24
|
+
this.svgIdValue = svgId;
|
|
25
|
+
this.tintColorValue = tintColor;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static from(props: FlexBoxProps, svgId: u32 = 0, tintColor: u32 = 0): Svg {
|
|
29
|
+
const svg = new Svg(svgId, tintColor);
|
|
30
|
+
return svg.applyProps(props);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static load(url: string, tintColor: u32 = 0): Svg {
|
|
34
|
+
return new Svg(0, tintColor).source(url);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static fromUrl(props: FlexBoxProps, url: string, tintColor: u32 = 0): Svg {
|
|
38
|
+
return Svg.load(url, tintColor).applyProps(props);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
svg(svgId: u32): this {
|
|
42
|
+
this.releaseOwnedSourceAsset();
|
|
43
|
+
this.sourceUrlValue = "";
|
|
44
|
+
this.svgIdValue = svgId;
|
|
45
|
+
if (this.hasBuiltHandle()) {
|
|
46
|
+
this.applySvgSource();
|
|
47
|
+
this.notifyRetainedMutation();
|
|
48
|
+
}
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
source(url: string): this {
|
|
53
|
+
if (url.length == 0) {
|
|
54
|
+
return this.clearSource();
|
|
55
|
+
}
|
|
56
|
+
if (this.ownedSvgAssetId != 0 && this.sourceUrlValue == url) {
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
this.releaseOwnedSourceAsset();
|
|
60
|
+
this.sourceUrlValue = url;
|
|
61
|
+
this.svgIdValue = acquireSvgAsset(url);
|
|
62
|
+
this.ownedSvgAssetId = this.svgIdValue;
|
|
63
|
+
if (this.hasBuiltHandle()) {
|
|
64
|
+
this.applySvgSource();
|
|
65
|
+
this.notifyRetainedMutation();
|
|
66
|
+
}
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
clearSource(): this {
|
|
71
|
+
this.releaseOwnedSourceAsset();
|
|
72
|
+
this.sourceUrlValue = "";
|
|
73
|
+
this.svgIdValue = 0;
|
|
74
|
+
if (this.hasBuiltHandle()) {
|
|
75
|
+
this.applySvgSource();
|
|
76
|
+
this.notifyRetainedMutation();
|
|
77
|
+
}
|
|
78
|
+
return this;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
tint(color: u32): this {
|
|
82
|
+
this.tintColorValue = color;
|
|
83
|
+
if (this.hasBuiltHandle()) {
|
|
84
|
+
this.applySvgSource();
|
|
85
|
+
this.notifyRetainedMutation();
|
|
86
|
+
}
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
altText(value: string): this {
|
|
91
|
+
this.semanticRole(SemanticRole.Image);
|
|
92
|
+
this.semanticLabel(value);
|
|
93
|
+
return this;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
build(): u64 {
|
|
97
|
+
this.buildStyledNode(NodeType.Svg, false);
|
|
98
|
+
this.applySvgSource();
|
|
99
|
+
return this.handle;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
dispose(): void {
|
|
103
|
+
this.releaseOwnedSourceAsset();
|
|
104
|
+
super.dispose();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
assetStateSignal(): Signal<AssetLoadState> {
|
|
108
|
+
return getSvgAssetState(this.svgIdValue);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
assetState(): AssetLoadState {
|
|
112
|
+
return this.assetStateSignal().value;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
assetError(): string {
|
|
116
|
+
return getSvgAssetError(this.svgIdValue);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
assetUrl(): string {
|
|
120
|
+
return this.sourceUrlValue.length > 0 ? this.sourceUrlValue : getSvgAssetUrl(this.svgIdValue);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
assetWidth(): f32 {
|
|
124
|
+
return getSvgAssetWidth(this.svgIdValue);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
assetHeight(): f32 {
|
|
128
|
+
return getSvgAssetHeight(this.svgIdValue);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private applySvgSource(): void {
|
|
132
|
+
ui.setSvg(this.handle, this.svgIdValue, this.tintColorValue);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private releaseOwnedSourceAsset(): void {
|
|
136
|
+
if (this.ownedSvgAssetId == 0) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
releaseSvgAsset(this.ownedSvgAssetId);
|
|
140
|
+
this.ownedSvgAssetId = 0;
|
|
141
|
+
}
|
|
142
|
+
}
|