@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
@@ -5,10 +5,70 @@ import { Disposable } from "./Disposable";
5
5
  import { allocateDynamicTextureId } from "./DynamicAssetIds";
6
6
  import { DrawContext } from "../drawing/DrawContext";
7
7
  import { throwObjectDisposed } from "./Errors";
8
+ import { LoadedEventArgs, onLoadedWith } from "./FrameScheduler";
8
9
  import { error } from "./Logger";
10
+ import { Node } from "./Node";
11
+ import { FontFace, FontsLoadedEventArgs } from "./Typography";
12
+ import { TextLayout } from "../text/TextLayout";
9
13
 
10
14
  const FUNCTION_BITMAP_CONSTRUCTOR = "Bitmap.constructor";
11
15
 
16
+ export class BitmapTextReadyEventArgs {
17
+ readonly node: Node;
18
+
19
+ constructor(node: Node) {
20
+ this.node = node;
21
+ }
22
+ }
23
+
24
+ export type BitmapTextReadyHandler<Owner> = (owner: Owner, event: BitmapTextReadyEventArgs) => void;
25
+
26
+ class BitmapTextReadyCallback {
27
+ constructor(
28
+ private readonly node: Node,
29
+ private readonly callback: (event: BitmapTextReadyEventArgs) => void,
30
+ ) {}
31
+
32
+ handleReady(): void {
33
+ onLoadedWith<BitmapTextReadyCallback>(this, handleBitmapTextLoaded);
34
+ }
35
+
36
+ handleLoaded(): void {
37
+ Bitmap.prepareText(this.node);
38
+ this.callback(new BitmapTextReadyEventArgs(this.node));
39
+ }
40
+ }
41
+
42
+ class BitmapTextReadyHandlerAction<Owner> {
43
+ constructor(
44
+ private readonly node: Node,
45
+ private readonly owner: Owner,
46
+ private readonly handler: BitmapTextReadyHandler<Owner>,
47
+ ) {}
48
+
49
+ handleReady(): void {
50
+ onLoadedWith<BitmapTextReadyHandlerAction<Owner>>(
51
+ this,
52
+ (action: BitmapTextReadyHandlerAction<Owner>, _event: LoadedEventArgs): void => {
53
+ action.handleLoaded();
54
+ },
55
+ );
56
+ }
57
+
58
+ handleLoaded(): void {
59
+ Bitmap.prepareText(this.node);
60
+ this.handler(this.owner, new BitmapTextReadyEventArgs(this.node));
61
+ }
62
+ }
63
+
64
+ function handleBitmapTextReady(callback: BitmapTextReadyCallback): void {
65
+ callback.handleReady();
66
+ }
67
+
68
+ function handleBitmapTextLoaded(callback: BitmapTextReadyCallback, _event: LoadedEventArgs): void {
69
+ callback.handleLoaded();
70
+ }
71
+
12
72
  function throwBitmapError(category: string, message: string): void {
13
73
  error(category, message);
14
74
  throw new Error(message);
@@ -25,6 +85,9 @@ function validateBitmapDimensions(width: u32, height: u32): u32 {
25
85
  return <u32>totalBytes;
26
86
  }
27
87
 
88
+ const MAX_DIRTY_RECTS: i32 = 16;
89
+ const RECT_STRIDE: i32 = 4; // x, y, w, h
90
+
28
91
  export class Bitmap implements Disposable {
29
92
  readonly width: u32;
30
93
  readonly height: u32;
@@ -32,7 +95,10 @@ export class Bitmap implements Disposable {
32
95
  private pixelBytes: Uint8Array;
33
96
  private offscreenId: u32 = 0;
34
97
  private canvasUsed: bool = false;
98
+ private drawContext: DrawContext | null = null;
35
99
  private disposed: bool = false;
100
+ private dirtyRects: StaticArray<u32> = new StaticArray<u32>(MAX_DIRTY_RECTS * RECT_STRIDE);
101
+ private dirtyCount: i32 = 0;
36
102
 
37
103
  constructor(width: u32, height: u32) {
38
104
  const byteLength = validateBitmapDimensions(width, height);
@@ -65,18 +131,140 @@ export class Bitmap implements Disposable {
65
131
  canvas(): DrawContext {
66
132
  this.ensureNotDisposed("Bitmap.canvas");
67
133
  this.canvasUsed = true;
134
+ if (this.drawContext !== null) {
135
+ return this.drawContext!;
136
+ }
68
137
  const ptr = ffi.fui_canvas_get_offscreen_ptr(this.offscreenId);
69
- return new DrawContext(ptr);
138
+ const context = new DrawContext(ptr);
139
+ this.drawContext = context;
140
+ return context;
141
+ }
142
+
143
+ /**
144
+ * Renders a retained text node into this bitmap's pixel buffer.
145
+ * Call `Bitmap.prepareText(node)` before the commit that makes the
146
+ * prepared glyphs visible in core.
147
+ *
148
+ * @param node A built Text or RichText node sized to match this bitmap's dimensions.
149
+ */
150
+ render(node: Node, x: f32 = 0.0, y: f32 = 0.0, scale: f32 = 1.0): void {
151
+ this.ensureNotDisposed("Bitmap.render");
152
+ const handle = node.builtHandle;
153
+ if (handle == <u64>0) return;
154
+ ui.renderNodeToRgba(handle, this.width, this.height, this.pixelPtr(), <u32>this.pixelBytes.length, scale, x, y);
155
+ }
156
+
157
+ renderTextLayout(layout: TextLayout, x: f32 = 0.0, y: f32 = 0.0, scale: f32 = 1.0): void {
158
+ this.ensureNotDisposed("Bitmap.renderTextLayout");
159
+ if (!layout.isReady) {
160
+ error("TextLayout", "Bitmap.renderTextLayout() called before the TextLayout was ready; register onReady/onReadyWith and render after the callback.");
161
+ return;
162
+ }
163
+ this.render(layout._drawNode(), x, y, scale);
164
+ }
165
+
166
+ /** Queue text shaping for a built text node before the next CommitFrame. */
167
+ static prepareText(node: Node): void {
168
+ ui.prepareNode(node.builtHandle);
169
+ }
170
+
171
+ onTextReady(node: Node, onReady: (event: BitmapTextReadyEventArgs) => void): this {
172
+ Bitmap.onTextReady(node, onReady);
173
+ return this;
174
+ }
175
+
176
+ onTextReadyWith<Owner>(owner: Owner, node: Node, onReady: BitmapTextReadyHandler<Owner>): this {
177
+ Bitmap.onTextReadyWith<Owner>(owner, node, onReady);
178
+ return this;
179
+ }
180
+
181
+ static onTextReady(node: Node, onReady: (event: BitmapTextReadyEventArgs) => void): void {
182
+ const callback = new BitmapTextReadyCallback(node, onReady);
183
+ FontFace.whenFontsLoadedWith<BitmapTextReadyCallback>(
184
+ node._requiredFontIds(),
185
+ callback,
186
+ handleBitmapTextReady,
187
+ );
188
+ }
189
+
190
+ static onTextReadyWith<Owner>(owner: Owner, node: Node, onReady: BitmapTextReadyHandler<Owner>): void {
191
+ const callback = new BitmapTextReadyHandlerAction<Owner>(node, owner, onReady);
192
+ FontFace.whenFontsLoadedWith<BitmapTextReadyHandlerAction<Owner>>(
193
+ node._requiredFontIds(),
194
+ callback,
195
+ (action: BitmapTextReadyHandlerAction<Owner>, _event: FontsLoadedEventArgs): void => {
196
+ action.handleReady();
197
+ },
198
+ );
199
+ }
200
+
201
+ /**
202
+ * Mark a rectangular region as dirty. Call one or more times before
203
+ * commit(). If no dirty rects are registered, commit() uploads the
204
+ * entire buffer (current behavior, backward‑compatible).
205
+ *
206
+ * @param x Left edge, clamped to [0, width).
207
+ * @param y Top edge, clamped to [0, height).
208
+ * @param w Width, clamped so x+w ≤ width.
209
+ * @param h Height, clamped so y+h ≤ height.
210
+ */
211
+ dirtyRect(x: u32, y: u32, w: u32, h: u32): void {
212
+ if (w == 0 || h == 0) return;
213
+ if (x >= this.width) return;
214
+ if (y >= this.height) return;
215
+ const cx = x;
216
+ const cy = y;
217
+ const cw = x + w > this.width ? this.width - x : w;
218
+ const ch = y + h > this.height ? this.height - y : h;
219
+ if (this.dirtyCount < MAX_DIRTY_RECTS) {
220
+ const base = this.dirtyCount * RECT_STRIDE;
221
+ unchecked(this.dirtyRects[base] = cx);
222
+ unchecked(this.dirtyRects[base + 1] = cy);
223
+ unchecked(this.dirtyRects[base + 2] = cw);
224
+ unchecked(this.dirtyRects[base + 3] = ch);
225
+ this.dirtyCount++;
226
+ }
227
+ }
228
+
229
+ clearDirtyRects(): void {
230
+ this.dirtyCount = 0;
231
+ }
232
+
233
+ hasDirtyRects(): bool {
234
+ return this.dirtyCount > 0;
70
235
  }
71
236
 
72
237
  commit(): u32 {
73
238
  this.ensureNotDisposed("Bitmap.commit");
74
239
  if (this.canvasUsed) {
75
- // Read the offscreen raster surface back into the pixel buffer
240
+ if (this.drawContext !== null) {
241
+ this.drawContext!.flush();
242
+ }
76
243
  ffi.fui_canvas_read_offscreen_pixels(
77
244
  this.offscreenId, this.pixelBytes.dataStart, this.width, this.height);
78
245
  }
79
- ui.bitmapCommit(this.textureId, this.pixelBytes, this.width, this.height);
246
+ if (this.dirtyCount > 0) {
247
+ for (let i: i32 = 0; i < this.dirtyCount; i++) {
248
+ const base = i * RECT_STRIDE;
249
+ const drx = unchecked(this.dirtyRects[base]);
250
+ const dry = unchecked(this.dirtyRects[base + 1]);
251
+ const drw = unchecked(this.dirtyRects[base + 2]);
252
+ const drh = unchecked(this.dirtyRects[base + 3]);
253
+ const rectBytes = new Uint8Array(<i32>(drw * drh * 4));
254
+ for (let row: u32 = 0; row < drh; row++) {
255
+ const srcIdx: i32 = <i32>(dry + row) * <i32>this.width * 4 + <i32>drx * 4;
256
+ const dstIdx: i32 = <i32>row * <i32>drw * 4;
257
+ memory.copy(rectBytes.dataStart + dstIdx, this.pixelBytes.dataStart + srcIdx, <i32>(drw * 4));
258
+ }
259
+ ffi.fui_bitmap_commit_dirty(
260
+ this.textureId, rectBytes.dataStart, drw * drh * 4,
261
+ this.width, this.height, drx, dry, drw, drh,
262
+ );
263
+ }
264
+ this.dirtyCount = 0;
265
+ } else {
266
+ ui.bitmapCommit(this.textureId, this.pixelBytes, this.width, this.height);
267
+ }
80
268
  markTextureAssetReady(this.textureId, <f32>this.width, <f32>this.height);
81
269
  return this.textureId;
82
270
  }
@@ -91,6 +279,7 @@ export class Bitmap implements Disposable {
91
279
  }
92
280
  ui.bitmapRelease(this.textureId);
93
281
  this.pixelBytes = new Uint8Array(0);
282
+ this.drawContext = null;
94
283
  }
95
284
 
96
285
  private ensureNotDisposed(functionName: string): void {
@@ -4,6 +4,7 @@ import { ContextMenu, ContextMenuAction, MenuItem } from "../controls/ContextMen
4
4
  import { NavLink } from "../controls/NavLink";
5
5
  import { TextInputCore } from "../controls/internal/TextInputCore";
6
6
  import { EventRouter } from "./EventRouter";
7
+ import { MobileTextSelectionToolbarManager } from "./MobileTextSelectionToolbarManager";
7
8
  import { HandleValue, KeyModifier, PointerEventType } from "./ffi";
8
9
  import {
9
10
  formatPrimaryShortcutLabel,
@@ -13,13 +14,13 @@ import {
13
14
  getPlatformFamily,
14
15
  PlatformFamily,
15
16
  } from "./Platform";
16
- import { Node } from "./Node";
17
+ import { ContextMenuEventArgs, Node, VisibilityChangedEventArgs } from "./Node";
17
18
  import { Image } from "../nodes/Image";
18
19
  import { Svg } from "../nodes/Svg";
19
20
  import { TextCore } from "../nodes/TextCore";
20
21
 
21
- function handleMenuVisibilityChanged(visible: bool): void {
22
- ContextMenuManager.handleMenuVisibilityChanged(visible);
22
+ function handleMenuVisibilityChanged(event: VisibilityChangedEventArgs): void {
23
+ ContextMenuManager.handleMenuVisibilityChanged(event.visible);
23
24
  }
24
25
 
25
26
  function appendMenuSection(items: Array<MenuItem>, section: Array<MenuItem>): void {
@@ -96,18 +97,93 @@ export class ContextMenuManager {
96
97
  if (menu === null) {
97
98
  return;
98
99
  }
100
+ const targetNode = EventRouter.getRegisteredNode(handle);
101
+ if (this.invokeCustomContextMenuHandler(targetNode, x, y)) {
102
+ return;
103
+ }
104
+ const items = this.buildBuiltInItems(handle, x, y, true);
99
105
 
100
- this.releaseActiveMenuLinkPreview();
106
+ const navigationItems = new Array<MenuItem>();
107
+ if (ffi.fui_can_navigate_back()) {
108
+ navigationItems.push(new MenuItem("Back", ContextMenuAction.NavigateBack, null, 0, resolveHistoryShortcutLabel(false)));
109
+ }
110
+ if (ffi.fui_can_navigate_forward()) {
111
+ navigationItems.push(new MenuItem("Forward", ContextMenuAction.NavigateForward, null, 0, resolveHistoryShortcutLabel(true)));
112
+ }
113
+ navigationItems.push(
114
+ new MenuItem("Reload Page", ContextMenuAction.ReloadPage, null, 0, formatPrimaryShortcutLabel("r")),
115
+ );
116
+ appendMenuSection(items, navigationItems);
117
+ if (items.length == 0) {
118
+ this.releaseActiveMenuLinkPreview();
119
+ return;
120
+ }
121
+ menu.items(items);
122
+ menu.showFromContextPointer(null, x, y);
123
+ }
124
+
125
+ static showForLongPress(handle: u64, x: f32, y: f32): bool {
126
+ const items = this.buildBuiltInItems(handle, x, y, false);
127
+ if (items.length == 0) {
128
+ this.releaseActiveMenuLinkPreview();
129
+ return false;
130
+ }
131
+ return MobileTextSelectionToolbarManager.showItemsAt(items, x, y);
132
+ }
133
+
134
+ static canShowForHandle(handle: u64): bool {
101
135
  const targetNode = EventRouter.getRegisteredNode(handle);
102
136
  let contextNode = targetNode;
103
137
  while (contextNode !== null) {
104
138
  if (contextNode.isContextMenuDisabled) {
105
- return;
139
+ return false;
140
+ }
141
+ contextNode = contextNode.parentNode;
142
+ }
143
+ return true;
144
+ }
145
+
146
+ static hideActiveMenu(): void {
147
+ ContextMenu.hideActiveMenu();
148
+ }
149
+
150
+ static handleMenuVisibilityChanged(visible: bool): void {
151
+ if (!visible) {
152
+ this.releaseActiveMenuLinkPreview();
153
+ }
154
+ }
155
+
156
+ private static resolveActiveMenu(): ContextMenu | null {
157
+ if (this.menu !== null) {
158
+ return this.menu;
159
+ }
160
+ return this.defaultMenu;
161
+ }
162
+
163
+ private static invokeCustomContextMenuHandler(targetNode: Node | null, x: f32, y: f32): bool {
164
+ this.releaseActiveMenuLinkPreview();
165
+ let contextNode = targetNode;
166
+ while (contextNode !== null) {
167
+ if (contextNode.isContextMenuDisabled) {
168
+ return true;
106
169
  }
107
170
  const handler = contextNode.contextMenuHandler;
108
171
  if (handler !== null) {
109
- handler(targetNode, x, y);
110
- return;
172
+ handler(new ContextMenuEventArgs(targetNode, x, y));
173
+ return true;
174
+ }
175
+ contextNode = contextNode.parentNode;
176
+ }
177
+ return false;
178
+ }
179
+
180
+ private static buildBuiltInItems(handle: u64, x: f32, y: f32, clearSelectionOnBackgroundMiss: bool): Array<MenuItem> {
181
+ this.releaseActiveMenuLinkPreview();
182
+ const targetNode = EventRouter.getRegisteredNode(handle);
183
+ let contextNode = targetNode;
184
+ while (contextNode !== null) {
185
+ if (contextNode.isContextMenuDisabled) {
186
+ return new Array<MenuItem>();
111
187
  }
112
188
  contextNode = contextNode.parentNode;
113
189
  }
@@ -118,8 +194,8 @@ export class ContextMenuManager {
118
194
  link.pinPreviewForContextMenu();
119
195
  this.activeMenuLink = link;
120
196
  const linkItems = new Array<MenuItem>();
121
- linkItems.push(new MenuItem("Open Link in New Tab", ContextMenuAction.OpenLinkInNewTab, link.href));
122
- linkItems.push(new MenuItem("Open Link", ContextMenuAction.OpenLink, link.href));
197
+ linkItems.push(new MenuItem("New Tab", ContextMenuAction.OpenLinkInNewTab, link.href));
198
+ linkItems.push(new MenuItem("Open", ContextMenuAction.OpenLink, link.href));
123
199
  appendMenuSection(items, linkItems);
124
200
  }
125
201
 
@@ -132,50 +208,22 @@ export class ContextMenuManager {
132
208
  const imageUrl = this.resolveImageUrl(targetNode, x, y);
133
209
  if (imageUrl !== null && imageUrl.length > 0) {
134
210
  const imageItems = new Array<MenuItem>();
135
- imageItems.push(new MenuItem("Open Image in New Tab", ContextMenuAction.OpenImageInNewTab, imageUrl));
136
- imageItems.push(new MenuItem("Open Image", ContextMenuAction.OpenImage, imageUrl));
211
+ imageItems.push(new MenuItem("New Tab", ContextMenuAction.OpenImageInNewTab, imageUrl));
212
+ imageItems.push(new MenuItem("Open", ContextMenuAction.OpenImage, imageUrl));
137
213
  appendMenuSection(items, imageItems);
138
214
  }
139
215
 
140
216
  if (selectionHit && textTarget === null && this.currentSelectionText.length > 0) {
141
217
  const selectionItems = new Array<MenuItem>();
142
- selectionItems.push(new MenuItem("Copy", ContextMenuAction.CopyCurrentSelection));
218
+ selectionItems.push(new MenuItem("Copy", ContextMenuAction.CopyCurrentSelection, this.currentSelectionText));
143
219
  appendMenuSection(items, selectionItems);
144
- } else if (!selectionHit && textTarget === null && link === null && imageUrl === null) {
220
+ } else if (clearSelectionOnBackgroundMiss && !selectionHit && textTarget === null && link === null && imageUrl === null) {
145
221
  this.currentSelectionText = "";
146
222
  this.currentSelectionHandleHints.length = 0;
147
223
  ui.clearCurrentSelection();
148
224
  }
149
225
 
150
- const navigationItems = new Array<MenuItem>();
151
- if (ffi.fui_can_navigate_back()) {
152
- navigationItems.push(new MenuItem("Back", ContextMenuAction.NavigateBack, null, 0, resolveHistoryShortcutLabel(false)));
153
- }
154
- if (ffi.fui_can_navigate_forward()) {
155
- navigationItems.push(new MenuItem("Forward", ContextMenuAction.NavigateForward, null, 0, resolveHistoryShortcutLabel(true)));
156
- }
157
- navigationItems.push(
158
- new MenuItem("Reload Page", ContextMenuAction.ReloadPage, null, 0, formatPrimaryShortcutLabel("r")),
159
- );
160
- appendMenuSection(items, navigationItems);
161
- menu.show(items, x, y);
162
- }
163
-
164
- static hideActiveMenu(): void {
165
- ContextMenu.hideActiveMenu();
166
- }
167
-
168
- static handleMenuVisibilityChanged(visible: bool): void {
169
- if (!visible) {
170
- this.releaseActiveMenuLinkPreview();
171
- }
172
- }
173
-
174
- private static resolveActiveMenu(): ContextMenu | null {
175
- if (this.menu !== null) {
176
- return this.menu;
177
- }
178
- return this.defaultMenu;
226
+ return items;
179
227
  }
180
228
 
181
229
  private static resolveNavLink(node: Node | null): NavLink | null {
@@ -227,6 +275,8 @@ export class ContextMenuManager {
227
275
 
228
276
  const selectionStart = this.resolveSelectionStart(target);
229
277
  const selectionEnd = this.resolveSelectionEnd(target);
278
+ const selectionStartByte = this.resolveSelectionStartByte(target);
279
+ const selectionEndByte = this.resolveSelectionEndByte(target);
230
280
  const content = this.resolveTextContent(target);
231
281
  const hasSelection = ui.hasTextSelection(handle) || ffi.fui_has_text_selection_snapshot(handle) || selectionStart != selectionEnd;
232
282
  const hasText = content.length > 0;
@@ -243,6 +293,9 @@ export class ContextMenuManager {
243
293
  formatUndoShortcutLabel(),
244
294
  !ui.canUndoTextEdit(handle),
245
295
  handle,
296
+ 0,
297
+ 0,
298
+ true,
246
299
  ),
247
300
  );
248
301
  items.push(
@@ -254,6 +307,9 @@ export class ContextMenuManager {
254
307
  formatRedoShortcutLabel(),
255
308
  !ui.canRedoTextEdit(handle),
256
309
  handle,
310
+ 0,
311
+ 0,
312
+ true,
257
313
  ),
258
314
  );
259
315
  items.push(MenuItem.separator());
@@ -266,7 +322,10 @@ export class ContextMenuManager {
266
322
  formatPrimaryShortcutLabel("x"),
267
323
  !hasSelection,
268
324
  handle,
269
- ).withSelectionRange(selectionStart, selectionEnd),
325
+ 0,
326
+ 0,
327
+ true,
328
+ ).withSelectionRange(selectionStartByte, selectionEndByte),
270
329
  );
271
330
  items.push(
272
331
  new MenuItem(
@@ -277,6 +336,9 @@ export class ContextMenuManager {
277
336
  formatPrimaryShortcutLabel("c"),
278
337
  !hasSelection,
279
338
  handle,
339
+ 0,
340
+ 0,
341
+ true,
280
342
  ),
281
343
  );
282
344
  items.push(
@@ -288,6 +350,9 @@ export class ContextMenuManager {
288
350
  formatPrimaryShortcutLabel("v"),
289
351
  false,
290
352
  handle,
353
+ 0,
354
+ 0,
355
+ true,
291
356
  ),
292
357
  );
293
358
  items.push(
@@ -299,6 +364,9 @@ export class ContextMenuManager {
299
364
  formatPrimaryShortcutLabel("a"),
300
365
  !hasText,
301
366
  handle,
367
+ 0,
368
+ 0,
369
+ true,
302
370
  ),
303
371
  );
304
372
  return items;
@@ -309,7 +377,7 @@ export class ContextMenuManager {
309
377
  new MenuItem(
310
378
  "Copy",
311
379
  ContextMenuAction.CopyCurrentSelection,
312
- null,
380
+ currentSelectionText,
313
381
  0,
314
382
  formatPrimaryShortcutLabel("c"),
315
383
  ),
@@ -368,6 +436,22 @@ export class ContextMenuManager {
368
436
  return target.selectionEnd;
369
437
  }
370
438
 
439
+ private static resolveSelectionStartByte(target: TextCore): u32 {
440
+ const parent = target.parentNode;
441
+ if (parent instanceof TextInputCore) {
442
+ return changetype<TextInputCore>(parent).selectionStartByteOffset;
443
+ }
444
+ return target.selectionStart;
445
+ }
446
+
447
+ private static resolveSelectionEndByte(target: TextCore): u32 {
448
+ const parent = target.parentNode;
449
+ if (parent instanceof TextInputCore) {
450
+ return changetype<TextInputCore>(parent).selectionEndByteOffset;
451
+ }
452
+ return target.selectionEnd;
453
+ }
454
+
371
455
  private static resolveTextContent(target: TextCore): string {
372
456
  const parent = target.parentNode;
373
457
  if (parent instanceof TextInputCore) {
@@ -1,12 +1,27 @@
1
1
  import {
2
2
  __fui_on_focus_changed,
3
3
  __fui_on_key_event,
4
- __fui_on_pointer_event,
4
+ __fui_on_pointer_event_with_metadata,
5
5
  __fui_on_scroll,
6
6
  } from "./event_exports";
7
+ import { PointerType } from "./Node";
7
8
 
8
9
  export function __fui_debug_pointer_event(eventType: u32, handle: u64, x: f32, y: f32, modifiers: u32 = 0): void {
9
- __fui_on_pointer_event(eventType, handle, x, y, modifiers);
10
+ __fui_on_pointer_event_with_metadata(
11
+ eventType,
12
+ handle,
13
+ x,
14
+ y,
15
+ modifiers,
16
+ -1,
17
+ <u32>PointerType.Mouse,
18
+ 0,
19
+ 0,
20
+ 0.0,
21
+ 0.0,
22
+ 0.0,
23
+ 0,
24
+ );
10
25
  }
11
26
 
12
27
  export function __fui_debug_focus_changed(handle: u64, focused: bool): void {
@@ -1,3 +1,4 @@
1
+ import { KeyModifier } from "./ffi";
1
2
  import { Signal } from "./Signal";
2
3
 
3
4
  const DEFAULT_DRAG_THRESHOLD: f32 = 4.0;
@@ -11,7 +12,7 @@ export class DragStartedEvent {
11
12
  constructor(
12
13
  readonly x: f32 = 0.0,
13
14
  readonly y: f32 = 0.0,
14
- readonly modifiers: u32 = 0,
15
+ readonly modifiers: KeyModifier = 0,
15
16
  ) {}
16
17
  }
17
18
 
@@ -23,7 +24,7 @@ export class DragDeltaEvent {
23
24
  readonly deltaY: f32 = 0.0,
24
25
  readonly totalDeltaX: f32 = 0.0,
25
26
  readonly totalDeltaY: f32 = 0.0,
26
- readonly modifiers: u32 = 0,
27
+ readonly modifiers: KeyModifier = 0,
27
28
  ) {}
28
29
  }
29
30
 
@@ -33,7 +34,7 @@ export class DragCompletedEvent {
33
34
  readonly y: f32 = 0.0,
34
35
  readonly totalDeltaX: f32 = 0.0,
35
36
  readonly totalDeltaY: f32 = 0.0,
36
- readonly modifiers: u32 = 0,
37
+ readonly modifiers: KeyModifier = 0,
37
38
  readonly cancelled: bool = false,
38
39
  ) {}
39
40
  }
@@ -53,7 +54,7 @@ export class DragGesture {
53
54
  private lastPointerY: f32 = 0.0;
54
55
  private lastDispatchedX: f32 = 0.0;
55
56
  private lastDispatchedY: f32 = 0.0;
56
- private lastModifiers: u32 = 0;
57
+ private lastModifiers: KeyModifier = 0;
57
58
 
58
59
  constructor(private readonly host: DragGestureHost) {}
59
60
 
@@ -66,7 +67,7 @@ export class DragGesture {
66
67
  return this;
67
68
  }
68
69
 
69
- handlePointerDown(x: f32, y: f32, modifiers: u32 = 0): void {
70
+ handlePointerDown(x: f32, y: f32, modifiers: KeyModifier = 0): void {
70
71
  if (this.pointerDownValue) {
71
72
  this.cancel();
72
73
  }
@@ -85,7 +86,7 @@ export class DragGesture {
85
86
  }
86
87
  }
87
88
 
88
- handlePointerMove(x: f32, y: f32, modifiers: u32 = 0): void {
89
+ handlePointerMove(x: f32, y: f32, modifiers: KeyModifier = 0): void {
89
90
  if (!this.pointerDownValue) {
90
91
  return;
91
92
  }
@@ -101,7 +102,7 @@ export class DragGesture {
101
102
  this.emitDelta(x, y, modifiers);
102
103
  }
103
104
 
104
- handlePointerUp(x: f32, y: f32, modifiers: u32 = 0): void {
105
+ handlePointerUp(x: f32, y: f32, modifiers: KeyModifier = 0): void {
105
106
  if (!this.pointerDownValue) {
106
107
  return;
107
108
  }