@effindomv2/fui-as 0.1.37 → 0.1.41

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 (119) hide show
  1. package/browser/routed-app-conventions.ts +1 -1
  2. package/browser/src/common-harness.ts +1 -27
  3. package/browser/src/index.ts +19 -26
  4. package/browser/src/routed-harness.ts +1 -194
  5. package/browser/src/shared-browser.ts +3 -0
  6. package/package.json +23 -13
  7. package/scripts/build.sh +70 -46
  8. package/scripts/check-runtime-dependency.sh +0 -19
  9. package/scripts/framework-host-services.ts +1 -1
  10. package/scripts/generate-host-events.ts +15 -241
  11. package/scripts/generate-host-services.ts +13 -261
  12. package/scripts/hostgen/assemblyscript-host-events.ts +165 -0
  13. package/scripts/hostgen/assemblyscript-host-services.ts +207 -0
  14. package/scripts/hostgen/common.ts +73 -0
  15. package/scripts/hostgen/registry.ts +159 -0
  16. package/src/Fui.ts +65 -5
  17. package/src/FuiExports.ts +12 -1
  18. package/src/bindings/ui.ts +157 -8
  19. package/src/controls/Button.ts +98 -71
  20. package/src/controls/Checkbox.ts +23 -21
  21. package/src/controls/ComboBox.ts +1201 -0
  22. package/src/controls/ContextMenu.ts +279 -165
  23. package/src/controls/Dialog.ts +91 -17
  24. package/src/controls/Dropdown.ts +101 -333
  25. package/src/controls/NavLink.ts +29 -6
  26. package/src/controls/Popup.ts +10 -5
  27. package/src/controls/ProgressBar.ts +1 -2
  28. package/src/controls/RadioButton.ts +34 -13
  29. package/src/controls/RadioGroup.ts +32 -19
  30. package/src/controls/Slider.ts +21 -23
  31. package/src/controls/Switch.ts +34 -17
  32. package/src/controls/index.ts +2 -1
  33. package/src/controls/internal/ButtonPresenter.ts +5 -2
  34. package/src/controls/internal/CheckboxIndicatorPresenter.ts +10 -10
  35. package/src/controls/internal/DropdownChevronPresenter.ts +3 -3
  36. package/src/controls/internal/DropdownFieldPresenter.ts +6 -6
  37. package/src/controls/internal/DropdownOptionRowPresenter.ts +5 -4
  38. package/src/controls/internal/PressableLabeledControl.ts +4 -7
  39. package/src/controls/internal/RadioIndicatorPresenter.ts +7 -7
  40. package/src/controls/internal/SelectablePopupList.ts +463 -0
  41. package/src/controls/internal/SliderPresenter.ts +5 -5
  42. package/src/controls/internal/SwitchIndicatorPresenter.ts +63 -17
  43. package/src/controls/internal/TextInputCore.ts +307 -41
  44. package/src/controls/internal/TextInputPresenter.ts +2 -2
  45. package/src/controls/templating.ts +10 -0
  46. package/src/core/Application.ts +17 -8
  47. package/src/core/Bitmap.ts +192 -3
  48. package/src/core/ContextMenuManager.ts +128 -44
  49. package/src/core/DebugApi.ts +17 -2
  50. package/src/core/DragGesture.ts +8 -7
  51. package/src/core/EventRouter.ts +332 -12
  52. package/src/core/Fetch.ts +15 -7
  53. package/src/core/FetchFfi.ts +1 -15
  54. package/src/core/File.ts +75 -34
  55. package/src/core/FocusAdornerManager.ts +3 -3
  56. package/src/core/FocusVisibility.ts +7 -0
  57. package/src/core/FrameScheduler.ts +104 -5
  58. package/src/core/ImageSampling.ts +43 -0
  59. package/src/core/Logger.ts +2 -0
  60. package/src/core/MobileTextSelectionToolbarManager.ts +901 -0
  61. package/src/core/Node.ts +1039 -97
  62. package/src/core/Platform.ts +16 -16
  63. package/src/core/SelectionHandleAdornerManager.ts +697 -0
  64. package/src/core/Theme.ts +34 -20
  65. package/src/core/ToolTipManager.ts +3 -3
  66. package/src/core/Typography.ts +411 -19
  67. package/src/core/Worker.ts +69 -33
  68. package/src/core/event_exports.ts +123 -5
  69. package/src/core/ffi.ts +5 -865
  70. package/src/core/generated/FetchHostAbi.ts +10 -0
  71. package/src/core/generated/FrameworkHostServices.ts +1 -1
  72. package/src/core/generated/HostAbi.ts +220 -0
  73. package/src/core/generated/UiAbi.ts +356 -0
  74. package/src/core/generated/UiEnums.ts +186 -0
  75. package/src/drawing/DrawCallback.ts +17 -1
  76. package/src/drawing/DrawContext.ts +140 -32
  77. package/src/nodes/CustomDrawable.ts +8 -1
  78. package/src/nodes/FlexBox.ts +40 -11
  79. package/src/nodes/Grid.ts +5 -5
  80. package/src/nodes/Image.ts +20 -1
  81. package/src/nodes/RichText.ts +36 -25
  82. package/src/nodes/ScrollBar.ts +2 -1
  83. package/src/nodes/ScrollBox.ts +7 -2
  84. package/src/nodes/ScrollView.ts +17 -6
  85. package/src/nodes/Svg.ts +18 -1
  86. package/src/nodes/Text.ts +1 -1
  87. package/src/nodes/TextCore.ts +115 -20
  88. package/src/nodes/index.ts +1 -1
  89. package/src/text/DynamicTextLayout.ts +275 -0
  90. package/src/text/TextLayout.ts +308 -0
  91. package/src/worker/Worker.ts +3 -36
  92. package/src/worker/WorkerJob.ts +5 -9
  93. package/src/worker/ffi.ts +0 -6
  94. package/browser/src/common-harness/host-imports.ts +0 -447
  95. package/browser/src/common-harness/interop.ts +0 -39
  96. package/browser/src/common-harness/managed-harness-bitmap-host.ts +0 -92
  97. package/browser/src/common-harness/managed-harness-canvas-host.ts +0 -72
  98. package/browser/src/common-harness/managed-harness-fetch-host.ts +0 -201
  99. package/browser/src/common-harness/managed-harness-file-host.ts +0 -1066
  100. package/browser/src/common-harness/managed-harness-file-payloads.ts +0 -143
  101. package/browser/src/common-harness/managed-harness-file-types.ts +0 -71
  102. package/browser/src/common-harness/managed-harness-session.ts +0 -15
  103. package/browser/src/common-harness/managed-harness.ts +0 -1472
  104. package/browser/src/common-harness/managed-history.ts +0 -168
  105. package/browser/src/common-harness/persisted-restore-policy.ts +0 -50
  106. package/browser/src/common-harness/persisted-ui-state-controller.ts +0 -309
  107. package/browser/src/common-harness/text-session-bridge.ts +0 -452
  108. package/browser/src/common-harness/types.ts +0 -206
  109. package/browser/src/common-harness/ui-chrome.ts +0 -206
  110. package/browser/src/common-harness/ui-imports.ts +0 -559
  111. package/browser/src/common-harness/wasm-module-cache.ts +0 -47
  112. package/browser/src/file-processing-worker.ts +0 -89
  113. package/browser/src/host-events.ts +0 -118
  114. package/browser/src/host-services.ts +0 -483
  115. package/browser/src/persisted-ui-state.ts +0 -206
  116. package/browser/src/routed-app-conventions.ts +0 -292
  117. package/browser/src/worker-bootstrap.ts +0 -916
  118. package/browser/src/worker-manager.ts +0 -234
  119. package/browser/src/worker-types.ts +0 -68
