@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,328 @@
1
+ import { FlexBox } from "../nodes/FlexBox";
2
+ import { ContextMenuManager } from "./ContextMenuManager";
3
+ import { disposeAllFetchRequests } from "./Fetch";
4
+ import { disposeAllFileRequests } from "./File";
5
+ import { FocusAdornerManager } from "./FocusAdornerManager";
6
+ import { ToolTipManager } from "./ToolTipManager";
7
+ import * as ui from "../bindings/ui";
8
+ import { flushCommit, markNeedsCommit, resetCommitState } from "./FrameScheduler";
9
+ import { HandleValue, Unit } from "./ffi";
10
+ import { Node } from "./Node";
11
+ import { cancelAllTimers } from "./Timers";
12
+ import {
13
+ Theme,
14
+ activeTheme,
15
+ isDarkMode,
16
+ isUsingSystemTheme,
17
+ setAccentColor,
18
+ useCustomTheme,
19
+ useSystemTheme,
20
+ } from "./Theme";
21
+ import { disposeAllWorkers } from "./Worker";
22
+ import { ControlTemplateSet, clearControlTemplates, getControlTemplates, useControlTemplates } from "../controls/ControlTemplateSet";
23
+
24
+ let mountedRoot: Node | null = null;
25
+ let mountedShell: FlexBox | null = null;
26
+ let exportedApplication: Application<Node> | null = null;
27
+
28
+ function createEmptyPage(): Node {
29
+ return new FlexBox().width(100.0, Unit.Percent).height(100.0, Unit.Percent);
30
+ }
31
+
32
+ class ApplicationShell extends FlexBox {
33
+ constructor(root: Node) {
34
+ super();
35
+ this.width(100.0, Unit.Percent)
36
+ .height(100.0, Unit.Percent)
37
+ .child(root)
38
+ .child(FocusAdornerManager.createDefaultHost())
39
+ .child(ContextMenuManager.createDefaultMenu())
40
+ .child(ToolTipManager.createDefaultHost());
41
+ }
42
+ }
43
+
44
+ export class ApplicationRegistration {
45
+ private buildPageFn: () => Node = createEmptyPage;
46
+ private useSystemThemeOnRegister: bool = false;
47
+ private customThemeOnRegister: Theme | null = null;
48
+ private accentColorOnRegister: u32 = 0;
49
+ private hasAccentColorOnRegister: bool = false;
50
+ private controlTemplatesOnRegister: ControlTemplateSet | null = null;
51
+ private hasControlTemplatesOnRegister: bool = false;
52
+
53
+ page(buildPage: () => Node): this {
54
+ this.buildPageFn = buildPage;
55
+ return this;
56
+ }
57
+
58
+ themeSystem(): this {
59
+ this.useSystemThemeOnRegister = true;
60
+ this.customThemeOnRegister = null;
61
+ this.hasAccentColorOnRegister = false;
62
+ return this;
63
+ }
64
+
65
+ theme(theme: Theme): this {
66
+ this.useSystemThemeOnRegister = false;
67
+ this.customThemeOnRegister = theme;
68
+ this.hasAccentColorOnRegister = false;
69
+ return this;
70
+ }
71
+
72
+ accentColor(color: u32): this {
73
+ this.useSystemThemeOnRegister = false;
74
+ this.customThemeOnRegister = null;
75
+ this.accentColorOnRegister = color;
76
+ this.hasAccentColorOnRegister = true;
77
+ return this;
78
+ }
79
+
80
+ controlTemplates(templates: ControlTemplateSet | null): this {
81
+ this.controlTemplatesOnRegister = templates;
82
+ this.hasControlTemplatesOnRegister = true;
83
+ return this;
84
+ }
85
+
86
+ register(): Application<Node> {
87
+ const app = createApplication(this.buildPageFn);
88
+ app.useControlTemplates(this.hasControlTemplatesOnRegister ? this.controlTemplatesOnRegister : null);
89
+ if (this.useSystemThemeOnRegister) {
90
+ app.useSystemTheme();
91
+ return app;
92
+ }
93
+ const customTheme = this.customThemeOnRegister;
94
+ if (customTheme !== null) {
95
+ app.useCustomTheme(customTheme);
96
+ return app;
97
+ }
98
+ if (this.hasAccentColorOnRegister) {
99
+ app.setAccentColor(this.accentColorOnRegister);
100
+ }
101
+ return app;
102
+ }
103
+ }
104
+
105
+ export class Application<TPage> {
106
+ private activePage: TPage | null = null;
107
+ private readonly buildPage: () => TPage;
108
+ private readonly getRoot: (page: TPage) => Node;
109
+ private readonly mountPage: ((page: TPage) => void) | null;
110
+ private readonly disposePage: ((page: TPage) => void) | null;
111
+
112
+ constructor(
113
+ buildPage: () => TPage,
114
+ getRoot: (page: TPage) => Node,
115
+ mountPage: ((page: TPage) => void) | null,
116
+ disposePage: ((page: TPage) => void) | null,
117
+ ) {
118
+ this.buildPage = buildPage;
119
+ this.getRoot = getRoot;
120
+ this.mountPage = mountPage;
121
+ this.disposePage = disposePage;
122
+ }
123
+
124
+ run(): void {
125
+ this.dispose();
126
+ const page = this.buildPage();
127
+ this.activePage = page;
128
+ if (this.mountPage !== null) {
129
+ this.mountPage(page);
130
+ return;
131
+ }
132
+ Application.mount(this.getRoot(page));
133
+ }
134
+
135
+ dispose(): void {
136
+ const page = this.activePage;
137
+ if (page === null) {
138
+ return;
139
+ }
140
+ this.activePage = null;
141
+ if (this.disposePage !== null) {
142
+ this.disposePage(page);
143
+ return;
144
+ }
145
+ Application.unmount();
146
+ }
147
+
148
+ flushRenders(): void {
149
+ Application.flushRenders();
150
+ }
151
+
152
+ getActivePage(): TPage | null {
153
+ return this.activePage;
154
+ }
155
+
156
+ useSystemTheme(): Theme {
157
+ return useSystemTheme();
158
+ }
159
+
160
+ useCustomTheme(theme: Theme): Theme {
161
+ return useCustomTheme(theme);
162
+ }
163
+
164
+ setAccentColor(color: u32): Theme {
165
+ return setAccentColor(color);
166
+ }
167
+
168
+ isDarkMode(): bool {
169
+ return isDarkMode();
170
+ }
171
+
172
+ isUsingSystemTheme(): bool {
173
+ return isUsingSystemTheme();
174
+ }
175
+
176
+ getTheme(): Theme {
177
+ return activeTheme.value;
178
+ }
179
+
180
+ useControlTemplates(templates: ControlTemplateSet | null): ControlTemplateSet | null {
181
+ return useControlTemplates(templates);
182
+ }
183
+
184
+ clearControlTemplates(): void {
185
+ clearControlTemplates();
186
+ }
187
+
188
+ getControlTemplates(): ControlTemplateSet | null {
189
+ return getControlTemplates();
190
+ }
191
+
192
+ static mount(root: Node): Node {
193
+ const previousShell = mountedShell;
194
+ if (previousShell !== null) {
195
+ previousShell.dispose();
196
+ mountedShell = null;
197
+ mountedRoot = null;
198
+ }
199
+
200
+ cancelAllTimers();
201
+ disposeAllFetchRequests();
202
+ disposeAllFileRequests();
203
+ disposeAllWorkers();
204
+ FocusAdornerManager.clear();
205
+ ToolTipManager.clear();
206
+ resetCommitState();
207
+ ui.reset();
208
+ ui.resizeWindow(ui.getViewportWidth(), ui.getViewportHeight());
209
+ const shell = new ApplicationShell(root);
210
+ shell.build();
211
+ ui.setRoot(shell.builtHandle);
212
+ mountedShell = shell;
213
+ mountedRoot = root;
214
+ markNeedsCommit();
215
+ return root;
216
+ }
217
+
218
+ static unmount(): void {
219
+ const previous = mountedShell;
220
+ if (previous === null) {
221
+ return;
222
+ }
223
+ previous.dispose();
224
+ mountedShell = null;
225
+ mountedRoot = null;
226
+ cancelAllTimers();
227
+ disposeAllFetchRequests();
228
+ disposeAllFileRequests();
229
+ disposeAllWorkers();
230
+ FocusAdornerManager.clear();
231
+ ToolTipManager.clear();
232
+ resetCommitState();
233
+ }
234
+
235
+ static flushRenders(): void {
236
+ const shell = mountedShell;
237
+ if (shell === null) {
238
+ resetCommitState();
239
+ return;
240
+ }
241
+ if (shell.builtHandle == <u64>HandleValue.Invalid) {
242
+ resetCommitState();
243
+ return;
244
+ }
245
+ flushCommit();
246
+ if (FocusAdornerManager.refreshAfterCommit()) {
247
+ flushCommit();
248
+ }
249
+ }
250
+
251
+ static capturePersistedUiState(): void {
252
+ const root = mountedRoot;
253
+ if (root === null) {
254
+ return;
255
+ }
256
+ root._capturePersistedStateTree();
257
+ }
258
+
259
+ static restorePersistedUiState(): void {
260
+ const root = mountedRoot;
261
+ if (root === null) {
262
+ return;
263
+ }
264
+ root._restorePersistedStateTree();
265
+ }
266
+
267
+ static register(configure: (registration: ApplicationRegistration) => void): Application<Node> {
268
+ const registration = new ApplicationRegistration();
269
+ configure(registration);
270
+ return registration.register();
271
+ }
272
+ }
273
+
274
+ function getNodeRoot(page: Node): Node {
275
+ return page;
276
+ }
277
+
278
+ export function createApplication(buildPage: () => Node): Application<Node> {
279
+ const app = new Application<Node>(buildPage, getNodeRoot, null, null);
280
+ exportedApplication = app;
281
+ return app;
282
+ }
283
+
284
+ export function createManagedApplication<TPage>(
285
+ buildPage: () => TPage,
286
+ getRoot: (page: TPage) => Node,
287
+ mountPage: ((page: TPage) => void) | null,
288
+ disposePage: ((page: TPage) => void) | null,
289
+ ): Application<TPage> {
290
+ const app = new Application<TPage>(buildPage, getRoot, mountPage, disposePage);
291
+ exportedApplication = changetype<Application<Node>>(app);
292
+ return app;
293
+ }
294
+
295
+ export function __runApp(): void {
296
+ const app = exportedApplication;
297
+ if (app === null) {
298
+ return;
299
+ }
300
+ app.run();
301
+ }
302
+
303
+ export function __disposeApp(): void {
304
+ const app = exportedApplication;
305
+ if (app === null) {
306
+ return;
307
+ }
308
+ app.dispose();
309
+ disposeAllFetchRequests();
310
+ disposeAllWorkers();
311
+ }
312
+
313
+ export function __flushRenders(): void {
314
+ const app = exportedApplication;
315
+ if (app === null) {
316
+ Application.flushRenders();
317
+ return;
318
+ }
319
+ app.flushRenders();
320
+ }
321
+
322
+ export function __fui_capture_persisted_ui_state(): void {
323
+ Application.capturePersistedUiState();
324
+ }
325
+
326
+ export function __fui_restore_persisted_ui_state(): void {
327
+ Application.restorePersistedUiState();
328
+ }
@@ -0,0 +1,264 @@
1
+ import * as ui from "../bindings/ui";
2
+ import { allocateDynamicSvgId, allocateDynamicTextureId } from "./DynamicAssetIds";
3
+ import { Signal } from "./Signal";
4
+ import { warn } from "./Logger";
5
+
6
+ export enum AssetLoadState {
7
+ Idle = 0,
8
+ Loading = 1,
9
+ Ready = 2,
10
+ Failed = 3,
11
+ }
12
+
13
+ class AssetRecord {
14
+ readonly state: Signal<AssetLoadState> = new Signal<AssetLoadState>(AssetLoadState.Idle);
15
+ error: string = "";
16
+ width: f32 = 0.0;
17
+ height: f32 = 0.0;
18
+ url: string = "";
19
+ }
20
+
21
+ const svgAssets = new Map<u32, AssetRecord>();
22
+ const textureAssets = new Map<u32, AssetRecord>();
23
+ const svgIdsByUrl = new Map<string, u32>();
24
+ const textureIdsByUrl = new Map<string, u32>();
25
+ const svgRefCounts = new Map<u32, i32>();
26
+ const textureRefCounts = new Map<u32, i32>();
27
+ const pinnedSvgIds = new Set<u32>();
28
+ const pinnedTextureIds = new Set<u32>();
29
+
30
+ function getAssetRecord(records: Map<u32, AssetRecord>, assetId: u32): AssetRecord {
31
+ if (!records.has(assetId)) {
32
+ records.set(assetId, new AssetRecord());
33
+ }
34
+ return unchecked(records.get(assetId));
35
+ }
36
+
37
+ function beginLoad(record: AssetRecord): void {
38
+ record.error = "";
39
+ record.width = 0.0;
40
+ record.height = 0.0;
41
+ record.state.value = AssetLoadState.Loading;
42
+ }
43
+
44
+ function markLoaded(record: AssetRecord, width: f32, height: f32): void {
45
+ record.error = "";
46
+ record.width = width;
47
+ record.height = height;
48
+ record.state.value = AssetLoadState.Ready;
49
+ }
50
+
51
+ function markFailed(record: AssetRecord, error: string): void {
52
+ record.error = error;
53
+ record.width = 0.0;
54
+ record.height = 0.0;
55
+ record.state.value = AssetLoadState.Failed;
56
+ }
57
+
58
+ function incrementRefCount(refCounts: Map<u32, i32>, assetId: u32): void {
59
+ const nextCount = (refCounts.has(assetId) ? unchecked(refCounts.get(assetId)) : 0) + 1;
60
+ refCounts.set(assetId, nextCount);
61
+ }
62
+
63
+ function decrementRefCount(refCounts: Map<u32, i32>, assetId: u32): i32 {
64
+ if (!refCounts.has(assetId)) {
65
+ return -1;
66
+ }
67
+ const nextCount = unchecked(refCounts.get(assetId)) - 1;
68
+ if (nextCount <= 0) {
69
+ refCounts.delete(assetId);
70
+ return 0;
71
+ }
72
+ refCounts.set(assetId, nextCount);
73
+ return nextCount;
74
+ }
75
+
76
+ function removeUrlBinding(urlToId: Map<string, u32>, url: string, assetId: u32): void {
77
+ if (url.length == 0 || !urlToId.has(url)) {
78
+ return;
79
+ }
80
+ if (unchecked(urlToId.get(url)) != assetId) {
81
+ return;
82
+ }
83
+ urlToId.delete(url);
84
+ }
85
+
86
+ export function getSvgAssetState(svgId: u32): Signal<AssetLoadState> {
87
+ return getAssetRecord(svgAssets, svgId).state;
88
+ }
89
+
90
+ export function getTextureAssetState(textureId: u32): Signal<AssetLoadState> {
91
+ return getAssetRecord(textureAssets, textureId).state;
92
+ }
93
+
94
+ export function getSvgAssetError(svgId: u32): string {
95
+ return getAssetRecord(svgAssets, svgId).error;
96
+ }
97
+
98
+ export function getSvgAssetUrl(svgId: u32): string {
99
+ return getAssetRecord(svgAssets, svgId).url;
100
+ }
101
+
102
+ export function getSvgAssetWidth(svgId: u32): f32 {
103
+ return getAssetRecord(svgAssets, svgId).width;
104
+ }
105
+
106
+ export function getSvgAssetHeight(svgId: u32): f32 {
107
+ return getAssetRecord(svgAssets, svgId).height;
108
+ }
109
+
110
+ export function getTextureAssetError(textureId: u32): string {
111
+ return getAssetRecord(textureAssets, textureId).error;
112
+ }
113
+
114
+ export function getTextureAssetUrl(textureId: u32): string {
115
+ return getAssetRecord(textureAssets, textureId).url;
116
+ }
117
+
118
+ export function getTextureAssetWidth(textureId: u32): f32 {
119
+ return getAssetRecord(textureAssets, textureId).width;
120
+ }
121
+
122
+ export function getTextureAssetHeight(textureId: u32): f32 {
123
+ return getAssetRecord(textureAssets, textureId).height;
124
+ }
125
+
126
+ function loadSvgInternal(svgId: u32, url: string, pinned: bool): void {
127
+ const record = getAssetRecord(svgAssets, svgId);
128
+ if (record.url.length > 0 && record.url != url) {
129
+ removeUrlBinding(svgIdsByUrl, record.url, svgId);
130
+ }
131
+ svgIdsByUrl.set(url, svgId);
132
+ if (pinned) {
133
+ pinnedSvgIds.add(svgId);
134
+ } else {
135
+ pinnedSvgIds.delete(svgId);
136
+ }
137
+ record.url = url;
138
+ beginLoad(record);
139
+ ui.loadSvg(svgId, url);
140
+ }
141
+
142
+ function loadTextureInternal(textureId: u32, url: string, pinned: bool): void {
143
+ const record = getAssetRecord(textureAssets, textureId);
144
+ if (record.url.length > 0 && record.url != url) {
145
+ removeUrlBinding(textureIdsByUrl, record.url, textureId);
146
+ }
147
+ textureIdsByUrl.set(url, textureId);
148
+ if (pinned) {
149
+ pinnedTextureIds.add(textureId);
150
+ } else {
151
+ pinnedTextureIds.delete(textureId);
152
+ }
153
+ record.url = url;
154
+ beginLoad(record);
155
+ ui.loadTexture(textureId, url);
156
+ }
157
+
158
+ export function loadSvg(svgId: u32, url: string): void {
159
+ loadSvgInternal(svgId, url, true);
160
+ }
161
+
162
+ export function loadTexture(textureId: u32, url: string): void {
163
+ loadTextureInternal(textureId, url, true);
164
+ }
165
+
166
+ export function acquireSvgAsset(url: string): u32 {
167
+ if (url.length == 0) {
168
+ return 0;
169
+ }
170
+ let svgId = 0;
171
+ if (svgIdsByUrl.has(url)) {
172
+ svgId = unchecked(svgIdsByUrl.get(url));
173
+ } else {
174
+ svgId = allocateDynamicSvgId();
175
+ loadSvgInternal(svgId, url, false);
176
+ }
177
+ incrementRefCount(svgRefCounts, svgId);
178
+ return svgId;
179
+ }
180
+
181
+ export function acquireTextureAsset(url: string): u32 {
182
+ if (url.length == 0) {
183
+ return 0;
184
+ }
185
+ let textureId = 0;
186
+ if (textureIdsByUrl.has(url)) {
187
+ textureId = unchecked(textureIdsByUrl.get(url));
188
+ } else {
189
+ textureId = allocateDynamicTextureId();
190
+ loadTextureInternal(textureId, url, false);
191
+ }
192
+ incrementRefCount(textureRefCounts, textureId);
193
+ return textureId;
194
+ }
195
+
196
+ export function releaseSvgAsset(svgId: u32): void {
197
+ if (svgId == 0) {
198
+ return;
199
+ }
200
+ const remainingRefCount = decrementRefCount(svgRefCounts, svgId);
201
+ if (remainingRefCount > 0 || remainingRefCount < 0 || pinnedSvgIds.has(svgId)) {
202
+ return;
203
+ }
204
+ const record = getAssetRecord(svgAssets, svgId);
205
+ removeUrlBinding(svgIdsByUrl, record.url, svgId);
206
+ svgAssets.delete(svgId);
207
+ ui.releaseSvg(svgId);
208
+ }
209
+
210
+ export function releaseTextureAsset(textureId: u32): void {
211
+ if (textureId == 0) {
212
+ return;
213
+ }
214
+ const remainingRefCount = decrementRefCount(textureRefCounts, textureId);
215
+ if (remainingRefCount > 0 || remainingRefCount < 0 || pinnedTextureIds.has(textureId)) {
216
+ return;
217
+ }
218
+ const record = getAssetRecord(textureAssets, textureId);
219
+ removeUrlBinding(textureIdsByUrl, record.url, textureId);
220
+ textureAssets.delete(textureId);
221
+ ui.releaseTexture(textureId);
222
+ }
223
+
224
+ export function ensureSvgAsset(url: string): u32 {
225
+ return acquireSvgAsset(url);
226
+ }
227
+
228
+ export function ensureTextureAsset(url: string): u32 {
229
+ return acquireTextureAsset(url);
230
+ }
231
+
232
+ export function __fui_on_svg_loaded(svgId: u32, width: f32, height: f32): void {
233
+ markLoaded(getAssetRecord(svgAssets, svgId), width, height);
234
+ }
235
+
236
+ export function __fui_on_svg_failed(svgId: u32, errorPtr: usize, errorLen: u32): void {
237
+ const error = errorLen > 0 ? String.UTF8.decodeUnsafe(errorPtr, <usize>errorLen, false) : "";
238
+ const record = getAssetRecord(svgAssets, svgId);
239
+ markFailed(record, error);
240
+ warn(
241
+ "Assets",
242
+ "SVG load failed for \"" +
243
+ record.url +
244
+ "\": " +
245
+ (error.length > 0 ? error : "unknown error"),
246
+ );
247
+ }
248
+
249
+ export function __fui_on_texture_loaded(textureId: u32, width: f32, height: f32): void {
250
+ markLoaded(getAssetRecord(textureAssets, textureId), width, height);
251
+ }
252
+
253
+ export function __fui_on_texture_failed(textureId: u32, errorPtr: usize, errorLen: u32): void {
254
+ const error = errorLen > 0 ? String.UTF8.decodeUnsafe(errorPtr, <usize>errorLen, false) : "";
255
+ const record = getAssetRecord(textureAssets, textureId);
256
+ markFailed(record, error);
257
+ warn(
258
+ "Assets",
259
+ "Texture load failed for \"" +
260
+ record.url +
261
+ "\": " +
262
+ (error.length > 0 ? error : "unknown error"),
263
+ );
264
+ }
@@ -0,0 +1,32 @@
1
+ import * as ui from "../bindings/ui";
2
+
3
+ let nextAttachedPropertyKey: u32 = 1;
4
+ const gridSharedSizeScopeKeys = new Map<u32, bool>();
5
+
6
+ export function allocateAttachedPropertyKey(): u32 {
7
+ const key = nextAttachedPropertyKey;
8
+ nextAttachedPropertyKey += 1;
9
+ return key;
10
+ }
11
+
12
+ export function setGridSharedSizeScope(key: u32, enabled: bool): bool {
13
+ const hasValue = gridSharedSizeScopeKeys.has(key);
14
+ if (enabled) {
15
+ if (hasValue) {
16
+ return false;
17
+ }
18
+ gridSharedSizeScopeKeys.set(key, true);
19
+ return true;
20
+ }
21
+ if (!hasValue) {
22
+ return false;
23
+ }
24
+ gridSharedSizeScopeKeys.delete(key);
25
+ return true;
26
+ }
27
+
28
+ export function applyAttachedProperties(key: u32, handle: u64): void {
29
+ if (gridSharedSizeScopeKeys.has(key)) {
30
+ ui.setIsSharedSizeScope(handle, true);
31
+ }
32
+ }
@@ -0,0 +1,70 @@
1
+ import * as ui from "../bindings/ui";
2
+ import { Disposable } from "./Disposable";
3
+ import { allocateDynamicTextureId } from "./DynamicAssetIds";
4
+ import { throwObjectDisposed } from "./Errors";
5
+ import { error } from "./Logger";
6
+
7
+ const FUNCTION_BITMAP_CONSTRUCTOR = "Bitmap.constructor";
8
+
9
+ function throwBitmapError(category: string, message: string): void {
10
+ error(category, message);
11
+ throw new Error(message);
12
+ }
13
+
14
+ function validateBitmapDimensions(width: u32, height: u32): u32 {
15
+ if (width == 0 || height == 0) {
16
+ throwBitmapError("Validation", FUNCTION_BITMAP_CONSTRUCTOR + ": width and height must be greater than zero");
17
+ }
18
+ const totalBytes = <u64>width * <u64>height * 4;
19
+ if (totalBytes > <u64>0x7fffffff) {
20
+ throwBitmapError("Validation", FUNCTION_BITMAP_CONSTRUCTOR + ": bitmap byte length exceeds the supported AssemblyScript array size");
21
+ }
22
+ return <u32>totalBytes;
23
+ }
24
+
25
+ export class Bitmap implements Disposable {
26
+ readonly width: u32;
27
+ readonly height: u32;
28
+ readonly textureId: u32;
29
+ private pixelBytes: Uint8Array;
30
+ private disposed: bool = false;
31
+
32
+ constructor(width: u32, height: u32) {
33
+ const byteLength = validateBitmapDimensions(width, height);
34
+ this.width = width;
35
+ this.height = height;
36
+ this.textureId = allocateDynamicTextureId();
37
+ this.pixelBytes = new Uint8Array(<i32>byteLength);
38
+ }
39
+
40
+ pixels(): Uint8Array {
41
+ this.ensureNotDisposed("Bitmap.pixels");
42
+ return this.pixelBytes;
43
+ }
44
+
45
+ pixelPtr(): usize {
46
+ const pixels = this.pixels();
47
+ return pixels.length > 0 ? pixels.dataStart : 0;
48
+ }
49
+
50
+ commit(): u32 {
51
+ this.ensureNotDisposed("Bitmap.commit");
52
+ ui.bitmapCommit(this.textureId, this.pixelBytes, this.width, this.height);
53
+ return this.textureId;
54
+ }
55
+
56
+ dispose(): void {
57
+ if (this.disposed) {
58
+ return;
59
+ }
60
+ this.disposed = true;
61
+ ui.bitmapRelease(this.textureId);
62
+ this.pixelBytes = new Uint8Array(0);
63
+ }
64
+
65
+ private ensureNotDisposed(functionName: string): void {
66
+ if (this.disposed) {
67
+ throwObjectDisposed(functionName, "Bitmap");
68
+ }
69
+ }
70
+ }