@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,459 @@
1
+ import * as ui from "../bindings/ui";
2
+ import { PopupPresenter } from "../controls/internal/PopupPresenter";
3
+ import { FlexBox, Portal, TextCore } from "../nodes";
4
+ import { BorderStyle, FlexDirection, HandleValue, Unit } from "./ffi";
5
+ import { flushCommit } from "./FrameScheduler";
6
+ import { keyboardFocusVisible } from "./FocusVisibility";
7
+ import { cancelTimer, scheduleTimer } from "./Timers";
8
+ import { activeTheme } from "./Theme";
9
+ import { Node } from "./Node";
10
+ import { ToolTip } from "./ToolTip";
11
+
12
+ const SHOW_TIMER_ID: u32 = 0x54545001;
13
+ const HIDE_TIMER_ID: u32 = 0x54545002;
14
+ const MIN_TOOLTIP_SURFACE_SIZE: f32 = 1.0;
15
+
16
+ enum ToolTipAnchorKind {
17
+ None = 0,
18
+ Owner = 1,
19
+ Pointer = 2,
20
+ }
21
+
22
+ export class ToolTipManager {
23
+ private static hostRoot: Portal | null = null;
24
+ private static panelNode: FlexBox | null = null;
25
+ private static labelNode: TextCore | null = null;
26
+ private static presenter: PopupPresenter | null = null;
27
+ private static activeOwner: Node | null = null;
28
+ private static activeToolTip: ToolTip | null = null;
29
+ private static pendingOwner: Node | null = null;
30
+ private static pendingToolTip: ToolTip | null = null;
31
+ private static hoveredOwner: Node | null = null;
32
+ private static hoveredToolTip: ToolTip | null = null;
33
+ private static suppressedHoverOwner: Node | null = null;
34
+ private static focusedOwner: Node | null = null;
35
+ private static focusedToolTip: ToolTip | null = null;
36
+ private static quickShowUntilMs: f64 = -1.0;
37
+ private static subscribedToFocusVisibility: bool = false;
38
+ private static hoveredPointerX: f32 = NaN;
39
+ private static hoveredPointerY: f32 = NaN;
40
+ private static pendingAnchorKind: ToolTipAnchorKind = ToolTipAnchorKind.None;
41
+ private static pendingPopupX: f32 = NaN;
42
+ private static pendingPopupY: f32 = NaN;
43
+ private static activeAnchorKind: ToolTipAnchorKind = ToolTipAnchorKind.None;
44
+ private static activeAnchorX: f32 = NaN;
45
+ private static activeAnchorY: f32 = NaN;
46
+ private static activeAnchorWidth: f32 = NaN;
47
+ private static activeAnchorHeight: f32 = NaN;
48
+ private static activePopupX: f32 = NaN;
49
+ private static activePopupY: f32 = NaN;
50
+
51
+ static createDefaultHost(): Portal {
52
+ const existingHost = this.hostRoot;
53
+ if (existingHost !== null) {
54
+ return existingHost;
55
+ }
56
+ const labelNode = new TextCore("");
57
+ const panelNode = new FlexBox()
58
+ .positionAbsolute()
59
+ .flexDirection(FlexDirection.Column)
60
+ .child(labelNode) as FlexBox;
61
+ const hostRoot = new Portal()
62
+ .positionAbsolute()
63
+ .position(0.0, 0.0)
64
+ .width(100.0, Unit.Percent)
65
+ .height(100.0, Unit.Percent) as Portal;
66
+ this.hostRoot = hostRoot;
67
+ this.panelNode = panelNode;
68
+ this.labelNode = labelNode;
69
+ this.presenter = new PopupPresenter(hostRoot, panelNode, null);
70
+ if (!this.subscribedToFocusVisibility) {
71
+ this.subscribedToFocusVisibility = true;
72
+ keyboardFocusVisible.subscribe(handleToolTipFocusVisibilityChanged);
73
+ }
74
+ return hostRoot;
75
+ }
76
+
77
+ static clear(): void {
78
+ cancelTimer(SHOW_TIMER_ID);
79
+ this.pendingOwner = null;
80
+ this.pendingToolTip = null;
81
+ this.hoveredOwner = null;
82
+ this.hoveredToolTip = null;
83
+ this.suppressedHoverOwner = null;
84
+ this.hoveredPointerX = NaN;
85
+ this.hoveredPointerY = NaN;
86
+ this.focusedOwner = null;
87
+ this.focusedToolTip = null;
88
+ this.pendingAnchorKind = ToolTipAnchorKind.None;
89
+ this.pendingPopupX = NaN;
90
+ this.pendingPopupY = NaN;
91
+ this.hideCurrent();
92
+ this.quickShowUntilMs = -1.0;
93
+ }
94
+
95
+ static handleToolTipChanged(owner: Node, toolTip: ToolTip | null): void {
96
+ if (toolTip === null) {
97
+ this.clearOwner(owner);
98
+ this.activateBestCandidate();
99
+ return;
100
+ }
101
+ if (this.hoveredOwner === owner) {
102
+ this.hoveredToolTip = toolTip;
103
+ }
104
+ if (this.focusedOwner === owner) {
105
+ this.focusedToolTip = toolTip;
106
+ }
107
+ if (this.activeOwner === owner) {
108
+ this.activeToolTip = toolTip;
109
+ this.showNow(owner, toolTip, true);
110
+ return;
111
+ }
112
+ if (this.pendingOwner === owner) {
113
+ this.pendingToolTip = toolTip;
114
+ this.activateBestCandidate();
115
+ }
116
+ }
117
+
118
+ static handlePointerEnter(owner: Node, toolTip: ToolTip | null, x: f32, y: f32): void {
119
+ if (toolTip === null || toolTip.contentText.length == 0) {
120
+ return;
121
+ }
122
+ if (this.suppressedHoverOwner === owner) {
123
+ this.suppressedHoverOwner = null;
124
+ }
125
+ this.hoveredOwner = owner;
126
+ this.hoveredToolTip = toolTip;
127
+ this.hoveredPointerX = x;
128
+ this.hoveredPointerY = y;
129
+ this.activateBestCandidate();
130
+ }
131
+
132
+ static handlePointerMove(owner: Node, x: f32, y: f32): void {
133
+ if (this.hoveredOwner !== owner) {
134
+ return;
135
+ }
136
+ this.hoveredPointerX = x;
137
+ this.hoveredPointerY = y;
138
+ if (this.pendingOwner === owner && this.pendingAnchorKind == ToolTipAnchorKind.Pointer) {
139
+ this.pendingPopupX = x;
140
+ this.pendingPopupY = y;
141
+ }
142
+ }
143
+
144
+ static handlePointerLeave(owner: Node): void {
145
+ if (this.hoveredOwner === owner) {
146
+ this.hoveredOwner = null;
147
+ this.hoveredToolTip = null;
148
+ this.hoveredPointerX = NaN;
149
+ this.hoveredPointerY = NaN;
150
+ }
151
+ if (this.suppressedHoverOwner === owner) {
152
+ this.suppressedHoverOwner = null;
153
+ }
154
+ this.activateBestCandidate();
155
+ }
156
+
157
+ static handlePointerDown(owner: Node): void {
158
+ if (this.activeOwner === owner || this.pendingOwner === owner) {
159
+ cancelTimer(SHOW_TIMER_ID);
160
+ this.hideCurrent();
161
+ this.pendingOwner = null;
162
+ this.pendingToolTip = null;
163
+ this.pendingAnchorKind = ToolTipAnchorKind.None;
164
+ this.pendingPopupX = NaN;
165
+ this.pendingPopupY = NaN;
166
+ }
167
+ }
168
+
169
+ static handleFocusChanged(owner: Node, toolTip: ToolTip | null, focused: bool): void {
170
+ if (focused) {
171
+ if (toolTip !== null && toolTip.opensOnFocus && toolTip.contentText.length > 0) {
172
+ this.focusedOwner = owner;
173
+ this.focusedToolTip = toolTip;
174
+ }
175
+ } else if (this.focusedOwner === owner) {
176
+ this.focusedOwner = null;
177
+ this.focusedToolTip = null;
178
+ }
179
+ this.activateBestCandidate();
180
+ }
181
+
182
+ static handleOwnerDestroyed(owner: Node): void {
183
+ this.clearOwner(owner);
184
+ this.activateBestCandidate();
185
+ }
186
+
187
+ static handleScroll(): void {
188
+ if (this.activeAnchorKind != ToolTipAnchorKind.Pointer || this.activeOwner === null) {
189
+ return;
190
+ }
191
+ this.suppressedHoverOwner = this.activeOwner;
192
+ if (this.hoveredOwner === this.activeOwner) {
193
+ this.hoveredOwner = null;
194
+ this.hoveredToolTip = null;
195
+ this.hoveredPointerX = NaN;
196
+ this.hoveredPointerY = NaN;
197
+ }
198
+ this.pendingOwner = null;
199
+ this.pendingToolTip = null;
200
+ this.pendingAnchorKind = ToolTipAnchorKind.None;
201
+ this.pendingPopupX = NaN;
202
+ this.pendingPopupY = NaN;
203
+ cancelTimer(SHOW_TIMER_ID);
204
+ this.hideCurrent();
205
+ this.activateBestCandidate();
206
+ }
207
+
208
+ private static clearOwner(owner: Node): void {
209
+ if (this.hoveredOwner === owner) {
210
+ this.hoveredOwner = null;
211
+ this.hoveredToolTip = null;
212
+ this.hoveredPointerX = NaN;
213
+ this.hoveredPointerY = NaN;
214
+ }
215
+ if (this.suppressedHoverOwner === owner) {
216
+ this.suppressedHoverOwner = null;
217
+ }
218
+ if (this.focusedOwner === owner) {
219
+ this.focusedOwner = null;
220
+ this.focusedToolTip = null;
221
+ }
222
+ if (this.pendingOwner === owner) {
223
+ this.pendingOwner = null;
224
+ this.pendingToolTip = null;
225
+ cancelTimer(SHOW_TIMER_ID);
226
+ }
227
+ if (this.activeOwner === owner) {
228
+ this.hideCurrent();
229
+ }
230
+ }
231
+
232
+ static activateBestCandidate(): void {
233
+ const hoveredCandidateOwner = this.hoveredOwner !== this.suppressedHoverOwner
234
+ ? this.hoveredOwner
235
+ : null;
236
+ const candidateOwner = hoveredCandidateOwner !== null
237
+ ? hoveredCandidateOwner
238
+ : (keyboardFocusVisible.value ? this.focusedOwner : null);
239
+ const candidateToolTip = hoveredCandidateOwner !== null
240
+ ? this.hoveredToolTip
241
+ : (keyboardFocusVisible.value ? this.focusedToolTip : null);
242
+ const candidateAnchorKind = hoveredCandidateOwner !== null
243
+ ? ToolTipAnchorKind.Pointer
244
+ : ToolTipAnchorKind.Owner;
245
+ if (candidateOwner === null || candidateToolTip === null || candidateToolTip.contentText.length == 0) {
246
+ this.pendingOwner = null;
247
+ this.pendingToolTip = null;
248
+ this.pendingAnchorKind = ToolTipAnchorKind.None;
249
+ this.pendingPopupX = NaN;
250
+ this.pendingPopupY = NaN;
251
+ cancelTimer(SHOW_TIMER_ID);
252
+ this.hideCurrent();
253
+ return;
254
+ }
255
+ if (
256
+ this.activeOwner === candidateOwner &&
257
+ this.activeToolTip === candidateToolTip
258
+ ) {
259
+ return;
260
+ }
261
+ this.requestShow(candidateOwner, candidateToolTip, candidateAnchorKind);
262
+ }
263
+
264
+ private static requestShow(owner: Node, toolTip: ToolTip, anchorKind: ToolTipAnchorKind): void {
265
+ this.pendingOwner = owner;
266
+ this.pendingToolTip = toolTip;
267
+ this.pendingAnchorKind = anchorKind;
268
+ this.pendingPopupX = anchorKind == ToolTipAnchorKind.Pointer ? this.hoveredPointerX : NaN;
269
+ this.pendingPopupY = anchorKind == ToolTipAnchorKind.Pointer ? this.hoveredPointerY : NaN;
270
+ cancelTimer(HIDE_TIMER_ID);
271
+ const now = this.nowMs();
272
+ const delayMs = now <= this.quickShowUntilMs ? 0 : toolTip.initialShowDelayMs;
273
+ cancelTimer(SHOW_TIMER_ID);
274
+ if (delayMs <= 0) {
275
+ this.commitPendingShow();
276
+ return;
277
+ }
278
+ scheduleTimer(SHOW_TIMER_ID, delayMs, handleToolTipShowTimer);
279
+ }
280
+
281
+ static commitPendingShow(): void {
282
+ const owner = this.pendingOwner;
283
+ const toolTip = this.pendingToolTip;
284
+ if (owner === null || toolTip === null) {
285
+ return;
286
+ }
287
+ this.pendingOwner = null;
288
+ this.pendingToolTip = null;
289
+ const anchorKind = this.pendingAnchorKind;
290
+ this.pendingAnchorKind = ToolTipAnchorKind.None;
291
+ this.showNow(owner, toolTip, false, anchorKind);
292
+ }
293
+
294
+ private static showNow(
295
+ owner: Node,
296
+ toolTip: ToolTip,
297
+ preserveCurrentPopupAnchor: bool = false,
298
+ anchorKind: ToolTipAnchorKind = ToolTipAnchorKind.Owner,
299
+ ): void {
300
+ const presenter = this.presenter;
301
+ const panelNode = this.panelNode;
302
+ const labelNode = this.labelNode;
303
+ const hostRoot = this.hostRoot;
304
+ if (presenter === null || panelNode === null || labelNode === null || hostRoot === null) {
305
+ return;
306
+ }
307
+ if (owner.builtHandle == <u64>HandleValue.Invalid || hostRoot.builtHandle == <u64>HandleValue.Invalid) {
308
+ return;
309
+ }
310
+ if (hostRoot.parentNode !== owner) {
311
+ owner.addChildNode(hostRoot);
312
+ }
313
+ const theme = activeTheme.value.toolTip;
314
+ const panelBackground = toolTip.hasPanelColorOverride ? toolTip.panelBackgroundColor : theme.panelBackground;
315
+ const textColor = toolTip.hasTextColorOverride ? toolTip.tooltipTextColor : theme.textColor;
316
+ labelNode.text(toolTip.contentText)
317
+ .font(theme.fontId, theme.fontSize)
318
+ .fontFamily(theme.fontFamily)
319
+ .textColor(textColor)
320
+ .width(0.0, Unit.Auto)
321
+ .height(0.0, Unit.Auto);
322
+ panelNode.padding(theme.paddingLeft, theme.paddingTop, theme.paddingRight, theme.paddingBottom)
323
+ .cornerRadius(theme.panelCornerRadius)
324
+ .border(1.0, theme.panelBorderColor, BorderStyle.Solid)
325
+ .dropShadow(theme.panelShadowColor, 0.0, theme.shadowOffsetY, theme.shadowBlur, theme.shadowSpread)
326
+ .bgColor(panelBackground)
327
+ .width(0.0, Unit.Auto)
328
+ .height(0.0, Unit.Auto);
329
+ presenter.placement(toolTip.popupPlacement);
330
+ presenter.anchorGap(8.0);
331
+ if (!preserveCurrentPopupAnchor || this.activeOwner !== owner) {
332
+ if (
333
+ anchorKind == ToolTipAnchorKind.Pointer &&
334
+ this.pendingPopupX == this.pendingPopupX &&
335
+ this.pendingPopupY == this.pendingPopupY
336
+ ) {
337
+ this.activeAnchorKind = ToolTipAnchorKind.Pointer;
338
+ this.activePopupX = this.pendingPopupX + toolTip.horizontalOffsetValuePx;
339
+ this.activePopupY = this.pendingPopupY + toolTip.verticalOffsetValuePx;
340
+ this.activeAnchorX = NaN;
341
+ this.activeAnchorY = NaN;
342
+ this.activeAnchorWidth = NaN;
343
+ this.activeAnchorHeight = NaN;
344
+ } else {
345
+ const bounds = this.estimateToolTipBounds(owner);
346
+ if (bounds === null) {
347
+ return;
348
+ }
349
+ this.activeAnchorKind = ToolTipAnchorKind.Owner;
350
+ this.activeAnchorX = unchecked(bounds[0]) + toolTip.horizontalOffsetValuePx;
351
+ this.activeAnchorY = unchecked(bounds[1]) + toolTip.verticalOffsetValuePx;
352
+ this.activeAnchorWidth = unchecked(bounds[2]);
353
+ this.activeAnchorHeight = unchecked(bounds[3]);
354
+ this.activePopupX = NaN;
355
+ this.activePopupY = NaN;
356
+ }
357
+ }
358
+ this.showAtResolvedAnchor(presenter, toolTip, MIN_TOOLTIP_SURFACE_SIZE, MIN_TOOLTIP_SURFACE_SIZE);
359
+ flushCommit();
360
+ const measuredBounds = panelNode.builtHandle != <u64>HandleValue.Invalid
361
+ ? ui.tryGetBounds(panelNode.builtHandle)
362
+ : null;
363
+ const measuredWidth = measuredBounds !== null
364
+ ? <f32>Math.max(MIN_TOOLTIP_SURFACE_SIZE, unchecked(measuredBounds[2]))
365
+ : MIN_TOOLTIP_SURFACE_SIZE;
366
+ const measuredHeight = measuredBounds !== null
367
+ ? <f32>Math.max(MIN_TOOLTIP_SURFACE_SIZE, unchecked(measuredBounds[3]))
368
+ : MIN_TOOLTIP_SURFACE_SIZE;
369
+ this.showAtResolvedAnchor(presenter, toolTip, measuredWidth, measuredHeight);
370
+ flushCommit();
371
+ this.activeOwner = owner;
372
+ this.activeToolTip = toolTip;
373
+ this.quickShowUntilMs = this.nowMs() + toolTip.betweenShowDelayMs;
374
+ cancelTimer(HIDE_TIMER_ID);
375
+ if (toolTip.showDurationMs > 0) {
376
+ scheduleTimer(HIDE_TIMER_ID, toolTip.showDurationMs, handleToolTipHideTimer);
377
+ }
378
+ }
379
+
380
+ static hideCurrent(): void {
381
+ cancelTimer(HIDE_TIMER_ID);
382
+ const presenter = this.presenter;
383
+ if (presenter !== null) {
384
+ presenter.hide();
385
+ }
386
+ const hostRoot = this.hostRoot;
387
+ const hostParent = hostRoot !== null ? hostRoot.parentNode : null;
388
+ if (hostParent !== null) {
389
+ hostParent.removeChildNode(changetype<Portal>(hostRoot));
390
+ }
391
+ this.activeOwner = null;
392
+ this.activeToolTip = null;
393
+ this.activeAnchorKind = ToolTipAnchorKind.None;
394
+ this.activeAnchorX = NaN;
395
+ this.activeAnchorY = NaN;
396
+ this.activeAnchorWidth = NaN;
397
+ this.activeAnchorHeight = NaN;
398
+ this.activePopupX = NaN;
399
+ this.activePopupY = NaN;
400
+ }
401
+
402
+ private static estimateToolTipBounds(owner: Node): Float32Array | null {
403
+ if (owner.builtHandle == <u64>HandleValue.Invalid) {
404
+ return null;
405
+ }
406
+ const bounds = ui.tryGetBounds(owner.builtHandle);
407
+ if (bounds !== null) {
408
+ return bounds;
409
+ }
410
+ const fallback = new Float32Array(4);
411
+ unchecked(fallback[0] = 0.0);
412
+ unchecked(fallback[1] = 0.0);
413
+ unchecked(fallback[2] = 1.0);
414
+ unchecked(fallback[3] = 1.0);
415
+ return fallback;
416
+ }
417
+
418
+ private static showAtResolvedAnchor(
419
+ presenter: PopupPresenter,
420
+ toolTip: ToolTip,
421
+ width: f32,
422
+ height: f32,
423
+ ): void {
424
+ if (
425
+ this.activeAnchorKind == ToolTipAnchorKind.Pointer &&
426
+ this.activePopupX == this.activePopupX &&
427
+ this.activePopupY == this.activePopupY
428
+ ) {
429
+ presenter.showAtPoint(this.activePopupX, this.activePopupY, width, height);
430
+ return;
431
+ }
432
+ presenter.showAnchored(
433
+ this.activeAnchorX,
434
+ this.activeAnchorY,
435
+ this.activeAnchorWidth,
436
+ this.activeAnchorHeight,
437
+ width,
438
+ height,
439
+ toolTip.popupPlacement,
440
+ );
441
+ }
442
+
443
+ private static nowMs(): f64 {
444
+ return ui.nowMs();
445
+ }
446
+ }
447
+
448
+ function handleToolTipShowTimer(): void {
449
+ ToolTipManager.commitPendingShow();
450
+ }
451
+
452
+ function handleToolTipHideTimer(): void {
453
+ ToolTipManager.hideCurrent();
454
+ ToolTipManager.activateBestCandidate();
455
+ }
456
+
457
+ function handleToolTipFocusVisibilityChanged(): void {
458
+ ToolTipManager.activateBestCandidate();
459
+ }
@@ -0,0 +1,34 @@
1
+ import { AnimationTiming } from "./Animation";
2
+
3
+ export class NodeTransitions {
4
+ private opacityTimingValue: AnimationTiming | null = null;
5
+ private backgroundColorTimingValue: AnimationTiming | null = null;
6
+ private scrollOffsetTimingValue: AnimationTiming | null = null;
7
+
8
+ opacity(timing: AnimationTiming | null): this {
9
+ this.opacityTimingValue = timing;
10
+ return this;
11
+ }
12
+
13
+ bgColor(timing: AnimationTiming | null): this {
14
+ this.backgroundColorTimingValue = timing;
15
+ return this;
16
+ }
17
+
18
+ scrollOffset(timing: AnimationTiming | null): this {
19
+ this.scrollOffsetTimingValue = timing;
20
+ return this;
21
+ }
22
+
23
+ get opacityTiming(): AnimationTiming | null {
24
+ return this.opacityTimingValue;
25
+ }
26
+
27
+ get backgroundColorTiming(): AnimationTiming | null {
28
+ return this.backgroundColorTimingValue;
29
+ }
30
+
31
+ get scrollOffsetTiming(): AnimationTiming | null {
32
+ return this.scrollOffsetTimingValue;
33
+ }
34
+ }
@@ -0,0 +1,204 @@
1
+ import * as ui from "../bindings/ui";
2
+ import { warn } from "./Logger";
3
+
4
+ export enum FontStyle {
5
+ Normal = 0,
6
+ Italic = 1,
7
+ }
8
+
9
+ export enum FontWeight {
10
+ Regular = 400,
11
+ Medium = 500,
12
+ Semibold = 600,
13
+ Bold = 700,
14
+ }
15
+
16
+ const STYLE_MISMATCH_PENALTY: i32 = 1000;
17
+ const MAX_FONT_SCORE: i32 = 0x7fffffff;
18
+ const FIRST_DYNAMIC_FONT_ID: u32 = 1024;
19
+
20
+ let nextDynamicFontId: u32 = FIRST_DYNAMIC_FONT_ID;
21
+
22
+ function absI32(value: i32): i32 {
23
+ return value < 0 ? -value : value;
24
+ }
25
+
26
+ function allocateDynamicFontId(): u32 {
27
+ const allocated = nextDynamicFontId;
28
+ nextDynamicFontId += 1;
29
+ return allocated;
30
+ }
31
+
32
+ export class FontFace {
33
+ constructor(readonly id: u32) {}
34
+
35
+ static load(url: string, id: u32 = allocateDynamicFontId()): FontFace {
36
+ return new FontFace(id).load(url);
37
+ }
38
+
39
+ load(url: string): this {
40
+ if (url.length == 0) {
41
+ warn("Typography", "FontFace.load() received an empty font URL.");
42
+ }
43
+ ui.loadFont(this.id, url);
44
+ return this;
45
+ }
46
+ }
47
+
48
+ export class FontStack {
49
+ private readonly fallbackIds: Array<u32> = new Array<u32>();
50
+
51
+ constructor(readonly id: u32) {}
52
+
53
+ static load(url: string, id: u32 = allocateDynamicFontId()): FontStack {
54
+ return new FontStack(id).load(url);
55
+ }
56
+
57
+ load(url: string): this {
58
+ if (url.length == 0) {
59
+ warn("Typography", "FontStack.load() received an empty font URL.");
60
+ }
61
+ ui.loadFont(this.id, url);
62
+ return this;
63
+ }
64
+
65
+ fallback(fontId: u32): this {
66
+ if (fontId == 0 || fontId == this.id) {
67
+ warn(
68
+ "Typography",
69
+ "FontStack.fallback() ignored font id " + fontId.toString() + " for stack " + this.id.toString() + ".",
70
+ );
71
+ return this;
72
+ }
73
+ for (let index = 0; index < this.fallbackIds.length; ++index) {
74
+ if (unchecked(this.fallbackIds[index]) == fontId) {
75
+ return this;
76
+ }
77
+ }
78
+ ui.registerFontFallback(this.id, fontId);
79
+ this.fallbackIds.push(fontId);
80
+ return this;
81
+ }
82
+
83
+ fallbackFace(face: FontFace): this {
84
+ return this.fallback(face.id);
85
+ }
86
+
87
+ fallbackStack(stack: FontStack): this {
88
+ return this.fallback(stack.id);
89
+ }
90
+
91
+ fallbackLoaded(url: string, fontId: u32 = allocateDynamicFontId()): this {
92
+ if (url.length == 0) {
93
+ warn("Typography", "FontStack.fallbackLoaded() received an empty font URL.");
94
+ }
95
+ ui.loadFont(fontId, url);
96
+ return this.fallback(fontId);
97
+ }
98
+ }
99
+
100
+ export class FontFamily {
101
+ constructor(
102
+ readonly regular: u32,
103
+ readonly bold: u32 = 0,
104
+ readonly italic: u32 = 0,
105
+ readonly boldItalic: u32 = 0,
106
+ readonly medium: u32 = 0,
107
+ readonly mediumItalic: u32 = 0,
108
+ readonly semibold: u32 = 0,
109
+ readonly semiboldItalic: u32 = 0,
110
+ ) {}
111
+
112
+ static withRegular(regular: u32): FontFamily {
113
+ return new FontFamily(regular);
114
+ }
115
+
116
+ static withRegularStack(regular: FontStack): FontFamily {
117
+ return new FontFamily(regular.id);
118
+ }
119
+
120
+ static withRegularFace(regular: FontFace): FontFamily {
121
+ return new FontFamily(regular.id);
122
+ }
123
+
124
+ static regularBold(regular: u32, bold: u32): FontFamily {
125
+ return new FontFamily(regular, bold);
126
+ }
127
+
128
+ static regularBoldStacks(regular: FontStack, bold: FontStack): FontFamily {
129
+ return new FontFamily(regular.id, bold.id);
130
+ }
131
+
132
+ resolve(weight: FontWeight = FontWeight.Regular, style: FontStyle = FontStyle.Normal): u32 {
133
+ const targetWeight = <i32>weight;
134
+ let bestId: u32 = 0;
135
+ let bestScore: i32 = MAX_FONT_SCORE;
136
+ let score = this.scoreCandidate(this.regular, FontWeight.Regular, FontStyle.Normal, targetWeight, style);
137
+ if (score < bestScore) {
138
+ bestScore = score;
139
+ bestId = this.regular;
140
+ }
141
+ score = this.scoreCandidate(this.bold, FontWeight.Bold, FontStyle.Normal, targetWeight, style);
142
+ if (score < bestScore) {
143
+ bestScore = score;
144
+ bestId = this.bold;
145
+ }
146
+ score = this.scoreCandidate(this.italic, FontWeight.Regular, FontStyle.Italic, targetWeight, style);
147
+ if (score < bestScore) {
148
+ bestScore = score;
149
+ bestId = this.italic;
150
+ }
151
+ score = this.scoreCandidate(this.boldItalic, FontWeight.Bold, FontStyle.Italic, targetWeight, style);
152
+ if (score < bestScore) {
153
+ bestScore = score;
154
+ bestId = this.boldItalic;
155
+ }
156
+ score = this.scoreCandidate(this.medium, FontWeight.Medium, FontStyle.Normal, targetWeight, style);
157
+ if (score < bestScore) {
158
+ bestScore = score;
159
+ bestId = this.medium;
160
+ }
161
+ score = this.scoreCandidate(this.mediumItalic, FontWeight.Medium, FontStyle.Italic, targetWeight, style);
162
+ if (score < bestScore) {
163
+ bestScore = score;
164
+ bestId = this.mediumItalic;
165
+ }
166
+ score = this.scoreCandidate(this.semibold, FontWeight.Semibold, FontStyle.Normal, targetWeight, style);
167
+ if (score < bestScore) {
168
+ bestScore = score;
169
+ bestId = this.semibold;
170
+ }
171
+ score = this.scoreCandidate(this.semiboldItalic, FontWeight.Semibold, FontStyle.Italic, targetWeight, style);
172
+ if (score < bestScore) {
173
+ bestId = this.semiboldItalic;
174
+ }
175
+ if (bestId == 0) {
176
+ warn(
177
+ "Typography",
178
+ "FontFamily.resolve() could not resolve a font face for weight " +
179
+ (<i32>weight).toString() +
180
+ " and style " +
181
+ (<u32>style).toString() +
182
+ "; the text will use font id 0.",
183
+ );
184
+ }
185
+ return bestId;
186
+ }
187
+
188
+ private scoreCandidate(
189
+ fontId: u32,
190
+ weight: FontWeight,
191
+ style: FontStyle,
192
+ targetWeight: i32,
193
+ targetStyle: FontStyle,
194
+ ): i32 {
195
+ if (fontId == 0) {
196
+ return MAX_FONT_SCORE;
197
+ }
198
+ let score = absI32(<i32>weight - targetWeight);
199
+ if (style != targetStyle) {
200
+ score += STYLE_MISMATCH_PENALTY;
201
+ }
202
+ return score;
203
+ }
204
+ }