@@ -1,447 +0,0 @@
1
- import type { BridgeRuntime } from '@effindomv2/runtime';
2
-
3
- import type { WorkerManager } from '../worker-manager';
4
- import type { HarnessExports } from './types';
5
- import type { PersistedUiStateController } from './persisted-ui-state-controller';
6
- import type { TextSessionBridge } from './text-session-bridge';
7
- import type { HarnessUiChrome } from './ui-chrome';
8
- import { toBigIntHandle, type AppHandleLike } from './interop';
9
-
10
- interface HostImportSessionLike {
11
- readonly exports: HarnessExports;
12
- readonly memory: WebAssembly.Memory;
13
- readonly textBufferPtr: number;
14
- readonly textBufferSize: number;
15
- }
16
-
17
- export interface HostImportDeps {
18
- getRuntime(): BridgeRuntime;
19
- getCurrentSession(): HostImportSessionLike;
20
- getCurrentSessionOrNull(): HostImportSessionLike | null;
21
- setAppFlushRequested(value: boolean): void;
22
- queueHarnessFrame(): void;
23
- uiChrome: HarnessUiChrome;
24
- readAppUtf8(ptr: number, len: number): string;
25
- writeAppFloat32(ptr: number, value: number): void;
26
- writeAppUint32(ptr: number, value: number): void;
27
- writeAppUtf8(ptr: number, capacity: number, text: string, context: string): number;
28
- textBridge: TextSessionBridge;
29
- persistedUiStateController: PersistedUiStateController;
30
- navigateWithinDocument(target: string, openInNewTab: boolean): void;
31
- canBrowserNavigateBack(): boolean;
32
- canBrowserNavigateForward(): boolean;
33
- navigateBrowserBack(): void;
34
- navigateBrowserForward(): void;
35
- cancelHostTimer(timerId: number): void;
36
- getHostTimer(timerId: number): number | undefined;
37
- setHostTimer(timerId: number, timeoutId: number): void;
38
- deleteHostTimer(timerId: number): void;
39
- workerManager: WorkerManager;
40
- debugLogsEnabled: boolean;
41
- notifySvgLoaded(session: HostImportSessionLike | null, svgId: number, width: number, height: number): void;
42
- notifySvgFailed(session: HostImportSessionLike | null, svgId: number, error: string): void;
43
- notifyTextureLoaded(session: HostImportSessionLike | null, textureId: number, width: number, height: number): void;
44
- notifyTextureFailed(session: HostImportSessionLike | null, textureId: number, error: string): void;
45
- }
46
-
47
- export function createHostImportModule(deps: HostImportDeps) {
48
- function safeNotify(label: string, notify: () => void): void {
49
- try {
50
- notify();
51
- } catch (error: unknown) {
52
- const message = error instanceof Error ? error.message : String(error);
53
- console.error(`[fui_host] ${label}: ${message}`);
54
- }
55
- }
56
-
57
- return {
58
- request_render(): void {
59
- const runtime = deps.getRuntime();
60
- deps.setAppFlushRequested(true);
61
- runtime.requestFrame();
62
- deps.queueHarnessFrame();
63
- },
64
- get_viewport_width(): number {
65
- const runtime = deps.getRuntime();
66
- const sizeSource = deps.uiChrome.getCanvasSizeSource(runtime.canvas);
67
- const rect = sizeSource.getBoundingClientRect();
68
- return sizeSource.clientWidth > 0 ? sizeSource.clientWidth : (rect.width > 0 ? rect.width : runtime.canvas.width);
69
- },
70
- get_viewport_height(): number {
71
- const runtime = deps.getRuntime();
72
- const sizeSource = deps.uiChrome.getCanvasSizeSource(runtime.canvas);
73
- const rect = sizeSource.getBoundingClientRect();
74
- return sizeSource.clientHeight > 0 ? sizeSource.clientHeight : (rect.height > 0 ? rect.height : runtime.canvas.height);
75
- },
76
- fui_set_pointer_capture(handle: AppHandleLike): void {
77
- deps.getRuntime().setCapturedPointerHandle(toBigIntHandle(handle));
78
- },
79
- fui_release_pointer_capture(): void {
80
- deps.getRuntime().setCapturedPointerHandle(null);
81
- },
82
- fui_reload_page(): void {
83
- window.location.reload();
84
- },
85
- fui_can_navigate_back(): number {
86
- return deps.canBrowserNavigateBack() ? 1 : 0;
87
- },
88
- fui_can_navigate_forward(): number {
89
- return deps.canBrowserNavigateForward() ? 1 : 0;
90
- },
91
- fui_navigate_back(): void {
92
- deps.navigateBrowserBack();
93
- },
94
- fui_navigate_forward(): void {
95
- deps.navigateBrowserForward();
96
- },
97
- fui_copy_text(ptr: number, len: number): void {
98
- const text = deps.readAppUtf8(ptr, len);
99
- window.__effindomCallbacks?.onClipboardWrite?.({ plainText: text });
100
- },
101
- fui_has_text_selection_snapshot(handle: AppHandleLike): number {
102
- return deps.textBridge.resolveFrozenOrLiveTextSelection(handle) !== null ? 1 : 0;
103
- },
104
- fui_freeze_text_selection_snapshot(handle: AppHandleLike): void {
105
- deps.textBridge.freezeTextSelectionSnapshot(handle);
106
- },
107
- fui_copy_text_selection_snapshot(handle: AppHandleLike): number {
108
- const snapshot = deps.textBridge.resolveFrozenOrLiveTextSelection(handle);
109
- if (snapshot === null) {
110
- return 0;
111
- }
112
- window.__effindomCallbacks?.onClipboardWrite?.({
113
- plainText: snapshot.text.slice(snapshot.start, snapshot.end),
114
- });
115
- return 1;
116
- },
117
- fui_cut_focused_text_selection(): number {
118
- const editor = deps.textBridge.getHiddenTextEditor();
119
- if (editor === null) {
120
- return 0;
121
- }
122
- const selectionStart = editor.selectionStart ?? 0;
123
- const selectionEnd = editor.selectionEnd ?? 0;
124
- if (selectionStart === selectionEnd) {
125
- return 0;
126
- }
127
- const start = Math.min(selectionStart, selectionEnd);
128
- const end = Math.max(selectionStart, selectionEnd);
129
- window.__effindomCallbacks?.onClipboardWrite?.({
130
- plainText: editor.value.slice(start, end),
131
- });
132
- editor.focus({ preventScroll: true });
133
- editor.setRangeText('', start, end, 'start');
134
- editor.dispatchEvent(new InputEvent('input', { bubbles: true, inputType: 'deleteByCut', data: null }));
135
- return 1;
136
- },
137
- fui_cut_text_selection_snapshot(handle: AppHandleLike): number {
138
- const runtime = deps.getRuntime();
139
- const snapshot = deps.textBridge.resolveFrozenOrLiveTextSelection(handle);
140
- if (snapshot === null) {
141
- return 0;
142
- }
143
- const { handleKey, text, start, end } = snapshot;
144
- window.__effindomCallbacks?.onClipboardWrite?.({
145
- plainText: text.slice(start, end),
146
- });
147
- const updatedText = text.slice(0, start) + text.slice(end);
148
- const editor = deps.textBridge.getHiddenTextEditor();
149
- if (editor !== null) {
150
- editor.focus({ preventScroll: true });
151
- editor.value = updatedText;
152
- editor.setSelectionRange(start, start, 'none');
153
- }
154
- window.setTimeout(() => {
155
- runtime.ui._ui_request_focus(toBigIntHandle(handle));
156
- deps.textBridge.withUiUtf8('', (uiPtr, uiLen) => {
157
- runtime.ui._ui_replace_text_range(toBigIntHandle(handle), start, end, uiPtr, uiLen, start);
158
- });
159
- runtime.commitFrame();
160
- deps.queueHarnessFrame();
161
- deps.textBridge.updateLiveTextAfterCut(handleKey, updatedText, start);
162
- const activeEditor = deps.textBridge.getHiddenTextEditor();
163
- if (activeEditor !== null) {
164
- activeEditor.focus({ preventScroll: true });
165
- activeEditor.setSelectionRange(start, start, 'none');
166
- }
167
- }, 0);
168
- deps.textBridge.clearFrozenTextSelectionSnapshot();
169
- return 1;
170
- },
171
- fui_cut_text_range_snapshot(handle: AppHandleLike, start: number, end: number): number {
172
- const textSnapshot = deps.textBridge.resolveFrozenOrLiveTextSelection(handle);
173
- const handleKey = toBigIntHandle(handle).toString();
174
- const text = textSnapshot?.handleKey === handleKey
175
- ? textSnapshot.text
176
- : deps.textBridge.getLatestText(handle);
177
- const resolvedText = text.length > 0 ? text : '';
178
- if (resolvedText.length === 0) {
179
- return 0;
180
- }
181
- const rangeStart = Math.max(0, Math.min(start, end));
182
- const rangeEnd = Math.max(rangeStart, Math.min(resolvedText.length, Math.max(start, end)));
183
- if (rangeStart === rangeEnd) {
184
- return 0;
185
- }
186
- window.__effindomCallbacks?.onClipboardWrite?.({
187
- plainText: resolvedText.slice(rangeStart, rangeEnd),
188
- });
189
- const updatedText = resolvedText.slice(0, rangeStart) + resolvedText.slice(rangeEnd);
190
- const editor = deps.textBridge.getHiddenTextEditor();
191
- if (editor !== null) {
192
- editor.focus({ preventScroll: true });
193
- editor.value = updatedText;
194
- editor.setSelectionRange(rangeStart, rangeStart, 'none');
195
- }
196
- window.setTimeout(() => {
197
- deps.getRuntime().ui._ui_request_focus(toBigIntHandle(handle));
198
- deps.textBridge.syncEditableTextToRuntime(handle, updatedText, rangeStart);
199
- deps.textBridge.updateLiveTextAfterCut(handleKey, updatedText, rangeStart);
200
- const activeEditor = deps.textBridge.getHiddenTextEditor();
201
- if (activeEditor !== null) {
202
- activeEditor.focus({ preventScroll: true });
203
- activeEditor.setSelectionRange(rangeStart, rangeStart, 'none');
204
- }
205
- }, 0);
206
- return 1;
207
- },
208
- fui_delete_focused_text_range(start: number, end: number): number {
209
- const editor = deps.textBridge.getHiddenTextEditor();
210
- if (editor === null) {
211
- return 0;
212
- }
213
- const rangeStart = Math.max(0, Math.min(start, end));
214
- const rangeEnd = Math.max(rangeStart, Math.max(start, end));
215
- editor.focus({ preventScroll: true });
216
- editor.setSelectionRange(rangeStart, rangeEnd);
217
- editor.setRangeText('', rangeStart, rangeEnd, 'start');
218
- editor.dispatchEvent(new InputEvent('input', { bubbles: true, inputType: 'deleteByCut', data: null }));
219
- return 1;
220
- },
221
- fui_commit_text_action_focus(handle: AppHandleLike): void {
222
- const runtime = deps.getRuntime();
223
- window.setTimeout(() => {
224
- runtime.ui._ui_request_focus(toBigIntHandle(handle));
225
- runtime.commitFrame();
226
- deps.queueHarnessFrame();
227
- const editor = deps.textBridge.getHiddenTextEditor();
228
- if (editor !== null) {
229
- editor.focus({ preventScroll: true });
230
- }
231
- }, 0);
232
- },
233
- fui_load_svg(svgId: number, ptr: number, len: number): void {
234
- const runtime = deps.getRuntime();
235
- const session = deps.getCurrentSessionOrNull();
236
- const url = deps.readAppUtf8(ptr, len);
237
- void runtime.loadSvg(svgId, url).then((result) => {
238
- if (deps.getCurrentSessionOrNull() !== session) {
239
- return;
240
- }
241
- safeNotify(`failed to deliver SVG ${String(svgId)} success callback`, () => {
242
- deps.notifySvgLoaded(session, svgId, result.width, result.height);
243
- });
244
- }).catch((error: unknown) => {
245
- const message = error instanceof Error ? error.message : String(error);
246
- console.error(`[fui_host] SVG ${String(svgId)} failed to load from ${url}: ${message}`);
247
- if (deps.getCurrentSessionOrNull() !== session) {
248
- return;
249
- }
250
- safeNotify(`failed to deliver SVG ${String(svgId)} failure callback`, () => {
251
- deps.notifySvgFailed(session, svgId, message);
252
- });
253
- });
254
- },
255
- fui_load_texture(textureId: number, ptr: number, len: number): void {
256
- const runtime = deps.getRuntime();
257
- const session = deps.getCurrentSessionOrNull();
258
- const url = deps.readAppUtf8(ptr, len);
259
- void runtime.loadTexture(textureId, url).then((result) => {
260
- if (deps.getCurrentSessionOrNull() !== session) {
261
- return;
262
- }
263
- safeNotify(`failed to deliver texture ${String(textureId)} success callback`, () => {
264
- deps.notifyTextureLoaded(session, textureId, result.width, result.height);
265
- });
266
- }).catch((error: unknown) => {
267
- const message = error instanceof Error ? error.message : String(error);
268
- console.error(`[fui_host] texture ${String(textureId)} failed to load from ${url}: ${message}`);
269
- if (deps.getCurrentSessionOrNull() !== session) {
270
- return;
271
- }
272
- safeNotify(`failed to deliver texture ${String(textureId)} failure callback`, () => {
273
- deps.notifyTextureFailed(session, textureId, message);
274
- });
275
- });
276
- },
277
- fui_release_svg(svgId: number): void {
278
- deps.getRuntime().releaseSvg(svgId);
279
- },
280
- fui_release_texture(textureId: number): void {
281
- deps.getRuntime().releaseTexture(textureId);
282
- },
283
- fui_load_font(fontId: number, ptr: number, len: number): void {
284
- const url = deps.readAppUtf8(ptr, len);
285
- deps.getRuntime().registerLazyFont(fontId, url);
286
- },
287
- fui_start_timer(timerId: number, delayMs: number): void {
288
- deps.cancelHostTimer(timerId);
289
- const session = deps.getCurrentSessionOrNull();
290
- const clampedDelayMs = Math.max(0, Math.ceil(delayMs));
291
- const timeoutId = window.setTimeout(() => {
292
- if (deps.getHostTimer(timerId) !== timeoutId) {
293
- return;
294
- }
295
- deps.deleteHostTimer(timerId);
296
- if (session === null || deps.getCurrentSessionOrNull() !== session) {
297
- return;
298
- }
299
- session.exports.__fui_on_timer(timerId);
300
- }, clampedDelayMs);
301
- deps.setHostTimer(timerId, timeoutId);
302
- },
303
- fui_cancel_timer(timerId: number): void {
304
- deps.cancelHostTimer(timerId);
305
- },
306
- fui_now_ms(): number {
307
- return performance.now();
308
- },
309
- fui_worker_start_string(workerId: number, entryPtr: number, entryLen: number, inputPtr: number, inputLen: number): void {
310
- deps.workerManager.startString(workerId, deps.readAppUtf8(entryPtr, entryLen), deps.readAppUtf8(inputPtr, inputLen));
311
- },
312
- fui_worker_cancel(workerId: number): void {
313
- deps.workerManager.cancel(workerId);
314
- },
315
- fui_set_cursor(style: number): void {
316
- if (deps.uiChrome.detectCoarsePointer()) {
317
- return;
318
- }
319
- const cursor =
320
- style === 1 ? 'pointer' :
321
- style === 2 ? 'text' :
322
- style === 3 ? 'move' :
323
- style === 4 ? 'grab' :
324
- style === 5 ? 'grabbing' :
325
- style === 6 ? 'ns-resize' :
326
- style === 7 ? 'ew-resize' :
327
- 'default';
328
- deps.getRuntime().canvas.style.cursor = cursor;
329
- },
330
- fui_is_dark_mode(): number {
331
- return window.matchMedia('(prefers-color-scheme: dark)').matches ? 1 : 0;
332
- },
333
- fui_get_accent_color(): number {
334
- return deps.uiChrome.readHostAccentColor();
335
- },
336
- fui_get_platform_family(): number {
337
- return deps.uiChrome.detectPlatformFamily();
338
- },
339
- fui_is_coarse_pointer(): number {
340
- return deps.uiChrome.detectCoarsePointer() ? 1 : 0;
341
- },
342
- fui_show_url_preview(ptr: number, len: number): void {
343
- const rawTarget = deps.readAppUtf8(ptr, len);
344
- try {
345
- const resolvedTarget = new URL(rawTarget, window.location.href);
346
- deps.uiChrome.setUrlPreviewText(resolvedTarget.href);
347
- } catch {
348
- deps.uiChrome.setUrlPreviewText(rawTarget);
349
- }
350
- },
351
- fui_hide_url_preview(): void {
352
- deps.uiChrome.setUrlPreviewText('');
353
- },
354
- fui_navigate_to(ptr: number, len: number, openInNewTab: number): void {
355
- deps.navigateWithinDocument(deps.readAppUtf8(ptr, len), openInNewTab !== 0);
356
- },
357
- fui_set_persisted_scroll_offset(nodeIdPtr: number, nodeIdLen: number, x: number, y: number): void {
358
- const nodeId = deps.readAppUtf8(nodeIdPtr, nodeIdLen);
359
- if (nodeId.length === 0) {
360
- return;
361
- }
362
- deps.persistedUiStateController.setCurrentPersistedScrollEntry(nodeId, x, y);
363
- },
364
- fui_try_get_persisted_scroll_offset(nodeIdPtr: number, nodeIdLen: number, outX: number, outY: number): number {
365
- const nodeId = deps.readAppUtf8(nodeIdPtr, nodeIdLen);
366
- if (nodeId.length === 0) {
367
- return 0;
368
- }
369
- const payload = deps.persistedUiStateController.getCurrentPersistedScrollEntry(nodeId);
370
- if (payload === null) {
371
- return 0;
372
- }
373
- deps.writeAppFloat32(outX, payload.x);
374
- deps.writeAppFloat32(outY, payload.y);
375
- return 1;
376
- },
377
- fui_set_persisted_state(
378
- nodeIdPtr: number,
379
- nodeIdLen: number,
380
- kindPtr: number,
381
- kindLen: number,
382
- version: number,
383
- payloadPtr: number,
384
- payloadLen: number,
385
- ): void {
386
- const nodeId = deps.readAppUtf8(nodeIdPtr, nodeIdLen);
387
- const kind = deps.readAppUtf8(kindPtr, kindLen);
388
- if (nodeId.length === 0 || kind.length === 0) {
389
- return;
390
- }
391
- deps.persistedUiStateController.setCurrentPersistedTextEntry(
392
- nodeId,
393
- kind,
394
- version >>> 0,
395
- deps.readAppUtf8(payloadPtr, payloadLen),
396
- );
397
- },
398
- fui_copy_persisted_state(
399
- nodeIdPtr: number,
400
- nodeIdLen: number,
401
- kindPtr: number,
402
- kindLen: number,
403
- outVersionPtr: number,
404
- payloadPtr: number,
405
- payloadCapacity: number,
406
- ): number {
407
- const nodeId = deps.readAppUtf8(nodeIdPtr, nodeIdLen);
408
- const kind = deps.readAppUtf8(kindPtr, kindLen);
409
- if (nodeId.length === 0 || kind.length === 0) {
410
- return -1;
411
- }
412
- const entry = deps.persistedUiStateController.getCurrentPersistedTextEntry(nodeId, kind);
413
- if (entry === null) {
414
- return -1;
415
- }
416
- deps.writeAppUint32(outVersionPtr, entry.version >>> 0);
417
- const payloadBytes = new TextEncoder().encode(entry.payload);
418
- if (payloadBytes.length > payloadCapacity) {
419
- return payloadBytes.length;
420
- }
421
- deps.writeAppUtf8(payloadPtr, payloadCapacity, entry.payload, `Persisted state ${kind}`);
422
- return payloadBytes.length;
423
- },
424
- fui_log(catPtr: number, catLen: number, msgPtr: number, msgLen: number): void {
425
- if (deps.getCurrentSessionOrNull() === null) {
426
- return;
427
- }
428
- const category = deps.readAppUtf8(catPtr, catLen);
429
- const message = deps.readAppUtf8(msgPtr, msgLen);
430
- const formatted = `[fui:${category}] ${message}`;
431
- if (category.startsWith('Warning/')) {
432
- console.warn(formatted);
433
- return;
434
- }
435
- if (category.startsWith('Error/')) {
436
- console.error(formatted);
437
- return;
438
- }
439
- if (deps.debugLogsEnabled) {
440
- console.debug(formatted);
441
- }
442
- },
443
- fui_logs_enabled(): number {
444
- return deps.debugLogsEnabled ? 1 : 0;
445
- },
446
- };
447
- }
@@ -1,39 +0,0 @@
1
- import type { BridgeRuntime, WasmHandleLike } from '@effindomv2/runtime';
2
- import {
3
- handleToBigInt,
4
- normalizePointerForWasm,
5
- pointerToHeapOffset,
6
- } from '@effindomv2/runtime';
7
-
8
- export type AppHandleLike = number | bigint;
9
-
10
- export function toBigIntHandle(handle: WasmHandleLike | AppHandleLike): bigint {
11
- return handleToBigInt(handle);
12
- }
13
-
14
- export function toNumberHandle(handle: WasmHandleLike | AppHandleLike): number {
15
- return pointerToHeapOffset(handle);
16
- }
17
-
18
- export function zeroPointer(runtime: BridgeRuntime): WasmHandleLike | number {
19
- return normalizePointerForWasm(runtime.ui, 0);
20
- }
21
-
22
- export function normalizePointer(runtime: BridgeRuntime, ptr: WasmHandleLike | number): WasmHandleLike | number {
23
- return normalizePointerForWasm(runtime.ui, ptr);
24
- }
25
-
26
- export function addUiPointer(
27
- runtime: BridgeRuntime,
28
- ptr: WasmHandleLike | number,
29
- byteOffset: number,
30
- ): WasmHandleLike | number {
31
- if (runtime.ui.usesMemory64 === true) {
32
- return toBigIntHandle(ptr) + BigInt(byteOffset);
33
- }
34
- return toNumberHandle(ptr) + byteOffset;
35
- }
36
-
37
- export function currentInteractionTimeMs(): bigint {
38
- return BigInt(Math.floor(performance.now()));
39
- }
@@ -1,92 +0,0 @@
1
- import type { BridgeRuntime } from '@effindomv2/runtime';
2
- import { writeBytesToHeap } from '@effindomv2/runtime';
3
-
4
- interface CustomBitmapRecord {
5
- readonly width: number;
6
- readonly height: number;
7
- readonly bytes: Uint8Array;
8
- }
9
-
10
- interface ManagedHarnessBitmapHostDependencies {
11
- getRuntime(): BridgeRuntime;
12
- readAppBytes(ptr: number, len: number): Uint8Array;
13
- notifyBitmapChanged(): void;
14
- }
15
-
16
- export function createManagedHarnessBitmapHost(dependencies: ManagedHarnessBitmapHostDependencies) {
17
- const customBitmapTextures = new Map<number, CustomBitmapRecord>();
18
- const customBitmapReplayRuntimes = new WeakSet<BridgeRuntime>();
19
-
20
- function uploadCustomBitmap(targetRuntime: BridgeRuntime, textureId: number, record: CustomBitmapRecord): void {
21
- const textureBytes = writeBytesToHeap(targetRuntime.core, record.bytes);
22
- try {
23
- targetRuntime.core._ed_register_texture_rgba(
24
- textureId,
25
- textureBytes.ptr,
26
- record.width,
27
- record.height,
28
- textureBytes.len,
29
- );
30
- } finally {
31
- textureBytes.dispose();
32
- }
33
- }
34
-
35
- function installReplay(targetRuntime: BridgeRuntime): void {
36
- if (customBitmapReplayRuntimes.has(targetRuntime)) {
37
- return;
38
- }
39
- const replayLoadedAssets = targetRuntime.replayLoadedAssets.bind(targetRuntime);
40
- targetRuntime.replayLoadedAssets = async (): Promise<void> => {
41
- await replayLoadedAssets();
42
- for (const [textureId, record] of customBitmapTextures.entries()) {
43
- uploadCustomBitmap(targetRuntime, textureId, record);
44
- }
45
- };
46
- customBitmapReplayRuntimes.add(targetRuntime);
47
- }
48
-
49
- function clearTextures(targetRuntime: BridgeRuntime): void {
50
- for (const textureId of customBitmapTextures.keys()) {
51
- targetRuntime.core._ed_unregister_texture(textureId);
52
- }
53
- customBitmapTextures.clear();
54
- }
55
-
56
- return {
57
- installReplay,
58
- clearTextures,
59
- imports: {
60
- fui_bitmap_commit(textureId: number, ptr: number, len: number, width: number, height: number): void {
61
- if (!Number.isInteger(textureId) || textureId <= 0) {
62
- throw new Error('Bitmap commit requires a non-zero texture ID.');
63
- }
64
- if (!Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0) {
65
- throw new Error('Bitmap commit requires positive integer dimensions.');
66
- }
67
- const expectedLength = width * height * 4;
68
- if (len !== expectedLength) {
69
- throw new Error(
70
- `Bitmap commit byte length mismatch: expected ${String(expectedLength)} bytes for ${String(width)}x${String(height)}, received ${String(len)}.`,
71
- );
72
- }
73
- const record: CustomBitmapRecord = {
74
- width,
75
- height,
76
- bytes: dependencies.readAppBytes(ptr, len),
77
- };
78
- customBitmapTextures.set(textureId, record);
79
- uploadCustomBitmap(dependencies.getRuntime(), textureId, record);
80
- dependencies.notifyBitmapChanged();
81
- },
82
- fui_bitmap_release(textureId: number): void {
83
- if (!Number.isInteger(textureId) || textureId <= 0) {
84
- return;
85
- }
86
- customBitmapTextures.delete(textureId);
87
- dependencies.getRuntime().core._ed_unregister_texture(textureId);
88
- dependencies.notifyBitmapChanged();
89
- },
90
- },
91
- };
92
- }
@@ -1,72 +0,0 @@
1
- import type { BridgeRuntime } from '@effindomv2/runtime';
2
- import { normalizePointerForWasm, pointerToHeapOffset } from '@effindomv2/runtime';
3
-
4
- interface ManagedHarnessCanvasHostDependencies {
5
- getRuntime(): BridgeRuntime;
6
- writeAppBytes(ptr: number, capacity: number, bytes: Uint8Array, context: string): number;
7
- }
8
-
9
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
- type AnyFn = (...args: any[]) => any;
11
-
12
- export function createManagedHarnessCanvasHost(deps: ManagedHarnessCanvasHostDependencies) {
13
- const surfaces = new Map<number, number>();
14
- const core = () => deps.getRuntime().core as unknown as Record<string, AnyFn>;
15
- const ptr = (p: number) => normalizePointerForWasm(deps.getRuntime().core, p);
16
-
17
- const I: Record<string, AnyFn> = {};
18
-
19
- I.fui_canvas_save = (p: number) => core()._ed_canvas_save(ptr(p));
20
- I.fui_canvas_restore = (p: number) => core()._ed_canvas_restore(ptr(p));
21
- I.fui_canvas_translate = (p: number, x: number, y: number) => core()._ed_canvas_translate(ptr(p), x, y);
22
- I.fui_canvas_scale = (p: number, sx: number, sy: number) => core()._ed_canvas_scale(ptr(p), sx, sy);
23
- I.fui_canvas_rotate = (p: number, d: number) => core()._ed_canvas_rotate(ptr(p), d);
24
- I.fui_canvas_clip_rect = (p: number, x: number, y: number, w: number, h: number) => core()._ed_canvas_clip_rect(ptr(p), x, y, w, h);
25
- I.fui_canvas_draw_rect = (p: number, x: number, y: number, w: number, h: number, fc: number, sc: number, sw: number) =>
26
- core()._ed_canvas_draw_rect(ptr(p), x, y, w, h, fc, sc, sw);
27
- I.fui_canvas_draw_circle = (p: number, cx: number, cy: number, r: number, fc: number, sc: number, sw: number) =>
28
- core()._ed_canvas_draw_circle(ptr(p), cx, cy, r, fc, sc, sw);
29
- I.fui_canvas_draw_line = (p: number, x1: number, y1: number, x2: number, y2: number, c: number, sw: number) =>
30
- core()._ed_canvas_draw_line(ptr(p), x1, y1, x2, y2, c, sw);
31
- I.fui_canvas_draw_round_rect = (p: number, x: number, y: number, w: number, h: number, rx: number, ry: number, fc: number, sc: number, sw: number) =>
32
- core()._ed_canvas_draw_round_rect(ptr(p), x, y, w, h, rx, ry, fc, sc, sw);
33
- I.fui_canvas_draw_path = (p: number, pid: number, fc: number, sc: number, sw: number) =>
34
- core()._ed_canvas_draw_path(ptr(p), pid, fc, sc, sw);
35
- I.fui_canvas_draw_text = (p: number, up: number, ul: number, x: number, y: number, fid: number, fs: number, c: number) =>
36
- core()._ed_canvas_draw_text(ptr(p), up, ul, x, y, fid, fs, c);
37
- I.fui_canvas_draw_image = (p: number, tid: number, x: number, y: number, w: number, h: number) =>
38
- core()._ed_canvas_draw_image(ptr(p), tid, x, y, w, h);
39
- I.fui_canvas_draw_svg = (p: number, sid: number, x: number, y: number, w: number, h: number) =>
40
- core()._ed_canvas_draw_svg(ptr(p), sid, x, y, w, h);
41
-
42
- I.fui_path_create = () => core()._ed_path_create();
43
- I.fui_path_destroy = (id: number) => core()._ed_path_destroy(id);
44
- I.fui_path_move_to = (id: number, x: number, y: number) => core()._ed_path_move_to(id, x, y);
45
- I.fui_path_line_to = (id: number, x: number, y: number) => core()._ed_path_line_to(id, x, y);
46
- I.fui_path_quad_to = (id: number, cx: number, cy: number, x: number, y: number) => core()._ed_path_quad_to(id, cx, cy, x, y);
47
- I.fui_path_cubic_to = (id: number, cx1: number, cy1: number, cx2: number, cy2: number, x: number, y: number) =>
48
- core()._ed_path_cubic_to(id, cx1, cy1, cx2, cy2, x, y);
49
- I.fui_path_close = (id: number) => core()._ed_path_close(id);
50
- I.fui_path_add_rect = (id: number, x: number, y: number, w: number, h: number) => core()._ed_path_add_rect(id, x, y, w, h);
51
- I.fui_path_add_circle = (id: number, cx: number, cy: number, r: number) => core()._ed_path_add_circle(id, cx, cy, r);
52
-
53
- I.fui_canvas_create_offscreen = (w: number, h: number) => {
54
- const id = core()._ed_canvas_create_offscreen(w, h) as number;
55
- if (id !== 0) surfaces.set(id, id);
56
- return id;
57
- };
58
- I.fui_canvas_get_offscreen_ptr = (id: number) => core()._ed_canvas_get_offscreen_canvas(id);
59
- I.fui_canvas_read_offscreen_pixels = (id: number, outPtr: number, w: number, h: number) => {
60
- const bytesLen = w * h * 4;
61
- const c = core();
62
- const bufPtr = c._malloc(bytesLen);
63
- try {
64
- c._ed_canvas_read_offscreen_pixels(id, pointerToHeapOffset(bufPtr));
65
- const off = pointerToHeapOffset(bufPtr);
66
- deps.writeAppBytes(outPtr, bytesLen, (c.HEAPU8 as unknown as Uint8Array).slice(off, off + bytesLen), 'canvas-read');
67
- } finally { c._free(bufPtr); }
68
- };
69
- I.fui_canvas_destroy_offscreen = (id: number) => { surfaces.delete(id); core()._ed_canvas_destroy_offscreen(id); };
70
-
71
- return { imports: I };
72
- }