@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.
@@ -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 items = this.buildBuiltInItems(handle, x, y, true);
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
- navigationItems.push(
114
- new MenuItem("Reload Page", ContextMenuAction.ReloadPage, null, 0, formatPrimaryShortcutLabel("r")),
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.buildBuiltInItems(handle, x, y, false);
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
- private static buildBuiltInItems(handle: u64, x: f32, y: f32, clearSelectionOnBackgroundMiss: bool): Array<MenuItem> {
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
- linkItems.push(new MenuItem("New Tab", ContextMenuAction.OpenLinkInNewTab, link.href));
198
- linkItems.push(new MenuItem("Open", ContextMenuAction.OpenLink, link.href));
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
- imageItems.push(new MenuItem("New Tab", ContextMenuAction.OpenImageInNewTab, imageUrl));
212
- imageItems.push(new MenuItem("Open", ContextMenuAction.OpenImage, imageUrl));
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 = ""): Array<MenuItem> {
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
- items.push(
317
- new MenuItem(
318
- "Cut",
319
- ContextMenuAction.CutTextSelection,
320
- selectedPayload,
321
- 0,
322
- formatPrimaryShortcutLabel("x"),
323
- !hasSelection,
324
- handle,
325
- 0,
326
- 0,
327
- true,
328
- ).withSelectionRange(selectionStartByte, selectionEndByte),
329
- );
330
- items.push(
331
- new MenuItem(
332
- "Copy",
333
- ContextMenuAction.CopyCurrentSelection,
334
- selectedPayload,
335
- 0,
336
- formatPrimaryShortcutLabel("c"),
337
- !hasSelection,
338
- handle,
339
- 0,
340
- 0,
341
- true,
342
- ),
343
- );
344
- items.push(
345
- new MenuItem(
346
- "Paste",
347
- ContextMenuAction.PasteText,
348
- null,
349
- 0,
350
- formatPrimaryShortcutLabel("v"),
351
- false,
352
- handle,
353
- 0,
354
- 0,
355
- true,
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(
@@ -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
- return;
2214
+ } else {
2215
+ const binding = this.pointerClickBinding;
2216
+ if (binding !== null) {
2217
+ binding.invoke(clickEvent);
2218
+ }
2170
2219
  }
2171
- const binding = this.pointerClickBinding;
2172
- if (binding !== null) {
2173
- binding.invoke(clickEvent);
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 && (this.pointerClickCallback !== null || this.pointerClickBinding !== null);
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.pointerClickCallback !== null || this.pointerClickBinding !== null;
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 && (this.pointerClickCallback !== null || this.pointerClickBinding !== null);
2345
+ this.clickPending = isPrimaryButton && this.hasPointerClickHandler();
2270
2346
  this.clickPendingCount = event.clickCount;
2271
2347
  }
2272
2348
  return;
@@ -1,13 +1,58 @@
1
- import { fui_get_platform_family, fui_is_coarse_pointer, KeyModifier } from "./ffi";
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 enum PlatformFamily {
4
- Unknown = 0,
5
- Apple = 1,
6
- Windows = 2,
7
- Linux = 3,
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
- const detectedPlatformFamily = <PlatformFamily>fui_get_platform_family();
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
@@ -1,5 +1,7 @@
1
1
  export {
2
2
  fui_get_accent_color,
3
+ fui_get_host_capabilities,
4
+ fui_get_host_environment,
3
5
  fui_get_platform_family,
4
6
  fui_is_coarse_pointer,
5
7
  fui_is_dark_mode,
@@ -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,
@@ -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;