@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,570 @@
1
+ import * as ui from "../bindings/ui";
2
+ import { HandlerAction } from "../core/Action";
3
+ import { Disposable, disposeAll } from "../core/Disposable";
4
+ import { DragCompletedEvent, DragDeltaEvent, DragGesture, DragGestureHost, DragStartedEvent } from "../core/DragGesture";
5
+ import { markNeedsCommit } from "../core/FrameScheduler";
6
+ import { AlignItems, CursorStyle, FlexDirection, HandleValue, Orientation, PointerEventType, Unit } from "../core/ffi";
7
+ import { isCoarsePointer } from "../core/Platform";
8
+ import { Signal } from "../core/Signal";
9
+ import { Theme, activeTheme } from "../core/Theme";
10
+ import { warn } from "../core/Logger";
11
+ import { FlexBox } from "./FlexBox";
12
+ import { Column } from "./helpers";
13
+ import { ScrollState } from "./ScrollState";
14
+
15
+ const DEFAULT_TRACK_WIDTH: f32 = 8.0;
16
+ const DEFAULT_THUMB_WIDTH: f32 = 8.0;
17
+ const DEFAULT_MIN_THUMB_HEIGHT: f32 = 18.0;
18
+
19
+ function noopPointerCallback(_x: f32, _y: f32): void {}
20
+
21
+ class ScrollMetrics {
22
+ viewportSize: f32 = 0.0;
23
+ contentSize: f32 = 0.0;
24
+ maxOffset: f32 = 0.0;
25
+ thumbSize: f32 = 0.0;
26
+ maxThumbOffset: f32 = 0.0;
27
+ thumbOffset: f32 = 0.0;
28
+ trailingSpacerSize: f32 = 0.0;
29
+ }
30
+
31
+ class ScrollBarTrackNode extends FlexBox {
32
+ private owner: ScrollBar | null = null;
33
+ private readonly inertCoarseValue: bool;
34
+
35
+ constructor(interactive: bool) {
36
+ super();
37
+ this.inertCoarseValue = !interactive;
38
+ this.onPointerDown(noopPointerCallback);
39
+ }
40
+
41
+ bindOwner(owner: ScrollBar): this {
42
+ this.owner = owner;
43
+ return this;
44
+ }
45
+
46
+ _handlePointerEvent(eventType: PointerEventType, x: f32, y: f32, modifiers: u32 = 0): void {
47
+ super._handlePointerEvent(eventType, x, y, modifiers);
48
+ if (this.inertCoarseValue) {
49
+ return;
50
+ }
51
+ if (eventType == PointerEventType.Down) {
52
+ const owner = this.owner;
53
+ if (owner !== null) {
54
+ owner.handleTrackPointerDown(this.builtHandle, x, y);
55
+ }
56
+ }
57
+ }
58
+ }
59
+
60
+ class ScrollBarThumbNode extends FlexBox implements DragGestureHost {
61
+ private owner: ScrollBar | null = null;
62
+ private readonly dragGesture!: DragGesture;
63
+ private readonly inertCoarseValue: bool;
64
+
65
+ constructor(interactive: bool) {
66
+ super();
67
+ this.inertCoarseValue = !interactive;
68
+ this.dragGesture = new DragGesture(this).threshold(0.0);
69
+ this.onPointerDown(noopPointerCallback);
70
+ if (interactive) {
71
+ this.cursor(CursorStyle.Grab);
72
+ this.onPointerMove(noopPointerCallback);
73
+ this.onPointerUp(noopPointerCallback);
74
+ }
75
+ this.dragGesture.started.bind(this, (thumb: ScrollBarThumbNode, event: DragStartedEvent): void => {
76
+ thumb.handleDragStarted(event);
77
+ });
78
+ this.dragGesture.delta.bind(this, (thumb: ScrollBarThumbNode, event: DragDeltaEvent): void => {
79
+ thumb.handleDragDelta(event);
80
+ });
81
+ this.dragGesture.completed.bind(this, (thumb: ScrollBarThumbNode, event: DragCompletedEvent): void => {
82
+ thumb.handleDragCompleted(event);
83
+ });
84
+ }
85
+
86
+ bindOwner(owner: ScrollBar): this {
87
+ this.owner = owner;
88
+ return this;
89
+ }
90
+
91
+ _captureDragPointer(): void {
92
+ this.capturePointer();
93
+ }
94
+
95
+ _releaseDragPointer(): void {
96
+ this.releasePointer();
97
+ }
98
+
99
+ build(): u64 {
100
+ const handle = super.build();
101
+ if (this.dragGesture.isDragging) {
102
+ this.capturePointer();
103
+ }
104
+ return handle;
105
+ }
106
+
107
+ _handlePointerEvent(eventType: PointerEventType, x: f32, y: f32, modifiers: u32 = 0): void {
108
+ super._handlePointerEvent(eventType, x, y, modifiers);
109
+ if (this.inertCoarseValue) {
110
+ return;
111
+ }
112
+ const owner = this.owner;
113
+ if (owner === null) {
114
+ return;
115
+ }
116
+ if (eventType == PointerEventType.Down) {
117
+ if (owner.canStartThumbDrag()) {
118
+ this.dragGesture.handlePointerDown(x, y, modifiers);
119
+ }
120
+ return;
121
+ }
122
+ if (eventType == PointerEventType.Move) {
123
+ this.dragGesture.handlePointerMove(x, y, modifiers);
124
+ return;
125
+ }
126
+ if (eventType == PointerEventType.Up) {
127
+ this.dragGesture.handlePointerUp(x, y, modifiers);
128
+ return;
129
+ }
130
+ }
131
+
132
+ cancelDrag(): void {
133
+ this.dragGesture.cancel();
134
+ }
135
+
136
+ isDragging(): bool {
137
+ return this.dragGesture.isDragging;
138
+ }
139
+
140
+ private handleDragStarted(event: DragStartedEvent): void {
141
+ const owner = this.owner;
142
+ if (owner !== null) {
143
+ owner.handleThumbDragStarted(event);
144
+ }
145
+ }
146
+
147
+ private handleDragDelta(event: DragDeltaEvent): void {
148
+ const owner = this.owner;
149
+ if (owner !== null) {
150
+ owner.handleThumbDragDelta(event);
151
+ }
152
+ }
153
+
154
+ private handleDragCompleted(event: DragCompletedEvent): void {
155
+ const owner = this.owner;
156
+ if (owner !== null) {
157
+ owner.handleThumbDragCompleted(event);
158
+ }
159
+ }
160
+ }
161
+
162
+ function clamp(value: f32, min: f32, max: f32): f32 {
163
+ if (value < min) {
164
+ return min;
165
+ }
166
+ if (value > max) {
167
+ return max;
168
+ }
169
+ return value;
170
+ }
171
+
172
+ export enum ScrollBarVisibility {
173
+ Always = 0,
174
+ Auto = 1,
175
+ Never = 2,
176
+ }
177
+
178
+ export class ScrollBar {
179
+ private targetHandle: u64 = <u64>HandleValue.Invalid;
180
+ private dragStartOffset: f32 = 0.0;
181
+ private readonly scrollState: ScrollState;
182
+ private readonly orientation: Orientation;
183
+ private readonly disposables: Array<Disposable> = new Array<Disposable>();
184
+ private readonly metrics: ScrollMetrics = new ScrollMetrics();
185
+ readonly chromeMetricVersion: Signal<i32> = new Signal<i32>(0);
186
+ private readonly trackNode: ScrollBarTrackNode;
187
+ private readonly trackStrip: FlexBox;
188
+ private readonly leadingSpacerNode: FlexBox;
189
+ private readonly thumbNode: ScrollBarThumbNode;
190
+ private readonly trailingSpacerNode: FlexBox;
191
+ private trackThicknessValue: f32 = DEFAULT_TRACK_WIDTH;
192
+ private thumbThicknessValue: f32 = DEFAULT_THUMB_WIDTH;
193
+ private minThumbHeightValue: f32 = DEFAULT_MIN_THUMB_HEIGHT;
194
+ private trackCornerRadiusValue: f32 = 0.0;
195
+ private thumbCornerRadiusValue: f32 = 0.0;
196
+ private trackColorValue: u32 = activeTheme.value.colors.scrollbarTrack;
197
+ private thumbColorValue: u32 = activeTheme.value.colors.scrollbarThumb;
198
+ private trackColorOverridden: bool = false;
199
+ private thumbColorOverridden: bool = false;
200
+ private chromeVisibleValue: bool = true;
201
+
202
+ constructor(scrollState: ScrollState, orientation: Orientation = Orientation.Vertical) {
203
+ this.scrollState = scrollState;
204
+ const resolvedOrientation = orientation == Orientation.Horizontal ? Orientation.Horizontal : Orientation.Vertical;
205
+ this.orientation = resolvedOrientation;
206
+ const interactive = !isCoarsePointer();
207
+ const leadingSpacerNode = new FlexBox();
208
+ const thumbNode = new ScrollBarThumbNode(interactive);
209
+ const trailingSpacerNode = new FlexBox();
210
+ const trackStrip = (resolvedOrientation == Orientation.Horizontal
211
+ ? new FlexBox().flexDirection(FlexDirection.Row).children([
212
+ leadingSpacerNode,
213
+ thumbNode,
214
+ trailingSpacerNode,
215
+ ])
216
+ : Column(
217
+ leadingSpacerNode,
218
+ thumbNode,
219
+ trailingSpacerNode,
220
+ ))
221
+ .alignItems(AlignItems.Center);
222
+ const trackNode = new ScrollBarTrackNode(interactive)
223
+ .clipToBounds(true)
224
+ .child(trackStrip) as ScrollBarTrackNode;
225
+ this.leadingSpacerNode = leadingSpacerNode;
226
+ this.thumbNode = thumbNode;
227
+ this.trailingSpacerNode = trailingSpacerNode;
228
+ this.trackStrip = trackStrip;
229
+ this.trackNode = trackNode;
230
+ this.thumbNode.bindOwner(this);
231
+ this.trackNode.bindOwner(this);
232
+ this.attachListeners();
233
+ this.handleThemeChanged(activeTheme.value);
234
+ this.applyGeometryStyle();
235
+ this.syncVisualState();
236
+ }
237
+
238
+ get axis(): Orientation {
239
+ return this.orientation;
240
+ }
241
+
242
+ get thickness(): f32 {
243
+ return this.trackThicknessValue;
244
+ }
245
+
246
+ trackWidth(value: f32): this {
247
+ this.trackThickness(value);
248
+ return this;
249
+ }
250
+
251
+ trackThickness(value: f32): this {
252
+ const next = value > 0.0 ? value : 1.0;
253
+ if (value <= 0.0) {
254
+ warn("Layout", "ScrollBar.trackThickness() received " + value.toString() + "; clamping to 1.0.");
255
+ }
256
+ if (this.trackThicknessValue == next) {
257
+ return this;
258
+ }
259
+ this.trackThicknessValue = next;
260
+ this.applyGeometryStyle();
261
+ this.chromeMetricVersion.value = this.chromeMetricVersion.value + 1;
262
+ return this;
263
+ }
264
+
265
+ thumbWidth(value: f32): this {
266
+ this.thumbThickness(value);
267
+ return this;
268
+ }
269
+
270
+ thumbThickness(value: f32): this {
271
+ if (value <= 0.0) {
272
+ warn("Layout", "ScrollBar.thumbThickness() received " + value.toString() + "; clamping to 1.0.");
273
+ }
274
+ this.thumbThicknessValue = value > 0.0 ? value : 1.0;
275
+ this.applyGeometryStyle();
276
+ return this;
277
+ }
278
+
279
+ thumbMinHeight(value: f32): this {
280
+ if (value <= 0.0) {
281
+ warn("Layout", "ScrollBar.thumbMinHeight() received " + value.toString() + "; clamping to 1.0.");
282
+ }
283
+ this.minThumbHeightValue = value > 0.0 ? value : 1.0;
284
+ this.syncVisualState();
285
+ return this;
286
+ }
287
+
288
+ trackCornerRadius(radius: f32): this {
289
+ this.trackCornerRadiusValue = radius > 0.0 ? radius : 0.0;
290
+ this.trackNode.cornerRadius(this.trackCornerRadiusValue);
291
+ return this;
292
+ }
293
+
294
+ thumbCornerRadius(radius: f32): this {
295
+ this.thumbCornerRadiusValue = radius > 0.0 ? radius : 0.0;
296
+ this.thumbNode.cornerRadius(this.thumbCornerRadiusValue);
297
+ return this;
298
+ }
299
+
300
+ trackColor(color: u32): this {
301
+ this.trackColorOverridden = true;
302
+ this.trackColorValue = color;
303
+ this.applyColorStyle();
304
+ return this;
305
+ }
306
+
307
+ thumbColor(color: u32): this {
308
+ this.thumbColorOverridden = true;
309
+ this.thumbColorValue = color;
310
+ this.applyColorStyle();
311
+ return this;
312
+ }
313
+
314
+ bindScrollHandle(handle: u64): void {
315
+ this.targetHandle = handle;
316
+ this.trackNode._bindScrollProxyTarget(handle);
317
+ this.trackStrip._bindScrollProxyTarget(handle);
318
+ this.leadingSpacerNode._bindScrollProxyTarget(handle);
319
+ this.thumbNode._bindScrollProxyTarget(handle);
320
+ this.trailingSpacerNode._bindScrollProxyTarget(handle);
321
+ }
322
+
323
+ clearScrollHandle(handle: u64): void {
324
+ if (this.targetHandle == handle) {
325
+ this.targetHandle = <u64>HandleValue.Invalid;
326
+ this.trackNode._bindScrollProxyTarget(<u64>HandleValue.Invalid);
327
+ this.trackStrip._bindScrollProxyTarget(<u64>HandleValue.Invalid);
328
+ this.leadingSpacerNode._bindScrollProxyTarget(<u64>HandleValue.Invalid);
329
+ this.thumbNode._bindScrollProxyTarget(<u64>HandleValue.Invalid);
330
+ this.trailingSpacerNode._bindScrollProxyTarget(<u64>HandleValue.Invalid);
331
+ }
332
+ }
333
+
334
+ render(): FlexBox {
335
+ return this.trackNode;
336
+ }
337
+
338
+ chromeVisible(flag: bool): void {
339
+ if (this.chromeVisibleValue == flag) {
340
+ return;
341
+ }
342
+ this.chromeVisibleValue = flag;
343
+ if (!flag && this.thumbNode.isDragging()) {
344
+ this.thumbNode.cancelDrag();
345
+ }
346
+ this.applyGeometryStyle();
347
+ this.syncVisualState();
348
+ this.applyThumbCursor();
349
+ }
350
+
351
+ dispose(): void {
352
+ this.thumbNode.cancelDrag();
353
+ disposeAll(this.disposables);
354
+ }
355
+
356
+ canStartThumbDrag(): bool {
357
+ const metrics = this.computeMetrics();
358
+ return this.chromeVisibleValue && metrics.maxOffset > 0.0 && metrics.maxThumbOffset > 0.0;
359
+ }
360
+
361
+ isDragging(): bool {
362
+ return this.thumbNode.isDragging();
363
+ }
364
+
365
+ handleThumbDragStarted(_event: DragStartedEvent): void {
366
+ this.dragStartOffset = this.axisOffset();
367
+ this.applyThumbCursor();
368
+ }
369
+
370
+ handleThumbDragCompleted(_event: DragCompletedEvent): void {
371
+ this.applyThumbCursor();
372
+ }
373
+
374
+ handleMetricsChanged(): void {
375
+ this.syncVisualState();
376
+ }
377
+
378
+ refreshNow(): void {
379
+ this.syncVisualState();
380
+ }
381
+
382
+ handleThumbDragDelta(event: DragDeltaEvent): void {
383
+ if (!this.thumbNode.isDragging()) {
384
+ return;
385
+ }
386
+ const metrics = this.computeMetrics();
387
+ if (metrics.maxOffset <= 0.0 || metrics.maxThumbOffset <= 0.0) {
388
+ return;
389
+ }
390
+ const delta = this.orientation == Orientation.Horizontal ? event.totalDeltaX : event.totalDeltaY;
391
+ const offsetPerThumbPixel = metrics.maxOffset / metrics.maxThumbOffset;
392
+ this.setScrollOffset(this.dragStartOffset + (delta * offsetPerThumbPixel), metrics.maxOffset);
393
+ }
394
+
395
+ handleTrackPointerDown(trackHandle: u64, pointerX: f32, pointerY: f32): void {
396
+ if (this.thumbNode.isDragging()) {
397
+ return;
398
+ }
399
+ const metrics = this.computeMetrics();
400
+ if (metrics.maxOffset <= 0.0 || metrics.maxThumbOffset <= 0.0) {
401
+ return;
402
+ }
403
+ const bounds = ui.tryGetBounds(trackHandle);
404
+ if (bounds === null) {
405
+ return;
406
+ }
407
+ const localPointer = this.orientation == Orientation.Horizontal
408
+ ? pointerX - unchecked(bounds[0])
409
+ : pointerY - unchecked(bounds[1]);
410
+ const targetThumbOffset = clamp(localPointer - (metrics.thumbSize * 0.5), 0.0, metrics.maxThumbOffset);
411
+ const nextOffset = (targetThumbOffset / metrics.maxThumbOffset) * metrics.maxOffset;
412
+ this.setScrollOffset(nextOffset, metrics.maxOffset);
413
+ }
414
+
415
+ private computeMetrics(): ScrollMetrics {
416
+ const metrics = this.metrics;
417
+ const viewportSize = this.orientation == Orientation.Horizontal
418
+ ? this.scrollState.viewportWidth.value
419
+ : this.scrollState.viewportHeight.value;
420
+ const contentSize = this.orientation == Orientation.Horizontal
421
+ ? this.scrollState.contentWidth.value
422
+ : this.scrollState.contentHeight.value;
423
+ const safeViewportSize = viewportSize > 0.0 ? viewportSize : 0.0;
424
+ const safeContentSize = contentSize > safeViewportSize ? contentSize : safeViewportSize;
425
+ const maxOffset = safeContentSize - safeViewportSize;
426
+ const rawThumbSize = safeContentSize > 0.0 ? safeViewportSize * (safeViewportSize / safeContentSize) : 0.0;
427
+ const thumbSize = safeViewportSize > 0.0 ? clamp(rawThumbSize, this.minThumbHeightValue, safeViewportSize) : 0.0;
428
+ const maxThumbOffset = safeViewportSize > thumbSize ? safeViewportSize - thumbSize : 0.0;
429
+ const offset = this.axisOffset();
430
+ const thumbOffset = maxOffset > 0.0 && maxThumbOffset > 0.0
431
+ ? clamp((offset / maxOffset) * maxThumbOffset, 0.0, maxThumbOffset)
432
+ : 0.0;
433
+
434
+ metrics.viewportSize = safeViewportSize;
435
+ metrics.contentSize = safeContentSize;
436
+ metrics.maxOffset = maxOffset;
437
+ metrics.thumbSize = thumbSize;
438
+ metrics.maxThumbOffset = maxThumbOffset;
439
+ metrics.thumbOffset = thumbOffset;
440
+ metrics.trailingSpacerSize = safeViewportSize - thumbSize - thumbOffset;
441
+ if (metrics.trailingSpacerSize < 0.0) {
442
+ metrics.trailingSpacerSize = 0.0;
443
+ }
444
+ return metrics;
445
+ }
446
+
447
+ private attachListeners(): void {
448
+ const offsetSignal = this.orientation == Orientation.Horizontal ? this.scrollState.offsetX : this.scrollState.offsetY;
449
+ const contentSignal = this.orientation == Orientation.Horizontal ? this.scrollState.contentWidth : this.scrollState.contentHeight;
450
+ const viewportSignal = this.orientation == Orientation.Horizontal ? this.scrollState.viewportWidth : this.scrollState.viewportHeight;
451
+ this.track(offsetSignal.addAction(new HandlerAction<ScrollBar, f32>(this, (scrollBar: ScrollBar, _value: f32): void => {
452
+ scrollBar.handleMetricsChanged();
453
+ })));
454
+ this.track(contentSignal.addAction(new HandlerAction<ScrollBar, f32>(this, (scrollBar: ScrollBar, _value: f32): void => {
455
+ scrollBar.handleMetricsChanged();
456
+ })));
457
+ this.track(viewportSignal.addAction(new HandlerAction<ScrollBar, f32>(this, (scrollBar: ScrollBar, _value: f32): void => {
458
+ scrollBar.handleMetricsChanged();
459
+ })));
460
+ this.track(activeTheme.addAction(new HandlerAction<ScrollBar, Theme>(this, (scrollBar: ScrollBar, theme: Theme): void => {
461
+ scrollBar.handleThemeChanged(theme);
462
+ })));
463
+ }
464
+
465
+ private track(disposable: Disposable): void {
466
+ this.disposables.push(disposable);
467
+ }
468
+
469
+ private syncVisualState(): void {
470
+ if (!this.chromeVisibleValue) {
471
+ if (this.orientation == Orientation.Horizontal) {
472
+ this.trackNode.width(0.0, Unit.Pixel);
473
+ this.trackStrip.width(0.0, Unit.Pixel);
474
+ this.leadingSpacerNode.width(0.0, Unit.Pixel);
475
+ this.thumbNode.width(0.0, Unit.Pixel);
476
+ this.trailingSpacerNode.width(0.0, Unit.Pixel);
477
+ return;
478
+ }
479
+ this.trackNode.height(0.0, Unit.Pixel);
480
+ this.trackStrip.height(0.0, Unit.Pixel);
481
+ this.leadingSpacerNode.height(0.0, Unit.Pixel);
482
+ this.thumbNode.height(0.0, Unit.Pixel);
483
+ this.trailingSpacerNode.height(0.0, Unit.Pixel);
484
+ return;
485
+ }
486
+ const metrics = this.computeMetrics();
487
+ if (this.orientation == Orientation.Horizontal) {
488
+ this.trackNode.width(metrics.viewportSize, Unit.Pixel);
489
+ this.trackStrip.width(metrics.viewportSize, Unit.Pixel);
490
+ this.leadingSpacerNode.width(metrics.thumbOffset, Unit.Pixel);
491
+ this.thumbNode.width(metrics.thumbSize, Unit.Pixel);
492
+ this.trailingSpacerNode.width(metrics.trailingSpacerSize, Unit.Pixel);
493
+ return;
494
+ }
495
+ this.trackNode.height(metrics.viewportSize, Unit.Pixel);
496
+ this.trackStrip.height(metrics.viewportSize, Unit.Pixel);
497
+ this.leadingSpacerNode.height(metrics.thumbOffset, Unit.Pixel);
498
+ this.thumbNode.height(metrics.thumbSize, Unit.Pixel);
499
+ this.trailingSpacerNode.height(metrics.trailingSpacerSize, Unit.Pixel);
500
+ }
501
+
502
+ private handleThemeChanged(theme: Theme): void {
503
+ if (!this.trackColorOverridden) {
504
+ this.trackColorValue = theme.colors.scrollbarTrack;
505
+ }
506
+ if (!this.thumbColorOverridden) {
507
+ this.thumbColorValue = theme.colors.scrollbarThumb;
508
+ }
509
+ this.applyColorStyle();
510
+ }
511
+
512
+ private applyGeometryStyle(): void {
513
+ const trackThickness = this.chromeVisibleValue ? this.trackThicknessValue : 0.0;
514
+ const thumbThickness = this.chromeVisibleValue ? this.thumbThicknessValue : 0.0;
515
+ if (this.orientation == Orientation.Horizontal) {
516
+ this.trackNode.height(trackThickness, Unit.Pixel);
517
+ this.trackStrip.height(trackThickness, Unit.Pixel);
518
+ this.leadingSpacerNode.height(trackThickness, Unit.Pixel);
519
+ this.trailingSpacerNode.height(trackThickness, Unit.Pixel);
520
+ this.thumbNode.height(thumbThickness, Unit.Pixel);
521
+ return;
522
+ }
523
+ this.trackNode.width(trackThickness, Unit.Pixel);
524
+ this.trackStrip.width(trackThickness, Unit.Pixel);
525
+ this.leadingSpacerNode.width(trackThickness, Unit.Pixel);
526
+ this.trailingSpacerNode.width(trackThickness, Unit.Pixel);
527
+ this.thumbNode.width(thumbThickness, Unit.Pixel);
528
+ }
529
+
530
+ private applyColorStyle(): void {
531
+ this.trackNode.bgColor(this.trackColorValue);
532
+ this.thumbNode.bgColor(this.thumbColorValue);
533
+ }
534
+
535
+ private applyThumbCursor(): void {
536
+ if (isCoarsePointer()) {
537
+ return;
538
+ }
539
+ if (!this.chromeVisibleValue) {
540
+ this.thumbNode.cursor(CursorStyle.Default);
541
+ return;
542
+ }
543
+ this.thumbNode.cursor(this.thumbNode.isDragging() ? CursorStyle.Grabbing : CursorStyle.Grab);
544
+ }
545
+
546
+ private axisPosition(x: f32, y: f32): f32 {
547
+ return this.orientation == Orientation.Horizontal ? x : y;
548
+ }
549
+
550
+ private axisOffset(): f32 {
551
+ return this.orientation == Orientation.Horizontal ? this.scrollState.offsetX.value : this.scrollState.offsetY.value;
552
+ }
553
+
554
+ private setScrollOffset(offset: f32, maxOffset: f32): void {
555
+ const clampedOffset = clamp(offset, 0.0, maxOffset);
556
+ if (this.orientation == Orientation.Horizontal) {
557
+ this.scrollState.offsetX.value = clampedOffset;
558
+ if (this.targetHandle != <u64>HandleValue.Invalid) {
559
+ ui.setScrollOffset(this.targetHandle, clampedOffset, this.scrollState.offsetY.value);
560
+ markNeedsCommit();
561
+ }
562
+ return;
563
+ }
564
+ this.scrollState.offsetY.value = clampedOffset;
565
+ if (this.targetHandle != <u64>HandleValue.Invalid) {
566
+ ui.setScrollOffset(this.targetHandle, this.scrollState.offsetX.value, clampedOffset);
567
+ markNeedsCommit();
568
+ }
569
+ }
570
+ }