@effindomv2/fui-as 0.1.50 → 0.2.0-alpha1
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/README.md +36 -0
- package/browser/favicon.ico +0 -0
- package/browser/src/assemblyscript-harness.ts +39 -0
- package/browser/src/index.ts +3 -0
- package/browser/src/routed-harness.ts +25 -1
- package/browser/src/shared-browser.ts +4 -1
- package/package.json +14 -5
- package/scripts/build.sh +31 -9
- package/scripts/framework-host-services.ts +12 -0
- package/src/Fui.ts +12 -1
- package/src/controls/Button.ts +3 -61
- package/src/controls/Checkbox.ts +16 -1
- package/src/controls/ContextMenu.ts +29 -0
- package/src/controls/RadioButton.ts +16 -1
- package/src/controls/Switch.ts +16 -1
- package/src/controls/internal/PressableLabeledControl.ts +36 -1
- package/src/core/Application.ts +6 -1
- package/src/core/ContextMenuManager.ts +89 -58
- package/src/core/EventRouter.ts +44 -0
- package/src/core/Node.ts +84 -8
- package/src/core/Platform.ts +64 -7
- package/src/core/ffi.ts +2 -0
- package/src/core/generated/FrameworkHostServices.ts +14 -0
- package/src/core/generated/HostAbi.ts +9 -0
- package/src/core/generated/UiEnums.ts +24 -0
- package/src/nodes/FlexBox.ts +10 -0
- package/src/nodes/Grid.ts +10 -0
- package/src/nodes/ScrollView.ts +10 -0
- package/src/nodes/TextCore.ts +10 -0
- package/browser/loading-overlay-body.html +0 -13
- package/browser/loading-overlay-styles.html +0 -52
|
@@ -11,7 +11,10 @@ import {
|
|
|
11
11
|
formatRedoShortcutLabel,
|
|
12
12
|
formatShortcutLabel,
|
|
13
13
|
formatUndoShortcutLabel,
|
|
14
|
+
getHostContext,
|
|
14
15
|
getPlatformFamily,
|
|
16
|
+
HostCapability,
|
|
17
|
+
HostContext,
|
|
15
18
|
PlatformFamily,
|
|
16
19
|
} from "./Platform";
|
|
17
20
|
import { ContextMenuEventArgs, Node, VisibilityChangedEventArgs } from "./Node";
|
|
@@ -101,18 +104,21 @@ export class ContextMenuManager {
|
|
|
101
104
|
if (this.invokeCustomContextMenuHandler(targetNode, x, y)) {
|
|
102
105
|
return;
|
|
103
106
|
}
|
|
104
|
-
const
|
|
107
|
+
const host = getHostContext();
|
|
108
|
+
const items = this.buildBuiltInItemsForHost(handle, x, y, true, host);
|
|
105
109
|
|
|
106
110
|
const navigationItems = new Array<MenuItem>();
|
|
107
|
-
if (ffi.fui_can_navigate_back()) {
|
|
111
|
+
if (host.supports(HostCapability.BrowserHistory) && ffi.fui_can_navigate_back()) {
|
|
108
112
|
navigationItems.push(new MenuItem("Back", ContextMenuAction.NavigateBack, null, 0, resolveHistoryShortcutLabel(false)));
|
|
109
113
|
}
|
|
110
|
-
if (ffi.fui_can_navigate_forward()) {
|
|
114
|
+
if (host.supports(HostCapability.BrowserHistory) && ffi.fui_can_navigate_forward()) {
|
|
111
115
|
navigationItems.push(new MenuItem("Forward", ContextMenuAction.NavigateForward, null, 0, resolveHistoryShortcutLabel(true)));
|
|
112
116
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
117
|
+
if (host.supports(HostCapability.Reload)) {
|
|
118
|
+
navigationItems.push(
|
|
119
|
+
new MenuItem("Reload Page", ContextMenuAction.ReloadPage, null, 0, formatPrimaryShortcutLabel("r")),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
116
122
|
appendMenuSection(items, navigationItems);
|
|
117
123
|
if (items.length == 0) {
|
|
118
124
|
this.releaseActiveMenuLinkPreview();
|
|
@@ -123,7 +129,7 @@ export class ContextMenuManager {
|
|
|
123
129
|
}
|
|
124
130
|
|
|
125
131
|
static showForLongPress(handle: u64, x: f32, y: f32): bool {
|
|
126
|
-
const items = this.
|
|
132
|
+
const items = this.buildBuiltInItemsForHost(handle, x, y, false, getHostContext());
|
|
127
133
|
if (items.length == 0) {
|
|
128
134
|
this.releaseActiveMenuLinkPreview();
|
|
129
135
|
return false;
|
|
@@ -169,7 +175,7 @@ export class ContextMenuManager {
|
|
|
169
175
|
}
|
|
170
176
|
const handler = contextNode.contextMenuHandler;
|
|
171
177
|
if (handler !== null) {
|
|
172
|
-
handler(new ContextMenuEventArgs(targetNode, x, y));
|
|
178
|
+
handler(new ContextMenuEventArgs(targetNode, x, y, getHostContext()));
|
|
173
179
|
return true;
|
|
174
180
|
}
|
|
175
181
|
contextNode = contextNode.parentNode;
|
|
@@ -177,7 +183,14 @@ export class ContextMenuManager {
|
|
|
177
183
|
return false;
|
|
178
184
|
}
|
|
179
185
|
|
|
180
|
-
|
|
186
|
+
/** @internal Exposed for deterministic host-policy tests; not part of the FUI public barrel. */
|
|
187
|
+
static buildBuiltInItemsForHost(
|
|
188
|
+
handle: u64,
|
|
189
|
+
x: f32,
|
|
190
|
+
y: f32,
|
|
191
|
+
clearSelectionOnBackgroundMiss: bool,
|
|
192
|
+
host: HostContext,
|
|
193
|
+
): Array<MenuItem> {
|
|
181
194
|
this.releaseActiveMenuLinkPreview();
|
|
182
195
|
const targetNode = EventRouter.getRegisteredNode(handle);
|
|
183
196
|
let contextNode = targetNode;
|
|
@@ -194,26 +207,34 @@ export class ContextMenuManager {
|
|
|
194
207
|
link.pinPreviewForContextMenu();
|
|
195
208
|
this.activeMenuLink = link;
|
|
196
209
|
const linkItems = new Array<MenuItem>();
|
|
197
|
-
|
|
198
|
-
|
|
210
|
+
if (host.supports(HostCapability.NewBrowsingContext)) {
|
|
211
|
+
linkItems.push(new MenuItem("New Tab", ContextMenuAction.OpenLinkInNewTab, link.href));
|
|
212
|
+
}
|
|
213
|
+
if (host.supports(HostCapability.OpenExternalUri)) {
|
|
214
|
+
linkItems.push(new MenuItem("Open", ContextMenuAction.OpenLink, link.href));
|
|
215
|
+
}
|
|
199
216
|
appendMenuSection(items, linkItems);
|
|
200
217
|
}
|
|
201
218
|
|
|
202
219
|
const textTarget = this.resolveTextTarget(targetNode);
|
|
203
220
|
const selectionHit = ui.isPointInSelection(x, y) || this.selectionHintContainsHandle(handle);
|
|
204
221
|
if (textTarget !== null) {
|
|
205
|
-
appendMenuSection(items, this.buildTextSection(textTarget, selectionHit ? this.currentSelectionText : ""));
|
|
222
|
+
appendMenuSection(items, this.buildTextSection(textTarget, selectionHit ? this.currentSelectionText : "", host));
|
|
206
223
|
}
|
|
207
224
|
|
|
208
225
|
const imageUrl = this.resolveImageUrl(targetNode, x, y);
|
|
209
226
|
if (imageUrl !== null && imageUrl.length > 0) {
|
|
210
227
|
const imageItems = new Array<MenuItem>();
|
|
211
|
-
|
|
212
|
-
|
|
228
|
+
if (host.supports(HostCapability.NewBrowsingContext)) {
|
|
229
|
+
imageItems.push(new MenuItem("New Tab", ContextMenuAction.OpenImageInNewTab, imageUrl));
|
|
230
|
+
}
|
|
231
|
+
if (host.supports(HostCapability.OpenExternalUri)) {
|
|
232
|
+
imageItems.push(new MenuItem("Open", ContextMenuAction.OpenImage, imageUrl));
|
|
233
|
+
}
|
|
213
234
|
appendMenuSection(items, imageItems);
|
|
214
235
|
}
|
|
215
236
|
|
|
216
|
-
if (selectionHit && textTarget === null && this.currentSelectionText.length > 0) {
|
|
237
|
+
if (selectionHit && textTarget === null && this.currentSelectionText.length > 0 && host.supports(HostCapability.ClipboardWrite)) {
|
|
217
238
|
const selectionItems = new Array<MenuItem>();
|
|
218
239
|
selectionItems.push(new MenuItem("Copy", ContextMenuAction.CopyCurrentSelection, this.currentSelectionText));
|
|
219
240
|
appendMenuSection(items, selectionItems);
|
|
@@ -266,7 +287,7 @@ export class ContextMenuManager {
|
|
|
266
287
|
return null;
|
|
267
288
|
}
|
|
268
289
|
|
|
269
|
-
private static buildTextSection(target: TextCore, currentSelectionText: string
|
|
290
|
+
private static buildTextSection(target: TextCore, currentSelectionText: string, host: HostContext): Array<MenuItem> {
|
|
270
291
|
const items = new Array<MenuItem>();
|
|
271
292
|
const handle = target.builtHandle;
|
|
272
293
|
if (handle == <u64>HandleValue.Invalid || !target.isSelectableText) {
|
|
@@ -313,48 +334,52 @@ export class ContextMenuManager {
|
|
|
313
334
|
),
|
|
314
335
|
);
|
|
315
336
|
items.push(MenuItem.separator());
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
337
|
+
if (host.supports(HostCapability.ClipboardWrite)) {
|
|
338
|
+
items.push(
|
|
339
|
+
new MenuItem(
|
|
340
|
+
"Cut",
|
|
341
|
+
ContextMenuAction.CutTextSelection,
|
|
342
|
+
selectedPayload,
|
|
343
|
+
0,
|
|
344
|
+
formatPrimaryShortcutLabel("x"),
|
|
345
|
+
!hasSelection,
|
|
346
|
+
handle,
|
|
347
|
+
0,
|
|
348
|
+
0,
|
|
349
|
+
true,
|
|
350
|
+
).withSelectionRange(selectionStartByte, selectionEndByte),
|
|
351
|
+
);
|
|
352
|
+
items.push(
|
|
353
|
+
new MenuItem(
|
|
354
|
+
"Copy",
|
|
355
|
+
ContextMenuAction.CopyCurrentSelection,
|
|
356
|
+
selectedPayload,
|
|
357
|
+
0,
|
|
358
|
+
formatPrimaryShortcutLabel("c"),
|
|
359
|
+
!hasSelection,
|
|
360
|
+
handle,
|
|
361
|
+
0,
|
|
362
|
+
0,
|
|
363
|
+
true,
|
|
364
|
+
),
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
if (host.supports(HostCapability.ClipboardRead)) {
|
|
368
|
+
items.push(
|
|
369
|
+
new MenuItem(
|
|
370
|
+
"Paste",
|
|
371
|
+
ContextMenuAction.PasteText,
|
|
372
|
+
null,
|
|
373
|
+
0,
|
|
374
|
+
formatPrimaryShortcutLabel("v"),
|
|
375
|
+
false,
|
|
376
|
+
handle,
|
|
377
|
+
0,
|
|
378
|
+
0,
|
|
379
|
+
true,
|
|
380
|
+
),
|
|
381
|
+
);
|
|
382
|
+
}
|
|
358
383
|
items.push(
|
|
359
384
|
new MenuItem(
|
|
360
385
|
"Select All",
|
|
@@ -372,6 +397,12 @@ export class ContextMenuManager {
|
|
|
372
397
|
return items;
|
|
373
398
|
}
|
|
374
399
|
|
|
400
|
+
if (!host.supports(HostCapability.ClipboardWrite)) {
|
|
401
|
+
items.push(
|
|
402
|
+
new MenuItem("Select All", ContextMenuAction.SelectAllText, null, 0, formatPrimaryShortcutLabel("a"), !hasText, handle),
|
|
403
|
+
);
|
|
404
|
+
return items;
|
|
405
|
+
}
|
|
375
406
|
if (currentSelectionText.length > 0) {
|
|
376
407
|
items.push(
|
|
377
408
|
new MenuItem(
|
package/src/core/EventRouter.ts
CHANGED
|
@@ -24,6 +24,7 @@ import { NavLink } from "../controls/NavLink";
|
|
|
24
24
|
import { TextInputCore } from "../controls/internal/TextInputCore";
|
|
25
25
|
import { Image } from "../nodes/Image";
|
|
26
26
|
import { Svg } from "../nodes/Svg";
|
|
27
|
+
import { ContextMenu } from "../controls/ContextMenu";
|
|
27
28
|
|
|
28
29
|
export interface GlobalKeyHandler {
|
|
29
30
|
handleGlobalKeyEvent(eventType: KeyEventType, key: string, modifiers: u32): bool;
|
|
@@ -44,6 +45,7 @@ export class EventRouter {
|
|
|
44
45
|
private static readonly keyFilterHandlers: Array<GlobalKeyHandler> = new Array<GlobalKeyHandler>();
|
|
45
46
|
private static readonly keyFilterTokens: Array<u32> = new Array<u32>();
|
|
46
47
|
private static capturedPointerHandle: u64 = <u64>HandleValue.Invalid;
|
|
48
|
+
private static selectionCursorHandle: u64 = <u64>HandleValue.Invalid;
|
|
47
49
|
private static currentCursorStyle: CursorStyle = CursorStyle.Default;
|
|
48
50
|
private static nextKeyFilterToken: u32 = 1;
|
|
49
51
|
private static focusedNode: Node | null = null;
|
|
@@ -70,6 +72,9 @@ export class EventRouter {
|
|
|
70
72
|
if (this.capturedPointerHandle == handle) {
|
|
71
73
|
this.capturedPointerHandle = <u64>HandleValue.Invalid;
|
|
72
74
|
}
|
|
75
|
+
if (this.selectionCursorHandle == handle) {
|
|
76
|
+
this.selectionCursorHandle = <u64>HandleValue.Invalid;
|
|
77
|
+
}
|
|
73
78
|
const index = getHandleIndex(handle);
|
|
74
79
|
this.nodes.delete(index);
|
|
75
80
|
this.generations.delete(index);
|
|
@@ -94,6 +99,15 @@ export class EventRouter {
|
|
|
94
99
|
showKeyboardFocusForPointerEvent(eventType);
|
|
95
100
|
SelectionHandleAdornerManager.recordPointerEvent(eventType, pointerType);
|
|
96
101
|
const pointedNode = this.resolveNode(handle);
|
|
102
|
+
if (
|
|
103
|
+
eventType == PointerEventType.Down &&
|
|
104
|
+
(button == 0 || pointerType == PointerType.Touch || pointerType == PointerType.Pen) &&
|
|
105
|
+
ContextMenu.dismissForOutsidePointerDown(x, y)
|
|
106
|
+
) {
|
|
107
|
+
DragDropManager.handlePointerEvent(null, eventType, x, y, modifiers);
|
|
108
|
+
this.applyCurrentCursor();
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
97
111
|
if (
|
|
98
112
|
eventType == PointerEventType.Down &&
|
|
99
113
|
!this.preservesSelectionOnPointerDownForRouting(pointedNode) &&
|
|
@@ -103,6 +117,14 @@ export class EventRouter {
|
|
|
103
117
|
this.applyCurrentCursor();
|
|
104
118
|
return true;
|
|
105
119
|
}
|
|
120
|
+
if (eventType == PointerEventType.Down && button == 0 && pointerType != PointerType.Touch) {
|
|
121
|
+
const selectionCursorNode = this.resolveSelectionCursorNode(pointedNode);
|
|
122
|
+
this.selectionCursorHandle = selectionCursorNode === null
|
|
123
|
+
? <u64>HandleValue.Invalid
|
|
124
|
+
: changetype<Node>(selectionCursorNode).builtHandle;
|
|
125
|
+
} else if (eventType == PointerEventType.Up || eventType == PointerEventType.Cancel) {
|
|
126
|
+
this.selectionCursorHandle = <u64>HandleValue.Invalid;
|
|
127
|
+
}
|
|
106
128
|
if (eventType == PointerEventType.Up) {
|
|
107
129
|
trackKeyboardScrollPointerUp(pointedNode, x, y);
|
|
108
130
|
}
|
|
@@ -553,6 +575,7 @@ export class EventRouter {
|
|
|
553
575
|
this.nodes.clear();
|
|
554
576
|
this.generations.clear();
|
|
555
577
|
this.capturedPointerHandle = <u64>HandleValue.Invalid;
|
|
578
|
+
this.selectionCursorHandle = <u64>HandleValue.Invalid;
|
|
556
579
|
this.hoverStack.length = 0;
|
|
557
580
|
this.focusedNode = null;
|
|
558
581
|
this.keyFilterHandlers.length = 0;
|
|
@@ -687,6 +710,22 @@ export class EventRouter {
|
|
|
687
710
|
return this.resolveNode(this.capturedPointerHandle);
|
|
688
711
|
}
|
|
689
712
|
|
|
713
|
+
private static resolveSelectionCursorNode(node: Node | null): Node | null {
|
|
714
|
+
if (node === null || !node.isEnabled || !node.isVisible) {
|
|
715
|
+
return null;
|
|
716
|
+
}
|
|
717
|
+
if (node.isSelectableText || node.isEditableText) {
|
|
718
|
+
return node;
|
|
719
|
+
}
|
|
720
|
+
if (node instanceof TextInputCore) {
|
|
721
|
+
const editor = changetype<TextInputCore>(node).editorNode;
|
|
722
|
+
if (editor.isEnabled && editor.isVisible && (editor.isSelectableText || editor.isEditableText)) {
|
|
723
|
+
return editor;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
|
|
690
729
|
private static applyCurrentCursor(): void {
|
|
691
730
|
const dragCursor = DragDropManager.cursorOverrideStyle();
|
|
692
731
|
if (dragCursor != CursorStyle.Default) {
|
|
@@ -698,6 +737,11 @@ export class EventRouter {
|
|
|
698
737
|
this.applyCursor(captured.cursorStyle);
|
|
699
738
|
return;
|
|
700
739
|
}
|
|
740
|
+
const selectionCursorNode = this.resolveNode(this.selectionCursorHandle);
|
|
741
|
+
if (selectionCursorNode !== null) {
|
|
742
|
+
this.applyCursor(changetype<Node>(selectionCursorNode).cursorStyle);
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
701
745
|
if (this.hoverStack.length == 0) {
|
|
702
746
|
this.applyCursor(CursorStyle.Default);
|
|
703
747
|
return;
|
package/src/core/Node.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { storePersistedTextState, tryLoadPersistedTextState } from "./PersistedU
|
|
|
23
23
|
import { ToolTip } from "./ToolTip";
|
|
24
24
|
import { ToolTipManager } from "./ToolTipManager";
|
|
25
25
|
import { throwNullArgument } from "./Errors";
|
|
26
|
+
import { HostContext } from "./Platform";
|
|
26
27
|
import { bind0, bind1, bind2, bindResult0, bindResult1 } from "./bind";
|
|
27
28
|
import {
|
|
28
29
|
CursorStyle,
|
|
@@ -435,11 +436,13 @@ export class ContextMenuEventArgs {
|
|
|
435
436
|
readonly target: Node | null;
|
|
436
437
|
readonly x: f32;
|
|
437
438
|
readonly y: f32;
|
|
439
|
+
readonly host: HostContext;
|
|
438
440
|
|
|
439
|
-
constructor(target: Node | null, x: f32, y: f32) {
|
|
441
|
+
constructor(target: Node | null, x: f32, y: f32, host: HostContext) {
|
|
440
442
|
this.target = target;
|
|
441
443
|
this.x = x;
|
|
442
444
|
this.y = y;
|
|
445
|
+
this.host = host;
|
|
443
446
|
}
|
|
444
447
|
}
|
|
445
448
|
|
|
@@ -684,6 +687,10 @@ export abstract class Node implements DragGestureHost, Disposable {
|
|
|
684
687
|
private portalFlag: bool = false;
|
|
685
688
|
private pointerClickCallback: ((event: PointerClickEventArgs) => void) | null = null;
|
|
686
689
|
private pointerClickBinding: Callback1<PointerClickEventArgs> | null = null;
|
|
690
|
+
private pointerDoubleClickCallback: ((event: PointerClickEventArgs) => void) | null = null;
|
|
691
|
+
private pointerDoubleClickBinding: Callback1<PointerClickEventArgs> | null = null;
|
|
692
|
+
private pointerTripleClickCallback: ((event: PointerClickEventArgs) => void) | null = null;
|
|
693
|
+
private pointerTripleClickBinding: Callback1<PointerClickEventArgs> | null = null;
|
|
687
694
|
private pointerDownEventCallback: ((event: PointerEventArgs) => void) | null = null;
|
|
688
695
|
private pointerDownEventBinding: Callback1<PointerEventArgs> | null = null;
|
|
689
696
|
private pointerMoveEventCallback: ((event: PointerEventArgs) => void) | null = null;
|
|
@@ -1104,6 +1111,44 @@ export abstract class Node implements DragGestureHost, Disposable {
|
|
|
1104
1111
|
return this;
|
|
1105
1112
|
}
|
|
1106
1113
|
|
|
1114
|
+
onPointerDoubleClick(cb: (event: PointerClickEventArgs) => void): this {
|
|
1115
|
+
this.pointerDoubleClickCallback = cb;
|
|
1116
|
+
this.pointerDoubleClickBinding = null;
|
|
1117
|
+
this.requireInteractive();
|
|
1118
|
+
return this;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
bindPointerDoubleClick<Owner>(owner: Owner, handler: Handler1<Owner, PointerClickEventArgs>): this {
|
|
1122
|
+
this.pointerDoubleClickCallback = null;
|
|
1123
|
+
this.pointerDoubleClickBinding = bind1<Owner, PointerClickEventArgs>(owner, handler);
|
|
1124
|
+
this.requireInteractive();
|
|
1125
|
+
return this;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
onPointerDoubleClickWith<Owner>(owner: Owner, handler: Handler1<Owner, PointerClickEventArgs>): this {
|
|
1129
|
+
this.bindPointerDoubleClick(owner, handler);
|
|
1130
|
+
return this;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
onPointerTripleClick(cb: (event: PointerClickEventArgs) => void): this {
|
|
1134
|
+
this.pointerTripleClickCallback = cb;
|
|
1135
|
+
this.pointerTripleClickBinding = null;
|
|
1136
|
+
this.requireInteractive();
|
|
1137
|
+
return this;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
bindPointerTripleClick<Owner>(owner: Owner, handler: Handler1<Owner, PointerClickEventArgs>): this {
|
|
1141
|
+
this.pointerTripleClickCallback = null;
|
|
1142
|
+
this.pointerTripleClickBinding = bind1<Owner, PointerClickEventArgs>(owner, handler);
|
|
1143
|
+
this.requireInteractive();
|
|
1144
|
+
return this;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
onPointerTripleClickWith<Owner>(owner: Owner, handler: Handler1<Owner, PointerClickEventArgs>): this {
|
|
1148
|
+
this.bindPointerTripleClick(owner, handler);
|
|
1149
|
+
return this;
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1107
1152
|
onPointerDown(cb: (event: PointerEventArgs) => void): this {
|
|
1108
1153
|
this.pointerDownEventCallback = cb;
|
|
1109
1154
|
this.pointerDownEventBinding = null;
|
|
@@ -2166,12 +2211,43 @@ export abstract class Node implements DragGestureHost, Disposable {
|
|
|
2166
2211
|
const callback = this.pointerClickCallback;
|
|
2167
2212
|
if (callback !== null) {
|
|
2168
2213
|
callback(clickEvent);
|
|
2169
|
-
|
|
2214
|
+
} else {
|
|
2215
|
+
const binding = this.pointerClickBinding;
|
|
2216
|
+
if (binding !== null) {
|
|
2217
|
+
binding.invoke(clickEvent);
|
|
2218
|
+
}
|
|
2170
2219
|
}
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2220
|
+
if (clickEvent.clickCount == 2) {
|
|
2221
|
+
const doubleCallback = this.pointerDoubleClickCallback;
|
|
2222
|
+
if (doubleCallback !== null) {
|
|
2223
|
+
doubleCallback(clickEvent);
|
|
2224
|
+
} else {
|
|
2225
|
+
const doubleBinding = this.pointerDoubleClickBinding;
|
|
2226
|
+
if (doubleBinding !== null) {
|
|
2227
|
+
doubleBinding.invoke(clickEvent);
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
} else if (clickEvent.clickCount == 3) {
|
|
2231
|
+
const tripleCallback = this.pointerTripleClickCallback;
|
|
2232
|
+
if (tripleCallback !== null) {
|
|
2233
|
+
tripleCallback(clickEvent);
|
|
2234
|
+
} else {
|
|
2235
|
+
const tripleBinding = this.pointerTripleClickBinding;
|
|
2236
|
+
if (tripleBinding !== null) {
|
|
2237
|
+
tripleBinding.invoke(clickEvent);
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2174
2240
|
}
|
|
2241
|
+
event.handled = clickEvent.handled;
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
private hasPointerClickHandler(): bool {
|
|
2245
|
+
return this.pointerClickCallback !== null
|
|
2246
|
+
|| this.pointerClickBinding !== null
|
|
2247
|
+
|| this.pointerDoubleClickCallback !== null
|
|
2248
|
+
|| this.pointerDoubleClickBinding !== null
|
|
2249
|
+
|| this.pointerTripleClickCallback !== null
|
|
2250
|
+
|| this.pointerTripleClickBinding !== null;
|
|
2175
2251
|
}
|
|
2176
2252
|
|
|
2177
2253
|
private cancelDragState(): void {
|
|
@@ -2212,7 +2288,7 @@ export abstract class Node implements DragGestureHost, Disposable {
|
|
|
2212
2288
|
if (!event.handled) {
|
|
2213
2289
|
if (event.eventType == PointerEventType.Down) {
|
|
2214
2290
|
const isPrimaryButton = isPrimaryActivationPointer(event);
|
|
2215
|
-
this.clickPending = isPrimaryButton &&
|
|
2291
|
+
this.clickPending = isPrimaryButton && this.hasPointerClickHandler();
|
|
2216
2292
|
this.clickPendingCount = event.clickCount;
|
|
2217
2293
|
} else if (event.eventType == PointerEventType.Up) {
|
|
2218
2294
|
if (this.clickPending) {
|
|
@@ -2259,14 +2335,14 @@ export abstract class Node implements DragGestureHost, Disposable {
|
|
|
2259
2335
|
if (dragGesture !== null && isPrimaryButton) {
|
|
2260
2336
|
const waitForLongPress = event.pointerType == PointerType.Touch || event.pointerType == PointerType.Pen;
|
|
2261
2337
|
dragGesture.handlePointerDown(nodeX, nodeY, _modifiers, waitForLongPress);
|
|
2262
|
-
this.dragClickPending = this.
|
|
2338
|
+
this.dragClickPending = this.hasPointerClickHandler();
|
|
2263
2339
|
this.dragClickPendingCount = event.clickCount;
|
|
2264
2340
|
this.clickPending = false;
|
|
2265
2341
|
this.clickPendingCount = 0;
|
|
2266
2342
|
} else {
|
|
2267
2343
|
this.dragClickPending = false;
|
|
2268
2344
|
this.dragClickPendingCount = 0;
|
|
2269
|
-
this.clickPending = isPrimaryButton &&
|
|
2345
|
+
this.clickPending = isPrimaryButton && this.hasPointerClickHandler();
|
|
2270
2346
|
this.clickPendingCount = event.clickCount;
|
|
2271
2347
|
}
|
|
2272
2348
|
return;
|
package/src/core/Platform.ts
CHANGED
|
@@ -1,13 +1,58 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
fui_get_host_capabilities,
|
|
3
|
+
fui_get_host_environment,
|
|
4
|
+
fui_get_platform_family,
|
|
5
|
+
fui_is_coarse_pointer,
|
|
6
|
+
HostCapability,
|
|
7
|
+
HostEnvironment,
|
|
8
|
+
KeyModifier,
|
|
9
|
+
PlatformFamily,
|
|
10
|
+
} from "./ffi";
|
|
2
11
|
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
12
|
+
export { HostCapability, HostEnvironment, PlatformFamily } from "./ffi";
|
|
13
|
+
|
|
14
|
+
const KNOWN_HOST_CAPABILITIES: u32 =
|
|
15
|
+
HostCapability.BrowserHistory |
|
|
16
|
+
HostCapability.Reload |
|
|
17
|
+
HostCapability.NewBrowsingContext |
|
|
18
|
+
HostCapability.OpenExternalUri |
|
|
19
|
+
HostCapability.ClipboardRead |
|
|
20
|
+
HostCapability.ClipboardWrite |
|
|
21
|
+
HostCapability.FileDialogs;
|
|
22
|
+
|
|
23
|
+
function resolvePlatformFamily(value: u32): PlatformFamily {
|
|
24
|
+
if (value <= <u32>PlatformFamily.Linux) {
|
|
25
|
+
return <PlatformFamily>value;
|
|
26
|
+
}
|
|
27
|
+
return PlatformFamily.Unknown;
|
|
8
28
|
}
|
|
9
29
|
|
|
10
|
-
|
|
30
|
+
function resolveHostEnvironment(value: u32): HostEnvironment {
|
|
31
|
+
if (value <= <u32>HostEnvironment.Headless) {
|
|
32
|
+
return <HostEnvironment>value;
|
|
33
|
+
}
|
|
34
|
+
return HostEnvironment.Unknown;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const detectedPlatformFamily = resolvePlatformFamily(fui_get_platform_family());
|
|
38
|
+
const detectedHostEnvironment = resolveHostEnvironment(fui_get_host_environment());
|
|
39
|
+
const detectedHostCapabilities = fui_get_host_capabilities() & KNOWN_HOST_CAPABILITIES;
|
|
40
|
+
|
|
41
|
+
export class HostContext {
|
|
42
|
+
readonly platformFamily: PlatformFamily;
|
|
43
|
+
readonly environment: HostEnvironment;
|
|
44
|
+
private readonly capabilities: u32;
|
|
45
|
+
|
|
46
|
+
constructor(platformFamily: PlatformFamily, environment: HostEnvironment, capabilities: u32) {
|
|
47
|
+
this.platformFamily = resolvePlatformFamily(<u32>platformFamily);
|
|
48
|
+
this.environment = resolveHostEnvironment(<u32>environment);
|
|
49
|
+
this.capabilities = capabilities & KNOWN_HOST_CAPABILITIES;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
supports(capability: HostCapability): bool {
|
|
53
|
+
return (this.capabilities & <u32>capability) != 0;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
11
56
|
|
|
12
57
|
class KeyboardPolicy {
|
|
13
58
|
constructor(
|
|
@@ -108,6 +153,18 @@ export function getPlatformFamily(): PlatformFamily {
|
|
|
108
153
|
return detectedPlatformFamily;
|
|
109
154
|
}
|
|
110
155
|
|
|
156
|
+
export function getHostEnvironment(): HostEnvironment {
|
|
157
|
+
return detectedHostEnvironment;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function getHostContext(): HostContext {
|
|
161
|
+
return new HostContext(detectedPlatformFamily, detectedHostEnvironment, detectedHostCapabilities);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function hasHostCapability(capability: HostCapability): bool {
|
|
165
|
+
return (detectedHostCapabilities & <u32>capability) != 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
111
168
|
export function isCoarsePointer(): bool {
|
|
112
169
|
return fui_is_coarse_pointer();
|
|
113
170
|
}
|
package/src/core/ffi.ts
CHANGED
|
@@ -8,6 +8,20 @@ export function fui_get_accent_color(): u32 {
|
|
|
8
8
|
return __host_fui_get_accent_color();
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
@external("fui_host", "fui_get_host_capabilities")
|
|
12
|
+
declare function __host_fui_get_host_capabilities(): u32;
|
|
13
|
+
|
|
14
|
+
export function fui_get_host_capabilities(): u32 {
|
|
15
|
+
return __host_fui_get_host_capabilities();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@external("fui_host", "fui_get_host_environment")
|
|
19
|
+
declare function __host_fui_get_host_environment(): u32;
|
|
20
|
+
|
|
21
|
+
export function fui_get_host_environment(): u32 {
|
|
22
|
+
return __host_fui_get_host_environment();
|
|
23
|
+
}
|
|
24
|
+
|
|
11
25
|
@external("fui_host", "fui_get_platform_family")
|
|
12
26
|
declare function __host_fui_get_platform_family(): u32;
|
|
13
27
|
|
|
@@ -15,6 +15,9 @@ export declare function get_viewport_height(): f32;
|
|
|
15
15
|
@external("fui_host", "get_device_pixel_ratio")
|
|
16
16
|
export declare function get_device_pixel_ratio(): f32;
|
|
17
17
|
|
|
18
|
+
@external("fui_host", "fui_set_application_caption")
|
|
19
|
+
export declare function fui_set_application_caption(captionPtr: usize, captionLen: u32): void;
|
|
20
|
+
|
|
18
21
|
@external("fui_host", "fui_set_pointer_capture")
|
|
19
22
|
export declare function fui_set_pointer_capture(handle: u64): void;
|
|
20
23
|
|
|
@@ -108,6 +111,12 @@ export declare function fui_get_accent_color(): u32;
|
|
|
108
111
|
@external("fui_host", "fui_get_platform_family")
|
|
109
112
|
export declare function fui_get_platform_family(): u32;
|
|
110
113
|
|
|
114
|
+
@external("fui_host", "fui_get_host_environment")
|
|
115
|
+
export declare function fui_get_host_environment(): u32;
|
|
116
|
+
|
|
117
|
+
@external("fui_host", "fui_get_host_capabilities")
|
|
118
|
+
export declare function fui_get_host_capabilities(): u32;
|
|
119
|
+
|
|
111
120
|
@external("fui_host", "fui_is_coarse_pointer")
|
|
112
121
|
export declare function fui_is_coarse_pointer(): bool;
|
|
113
122
|
|
|
@@ -166,6 +166,30 @@ export enum SemanticRole {
|
|
|
166
166
|
ComboBox = 16,
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
export enum PlatformFamily {
|
|
170
|
+
Unknown = 0,
|
|
171
|
+
Apple = 1,
|
|
172
|
+
Windows = 2,
|
|
173
|
+
Linux = 3,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export enum HostEnvironment {
|
|
177
|
+
Unknown = 0,
|
|
178
|
+
Browser = 1,
|
|
179
|
+
Desktop = 2,
|
|
180
|
+
Headless = 3,
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export enum HostCapability {
|
|
184
|
+
BrowserHistory = 1,
|
|
185
|
+
Reload = 2,
|
|
186
|
+
NewBrowsingContext = 4,
|
|
187
|
+
OpenExternalUri = 8,
|
|
188
|
+
ClipboardRead = 16,
|
|
189
|
+
ClipboardWrite = 32,
|
|
190
|
+
FileDialogs = 64,
|
|
191
|
+
}
|
|
192
|
+
|
|
169
193
|
export enum CursorStyle {
|
|
170
194
|
Default = 0,
|
|
171
195
|
Pointer = 1,
|
package/src/nodes/FlexBox.ts
CHANGED
|
@@ -619,6 +619,16 @@ export class FlexBox extends Node {
|
|
|
619
619
|
return this;
|
|
620
620
|
}
|
|
621
621
|
|
|
622
|
+
onPointerDoubleClick(cb: (event: PointerClickEventArgs) => void): this {
|
|
623
|
+
super.onPointerDoubleClick(cb);
|
|
624
|
+
return this;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
onPointerTripleClick(cb: (event: PointerClickEventArgs) => void): this {
|
|
628
|
+
super.onPointerTripleClick(cb);
|
|
629
|
+
return this;
|
|
630
|
+
}
|
|
631
|
+
|
|
622
632
|
onPointerEnter(cb: (event: PointerEventArgs) => void): this {
|
|
623
633
|
super.onPointerEnter(cb);
|
|
624
634
|
return this;
|