@effindomv2/fui-as 0.1.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/LICENSE.md +7 -0
- package/browser/src/common-harness/host-imports.ts +430 -0
- package/browser/src/common-harness/interop.ts +39 -0
- package/browser/src/common-harness/managed-harness-bitmap-host.ts +92 -0
- package/browser/src/common-harness/managed-harness-fetch-host.ts +201 -0
- package/browser/src/common-harness/managed-harness-file-host.ts +1101 -0
- package/browser/src/common-harness/managed-harness-file-payloads.ts +143 -0
- package/browser/src/common-harness/managed-harness-file-types.ts +106 -0
- package/browser/src/common-harness/managed-harness-session.ts +15 -0
- package/browser/src/common-harness/managed-harness.ts +1323 -0
- package/browser/src/common-harness/managed-history.ts +168 -0
- package/browser/src/common-harness/persisted-restore-policy.ts +50 -0
- package/browser/src/common-harness/persisted-ui-state-controller.ts +309 -0
- package/browser/src/common-harness/text-session-bridge.ts +452 -0
- package/browser/src/common-harness/types.ts +205 -0
- package/browser/src/common-harness/ui-chrome.ts +191 -0
- package/browser/src/common-harness/ui-imports.ts +529 -0
- package/browser/src/common-harness/wasm-module-cache.ts +47 -0
- package/browser/src/common-harness.ts +27 -0
- package/browser/src/file-processing-worker.ts +89 -0
- package/browser/src/host-events.ts +97 -0
- package/browser/src/host-services.ts +203 -0
- package/browser/src/index.ts +62 -0
- package/browser/src/persisted-ui-state.ts +206 -0
- package/browser/src/routed-harness.ts +198 -0
- package/browser/src/worker-bootstrap.ts +483 -0
- package/browser/src/worker-manager.ts +230 -0
- package/browser/src/worker-types.ts +50 -0
- package/package.json +89 -0
- package/scripts/build-demo-as.sh +91 -0
- package/scripts/build.sh +325 -0
- package/scripts/generate-host-events.ts +175 -0
- package/scripts/generate-host-services.ts +157 -0
- package/src/Fui.ts +205 -0
- package/src/FuiExports.ts +55 -0
- package/src/FuiPrimitives.ts +15 -0
- package/src/FuiWorker.ts +3 -0
- package/src/FuiWorkerExports.ts +6 -0
- package/src/bindings/ui.ts +531 -0
- package/src/color.ts +86 -0
- package/src/controls/AntiSelectionArea.ts +23 -0
- package/src/controls/Button.ts +750 -0
- package/src/controls/Checkbox.ts +181 -0
- package/src/controls/ContextMenu.ts +885 -0
- package/src/controls/ControlTemplateSet.ts +37 -0
- package/src/controls/Dialog.ts +355 -0
- package/src/controls/Dropdown.ts +856 -0
- package/src/controls/Form.ts +110 -0
- package/src/controls/NavLink.ts +211 -0
- package/src/controls/Popup.ts +129 -0
- package/src/controls/ProgressBar.ts +180 -0
- package/src/controls/RadioButton.ts +135 -0
- package/src/controls/RadioGroup.ts +244 -0
- package/src/controls/SelectionArea.ts +75 -0
- package/src/controls/Slider.ts +471 -0
- package/src/controls/Switch.ts +132 -0
- package/src/controls/TextArea.ts +20 -0
- package/src/controls/TextInput.ts +7 -0
- package/src/controls/index.ts +18 -0
- package/src/controls/internal/ButtonPresenter.ts +95 -0
- package/src/controls/internal/CheckboxIndicatorPresenter.ts +93 -0
- package/src/controls/internal/DropdownChevronPresenter.ts +67 -0
- package/src/controls/internal/DropdownFieldPresenter.ts +110 -0
- package/src/controls/internal/DropdownOptionRowPresenter.ts +82 -0
- package/src/controls/internal/PopupPresenter.ts +198 -0
- package/src/controls/internal/PressableIndicatorPresenter.ts +32 -0
- package/src/controls/internal/PressableLabeledControl.ts +221 -0
- package/src/controls/internal/RadioIndicatorPresenter.ts +73 -0
- package/src/controls/internal/SliderPresenter.ts +157 -0
- package/src/controls/internal/SwitchIndicatorPresenter.ts +72 -0
- package/src/controls/internal/TextInputCore.ts +695 -0
- package/src/controls/internal/TextInputPresenter.ts +72 -0
- package/src/controls/templating.ts +54 -0
- package/src/core/Action.ts +94 -0
- package/src/core/Actions.ts +37 -0
- package/src/core/Animation.ts +412 -0
- package/src/core/Application.ts +328 -0
- package/src/core/Assets.ts +264 -0
- package/src/core/AttachedProperties.ts +32 -0
- package/src/core/Bitmap.ts +70 -0
- package/src/core/BoundCallback.ts +104 -0
- package/src/core/Callbacks.ts +17 -0
- package/src/core/ContextMenuManager.ts +466 -0
- package/src/core/DebugApi.ts +30 -0
- package/src/core/Disposable.ts +10 -0
- package/src/core/DragDropManager.ts +179 -0
- package/src/core/DragGesture.ts +184 -0
- package/src/core/DynamicAssetIds.ts +24 -0
- package/src/core/Errors.ts +48 -0
- package/src/core/EventRouter.ts +408 -0
- package/src/core/ExternalDropManager.ts +122 -0
- package/src/core/Fetch.ts +264 -0
- package/src/core/FetchFfi.ts +15 -0
- package/src/core/File.ts +1002 -0
- package/src/core/FocusAdornerManager.ts +263 -0
- package/src/core/FocusVisibility.ts +36 -0
- package/src/core/FrameScheduler.ts +28 -0
- package/src/core/KeyboardScroll.ts +161 -0
- package/src/core/KeyboardScrollTracker.ts +386 -0
- package/src/core/Logger.ts +80 -0
- package/src/core/Navigation.ts +13 -0
- package/src/core/Node.ts +1708 -0
- package/src/core/PersistedState.ts +102 -0
- package/src/core/PersistedUiState.ts +142 -0
- package/src/core/Platform.ts +219 -0
- package/src/core/Signal.ts +89 -0
- package/src/core/Theme.ts +365 -0
- package/src/core/Timers.ts +129 -0
- package/src/core/ToolTip.ts +122 -0
- package/src/core/ToolTipManager.ts +459 -0
- package/src/core/Transitions.ts +34 -0
- package/src/core/Typography.ts +204 -0
- package/src/core/Worker.ts +196 -0
- package/src/core/bind.ts +37 -0
- package/src/core/event_exports.ts +596 -0
- package/src/core/ffi.ts +728 -0
- package/src/host-services/runtime.ts +25 -0
- package/src/nodes/FlexBox.ts +789 -0
- package/src/nodes/GradientStop.ts +9 -0
- package/src/nodes/Grid.ts +183 -0
- package/src/nodes/Image.ts +189 -0
- package/src/nodes/Portal.ts +14 -0
- package/src/nodes/RichText.ts +312 -0
- package/src/nodes/ScrollBar.ts +570 -0
- package/src/nodes/ScrollBox.ts +415 -0
- package/src/nodes/ScrollState.ts +10 -0
- package/src/nodes/ScrollView.ts +511 -0
- package/src/nodes/Svg.ts +142 -0
- package/src/nodes/Text.ts +145 -0
- package/src/nodes/TextCore.ts +558 -0
- package/src/nodes/VirtualList.ts +431 -0
- package/src/nodes/helpers.ts +25 -0
- package/src/nodes/index.ts +14 -0
- package/src/tsconfig.json +7 -0
- package/src/worker/Worker.ts +169 -0
- package/src/worker/WorkerJob.ts +65 -0
- package/src/worker/ffi.ts +23 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Node } from "./Node";
|
|
2
|
+
|
|
3
|
+
export abstract class PersistedStateAdapter {
|
|
4
|
+
readonly kind: string;
|
|
5
|
+
readonly version: u32;
|
|
6
|
+
|
|
7
|
+
constructor(kind: string, version: u32 = 1) {
|
|
8
|
+
if (kind.length == 0) {
|
|
9
|
+
throw new Error("PersistedStateAdapter requires a non-empty kind.");
|
|
10
|
+
}
|
|
11
|
+
this.kind = kind;
|
|
12
|
+
this.version = version;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
abstract capture(node: Node): string | null;
|
|
16
|
+
abstract restore(node: Node, payload: string, version: u32): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export abstract class PersistedNodeState<TNode extends Node> extends PersistedStateAdapter {
|
|
20
|
+
capture(node: Node): string | null {
|
|
21
|
+
return this.captureSerialized(changetype<TNode>(node));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
restore(node: Node, payload: string, version: u32): void {
|
|
25
|
+
this.restoreSerialized(changetype<TNode>(node), payload, version);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
protected abstract captureSerialized(node: TNode): string | null;
|
|
29
|
+
protected abstract restoreSerialized(node: TNode, payload: string, version: u32): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export abstract class PersistedStateCodec<TValue> {
|
|
33
|
+
abstract encode(value: TValue): string;
|
|
34
|
+
abstract decode(payload: string, version: u32): TValue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export abstract class PersistedValueState<TNode extends Node, TValue> extends PersistedNodeState<TNode> {
|
|
38
|
+
protected readonly codec: PersistedStateCodec<TValue>;
|
|
39
|
+
|
|
40
|
+
constructor(kind: string, codec: PersistedStateCodec<TValue>, version: u32 = 1) {
|
|
41
|
+
super(kind, version);
|
|
42
|
+
this.codec = codec;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
protected shouldCaptureValue(_node: TNode): bool {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
protected captureSerialized(node: TNode): string | null {
|
|
50
|
+
if (!this.shouldCaptureValue(node)) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return this.codec.encode(this.captureValue(node));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
protected restoreSerialized(node: TNode, payload: string, version: u32): void {
|
|
57
|
+
this.restoreValue(node, this.codec.decode(payload, version));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
protected abstract captureValue(node: TNode): TValue;
|
|
61
|
+
protected abstract restoreValue(node: TNode, value: TValue): void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export class PersistedStringCodec extends PersistedStateCodec<string> {
|
|
65
|
+
encode(value: string): string {
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
decode(payload: string, _version: u32): string {
|
|
70
|
+
return payload;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export class PersistedBoolCodec extends PersistedStateCodec<bool> {
|
|
75
|
+
encode(value: bool): string {
|
|
76
|
+
return value.toString();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
decode(payload: string, _version: u32): bool {
|
|
80
|
+
return bool.parse(payload);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export class PersistedInt32Codec extends PersistedStateCodec<i32> {
|
|
85
|
+
encode(value: i32): string {
|
|
86
|
+
return value.toString();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
decode(payload: string, _version: u32): i32 {
|
|
90
|
+
return i32.parse(payload);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export class PersistedFloat32Codec extends PersistedStateCodec<f32> {
|
|
95
|
+
encode(value: f32): string {
|
|
96
|
+
return value.toString();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
decode(payload: string, _version: u32): f32 {
|
|
100
|
+
return f32.parse(payload);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fui_copy_persisted_state,
|
|
3
|
+
fui_set_persisted_scroll_offset,
|
|
4
|
+
fui_set_persisted_state,
|
|
5
|
+
fui_try_get_persisted_scroll_offset,
|
|
6
|
+
} from "./ffi";
|
|
7
|
+
import { __fui_text_buffer, __fui_text_buffer_size } from "./event_exports";
|
|
8
|
+
import { ScrollState } from "../nodes/ScrollState";
|
|
9
|
+
|
|
10
|
+
export class PersistedScrollOffset {
|
|
11
|
+
constructor(
|
|
12
|
+
readonly x: f32,
|
|
13
|
+
readonly y: f32,
|
|
14
|
+
) {}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class PersistedTextState {
|
|
18
|
+
constructor(
|
|
19
|
+
readonly version: u32,
|
|
20
|
+
readonly payload: string,
|
|
21
|
+
) {}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const persistedScrollRestoreBuffer = new Float32Array(2);
|
|
25
|
+
const persistedTextStateVersionBuffer = new Uint32Array(1);
|
|
26
|
+
|
|
27
|
+
function encodeUtf8(text: string): Uint8Array {
|
|
28
|
+
return Uint8Array.wrap(String.UTF8.encode(text, false));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function axisRestoreReady(desiredOffset: f32, contentSize: f32, viewportSize: f32, enabled: bool): bool {
|
|
32
|
+
if (!enabled || desiredOffset == 0.0) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
return viewportSize > 0.0 && contentSize > viewportSize;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function clampAxisOffset(offset: f32, contentSize: f32, viewportSize: f32, enabled: bool): f32 {
|
|
39
|
+
if (!enabled) {
|
|
40
|
+
return 0.0;
|
|
41
|
+
}
|
|
42
|
+
const maxOffset = contentSize > viewportSize ? contentSize - viewportSize : 0.0;
|
|
43
|
+
if (offset < 0.0) {
|
|
44
|
+
return 0.0;
|
|
45
|
+
}
|
|
46
|
+
if (offset > maxOffset) {
|
|
47
|
+
return maxOffset;
|
|
48
|
+
}
|
|
49
|
+
return offset;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function storePersistedScrollOffset(nodeId: string, x: f32, y: f32): void {
|
|
53
|
+
const bytes = encodeUtf8(nodeId);
|
|
54
|
+
fui_set_persisted_scroll_offset(bytes.length > 0 ? bytes.dataStart : 0, <u32>bytes.length, x, y);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function storePersistedTextState(nodeId: string, kind: string, version: u32, payload: string): void {
|
|
58
|
+
const nodeIdBytes = encodeUtf8(nodeId);
|
|
59
|
+
const kindBytes = encodeUtf8(kind);
|
|
60
|
+
const payloadBytes = encodeUtf8(payload);
|
|
61
|
+
fui_set_persisted_state(
|
|
62
|
+
nodeIdBytes.length > 0 ? nodeIdBytes.dataStart : 0,
|
|
63
|
+
<u32>nodeIdBytes.length,
|
|
64
|
+
kindBytes.length > 0 ? kindBytes.dataStart : 0,
|
|
65
|
+
<u32>kindBytes.length,
|
|
66
|
+
version,
|
|
67
|
+
payloadBytes.length > 0 ? payloadBytes.dataStart : 0,
|
|
68
|
+
<u32>payloadBytes.length,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function tryLoadPersistedScrollOffset(nodeId: string): PersistedScrollOffset | null {
|
|
73
|
+
const bytes = encodeUtf8(nodeId);
|
|
74
|
+
const basePtr = persistedScrollRestoreBuffer.dataStart;
|
|
75
|
+
if (!fui_try_get_persisted_scroll_offset(
|
|
76
|
+
bytes.length > 0 ? bytes.dataStart : 0,
|
|
77
|
+
<u32>bytes.length,
|
|
78
|
+
basePtr,
|
|
79
|
+
basePtr + sizeof<f32>(),
|
|
80
|
+
)) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
return new PersistedScrollOffset(
|
|
84
|
+
persistedScrollRestoreBuffer[0],
|
|
85
|
+
persistedScrollRestoreBuffer[1],
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function tryLoadPersistedTextState(nodeId: string, kind: string): PersistedTextState | null {
|
|
90
|
+
const nodeIdBytes = encodeUtf8(nodeId);
|
|
91
|
+
const kindBytes = encodeUtf8(kind);
|
|
92
|
+
const versionPtr = persistedTextStateVersionBuffer.dataStart;
|
|
93
|
+
const payloadPtr = __fui_text_buffer();
|
|
94
|
+
const payloadCapacity = __fui_text_buffer_size();
|
|
95
|
+
const copied = fui_copy_persisted_state(
|
|
96
|
+
nodeIdBytes.length > 0 ? nodeIdBytes.dataStart : 0,
|
|
97
|
+
<u32>nodeIdBytes.length,
|
|
98
|
+
kindBytes.length > 0 ? kindBytes.dataStart : 0,
|
|
99
|
+
<u32>kindBytes.length,
|
|
100
|
+
versionPtr,
|
|
101
|
+
payloadPtr,
|
|
102
|
+
payloadCapacity,
|
|
103
|
+
);
|
|
104
|
+
if (copied < 0) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
if (<u32>copied > payloadCapacity) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
"Persisted state payload for " +
|
|
110
|
+
kind +
|
|
111
|
+
" on " +
|
|
112
|
+
nodeId +
|
|
113
|
+
" exceeded the shared text buffer capacity.",
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return new PersistedTextState(
|
|
117
|
+
persistedTextStateVersionBuffer[0],
|
|
118
|
+
copied == 0 ? "" : String.UTF8.decodeUnsafe(payloadPtr, <usize>copied, false),
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function canRestorePersistedScrollOffset(
|
|
123
|
+
state: ScrollState,
|
|
124
|
+
offset: PersistedScrollOffset,
|
|
125
|
+
enableX: bool,
|
|
126
|
+
enableY: bool,
|
|
127
|
+
): bool {
|
|
128
|
+
return axisRestoreReady(offset.x, state.contentWidth.value, state.viewportWidth.value, enableX) &&
|
|
129
|
+
axisRestoreReady(offset.y, state.contentHeight.value, state.viewportHeight.value, enableY);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function clampPersistedScrollOffset(
|
|
133
|
+
state: ScrollState,
|
|
134
|
+
offset: PersistedScrollOffset,
|
|
135
|
+
enableX: bool,
|
|
136
|
+
enableY: bool,
|
|
137
|
+
): PersistedScrollOffset {
|
|
138
|
+
return new PersistedScrollOffset(
|
|
139
|
+
clampAxisOffset(offset.x, state.contentWidth.value, state.viewportWidth.value, enableX),
|
|
140
|
+
clampAxisOffset(offset.y, state.contentHeight.value, state.viewportHeight.value, enableY),
|
|
141
|
+
);
|
|
142
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { fui_get_platform_family, fui_is_coarse_pointer, KeyModifier } from "./ffi";
|
|
2
|
+
|
|
3
|
+
export enum PlatformFamily {
|
|
4
|
+
Unknown = 0,
|
|
5
|
+
Apple = 1,
|
|
6
|
+
Windows = 2,
|
|
7
|
+
Linux = 3,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const detectedPlatformFamily = <PlatformFamily>fui_get_platform_family();
|
|
11
|
+
|
|
12
|
+
class KeyboardPolicy {
|
|
13
|
+
constructor(
|
|
14
|
+
readonly primaryShortcutModifier: u32,
|
|
15
|
+
readonly wordNavigationModifier: u32,
|
|
16
|
+
readonly lineBoundaryModifier: u32,
|
|
17
|
+
readonly documentBoundaryModifier: u32,
|
|
18
|
+
readonly redoUsesPrimaryY: bool,
|
|
19
|
+
readonly redoUsesShiftedPrimaryZ: bool,
|
|
20
|
+
) {}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const APPLE_KEYBOARD_POLICY = new KeyboardPolicy(
|
|
24
|
+
KeyModifier.Meta,
|
|
25
|
+
KeyModifier.Alt,
|
|
26
|
+
KeyModifier.Meta,
|
|
27
|
+
KeyModifier.Meta,
|
|
28
|
+
false,
|
|
29
|
+
true,
|
|
30
|
+
);
|
|
31
|
+
const DEFAULT_KEYBOARD_POLICY = new KeyboardPolicy(
|
|
32
|
+
KeyModifier.Ctrl,
|
|
33
|
+
KeyModifier.Ctrl,
|
|
34
|
+
0,
|
|
35
|
+
KeyModifier.Ctrl,
|
|
36
|
+
true,
|
|
37
|
+
true,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
function resolveKeyboardPolicy(platformFamily: PlatformFamily = detectedPlatformFamily): KeyboardPolicy {
|
|
41
|
+
if (platformFamily == PlatformFamily.Apple) {
|
|
42
|
+
return APPLE_KEYBOARD_POLICY;
|
|
43
|
+
}
|
|
44
|
+
return DEFAULT_KEYBOARD_POLICY;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function matchesShortcutKey(key: string, expected: string): bool {
|
|
48
|
+
return key.length == expected.length && key.toLowerCase() == expected;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function formatShortcutKeyToken(key: string, platformFamily: PlatformFamily): string {
|
|
52
|
+
if (key == "ArrowLeft") {
|
|
53
|
+
return platformFamily == PlatformFamily.Apple ? "←" : "Left";
|
|
54
|
+
}
|
|
55
|
+
if (key == "ArrowRight") {
|
|
56
|
+
return platformFamily == PlatformFamily.Apple ? "→" : "Right";
|
|
57
|
+
}
|
|
58
|
+
if (key == "ArrowUp") {
|
|
59
|
+
return platformFamily == PlatformFamily.Apple ? "↑" : "Up";
|
|
60
|
+
}
|
|
61
|
+
if (key == "ArrowDown") {
|
|
62
|
+
return platformFamily == PlatformFamily.Apple ? "↓" : "Down";
|
|
63
|
+
}
|
|
64
|
+
if (key == "PageUp") {
|
|
65
|
+
return "PgUp";
|
|
66
|
+
}
|
|
67
|
+
if (key == "PageDown") {
|
|
68
|
+
return "PgDn";
|
|
69
|
+
}
|
|
70
|
+
if (key.length == 1) {
|
|
71
|
+
return key.toUpperCase();
|
|
72
|
+
}
|
|
73
|
+
return key;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function appendShortcutModifierTokens(tokens: Array<string>, modifiers: u32, platformFamily: PlatformFamily): void {
|
|
77
|
+
if (platformFamily == PlatformFamily.Apple) {
|
|
78
|
+
if ((modifiers & KeyModifier.Ctrl) != 0) {
|
|
79
|
+
tokens.push("⌃");
|
|
80
|
+
}
|
|
81
|
+
if ((modifiers & KeyModifier.Alt) != 0) {
|
|
82
|
+
tokens.push("⌥");
|
|
83
|
+
}
|
|
84
|
+
if ((modifiers & KeyModifier.Shift) != 0) {
|
|
85
|
+
tokens.push("⇧");
|
|
86
|
+
}
|
|
87
|
+
if ((modifiers & KeyModifier.Meta) != 0) {
|
|
88
|
+
tokens.push("⌘");
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if ((modifiers & KeyModifier.Ctrl) != 0) {
|
|
94
|
+
tokens.push("Ctrl");
|
|
95
|
+
}
|
|
96
|
+
if ((modifiers & KeyModifier.Alt) != 0) {
|
|
97
|
+
tokens.push("Alt");
|
|
98
|
+
}
|
|
99
|
+
if ((modifiers & KeyModifier.Shift) != 0) {
|
|
100
|
+
tokens.push("Shift");
|
|
101
|
+
}
|
|
102
|
+
if ((modifiers & KeyModifier.Meta) != 0) {
|
|
103
|
+
tokens.push("Meta");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function getPlatformFamily(): PlatformFamily {
|
|
108
|
+
return detectedPlatformFamily;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function isCoarsePointer(): bool {
|
|
112
|
+
return fui_is_coarse_pointer();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function resolvePrimaryShortcutModifier(platformFamily: PlatformFamily): u32 {
|
|
116
|
+
return resolveKeyboardPolicy(platformFamily).primaryShortcutModifier;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function hasPrimaryShortcutModifier(
|
|
120
|
+
modifiers: u32,
|
|
121
|
+
platformFamily: PlatformFamily = detectedPlatformFamily,
|
|
122
|
+
): bool {
|
|
123
|
+
return (modifiers & resolvePrimaryShortcutModifier(platformFamily)) != 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function resolveWordNavigationModifier(platformFamily: PlatformFamily = detectedPlatformFamily): u32 {
|
|
127
|
+
return resolveKeyboardPolicy(platformFamily).wordNavigationModifier;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function hasWordNavigationModifier(
|
|
131
|
+
modifiers: u32,
|
|
132
|
+
platformFamily: PlatformFamily = detectedPlatformFamily,
|
|
133
|
+
): bool {
|
|
134
|
+
return (modifiers & resolveWordNavigationModifier(platformFamily)) != 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function resolveLineBoundaryModifier(platformFamily: PlatformFamily = detectedPlatformFamily): u32 {
|
|
138
|
+
return resolveKeyboardPolicy(platformFamily).lineBoundaryModifier;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function hasLineBoundaryModifier(
|
|
142
|
+
modifiers: u32,
|
|
143
|
+
platformFamily: PlatformFamily = detectedPlatformFamily,
|
|
144
|
+
): bool {
|
|
145
|
+
const modifier = resolveLineBoundaryModifier(platformFamily);
|
|
146
|
+
return modifier != 0 && (modifiers & modifier) != 0;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function resolveDocumentBoundaryModifier(platformFamily: PlatformFamily = detectedPlatformFamily): u32 {
|
|
150
|
+
return resolveKeyboardPolicy(platformFamily).documentBoundaryModifier;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function hasDocumentBoundaryModifier(
|
|
154
|
+
modifiers: u32,
|
|
155
|
+
platformFamily: PlatformFamily = detectedPlatformFamily,
|
|
156
|
+
): bool {
|
|
157
|
+
return (modifiers & resolveDocumentBoundaryModifier(platformFamily)) != 0;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function isUndoShortcut(
|
|
161
|
+
key: string,
|
|
162
|
+
modifiers: u32,
|
|
163
|
+
platformFamily: PlatformFamily = detectedPlatformFamily,
|
|
164
|
+
): bool {
|
|
165
|
+
return (modifiers & KeyModifier.Shift) == 0 &&
|
|
166
|
+
hasPrimaryShortcutModifier(modifiers, platformFamily) &&
|
|
167
|
+
matchesShortcutKey(key, "z");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function isRedoShortcut(
|
|
171
|
+
key: string,
|
|
172
|
+
modifiers: u32,
|
|
173
|
+
platformFamily: PlatformFamily = detectedPlatformFamily,
|
|
174
|
+
): bool {
|
|
175
|
+
const policy = resolveKeyboardPolicy(platformFamily);
|
|
176
|
+
const hasPrimaryModifier = hasPrimaryShortcutModifier(modifiers, platformFamily);
|
|
177
|
+
if (!hasPrimaryModifier) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
if (policy.redoUsesPrimaryY && matchesShortcutKey(key, "y")) {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
return policy.redoUsesShiftedPrimaryZ &&
|
|
184
|
+
(modifiers & KeyModifier.Shift) != 0 &&
|
|
185
|
+
matchesShortcutKey(key, "z");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function formatShortcutLabel(
|
|
189
|
+
key: string,
|
|
190
|
+
modifiers: u32,
|
|
191
|
+
platformFamily: PlatformFamily = detectedPlatformFamily,
|
|
192
|
+
): string {
|
|
193
|
+
const tokens = new Array<string>();
|
|
194
|
+
appendShortcutModifierTokens(tokens, modifiers, platformFamily);
|
|
195
|
+
tokens.push(formatShortcutKeyToken(key, platformFamily));
|
|
196
|
+
if (platformFamily == PlatformFamily.Apple) {
|
|
197
|
+
return tokens.join("");
|
|
198
|
+
}
|
|
199
|
+
return tokens.join("+");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function formatPrimaryShortcutLabel(
|
|
203
|
+
key: string,
|
|
204
|
+
platformFamily: PlatformFamily = detectedPlatformFamily,
|
|
205
|
+
): string {
|
|
206
|
+
return formatShortcutLabel(key, resolvePrimaryShortcutModifier(platformFamily), platformFamily);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function formatUndoShortcutLabel(platformFamily: PlatformFamily = detectedPlatformFamily): string {
|
|
210
|
+
return formatPrimaryShortcutLabel("z", platformFamily);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function formatRedoShortcutLabel(platformFamily: PlatformFamily = detectedPlatformFamily): string {
|
|
214
|
+
const policy = resolveKeyboardPolicy(platformFamily);
|
|
215
|
+
if (policy.redoUsesPrimaryY) {
|
|
216
|
+
return formatPrimaryShortcutLabel("y", platformFamily);
|
|
217
|
+
}
|
|
218
|
+
return formatShortcutLabel("z", policy.primaryShortcutModifier | KeyModifier.Shift, platformFamily);
|
|
219
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Action, CallbackAction, HandlerAction, SignalHandler } from "./Action";
|
|
2
|
+
import { markNeedsCommit } from "./FrameScheduler";
|
|
3
|
+
import { describeValue, log } from "./Logger";
|
|
4
|
+
|
|
5
|
+
export class Signal<T> {
|
|
6
|
+
private currentValue: T;
|
|
7
|
+
private head: Action<T> | null = null;
|
|
8
|
+
|
|
9
|
+
constructor(initial: T) {
|
|
10
|
+
this.currentValue = initial;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get value(): T {
|
|
14
|
+
return this.currentValue;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
set value(next: T) {
|
|
18
|
+
this.set(next);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
set(next: T): bool {
|
|
22
|
+
if (this.currentValue === next) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
this.currentValue = next;
|
|
26
|
+
log("Signal", "value changed to " + describeValue<T>(next));
|
|
27
|
+
const notified = this.head !== null;
|
|
28
|
+
let current = this.head;
|
|
29
|
+
while (current !== null) {
|
|
30
|
+
const nextAction = current.next;
|
|
31
|
+
log("Action", current.debugName() + " invoked with " + describeValue<T>(next));
|
|
32
|
+
changetype<Action<T>>(current).invoke(next);
|
|
33
|
+
current = nextAction;
|
|
34
|
+
}
|
|
35
|
+
if (notified) {
|
|
36
|
+
markNeedsCommit();
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
hasListeners(): bool {
|
|
42
|
+
return this.head !== null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
addAction(action: Action<T>): Action<T> {
|
|
46
|
+
if (action._isAttachedTo(this)) {
|
|
47
|
+
return action;
|
|
48
|
+
}
|
|
49
|
+
action.dispose();
|
|
50
|
+
action.next = null;
|
|
51
|
+
if (this.head === null) {
|
|
52
|
+
this.head = action;
|
|
53
|
+
} else {
|
|
54
|
+
let current = changetype<Action<T>>(this.head);
|
|
55
|
+
while (current.next !== null) {
|
|
56
|
+
current = changetype<Action<T>>(current.next);
|
|
57
|
+
}
|
|
58
|
+
current.next = action;
|
|
59
|
+
}
|
|
60
|
+
action._attach(this);
|
|
61
|
+
return action;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
bind<Owner>(owner: Owner, handler: SignalHandler<Owner, T>): Action<T> {
|
|
65
|
+
return this.addAction(new HandlerAction<Owner, T>(owner, handler));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
subscribe(callback: () => void): Action<T> {
|
|
69
|
+
return this.addAction(new CallbackAction<T>(callback));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
removeAction(target: Action<T>): void {
|
|
73
|
+
let previous: Action<T> | null = null;
|
|
74
|
+
let current = this.head;
|
|
75
|
+
while (current !== null) {
|
|
76
|
+
if (current === target) {
|
|
77
|
+
if (previous === null) {
|
|
78
|
+
this.head = current.next;
|
|
79
|
+
} else {
|
|
80
|
+
previous.next = current.next;
|
|
81
|
+
}
|
|
82
|
+
target._detach();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
previous = current;
|
|
86
|
+
current = current.next;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|