@effindomv2/fui-as 0.1.36 → 0.1.39
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.
- package/browser/routed-app-conventions.ts +1 -1
- package/browser/src/common-harness.ts +1 -27
- package/browser/src/index.ts +19 -26
- package/browser/src/routed-harness.ts +1 -194
- package/browser/src/shared-browser.ts +6 -0
- package/package.json +26 -16
- package/scripts/build.sh +51 -47
- package/scripts/framework-host-services.ts +1 -1
- package/scripts/generate-host-events.ts +15 -241
- package/scripts/generate-host-services.ts +13 -261
- package/scripts/hostgen/assemblyscript-host-events.ts +165 -0
- package/scripts/hostgen/assemblyscript-host-services.ts +207 -0
- package/scripts/hostgen/common.ts +73 -0
- package/scripts/hostgen/registry.ts +159 -0
- package/src/Fui.ts +70 -4
- package/src/FuiExports.ts +13 -1
- package/src/bindings/ui.ts +165 -8
- package/src/controls/Button.ts +98 -71
- package/src/controls/Checkbox.ts +23 -21
- package/src/controls/ComboBox.ts +1201 -0
- package/src/controls/ContextMenu.ts +279 -165
- package/src/controls/Dialog.ts +91 -17
- package/src/controls/Dropdown.ts +101 -333
- package/src/controls/NavLink.ts +29 -6
- package/src/controls/Popup.ts +10 -5
- package/src/controls/ProgressBar.ts +1 -2
- package/src/controls/RadioButton.ts +34 -13
- package/src/controls/RadioGroup.ts +32 -19
- package/src/controls/Slider.ts +21 -23
- package/src/controls/Switch.ts +34 -17
- package/src/controls/index.ts +2 -1
- package/src/controls/internal/ButtonPresenter.ts +5 -2
- package/src/controls/internal/CheckboxIndicatorPresenter.ts +10 -10
- package/src/controls/internal/DropdownChevronPresenter.ts +3 -3
- package/src/controls/internal/DropdownFieldPresenter.ts +6 -6
- package/src/controls/internal/DropdownOptionRowPresenter.ts +5 -4
- package/src/controls/internal/PressableLabeledControl.ts +4 -7
- package/src/controls/internal/RadioIndicatorPresenter.ts +7 -7
- package/src/controls/internal/SelectablePopupList.ts +463 -0
- package/src/controls/internal/SliderPresenter.ts +5 -5
- package/src/controls/internal/SwitchIndicatorPresenter.ts +63 -17
- package/src/controls/internal/TextInputCore.ts +307 -41
- package/src/controls/internal/TextInputPresenter.ts +2 -2
- package/src/controls/templating.ts +10 -0
- package/src/core/Application.ts +19 -9
- package/src/core/Bitmap.ts +219 -1
- package/src/core/ContextMenuManager.ts +128 -44
- package/src/core/DebugApi.ts +17 -2
- package/src/core/DragGesture.ts +8 -7
- package/src/core/EventRouter.ts +332 -12
- package/src/core/Fetch.ts +15 -7
- package/src/core/FetchFfi.ts +1 -15
- package/src/core/File.ts +75 -34
- package/src/core/FocusAdornerManager.ts +3 -3
- package/src/core/FocusVisibility.ts +7 -0
- package/src/core/FrameScheduler.ts +126 -0
- package/src/core/ImageSampling.ts +43 -0
- package/src/core/Logger.ts +2 -0
- package/src/core/MobileTextSelectionToolbarManager.ts +901 -0
- package/src/core/Node.ts +1039 -97
- package/src/core/Platform.ts +16 -16
- package/src/core/SelectionHandleAdornerManager.ts +697 -0
- package/src/core/Theme.ts +34 -20
- package/src/core/ToolTipManager.ts +3 -3
- package/src/core/Typography.ts +411 -19
- package/src/core/Worker.ts +69 -33
- package/src/core/event_exports.ts +123 -5
- package/src/core/ffi.ts +5 -746
- package/src/core/generated/FetchHostAbi.ts +10 -0
- package/src/core/generated/FrameworkHostServices.ts +1 -1
- package/src/core/generated/HostAbi.ts +220 -0
- package/src/core/generated/UiAbi.ts +356 -0
- package/src/core/generated/UiEnums.ts +186 -0
- package/src/drawing/DrawCallback.ts +34 -0
- package/src/drawing/DrawContext.ts +189 -0
- package/src/drawing/Paint.ts +34 -0
- package/src/drawing/Path.ts +40 -0
- package/src/nodes/CustomDrawable.ts +61 -0
- package/src/nodes/FlexBox.ts +57 -11
- package/src/nodes/Grid.ts +5 -5
- package/src/nodes/Image.ts +20 -1
- package/src/nodes/RichText.ts +36 -25
- package/src/nodes/ScrollBar.ts +2 -1
- package/src/nodes/ScrollBox.ts +7 -2
- package/src/nodes/ScrollView.ts +17 -6
- package/src/nodes/Svg.ts +18 -1
- package/src/nodes/Text.ts +1 -1
- package/src/nodes/TextCore.ts +115 -20
- package/src/nodes/index.ts +3 -1
- package/src/text/DynamicTextLayout.ts +275 -0
- package/src/text/TextLayout.ts +308 -0
- package/src/worker/Worker.ts +3 -36
- package/src/worker/WorkerJob.ts +5 -9
- package/src/worker/ffi.ts +0 -6
- package/browser/src/common-harness/host-imports.ts +0 -447
- package/browser/src/common-harness/interop.ts +0 -39
- package/browser/src/common-harness/managed-harness-bitmap-host.ts +0 -92
- package/browser/src/common-harness/managed-harness-fetch-host.ts +0 -201
- package/browser/src/common-harness/managed-harness-file-host.ts +0 -1066
- package/browser/src/common-harness/managed-harness-file-payloads.ts +0 -143
- package/browser/src/common-harness/managed-harness-file-types.ts +0 -71
- package/browser/src/common-harness/managed-harness-session.ts +0 -15
- package/browser/src/common-harness/managed-harness.ts +0 -1451
- package/browser/src/common-harness/managed-history.ts +0 -168
- package/browser/src/common-harness/persisted-restore-policy.ts +0 -50
- package/browser/src/common-harness/persisted-ui-state-controller.ts +0 -309
- package/browser/src/common-harness/text-session-bridge.ts +0 -452
- package/browser/src/common-harness/types.ts +0 -205
- package/browser/src/common-harness/ui-chrome.ts +0 -206
- package/browser/src/common-harness/ui-imports.ts +0 -553
- package/browser/src/common-harness/wasm-module-cache.ts +0 -47
- package/browser/src/file-processing-worker.ts +0 -89
- package/browser/src/host-events.ts +0 -118
- package/browser/src/host-services.ts +0 -483
- package/browser/src/persisted-ui-state.ts +0 -206
- package/browser/src/routed-app-conventions.ts +0 -292
- package/browser/src/worker-bootstrap.ts +0 -916
- package/browser/src/worker-manager.ts +0 -234
- package/browser/src/worker-types.ts +0 -68
package/src/core/Application.ts
CHANGED
|
@@ -3,9 +3,11 @@ import { ContextMenuManager } from "./ContextMenuManager";
|
|
|
3
3
|
import { disposeAllFetchRequests } from "./Fetch";
|
|
4
4
|
import { disposeAllFileRequests } from "./File";
|
|
5
5
|
import { FocusAdornerManager } from "./FocusAdornerManager";
|
|
6
|
+
import { MobileTextSelectionToolbarManager } from "./MobileTextSelectionToolbarManager";
|
|
7
|
+
import { SelectionHandleAdornerManager } from "./SelectionHandleAdornerManager";
|
|
6
8
|
import { ToolTipManager } from "./ToolTipManager";
|
|
7
9
|
import * as ui from "../bindings/ui";
|
|
8
|
-
import { flushCommit, markNeedsCommit, resetCommitState } from "./FrameScheduler";
|
|
10
|
+
import { flushCommit, fireLoadedCallbacks, markNeedsCommit, resetCommitState } from "./FrameScheduler";
|
|
9
11
|
import { HandleValue, Unit } from "./ffi";
|
|
10
12
|
import { Node } from "./Node";
|
|
11
13
|
import { cancelAllTimers } from "./Timers";
|
|
@@ -29,12 +31,20 @@ function createEmptyPage(): Node {
|
|
|
29
31
|
return new FlexBox().width(100.0, Unit.Percent).height(100.0, Unit.Percent);
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
function disposeAppScopedAsyncWork(): void {
|
|
35
|
+
disposeAllFetchRequests();
|
|
36
|
+
disposeAllFileRequests();
|
|
37
|
+
disposeAllWorkers();
|
|
38
|
+
}
|
|
39
|
+
|
|
32
40
|
class ApplicationShell extends FlexBox {
|
|
33
41
|
constructor(root: Node) {
|
|
34
42
|
super();
|
|
35
43
|
this.width(100.0, Unit.Percent)
|
|
36
44
|
.height(100.0, Unit.Percent)
|
|
37
45
|
.child(root)
|
|
46
|
+
.child(SelectionHandleAdornerManager.createDefaultHost())
|
|
47
|
+
.child(MobileTextSelectionToolbarManager.createDefaultHost())
|
|
38
48
|
.child(FocusAdornerManager.createDefaultHost())
|
|
39
49
|
.child(ContextMenuManager.createDefaultMenu())
|
|
40
50
|
.child(ToolTipManager.createDefaultHost());
|
|
@@ -198,10 +208,10 @@ export class Application<TPage> {
|
|
|
198
208
|
}
|
|
199
209
|
|
|
200
210
|
cancelAllTimers();
|
|
201
|
-
|
|
202
|
-
disposeAllFileRequests();
|
|
203
|
-
disposeAllWorkers();
|
|
211
|
+
disposeAppScopedAsyncWork();
|
|
204
212
|
FocusAdornerManager.clear();
|
|
213
|
+
SelectionHandleAdornerManager.clear();
|
|
214
|
+
MobileTextSelectionToolbarManager.clear();
|
|
205
215
|
ToolTipManager.clear();
|
|
206
216
|
resetCommitState();
|
|
207
217
|
ui.reset();
|
|
@@ -211,6 +221,7 @@ export class Application<TPage> {
|
|
|
211
221
|
ui.setRoot(shell.builtHandle);
|
|
212
222
|
mountedShell = shell;
|
|
213
223
|
mountedRoot = root;
|
|
224
|
+
fireLoadedCallbacks();
|
|
214
225
|
markNeedsCommit();
|
|
215
226
|
return root;
|
|
216
227
|
}
|
|
@@ -224,10 +235,10 @@ export class Application<TPage> {
|
|
|
224
235
|
mountedShell = null;
|
|
225
236
|
mountedRoot = null;
|
|
226
237
|
cancelAllTimers();
|
|
227
|
-
|
|
228
|
-
disposeAllFileRequests();
|
|
229
|
-
disposeAllWorkers();
|
|
238
|
+
disposeAppScopedAsyncWork();
|
|
230
239
|
FocusAdornerManager.clear();
|
|
240
|
+
SelectionHandleAdornerManager.clear();
|
|
241
|
+
MobileTextSelectionToolbarManager.clear();
|
|
231
242
|
ToolTipManager.clear();
|
|
232
243
|
resetCommitState();
|
|
233
244
|
}
|
|
@@ -330,8 +341,7 @@ export function __disposeApp(): void {
|
|
|
330
341
|
return;
|
|
331
342
|
}
|
|
332
343
|
app.dispose();
|
|
333
|
-
|
|
334
|
-
disposeAllWorkers();
|
|
344
|
+
disposeAppScopedAsyncWork();
|
|
335
345
|
}
|
|
336
346
|
|
|
337
347
|
export function __flushRenders(): void {
|
package/src/core/Bitmap.ts
CHANGED
|
@@ -1,12 +1,74 @@
|
|
|
1
1
|
import * as ui from "../bindings/ui";
|
|
2
|
+
import * as ffi from "../core/ffi";
|
|
2
3
|
import { markTextureAssetReady } from "./Assets";
|
|
3
4
|
import { Disposable } from "./Disposable";
|
|
4
5
|
import { allocateDynamicTextureId } from "./DynamicAssetIds";
|
|
6
|
+
import { DrawContext } from "../drawing/DrawContext";
|
|
5
7
|
import { throwObjectDisposed } from "./Errors";
|
|
8
|
+
import { LoadedEventArgs, onLoadedWith } from "./FrameScheduler";
|
|
6
9
|
import { error } from "./Logger";
|
|
10
|
+
import { Node } from "./Node";
|
|
11
|
+
import { FontFace, FontsLoadedEventArgs } from "./Typography";
|
|
12
|
+
import { TextLayout } from "../text/TextLayout";
|
|
7
13
|
|
|
8
14
|
const FUNCTION_BITMAP_CONSTRUCTOR = "Bitmap.constructor";
|
|
9
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
|
+
|
|
10
72
|
function throwBitmapError(category: string, message: string): void {
|
|
11
73
|
error(category, message);
|
|
12
74
|
throw new Error(message);
|
|
@@ -23,12 +85,20 @@ function validateBitmapDimensions(width: u32, height: u32): u32 {
|
|
|
23
85
|
return <u32>totalBytes;
|
|
24
86
|
}
|
|
25
87
|
|
|
88
|
+
const MAX_DIRTY_RECTS: i32 = 16;
|
|
89
|
+
const RECT_STRIDE: i32 = 4; // x, y, w, h
|
|
90
|
+
|
|
26
91
|
export class Bitmap implements Disposable {
|
|
27
92
|
readonly width: u32;
|
|
28
93
|
readonly height: u32;
|
|
29
94
|
readonly textureId: u32;
|
|
30
95
|
private pixelBytes: Uint8Array;
|
|
96
|
+
private offscreenId: u32 = 0;
|
|
97
|
+
private canvasUsed: bool = false;
|
|
98
|
+
private drawContext: DrawContext | null = null;
|
|
31
99
|
private disposed: bool = false;
|
|
100
|
+
private dirtyRects: StaticArray<u32> = new StaticArray<u32>(MAX_DIRTY_RECTS * RECT_STRIDE);
|
|
101
|
+
private dirtyCount: i32 = 0;
|
|
32
102
|
|
|
33
103
|
constructor(width: u32, height: u32) {
|
|
34
104
|
const byteLength = validateBitmapDimensions(width, height);
|
|
@@ -36,6 +106,7 @@ export class Bitmap implements Disposable {
|
|
|
36
106
|
this.height = height;
|
|
37
107
|
this.textureId = allocateDynamicTextureId();
|
|
38
108
|
this.pixelBytes = new Uint8Array(<i32>byteLength);
|
|
109
|
+
this.offscreenId = ffi.fui_canvas_create_offscreen(width, height);
|
|
39
110
|
}
|
|
40
111
|
|
|
41
112
|
pixels(): Uint8Array {
|
|
@@ -48,9 +119,152 @@ export class Bitmap implements Disposable {
|
|
|
48
119
|
return pixels.length > 0 ? pixels.dataStart : 0;
|
|
49
120
|
}
|
|
50
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Returns a `DrawContext` targeting this bitmap's off‑screen raster
|
|
124
|
+
* surface. Drawing commands issued on the context are accumulated on
|
|
125
|
+
* the off‑screen canvas and uploaded to the GPU texture on the next
|
|
126
|
+
* `commit()` call.
|
|
127
|
+
*
|
|
128
|
+
* The returned `DrawContext` is valid for the lifetime of the `Bitmap`.
|
|
129
|
+
* Do not store it beyond the `Bitmap`'s disposal.
|
|
130
|
+
*/
|
|
131
|
+
canvas(): DrawContext {
|
|
132
|
+
this.ensureNotDisposed("Bitmap.canvas");
|
|
133
|
+
this.canvasUsed = true;
|
|
134
|
+
if (this.drawContext !== null) {
|
|
135
|
+
return this.drawContext!;
|
|
136
|
+
}
|
|
137
|
+
const ptr = ffi.fui_canvas_get_offscreen_ptr(this.offscreenId);
|
|
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;
|
|
235
|
+
}
|
|
236
|
+
|
|
51
237
|
commit(): u32 {
|
|
52
238
|
this.ensureNotDisposed("Bitmap.commit");
|
|
53
|
-
|
|
239
|
+
if (this.canvasUsed) {
|
|
240
|
+
if (this.drawContext !== null) {
|
|
241
|
+
this.drawContext!.flush();
|
|
242
|
+
}
|
|
243
|
+
ffi.fui_canvas_read_offscreen_pixels(
|
|
244
|
+
this.offscreenId, this.pixelBytes.dataStart, this.width, this.height);
|
|
245
|
+
}
|
|
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
|
+
}
|
|
54
268
|
markTextureAssetReady(this.textureId, <f32>this.width, <f32>this.height);
|
|
55
269
|
return this.textureId;
|
|
56
270
|
}
|
|
@@ -60,8 +274,12 @@ export class Bitmap implements Disposable {
|
|
|
60
274
|
return;
|
|
61
275
|
}
|
|
62
276
|
this.disposed = true;
|
|
277
|
+
if (this.offscreenId !== 0) {
|
|
278
|
+
ffi.fui_canvas_destroy_offscreen(this.offscreenId);
|
|
279
|
+
}
|
|
63
280
|
ui.bitmapRelease(this.textureId);
|
|
64
281
|
this.pixelBytes = new Uint8Array(0);
|
|
282
|
+
this.drawContext = null;
|
|
65
283
|
}
|
|
66
284
|
|
|
67
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(
|
|
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
|
-
|
|
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("
|
|
122
|
-
linkItems.push(new MenuItem("Open
|
|
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("
|
|
136
|
-
imageItems.push(new MenuItem("Open
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|
package/src/core/DebugApi.ts
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
import {
|
|
2
2
|
__fui_on_focus_changed,
|
|
3
3
|
__fui_on_key_event,
|
|
4
|
-
|
|
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
|
-
|
|
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 {
|