@effindomv2/fui-as 0.1.51 → 0.2.0
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 +25 -8
- package/scripts/framework-host-services.ts +12 -0
- package/src/Fui.ts +12 -1
- package/src/controls/ContextMenu.ts +29 -0
- 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 +4 -1
- 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/browser/loading-overlay-body.html +0 -13
- package/browser/loading-overlay-styles.html +0 -52
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# @effindomv2/fui-as
|
|
2
|
+
|
|
3
|
+
EffinDOM's retained-mode UI SDK for AssemblyScript and the browser.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
Create a new application with the official scaffold:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @effindomv2/create-fui-as-app my-app
|
|
11
|
+
cd my-app
|
|
12
|
+
npm install
|
|
13
|
+
npm run dev
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The scaffold includes host-service generation, a routed application option, the EffinDOM browser harness, and a production build.
|
|
17
|
+
|
|
18
|
+
## Install in an existing application
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @effindomv2/fui-as
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Build an application WASM module with AssemblyScript, then mount it with the generated FUI-AS browser harness. See the package documentation and scaffolded application for the complete setup.
|
|
25
|
+
|
|
26
|
+
`@effindomv2/fui-as` uses an exact, tested `@effindomv2/runtime` dependency. The runtime first attempts to reuse immutable assets from `https://runtimes.effindom.dev`, then falls back to the runtime assets published with the application.
|
|
27
|
+
|
|
28
|
+
## Links
|
|
29
|
+
|
|
30
|
+
- [FUI-AS repository](https://github.com/zion-sati/fui-as)
|
|
31
|
+
- [EffinDOM runtime](https://www.npmjs.com/package/@effindomv2/runtime)
|
|
32
|
+
- [Create FUI-AS App](https://www.npmjs.com/package/@effindomv2/create-fui-as-app)
|
|
33
|
+
|
|
34
|
+
## License
|
|
35
|
+
|
|
36
|
+
AGPL-3.0-only or a commercial EffinDOM license. See `LICENSE.md` and `COMMERCIAL.md`.
|
package/browser/favicon.ico
CHANGED
|
Binary file
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
startHarness as startRuntimeHarness,
|
|
3
|
+
startManagedHarness as startRuntimeManagedHarness,
|
|
4
|
+
type HarnessExports,
|
|
5
|
+
type HarnessOptions,
|
|
6
|
+
type ManagedHarnessOptions,
|
|
7
|
+
type WasmAppInstantiator,
|
|
8
|
+
} from '@effindomv2/runtime/managed-harness';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Instantiates an AssemblyScript application with its language-level abort
|
|
12
|
+
* import. This belongs to FUI-AS rather than the framework-agnostic runtime.
|
|
13
|
+
*/
|
|
14
|
+
export const instantiateAssemblyScriptApp: WasmAppInstantiator = async (module, imports) => {
|
|
15
|
+
const environment = imports.env;
|
|
16
|
+
return WebAssembly.instantiate(module, {
|
|
17
|
+
...imports,
|
|
18
|
+
env: {
|
|
19
|
+
...environment,
|
|
20
|
+
abort(_message?: number, _fileName?: number, line?: number, column?: number): never {
|
|
21
|
+
throw new Error(`AssemblyScript application aborted at ${String(line ?? 0)}:${String(column ?? 0)}.`);
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export function startHarness<Exports extends HarnessExports>(options: HarnessOptions<Exports>): void {
|
|
28
|
+
startRuntimeHarness({
|
|
29
|
+
...options,
|
|
30
|
+
instantiateApp: instantiateAssemblyScriptApp,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function startManagedHarness(options: ManagedHarnessOptions): void {
|
|
35
|
+
startRuntimeManagedHarness({
|
|
36
|
+
...options,
|
|
37
|
+
instantiateApp: instantiateAssemblyScriptApp,
|
|
38
|
+
});
|
|
39
|
+
}
|
package/browser/src/index.ts
CHANGED
|
@@ -1 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
import {
|
|
2
|
+
startRoutedHarness as startRuntimeRoutedHarness,
|
|
3
|
+
} from '@effindomv2/runtime/routed-harness';
|
|
4
|
+
import type { HarnessExports } from '@effindomv2/runtime/managed-harness';
|
|
5
|
+
import type {
|
|
6
|
+
RoutedHarnessConfig,
|
|
7
|
+
RoutedHarnessRoute,
|
|
8
|
+
} from '@effindomv2/runtime/routed-harness';
|
|
9
|
+
import { instantiateAssemblyScriptApp } from './assemblyscript-harness';
|
|
10
|
+
|
|
11
|
+
export type {
|
|
12
|
+
RoutedHarnessConfig,
|
|
13
|
+
RoutedHarnessManagerState,
|
|
14
|
+
RoutedHarnessRoute,
|
|
15
|
+
} from '@effindomv2/runtime/routed-harness';
|
|
16
|
+
|
|
17
|
+
export function startRoutedHarness<
|
|
18
|
+
TExports extends HarnessExports,
|
|
19
|
+
TRoute extends RoutedHarnessRoute = RoutedHarnessRoute,
|
|
20
|
+
>(config: RoutedHarnessConfig<TExports, TRoute>): void {
|
|
21
|
+
startRuntimeRoutedHarness({
|
|
22
|
+
...config,
|
|
23
|
+
instantiateApp: instantiateAssemblyScriptApp,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// Preserve the complete runtime browser API for FUI-AS consumers. The two
|
|
2
|
+
// application mount entry points are explicitly replaced below so
|
|
3
|
+
// AssemblyScript apps always receive their language-level imports.
|
|
1
4
|
export * from '@effindomv2/runtime/managed-harness';
|
|
2
5
|
export * from '@effindomv2/runtime/routed-app-conventions';
|
|
3
|
-
export
|
|
6
|
+
export { startHarness, startManagedHarness } from './assemblyscript-harness';
|
package/package.json
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effindomv2/fui-as",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "AGPL-3.0-only OR LicenseRef-EffinDom-Commercial",
|
|
6
6
|
"description": "EffinDom v2 AssemblyScript frontend framework SDK and browser harness",
|
|
7
|
+
"repository": "https://github.com/zion-sati/fui-as",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"effindom",
|
|
10
|
+
"assemblyscript",
|
|
11
|
+
"webassembly",
|
|
12
|
+
"wasm",
|
|
13
|
+
"retained-mode",
|
|
14
|
+
"ui-framework",
|
|
15
|
+
"canvas-ui",
|
|
16
|
+
"skia",
|
|
17
|
+
"accessibility"
|
|
18
|
+
],
|
|
7
19
|
"type": "module",
|
|
8
20
|
"publishConfig": {
|
|
9
21
|
"access": "public"
|
|
@@ -70,8 +82,6 @@
|
|
|
70
82
|
"src",
|
|
71
83
|
"browser/routed-app-conventions.ts",
|
|
72
84
|
"browser/favicon.ico",
|
|
73
|
-
"browser/loading-overlay-styles.html",
|
|
74
|
-
"browser/loading-overlay-body.html",
|
|
75
85
|
"browser/src",
|
|
76
86
|
"scripts",
|
|
77
87
|
"LICENSE.md",
|
|
@@ -95,8 +105,7 @@
|
|
|
95
105
|
"typecheck": "npm run guard:runtime-dependency && npm run check:abi && tsc -p tsconfig.json --noEmit"
|
|
96
106
|
},
|
|
97
107
|
"dependencies": {
|
|
98
|
-
"@
|
|
99
|
-
"@effindomv2/runtime": "0.1.26"
|
|
108
|
+
"@effindomv2/runtime": "0.2.2"
|
|
100
109
|
},
|
|
101
110
|
"devDependencies": {
|
|
102
111
|
"@as-pect/assembly": "9.0.0",
|
package/scripts/build.sh
CHANGED
|
@@ -20,8 +20,8 @@ HOST_EVENT_GENERATOR_BUILD="${PACKAGE_DIR}/build/generate-host-events.mjs"
|
|
|
20
20
|
ABI_GENERATOR_BUILD="${PACKAGE_DIR}/build/generate-abi.mjs"
|
|
21
21
|
RUNTIME_CONFIG_FILE="effindom-runtime-config.js"
|
|
22
22
|
DEFAULT_MANIFEST_PATH="./runtime/dist/effindom.v2.manifest.json"
|
|
23
|
-
LOADING_OVERLAY_STYLES_FILE="${
|
|
24
|
-
LOADING_OVERLAY_BODY_FILE="${
|
|
23
|
+
LOADING_OVERLAY_STYLES_FILE="${REPO_ROOT}/v2/browser-bridge/src/managed-harness/loading-overlay-styles.css"
|
|
24
|
+
LOADING_OVERLAY_BODY_FILE="${REPO_ROOT}/v2/browser-bridge/src/managed-harness/loading-overlay-body.html"
|
|
25
25
|
RUNTIME_PACKAGE_DIR="${PACKAGE_DIR}/node_modules/@effindomv2/runtime"
|
|
26
26
|
FILE_PROCESSING_WORKER_SOURCE="${REPO_ROOT}/v2/browser-bridge/src/managed-harness/file-processing-worker.ts"
|
|
27
27
|
WORKER_BOOTSTRAP_SOURCE="${REPO_ROOT}/v2/browser-bridge/src/managed-harness/worker-bootstrap.ts"
|
|
@@ -29,6 +29,12 @@ WORKER_BOOTSTRAP_SOURCE="${REPO_ROOT}/v2/browser-bridge/src/managed-harness/work
|
|
|
29
29
|
if [ ! -d "${RUNTIME_PACKAGE_DIR}" ]; then
|
|
30
30
|
RUNTIME_PACKAGE_DIR="${REPO_ROOT}/node_modules/@effindomv2/runtime"
|
|
31
31
|
fi
|
|
32
|
+
if [ ! -f "${LOADING_OVERLAY_STYLES_FILE}" ]; then
|
|
33
|
+
LOADING_OVERLAY_STYLES_FILE="${RUNTIME_PACKAGE_DIR}/src/managed-harness/loading-overlay-styles.css"
|
|
34
|
+
fi
|
|
35
|
+
if [ ! -f "${LOADING_OVERLAY_BODY_FILE}" ]; then
|
|
36
|
+
LOADING_OVERLAY_BODY_FILE="${RUNTIME_PACKAGE_DIR}/src/managed-harness/loading-overlay-body.html"
|
|
37
|
+
fi
|
|
32
38
|
if [ ! -f "${FILE_PROCESSING_WORKER_SOURCE}" ]; then
|
|
33
39
|
FILE_PROCESSING_WORKER_SOURCE="${RUNTIME_PACKAGE_DIR}/src/managed-harness/file-processing-worker.ts"
|
|
34
40
|
fi
|
|
@@ -192,9 +198,9 @@ resolve_runtime_dist_dir() {
|
|
|
192
198
|
)
|
|
193
199
|
|
|
194
200
|
if [ -f "${public_manifest}" ] && [ -f "${package_manifest}" ] && ! cmp -s "${public_manifest}" "${package_manifest}"; then
|
|
195
|
-
echo "Note: using ${public_runtime_dir} for
|
|
201
|
+
echo "Note: using ${public_runtime_dir} for local runtime assets." >&2
|
|
196
202
|
echo " ${package_runtime_dir} is a staged package copy and may be stale." >&2
|
|
197
|
-
echo "
|
|
203
|
+
echo " Rebuild the local runtime assets before changing the ABI." >&2
|
|
198
204
|
fi
|
|
199
205
|
|
|
200
206
|
for candidate in "${candidates[@]}"; do
|
|
@@ -211,7 +217,7 @@ resolve_runtime_dist_dir() {
|
|
|
211
217
|
echo " - ${REPO_ROOT}/node_modules/@effindomv2/runtime/dist" >&2
|
|
212
218
|
echo " - ${REPO_ROOT}/public/v2/browser-bridge" >&2
|
|
213
219
|
echo " - ${REPO_ROOT}/v2/browser-bridge/dist" >&2
|
|
214
|
-
echo "
|
|
220
|
+
echo "Build the local runtime assets or install @effindomv2/runtime first." >&2
|
|
215
221
|
echo "Install @effindomv2/runtime or build runtime assets first." >&2
|
|
216
222
|
exit 1
|
|
217
223
|
}
|
|
@@ -269,15 +275,26 @@ copy_runtime_assets() {
|
|
|
269
275
|
rm -rf "${destination}/runtime"
|
|
270
276
|
mkdir -p "${destination}/runtime/dist"
|
|
271
277
|
cp "${RUNTIME_DIST_DIR}/effindom.v2.manifest.json" "${destination}/runtime/dist/effindom.v2.manifest.json"
|
|
278
|
+
MANIFEST_PATH="${destination}/runtime/dist/effindom.v2.manifest.json" node <<'NODE'
|
|
279
|
+
const fs = require('node:fs');
|
|
280
|
+
const manifestPath = process.env.MANIFEST_PATH;
|
|
281
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
282
|
+
for (const font of Object.values(manifest.assets?.fonts ?? {})) {
|
|
283
|
+
if (typeof font.url === 'string' && font.url.startsWith('../fonts/')) {
|
|
284
|
+
font.url = `./fonts/${font.url.slice('../fonts/'.length)}`;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
288
|
+
NODE
|
|
272
289
|
if [ -f "${RUNTIME_DIST_DIR}/icu-asset.json" ]; then
|
|
273
290
|
cp "${RUNTIME_DIST_DIR}/icu-asset.json" "${destination}/runtime/dist/icu-asset.json"
|
|
274
291
|
fi
|
|
275
292
|
cp -R "${RUNTIME_DIST_DIR}/runtime" "${destination}/runtime/dist/runtime"
|
|
276
|
-
mkdir -p "${destination}/runtime/fonts"
|
|
293
|
+
mkdir -p "${destination}/runtime/dist/fonts"
|
|
277
294
|
if [ -d "${REPO_ROOT}/public/v2/fonts" ]; then
|
|
278
|
-
cp -R "${REPO_ROOT}/public/v2/fonts/." "${destination}/runtime/fonts/"
|
|
295
|
+
cp -R "${REPO_ROOT}/public/v2/fonts/." "${destination}/runtime/dist/fonts/"
|
|
279
296
|
elif [ -d "${RUNTIME_DIST_DIR}/fonts" ]; then
|
|
280
|
-
cp -R "${RUNTIME_DIST_DIR}/fonts/." "${destination}/runtime/fonts/"
|
|
297
|
+
cp -R "${RUNTIME_DIST_DIR}/fonts/." "${destination}/runtime/dist/fonts/"
|
|
281
298
|
fi
|
|
282
299
|
}
|
|
283
300
|
|
|
@@ -30,6 +30,18 @@ export const frameworkHostServices = defineHostServices({
|
|
|
30
30
|
returns: "u32",
|
|
31
31
|
implementation: () => unsupported(),
|
|
32
32
|
}),
|
|
33
|
+
hostEnvironment: hostService({
|
|
34
|
+
importName: "fui_get_host_environment",
|
|
35
|
+
args: [] as const,
|
|
36
|
+
returns: "u32",
|
|
37
|
+
implementation: () => unsupported(),
|
|
38
|
+
}),
|
|
39
|
+
hostCapabilities: hostService({
|
|
40
|
+
importName: "fui_get_host_capabilities",
|
|
41
|
+
args: [] as const,
|
|
42
|
+
returns: "u32",
|
|
43
|
+
implementation: () => unsupported(),
|
|
44
|
+
}),
|
|
33
45
|
isCoarsePointer: hostService({
|
|
34
46
|
importName: "fui_is_coarse_pointer",
|
|
35
47
|
args: [] as const,
|
package/src/Fui.ts
CHANGED
|
@@ -173,7 +173,18 @@ export { Worker, WorkerCompletedEventArgs, WorkerErrorEventArgs, WorkerProgressE
|
|
|
173
173
|
export { Worker as WorkerRuntime } from "./worker/Worker";
|
|
174
174
|
export { WorkerJob } from "./worker/WorkerJob";
|
|
175
175
|
export { currentRoute, navigateTo } from "./core/Navigation";
|
|
176
|
-
export {
|
|
176
|
+
export {
|
|
177
|
+
getHostContext,
|
|
178
|
+
getHostEnvironment,
|
|
179
|
+
getPlatformFamily,
|
|
180
|
+
hasHostCapability,
|
|
181
|
+
hasPrimaryShortcutModifier,
|
|
182
|
+
HostCapability,
|
|
183
|
+
HostContext,
|
|
184
|
+
HostEnvironment,
|
|
185
|
+
PlatformFamily,
|
|
186
|
+
resolvePrimaryShortcutModifier,
|
|
187
|
+
} from "./core/Platform";
|
|
177
188
|
export {
|
|
178
189
|
Colors,
|
|
179
190
|
ContextMenuItemTheme,
|
|
@@ -22,6 +22,7 @@ import { warn } from "../core/Logger";
|
|
|
22
22
|
import { FontFamily, FontStyle, FontWeight } from "../core/Typography";
|
|
23
23
|
import { Border, FlexBox, Grid, Portal, TextCore } from "../nodes";
|
|
24
24
|
import { PopupPresenter } from "./internal/PopupPresenter";
|
|
25
|
+
import { getHostContext, HostCapability } from "../core/Platform";
|
|
25
26
|
|
|
26
27
|
const MENU_WIDTH: f32 = 220.0;
|
|
27
28
|
const MENU_SEPARATOR_HEIGHT: f32 = 9.0;
|
|
@@ -304,6 +305,17 @@ function commitFocusedTextActionIfNeeded(item: MenuItem): void {
|
|
|
304
305
|
}
|
|
305
306
|
|
|
306
307
|
export function runContextMenuAction(item: MenuItem): void {
|
|
308
|
+
const host = getHostContext();
|
|
309
|
+
if (
|
|
310
|
+
((item.action == ContextMenuAction.NavigateBack || item.action == ContextMenuAction.NavigateForward) && !host.supports(HostCapability.BrowserHistory)) ||
|
|
311
|
+
(item.action == ContextMenuAction.ReloadPage && !host.supports(HostCapability.Reload)) ||
|
|
312
|
+
((item.action == ContextMenuAction.OpenLinkInNewTab || item.action == ContextMenuAction.OpenImageInNewTab) && !host.supports(HostCapability.NewBrowsingContext)) ||
|
|
313
|
+
((item.action == ContextMenuAction.OpenLink || item.action == ContextMenuAction.OpenImage) && !host.supports(HostCapability.OpenExternalUri)) ||
|
|
314
|
+
((item.action == ContextMenuAction.CopyCurrentSelection || item.action == ContextMenuAction.CutTextSelection) && !host.supports(HostCapability.ClipboardWrite)) ||
|
|
315
|
+
(item.action == ContextMenuAction.PasteText && !host.supports(HostCapability.ClipboardRead))
|
|
316
|
+
) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
307
319
|
if (item.disabled) {
|
|
308
320
|
const actionNeedsLiveSelection =
|
|
309
321
|
item.targetHandle != <u64>HandleValue.Invalid &&
|
|
@@ -512,6 +524,23 @@ export class ContextMenu extends Portal implements GlobalKeyHandler {
|
|
|
512
524
|
}
|
|
513
525
|
}
|
|
514
526
|
|
|
527
|
+
static dismissForOutsidePointerDown(sceneX: f32, sceneY: f32): bool {
|
|
528
|
+
const menu = ContextMenu.activeInstance;
|
|
529
|
+
if (menu === null || !menu.isMenuVisible) {
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
const bounds = menu.panel.getBounds();
|
|
533
|
+
const left = unchecked(bounds[0]);
|
|
534
|
+
const top = unchecked(bounds[1]);
|
|
535
|
+
const right = left + unchecked(bounds[2]);
|
|
536
|
+
const bottom = top + unchecked(bounds[3]);
|
|
537
|
+
if (sceneX >= left && sceneX <= right && sceneY >= top && sceneY <= bottom) {
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
menu.hide();
|
|
541
|
+
return true;
|
|
542
|
+
}
|
|
543
|
+
|
|
515
544
|
static invokeActiveSlot(slot: i32): void {
|
|
516
545
|
const menu = ContextMenu.activeInstance;
|
|
517
546
|
if (menu !== null) {
|
package/src/core/Application.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { SelectionHandleAdornerManager } from "./SelectionHandleAdornerManager";
|
|
|
10
10
|
import { ToolTipManager } from "./ToolTipManager";
|
|
11
11
|
import * as ui from "../bindings/ui";
|
|
12
12
|
import { flushCommit, fireLoadedCallbacks, markNeedsCommit, resetCommitState } from "./FrameScheduler";
|
|
13
|
-
import { HandleValue, Unit } from "./ffi";
|
|
13
|
+
import { fui_set_application_caption, HandleValue, Unit } from "./ffi";
|
|
14
14
|
import { Node } from "./Node";
|
|
15
15
|
import { cancelAllTimers } from "./Timers";
|
|
16
16
|
import {
|
|
@@ -247,6 +247,11 @@ export class Application<TPage> {
|
|
|
247
247
|
return root;
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
+
static caption(value: string): void {
|
|
251
|
+
const bytes = Uint8Array.wrap(String.UTF8.encode(value, false));
|
|
252
|
+
fui_set_application_caption(bytes.length > 0 ? bytes.dataStart : 0, <u32>bytes.length);
|
|
253
|
+
}
|
|
254
|
+
|
|
250
255
|
static unmount(): void {
|
|
251
256
|
const previous = mountedShell;
|
|
252
257
|
if (previous === null) {
|
|
@@ -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
|
|
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,
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
<div
|
|
2
|
-
class="effindom-loading-overlay"
|
|
3
|
-
id="effindom-loading-overlay"
|
|
4
|
-
data-state="loading"
|
|
5
|
-
aria-live="polite"
|
|
6
|
-
aria-hidden="true"
|
|
7
|
-
hidden>
|
|
8
|
-
<div class="effindom-loading-card">
|
|
9
|
-
<p class="effindom-loading-kicker">EffinDom backstage</p>
|
|
10
|
-
<h2 class="effindom-loading-title" id="effindom-loading-title">Teaching the pixels their lines...</h2>
|
|
11
|
-
<p class="effindom-loading-detail" id="effindom-loading-detail">The runtime orchestra is tuning up behind the canvas.</p>
|
|
12
|
-
</div>
|
|
13
|
-
</div>
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
.effindom-loading-overlay {
|
|
2
|
-
position: absolute;
|
|
3
|
-
inset: 0;
|
|
4
|
-
display: grid;
|
|
5
|
-
place-items: center;
|
|
6
|
-
padding: 24px;
|
|
7
|
-
box-sizing: border-box;
|
|
8
|
-
background: linear-gradient(180deg, rgba(2, 6, 23, 0.72), rgba(10, 20, 34, 0.82));
|
|
9
|
-
backdrop-filter: blur(10px);
|
|
10
|
-
z-index: 2;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
.effindom-loading-overlay[hidden] {
|
|
14
|
-
display: none;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
.effindom-loading-card {
|
|
18
|
-
max-width: 420px;
|
|
19
|
-
padding: 22px 24px;
|
|
20
|
-
border: 1px solid rgba(148, 163, 184, 0.32);
|
|
21
|
-
border-radius: 18px;
|
|
22
|
-
background: rgba(6, 12, 21, 0.80);
|
|
23
|
-
text-align: center;
|
|
24
|
-
box-shadow: 0 18px 48px rgba(2, 6, 23, 0.30);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
.effindom-loading-overlay[data-state="error"] .effindom-loading-card {
|
|
28
|
-
border-color: rgba(248, 113, 113, 0.50);
|
|
29
|
-
background: rgba(69, 10, 10, 0.78);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.effindom-loading-kicker {
|
|
33
|
-
margin: 0 0 10px;
|
|
34
|
-
font-size: 11px;
|
|
35
|
-
font-weight: 700;
|
|
36
|
-
letter-spacing: 0.12em;
|
|
37
|
-
text-transform: uppercase;
|
|
38
|
-
color: #7dd3fc;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
.effindom-loading-title {
|
|
42
|
-
margin: 0;
|
|
43
|
-
font-size: 28px;
|
|
44
|
-
line-height: 1.15;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
.effindom-loading-detail {
|
|
48
|
-
margin: 12px 0 0;
|
|
49
|
-
font-size: 14px;
|
|
50
|
-
line-height: 1.55;
|
|
51
|
-
color: #cbd5e1;
|
|
52
|
-
}
|