@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,365 @@
1
+ import { rgb, rgba } from "../color";
2
+ import { Action, SignalHandler } from "./Action";
3
+ import * as ffi from "./ffi";
4
+ import { Signal } from "./Signal";
5
+ import { FontFamily, FontStack } from "./Typography";
6
+
7
+ const DEFAULT_ACCENT_COLOR: u32 = rgb(0x25, 0x63, 0xeb);
8
+ const WHITE: u32 = rgb(0xff, 0xff, 0xff);
9
+ const BLACK: u32 = rgb(0x00, 0x00, 0x00);
10
+
11
+ enum ThemeSource {
12
+ System = 0,
13
+ Custom = 1,
14
+ }
15
+
16
+ export class Theme {
17
+ constructor(
18
+ readonly colors: Colors,
19
+ readonly spacing: Spacing,
20
+ readonly fonts: Fonts,
21
+ readonly contextMenu: ContextMenuTheme,
22
+ readonly toolTip: ToolTipTheme,
23
+ ) {}
24
+ }
25
+
26
+ export class Colors {
27
+ constructor(
28
+ readonly background: u32,
29
+ readonly surface: u32,
30
+ readonly textPrimary: u32,
31
+ readonly textMuted: u32,
32
+ readonly accent: u32,
33
+ readonly accentPressed: u32,
34
+ readonly accentHovered: u32,
35
+ readonly border: u32,
36
+ readonly selection: u32,
37
+ readonly scrollbarTrack: u32,
38
+ readonly scrollbarThumb: u32,
39
+ readonly dialogBackdrop: u32,
40
+ readonly dialogShadow: u32,
41
+ readonly focusRing: u32,
42
+ ) {}
43
+ }
44
+
45
+ export class Spacing {
46
+ constructor(
47
+ readonly xs: f32,
48
+ readonly sm: f32,
49
+ readonly md: f32,
50
+ readonly lg: f32,
51
+ readonly xl: f32,
52
+ ) {}
53
+ }
54
+
55
+ export class Fonts {
56
+ readonly bodyFamily: FontFamily;
57
+ readonly headingFamily: FontFamily;
58
+ readonly monoFamily: FontFamily;
59
+
60
+ constructor(
61
+ readonly body: u32,
62
+ readonly heading: u32,
63
+ readonly sizeBody: f32,
64
+ readonly sizeHeading: f32,
65
+ readonly mono: u32 = 5,
66
+ readonly monoBold: u32 = 6,
67
+ readonly sizeMono: f32 = sizeBody,
68
+ bodyFamily: FontFamily | null = null,
69
+ headingFamily: FontFamily | null = null,
70
+ monoFamily: FontFamily | null = null,
71
+ ) {
72
+ this.bodyFamily = bodyFamily !== null ? changetype<FontFamily>(bodyFamily) : FontFamily.regularBold(body, heading);
73
+ this.headingFamily = headingFamily !== null ? changetype<FontFamily>(headingFamily) : FontFamily.regularBold(heading, heading);
74
+ this.monoFamily = monoFamily !== null ? changetype<FontFamily>(monoFamily) : FontFamily.regularBold(mono, monoBold);
75
+ }
76
+ }
77
+
78
+ export class ContextMenuItemTheme {
79
+ constructor(
80
+ readonly background: u32,
81
+ readonly hoverBackground: u32,
82
+ readonly textColor: u32,
83
+ readonly cornerRadius: f32,
84
+ readonly fontId: u32,
85
+ readonly fontFamily: FontFamily,
86
+ readonly fontSize: f32,
87
+ readonly height: f32,
88
+ readonly paddingLeft: f32,
89
+ readonly paddingTop: f32,
90
+ readonly paddingRight: f32,
91
+ readonly paddingBottom: f32,
92
+ ) {}
93
+ }
94
+
95
+ export class ContextMenuTheme {
96
+ constructor(
97
+ readonly panelBackground: u32,
98
+ readonly panelBorderColor: u32,
99
+ readonly panelShadowColor: u32,
100
+ readonly panelCornerRadius: f32,
101
+ readonly separatorColor: u32,
102
+ readonly shadowOffsetY: f32,
103
+ readonly shadowBlur: f32,
104
+ readonly shadowSpread: f32,
105
+ readonly item: ContextMenuItemTheme,
106
+ ) {}
107
+ }
108
+
109
+ export class ToolTipTheme {
110
+ constructor(
111
+ readonly panelBackground: u32,
112
+ readonly panelBorderColor: u32,
113
+ readonly panelShadowColor: u32,
114
+ readonly panelCornerRadius: f32,
115
+ readonly textColor: u32,
116
+ readonly fontId: u32,
117
+ readonly fontFamily: FontFamily,
118
+ readonly fontSize: f32,
119
+ readonly maxWidth: f32,
120
+ readonly paddingLeft: f32,
121
+ readonly paddingTop: f32,
122
+ readonly paddingRight: f32,
123
+ readonly paddingBottom: f32,
124
+ readonly shadowOffsetY: f32,
125
+ readonly shadowBlur: f32,
126
+ readonly shadowSpread: f32,
127
+ ) {}
128
+ }
129
+
130
+ function colorRed(color: u32): u32 {
131
+ return (color >>> 24) & 0xff;
132
+ }
133
+
134
+ function colorGreen(color: u32): u32 {
135
+ return (color >>> 16) & 0xff;
136
+ }
137
+
138
+ function colorBlue(color: u32): u32 {
139
+ return (color >>> 8) & 0xff;
140
+ }
141
+
142
+ function colorAlpha(color: u32): u32 {
143
+ return color & 0xff;
144
+ }
145
+
146
+ function clampUnit(value: f32): f32 {
147
+ if (value < 0.0) {
148
+ return 0.0;
149
+ }
150
+ if (value > 1.0) {
151
+ return 1.0;
152
+ }
153
+ return value;
154
+ }
155
+
156
+ function mixChannel(from: u32, to: u32, amount: f32): u32 {
157
+ const weight = clampUnit(amount);
158
+ return <u32>Math.round(<f32>from + ((<f32>to - <f32>from) * weight));
159
+ }
160
+
161
+ function mixColor(from: u32, to: u32, amount: f32): u32 {
162
+ return rgba(
163
+ mixChannel(colorRed(from), colorRed(to), amount),
164
+ mixChannel(colorGreen(from), colorGreen(to), amount),
165
+ mixChannel(colorBlue(from), colorBlue(to), amount),
166
+ mixChannel(colorAlpha(from), colorAlpha(to), amount),
167
+ );
168
+ }
169
+
170
+ function withAlpha(color: u32, alpha: u32): u32 {
171
+ return rgba(colorRed(color), colorGreen(color), colorBlue(color), alpha);
172
+ }
173
+
174
+ function normalizeAccentColor(color: u32): u32 {
175
+ const normalized = color >>> 0;
176
+ if (normalized == 0) {
177
+ return DEFAULT_ACCENT_COLOR;
178
+ }
179
+ const alpha = colorAlpha(normalized);
180
+ if (alpha == 0) {
181
+ return withAlpha(normalized, 0xff);
182
+ }
183
+ return normalized;
184
+ }
185
+
186
+ function estimateThemeDark(theme: Theme): bool {
187
+ const background = theme.colors.background;
188
+ const luminance =
189
+ (<f32>colorRed(background) * 0.2126) +
190
+ (<f32>colorGreen(background) * 0.7152) +
191
+ (<f32>colorBlue(background) * 0.0722);
192
+ return luminance < 128.0;
193
+ }
194
+
195
+ const DEFAULT_SPACING = new Spacing(4.0, 8.0, 16.0, 24.0, 32.0);
196
+ const DEFAULT_BODY_STACK = new FontStack(1).fallback(3);
197
+ const DEFAULT_HEADING_STACK = new FontStack(2).fallback(3);
198
+ const DEFAULT_MONO_STACK = new FontStack(5).fallback(4).fallback(3);
199
+ const DEFAULT_MONO_BOLD_STACK = new FontStack(6).fallback(4).fallback(3);
200
+
201
+ const DEFAULT_FONTS = new Fonts(
202
+ 1,
203
+ 2,
204
+ 16.0,
205
+ 24.0,
206
+ 5,
207
+ 6,
208
+ 15.0,
209
+ new FontFamily(1, 2, 9, 10),
210
+ FontFamily.regularBoldStacks(DEFAULT_HEADING_STACK, DEFAULT_HEADING_STACK),
211
+ FontFamily.regularBoldStacks(DEFAULT_MONO_STACK, DEFAULT_MONO_BOLD_STACK),
212
+ );
213
+
214
+ export function generateTheme(isDark: bool, accentColor: u32 = DEFAULT_ACCENT_COLOR): Theme {
215
+ const accent = normalizeAccentColor(accentColor);
216
+ const background = isDark ? rgb(0x04, 0x0a, 0x14) : rgb(0xf8, 0xfa, 0xfc);
217
+ const surface = isDark ? rgb(0x0f, 0x17, 0x28) : rgb(0xff, 0xff, 0xff);
218
+ const textPrimary = isDark ? rgb(0xf8, 0xfa, 0xfc) : rgb(0x0f, 0x17, 0x2a);
219
+ const textMuted = isDark ? rgb(0x94, 0xa3, 0xb8) : rgb(0x47, 0x55, 0x69);
220
+ const border = isDark ? rgb(0x24, 0x3b, 0x53) : rgb(0xcb, 0xd5, 0xe1);
221
+ const accentHovered = isDark
222
+ ? mixColor(accent, WHITE, 0.14)
223
+ : mixColor(accent, WHITE, 0.10);
224
+ const accentPressed = isDark
225
+ ? mixColor(accent, BLACK, 0.24)
226
+ : mixColor(accent, BLACK, 0.16);
227
+ const selection = withAlpha(accent, isDark ? 0x40 : 0x33);
228
+ const scrollbarTrack = isDark ? rgb(0x12, 0x21, 0x33) : rgb(0xe2, 0xe8, 0xf0);
229
+ const scrollbarThumb = isDark
230
+ ? mixColor(accent, surface, 0.55)
231
+ : mixColor(accent, surface, 0.40);
232
+ const dialogBackdrop = isDark ? rgba(0x00, 0x00, 0x00, 0x24) : rgba(0x00, 0x00, 0x00, 0x18);
233
+ const dialogShadow = isDark ? rgba(0x00, 0x00, 0x00, 0xd8) : rgba(0x00, 0x00, 0x00, 0x88);
234
+ const focusRing = accent;
235
+ const contextMenuPanelBackground = isDark ? rgba(0x18, 0x1d, 0x26, 0xd8) : rgba(0xff, 0xff, 0xff, 0xdc);
236
+ const contextMenuPanelBorderColor = isDark ? rgba(0xff, 0xff, 0xff, 0x10) : rgba(0x0f, 0x17, 0x2a, 0x14);
237
+ const contextMenuPanelShadowColor = isDark ? rgba(0x00, 0x00, 0x00, 0xb0) : rgba(0x0f, 0x17, 0x2a, 0x24);
238
+ const contextMenuItemBackground = rgba(0x00, 0x00, 0x00, 0x00);
239
+ const contextMenuItemHover = isDark ? rgba(0xff, 0xff, 0xff, 0x0c) : rgba(0x0f, 0x17, 0x2a, 0x08);
240
+ const contextMenuSeparatorColor = isDark ? rgba(0xff, 0xff, 0xff, 0x10) : rgba(0x0f, 0x17, 0x2a, 0x12);
241
+ const toolTipPanelBackground = isDark ? rgba(0x11, 0x17, 0x20, 0xf0) : rgba(0xff, 0xff, 0xff, 0xf8);
242
+ const toolTipPanelBorderColor = isDark ? rgba(0xff, 0xff, 0xff, 0x12) : rgba(0x0f, 0x17, 0x2a, 0x12);
243
+ const toolTipPanelShadowColor = isDark ? rgba(0x00, 0x00, 0x00, 0xb8) : rgba(0x0f, 0x17, 0x2a, 0x22);
244
+
245
+ return new Theme(
246
+ new Colors(
247
+ background,
248
+ surface,
249
+ textPrimary,
250
+ textMuted,
251
+ accent,
252
+ accentPressed,
253
+ accentHovered,
254
+ border,
255
+ selection,
256
+ scrollbarTrack,
257
+ scrollbarThumb,
258
+ dialogBackdrop,
259
+ dialogShadow,
260
+ focusRing,
261
+ ),
262
+ DEFAULT_SPACING,
263
+ DEFAULT_FONTS,
264
+ new ContextMenuTheme(
265
+ contextMenuPanelBackground,
266
+ contextMenuPanelBorderColor,
267
+ contextMenuPanelShadowColor,
268
+ isDark ? 16.0 : 14.0,
269
+ contextMenuSeparatorColor,
270
+ 12.0,
271
+ 28.0,
272
+ 0.0,
273
+ new ContextMenuItemTheme(
274
+ contextMenuItemBackground,
275
+ contextMenuItemHover,
276
+ textPrimary,
277
+ isDark ? 10.0 : 9.0,
278
+ DEFAULT_FONTS.body,
279
+ DEFAULT_FONTS.bodyFamily,
280
+ 13.0,
281
+ 30.0,
282
+ 12.0,
283
+ 6.0,
284
+ 12.0,
285
+ 6.0,
286
+ ),
287
+ ),
288
+ new ToolTipTheme(
289
+ toolTipPanelBackground,
290
+ toolTipPanelBorderColor,
291
+ toolTipPanelShadowColor,
292
+ isDark ? 12.0 : 10.0,
293
+ textPrimary,
294
+ DEFAULT_FONTS.body,
295
+ DEFAULT_FONTS.bodyFamily,
296
+ 13.0,
297
+ 280.0,
298
+ 10.0,
299
+ 7.0,
300
+ 10.0,
301
+ 7.0,
302
+ 10.0,
303
+ 24.0,
304
+ 0.0,
305
+ ),
306
+ );
307
+ }
308
+
309
+ export const defaultLightTheme = generateTheme(false, DEFAULT_ACCENT_COLOR);
310
+ export const defaultDarkTheme = generateTheme(true, DEFAULT_ACCENT_COLOR);
311
+
312
+ let themeSource: ThemeSource = ThemeSource.System;
313
+ let systemDarkMode: bool = true;
314
+ let systemAccentColor: u32 = DEFAULT_ACCENT_COLOR;
315
+ let currentDarkMode: bool = true;
316
+
317
+ export const activeTheme = new Signal<Theme>(defaultDarkTheme);
318
+
319
+ export function bindTheme<Owner>(owner: Owner, handler: SignalHandler<Owner, Theme>): Action<Theme> {
320
+ const action = activeTheme.bind(owner, handler);
321
+ handler(owner, activeTheme.value);
322
+ return action;
323
+ }
324
+
325
+ function applyTheme(theme: Theme, source: ThemeSource, isDark: bool): Theme {
326
+ themeSource = source;
327
+ currentDarkMode = isDark;
328
+ activeTheme.value = theme;
329
+ return theme;
330
+ }
331
+
332
+ function applySystemTheme(): Theme {
333
+ return applyTheme(generateTheme(systemDarkMode, systemAccentColor), ThemeSource.System, systemDarkMode);
334
+ }
335
+
336
+ export function useSystemTheme(): Theme {
337
+ systemDarkMode = ffi.fui_is_dark_mode();
338
+ systemAccentColor = normalizeAccentColor(ffi.fui_get_accent_color());
339
+ return applySystemTheme();
340
+ }
341
+
342
+ export function useCustomTheme(theme: Theme): Theme {
343
+ return applyTheme(theme, ThemeSource.Custom, estimateThemeDark(theme));
344
+ }
345
+
346
+ export function setAccentColor(color: u32): Theme {
347
+ return useCustomTheme(generateTheme(currentDarkMode, color));
348
+ }
349
+
350
+ export function isDarkMode(): bool {
351
+ return currentDarkMode;
352
+ }
353
+
354
+ export function isUsingSystemTheme(): bool {
355
+ return themeSource == ThemeSource.System;
356
+ }
357
+
358
+ export function handleSystemDarkModeChanged(isDark: bool): Theme {
359
+ systemDarkMode = isDark;
360
+ if (themeSource != ThemeSource.System) {
361
+ return activeTheme.value;
362
+ }
363
+ systemAccentColor = normalizeAccentColor(ffi.fui_get_accent_color());
364
+ return applySystemTheme();
365
+ }
@@ -0,0 +1,129 @@
1
+ import * as ui from "../bindings/ui";
2
+
3
+ type TimerCallback = () => void;
4
+
5
+ const HOST_TIMER_ID: u32 = 1;
6
+
7
+ class TimerEntry {
8
+ constructor(
9
+ public timerId: u32,
10
+ public dueAtMs: f64,
11
+ public callback: TimerCallback,
12
+ ) {}
13
+ }
14
+
15
+ const activeTimers = new Map<u32, TimerEntry>();
16
+ let hostTimerArmed: bool = false;
17
+
18
+ function findNextDueAt(): f64 {
19
+ let nextDueAt = f64.MAX_VALUE;
20
+ const entries = activeTimers.values();
21
+ for (let i = 0; i < entries.length; ++i) {
22
+ const entry = unchecked(entries[i]);
23
+ if (entry.dueAtMs < nextDueAt) {
24
+ nextDueAt = entry.dueAtMs;
25
+ }
26
+ }
27
+ return nextDueAt;
28
+ }
29
+
30
+ function rearmHostTimer(): void {
31
+ if (activeTimers.size == 0) {
32
+ if (hostTimerArmed) {
33
+ ui.cancelTimer(HOST_TIMER_ID);
34
+ hostTimerArmed = false;
35
+ }
36
+ return;
37
+ }
38
+ const nextDueAt = findNextDueAt();
39
+ const now = ui.nowMs();
40
+ const delayMs = nextDueAt <= now ? 0 : <i32>Math.ceil(nextDueAt - now);
41
+ ui.startTimer(HOST_TIMER_ID, delayMs);
42
+ hostTimerArmed = true;
43
+ }
44
+
45
+ function sortDueTimerIds(timerIds: Array<u32>): void {
46
+ for (let i = 0; i < timerIds.length; ++i) {
47
+ let bestIndex = i;
48
+ let bestDueAt = f64.MAX_VALUE;
49
+ let bestTimerId = u32.MAX_VALUE;
50
+ for (let j = i; j < timerIds.length; ++j) {
51
+ const timerId = unchecked(timerIds[j]);
52
+ const entry = activeTimers.get(timerId);
53
+ if (entry === null) {
54
+ continue;
55
+ }
56
+ const dueAt = entry.dueAtMs;
57
+ if (dueAt < bestDueAt || (dueAt == bestDueAt && timerId < bestTimerId)) {
58
+ bestDueAt = dueAt;
59
+ bestTimerId = timerId;
60
+ bestIndex = j;
61
+ }
62
+ }
63
+ if (bestIndex != i) {
64
+ const current = unchecked(timerIds[i]);
65
+ unchecked(timerIds[i] = unchecked(timerIds[bestIndex]));
66
+ unchecked(timerIds[bestIndex] = current);
67
+ }
68
+ }
69
+ }
70
+
71
+ export function scheduleTimer(timerId: u32, delayMs: i32, callback: TimerCallback): void {
72
+ const dueAtMs = ui.nowMs() + <f64>(delayMs >= 0 ? delayMs : 0);
73
+ activeTimers.set(timerId, new TimerEntry(timerId, dueAtMs, callback));
74
+ rearmHostTimer();
75
+ }
76
+
77
+ export function cancelTimer(timerId: u32): void {
78
+ if (!activeTimers.delete(timerId)) {
79
+ return;
80
+ }
81
+ rearmHostTimer();
82
+ }
83
+
84
+ export function cancelAllTimers(): void {
85
+ activeTimers.clear();
86
+ if (!hostTimerArmed) {
87
+ return;
88
+ }
89
+ ui.cancelTimer(HOST_TIMER_ID);
90
+ hostTimerArmed = false;
91
+ }
92
+
93
+ export function hasTimer(timerId: u32): bool {
94
+ return activeTimers.has(timerId);
95
+ }
96
+
97
+ export function handleTimer(timerId: u32): void {
98
+ if (timerId != HOST_TIMER_ID) {
99
+ return;
100
+ }
101
+ hostTimerArmed = false;
102
+ if (activeTimers.size == 0) {
103
+ return;
104
+ }
105
+ const now = ui.nowMs();
106
+ const dueTimerIds = new Array<u32>();
107
+ const entries = activeTimers.values();
108
+ for (let i = 0; i < entries.length; ++i) {
109
+ const entry = unchecked(entries[i]);
110
+ if (entry.dueAtMs <= now) {
111
+ dueTimerIds.push(entry.timerId);
112
+ }
113
+ }
114
+ if (dueTimerIds.length == 0) {
115
+ rearmHostTimer();
116
+ return;
117
+ }
118
+ sortDueTimerIds(dueTimerIds);
119
+ for (let i = 0; i < dueTimerIds.length; ++i) {
120
+ const logicalTimerId = unchecked(dueTimerIds[i]);
121
+ const entry = activeTimers.get(logicalTimerId);
122
+ if (entry === null || entry.dueAtMs > now) {
123
+ continue;
124
+ }
125
+ activeTimers.delete(logicalTimerId);
126
+ entry.callback();
127
+ }
128
+ rearmHostTimer();
129
+ }
@@ -0,0 +1,122 @@
1
+ import { PopupPlacement } from "../controls/internal/PopupPresenter";
2
+
3
+ export { PopupPlacement } from "../controls/internal/PopupPresenter";
4
+
5
+ export class ToolTip {
6
+ private textValue: string = "";
7
+ private initialShowDelayMsValue: i32 = 700;
8
+ private betweenShowDelayMsValue: i32 = 100;
9
+ private showDurationMsValue: i32 = 5000;
10
+ private placementValue: PopupPlacement = PopupPlacement.Top;
11
+ private horizontalOffsetValue: f32 = 0.0;
12
+ private verticalOffsetValue: f32 = 0.0;
13
+ private openOnFocusValue: bool = true;
14
+ private panelBackgroundColorValue: u32 = 0;
15
+ private textColorValue: u32 = 0;
16
+ private panelBackgroundOverridden: bool = false;
17
+ private textColorOverridden: bool = false;
18
+
19
+ static text(value: string): ToolTip {
20
+ return new ToolTip().text(value);
21
+ }
22
+
23
+ text(value: string): this {
24
+ this.textValue = value;
25
+ return this;
26
+ }
27
+
28
+ get contentText(): string {
29
+ return this.textValue;
30
+ }
31
+
32
+ initialShowDelay(value: i32): this {
33
+ this.initialShowDelayMsValue = value >= 0 ? value : 0;
34
+ return this;
35
+ }
36
+
37
+ get initialShowDelayMs(): i32 {
38
+ return this.initialShowDelayMsValue;
39
+ }
40
+
41
+ betweenShowDelay(value: i32): this {
42
+ this.betweenShowDelayMsValue = value >= 0 ? value : 0;
43
+ return this;
44
+ }
45
+
46
+ get betweenShowDelayMs(): i32 {
47
+ return this.betweenShowDelayMsValue;
48
+ }
49
+
50
+ showDuration(value: i32): this {
51
+ this.showDurationMsValue = value >= 0 ? value : 0;
52
+ return this;
53
+ }
54
+
55
+ get showDurationMs(): i32 {
56
+ return this.showDurationMsValue;
57
+ }
58
+
59
+ placement(value: PopupPlacement): this {
60
+ this.placementValue = value;
61
+ return this;
62
+ }
63
+
64
+ get popupPlacement(): PopupPlacement {
65
+ return this.placementValue;
66
+ }
67
+
68
+ horizontalOffset(value: f32): this {
69
+ this.horizontalOffsetValue = value;
70
+ return this;
71
+ }
72
+
73
+ get horizontalOffsetValuePx(): f32 {
74
+ return this.horizontalOffsetValue;
75
+ }
76
+
77
+ verticalOffset(value: f32): this {
78
+ this.verticalOffsetValue = value;
79
+ return this;
80
+ }
81
+
82
+ get verticalOffsetValuePx(): f32 {
83
+ return this.verticalOffsetValue;
84
+ }
85
+
86
+ openOnFocus(flag: bool = true): this {
87
+ this.openOnFocusValue = flag;
88
+ return this;
89
+ }
90
+
91
+ get opensOnFocus(): bool {
92
+ return this.openOnFocusValue;
93
+ }
94
+
95
+ panelColor(color: u32): this {
96
+ this.panelBackgroundOverridden = true;
97
+ this.panelBackgroundColorValue = color;
98
+ return this;
99
+ }
100
+
101
+ get hasPanelColorOverride(): bool {
102
+ return this.panelBackgroundOverridden;
103
+ }
104
+
105
+ get panelBackgroundColor(): u32 {
106
+ return this.panelBackgroundColorValue;
107
+ }
108
+
109
+ textColor(color: u32): this {
110
+ this.textColorOverridden = true;
111
+ this.textColorValue = color;
112
+ return this;
113
+ }
114
+
115
+ get hasTextColorOverride(): bool {
116
+ return this.textColorOverridden;
117
+ }
118
+
119
+ get tooltipTextColor(): u32 {
120
+ return this.textColorValue;
121
+ }
122
+ }