@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,110 @@
|
|
|
1
|
+
import { EventRouter, GlobalKeyHandler } from "../core/EventRouter";
|
|
2
|
+
import { FlexDirection, KeyEventType, SemanticRole } from "../core/ffi";
|
|
3
|
+
import { FlexBox } from "../nodes";
|
|
4
|
+
import { Button } from "./Button";
|
|
5
|
+
|
|
6
|
+
export class Form extends FlexBox implements GlobalKeyHandler {
|
|
7
|
+
private defaultButton: Button | null = null;
|
|
8
|
+
private cancelButton: Button | null = null;
|
|
9
|
+
private keyFilterToken: u32 = 0;
|
|
10
|
+
private armedKey: string | null = null;
|
|
11
|
+
private armedButton: Button | null = null;
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
super();
|
|
15
|
+
this.flexDirection(FlexDirection.Column);
|
|
16
|
+
this.semanticRole(SemanticRole.Form);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
defaultBtn(button: Button): this {
|
|
20
|
+
this.defaultButton = button;
|
|
21
|
+
return this;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
cancelBtn(button: Button): this {
|
|
25
|
+
this.cancelButton = button;
|
|
26
|
+
return this;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
activate(): void {
|
|
30
|
+
if (this.keyFilterToken != 0 || (this.defaultButton === null && this.cancelButton === null)) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
this.keyFilterToken = EventRouter.pushKeyFilter(this);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
deactivate(): void {
|
|
37
|
+
this.cancelArmedButton();
|
|
38
|
+
if (this.keyFilterToken != 0) {
|
|
39
|
+
EventRouter.removeKeyFilter(this.keyFilterToken);
|
|
40
|
+
this.keyFilterToken = 0;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
handleGlobalKeyEvent(eventType: KeyEventType, key: string, modifiers: u32): bool {
|
|
45
|
+
if (key == "Enter" && this.shouldDeferEnterToFocusedButton(modifiers)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
if (eventType == KeyEventType.Down) {
|
|
49
|
+
return this.handleKeyDown(key);
|
|
50
|
+
}
|
|
51
|
+
if (eventType == KeyEventType.Up) {
|
|
52
|
+
return this.handleKeyUp(key);
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private handleKeyDown(key: string): bool {
|
|
58
|
+
const button = this.resolveButtonForKey(key);
|
|
59
|
+
if (button === null) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
if (this.armedKey !== null) {
|
|
63
|
+
return changetype<string>(this.armedKey) == key;
|
|
64
|
+
}
|
|
65
|
+
this.armedKey = key;
|
|
66
|
+
this.armedButton = button;
|
|
67
|
+
button.beginPress();
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private handleKeyUp(key: string): bool {
|
|
72
|
+
if (this.armedKey === null || changetype<string>(this.armedKey) != key) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
const button = this.armedButton;
|
|
76
|
+
this.armedKey = null;
|
|
77
|
+
this.armedButton = null;
|
|
78
|
+
if (button !== null) {
|
|
79
|
+
button.endPress(true);
|
|
80
|
+
}
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private cancelArmedButton(): void {
|
|
85
|
+
const button = this.armedButton;
|
|
86
|
+
this.armedKey = null;
|
|
87
|
+
this.armedButton = null;
|
|
88
|
+
if (button !== null) {
|
|
89
|
+
button.cancelPress();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private resolveButtonForKey(key: string): Button | null {
|
|
94
|
+
if (key == "Enter") {
|
|
95
|
+
return this.defaultButton;
|
|
96
|
+
}
|
|
97
|
+
if (key == "Escape") {
|
|
98
|
+
return this.cancelButton;
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private shouldDeferEnterToFocusedButton(modifiers: u32): bool {
|
|
104
|
+
if (modifiers != 0) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
const focusedNode = EventRouter.getFocusedNode();
|
|
108
|
+
return focusedNode !== null && focusedNode instanceof Button && focusedNode.isEnabled;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CursorStyle,
|
|
3
|
+
KeyEventType,
|
|
4
|
+
PointerEventType,
|
|
5
|
+
SemanticRole,
|
|
6
|
+
fui_hide_url_preview,
|
|
7
|
+
fui_show_url_preview,
|
|
8
|
+
} from "../core/ffi";
|
|
9
|
+
import { HandlerAction } from "../core/Action";
|
|
10
|
+
import { Disposable, disposeAll } from "../core/Disposable";
|
|
11
|
+
import { FocusAdornerManager } from "../core/FocusAdornerManager";
|
|
12
|
+
import { keyboardFocusVisible } from "../core/FocusVisibility";
|
|
13
|
+
import { navigateTo } from "../core/Navigation";
|
|
14
|
+
import { hasPrimaryShortcutModifier } from "../core/Platform";
|
|
15
|
+
import { Theme, activeTheme } from "../core/Theme";
|
|
16
|
+
import { FlexBox } from "../nodes";
|
|
17
|
+
|
|
18
|
+
export class NavLink extends FlexBox {
|
|
19
|
+
private static activePreviewOwner: NavLink | null = null;
|
|
20
|
+
private hrefValue: string;
|
|
21
|
+
private openInNewTabValue: bool;
|
|
22
|
+
private readonly disposables: Array<Disposable> = new Array<Disposable>();
|
|
23
|
+
private navigateCallback: ((path: string) => void) | null = null;
|
|
24
|
+
private pointerPressed: bool = false;
|
|
25
|
+
private pointerPressedOpenInNewTab: bool = false;
|
|
26
|
+
private enterPressed: bool = false;
|
|
27
|
+
private enterPressedOpenInNewTab: bool = false;
|
|
28
|
+
private hovered: bool = false;
|
|
29
|
+
private focused: bool = false;
|
|
30
|
+
private previewVisible: bool = false;
|
|
31
|
+
private previewPinnedForContextMenu: bool = false;
|
|
32
|
+
private disposed: bool = false;
|
|
33
|
+
|
|
34
|
+
constructor(href: string, label: string = href, openInNewTab: bool = false) {
|
|
35
|
+
super();
|
|
36
|
+
this.hrefValue = href;
|
|
37
|
+
this.openInNewTabValue = openInNewTab;
|
|
38
|
+
this.semanticRole(SemanticRole.Link);
|
|
39
|
+
this.semanticLabel(label);
|
|
40
|
+
this.cursor(CursorStyle.Pointer);
|
|
41
|
+
this.focusable(true);
|
|
42
|
+
this.track(activeTheme.addAction(new HandlerAction<NavLink, Theme>(this, (link: NavLink, _theme: Theme): void => {
|
|
43
|
+
link.syncFocusChrome();
|
|
44
|
+
})));
|
|
45
|
+
this.track(keyboardFocusVisible.addAction(new HandlerAction<NavLink, bool>(this, (link: NavLink, _visible: bool): void => {
|
|
46
|
+
link.syncFocusChrome();
|
|
47
|
+
})));
|
|
48
|
+
this.syncFocusChrome();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get href(): string {
|
|
52
|
+
return this.hrefValue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
hrefTo(nextHref: string): this {
|
|
56
|
+
this.hrefValue = nextHref;
|
|
57
|
+
if (this.previewVisible) {
|
|
58
|
+
this.showPreview();
|
|
59
|
+
}
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
onNavigate(cb: (path: string) => void): this {
|
|
64
|
+
this.navigateCallback = cb;
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
dispose(): void {
|
|
69
|
+
this.hidePreview();
|
|
70
|
+
if (!this.disposed) {
|
|
71
|
+
this.disposed = true;
|
|
72
|
+
disposeAll(this.disposables);
|
|
73
|
+
}
|
|
74
|
+
super.dispose();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
_handlePointerEvent(eventType: PointerEventType, x: f32, y: f32, modifiers: u32 = 0): void {
|
|
78
|
+
super._handlePointerEvent(eventType, x, y, modifiers);
|
|
79
|
+
if (eventType == PointerEventType.Enter) {
|
|
80
|
+
this.hovered = true;
|
|
81
|
+
this.showPreview();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (eventType == PointerEventType.Leave) {
|
|
85
|
+
this.hovered = false;
|
|
86
|
+
this.pointerPressed = false;
|
|
87
|
+
this.pointerPressedOpenInNewTab = false;
|
|
88
|
+
if (!this.previewPinnedForContextMenu && !this.focused) {
|
|
89
|
+
this.hidePreview();
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (eventType == PointerEventType.Down) {
|
|
94
|
+
this.pointerPressed = true;
|
|
95
|
+
this.pointerPressedOpenInNewTab = this.shouldOpenInNewTab(modifiers);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (eventType == PointerEventType.Up && this.pointerPressed) {
|
|
99
|
+
this.pointerPressed = false;
|
|
100
|
+
this.activate(this.pointerPressedOpenInNewTab || this.shouldOpenInNewTab(modifiers));
|
|
101
|
+
this.pointerPressedOpenInNewTab = false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
_handleFocusChanged(focused: bool): void {
|
|
106
|
+
super._handleFocusChanged(focused);
|
|
107
|
+
this.focused = focused;
|
|
108
|
+
this.syncFocusChrome();
|
|
109
|
+
if (focused) {
|
|
110
|
+
this.showPreview();
|
|
111
|
+
}
|
|
112
|
+
if (!focused) {
|
|
113
|
+
this.enterPressed = false;
|
|
114
|
+
this.enterPressedOpenInNewTab = false;
|
|
115
|
+
if (!this.hovered && !this.previewPinnedForContextMenu) {
|
|
116
|
+
this.hidePreview();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
_handleKeyEvent(eventType: KeyEventType, key: string, modifiers: u32): bool {
|
|
122
|
+
const callbackHandled = super._handleKeyEvent(eventType, key, modifiers);
|
|
123
|
+
if (key != "Enter") {
|
|
124
|
+
return callbackHandled;
|
|
125
|
+
}
|
|
126
|
+
if (modifiers != 0 && !hasPrimaryShortcutModifier(modifiers)) {
|
|
127
|
+
return callbackHandled;
|
|
128
|
+
}
|
|
129
|
+
if (eventType == KeyEventType.Down) {
|
|
130
|
+
this.enterPressed = true;
|
|
131
|
+
this.enterPressedOpenInNewTab = this.shouldOpenInNewTab(modifiers);
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
if (eventType == KeyEventType.Up && this.enterPressed) {
|
|
135
|
+
this.enterPressed = false;
|
|
136
|
+
this.activate(this.enterPressedOpenInNewTab || this.shouldOpenInNewTab(modifiers));
|
|
137
|
+
this.enterPressedOpenInNewTab = false;
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
return callbackHandled;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private activate(openInNewTab: bool): void {
|
|
144
|
+
navigateTo(this.hrefValue, openInNewTab);
|
|
145
|
+
const callback = this.navigateCallback;
|
|
146
|
+
if (callback !== null) {
|
|
147
|
+
callback(this.hrefValue);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private shouldOpenInNewTab(modifiers: u32): bool {
|
|
152
|
+
return this.openInNewTabValue || hasPrimaryShortcutModifier(modifiers);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
pinPreviewForContextMenu(): void {
|
|
156
|
+
this.previewPinnedForContextMenu = true;
|
|
157
|
+
this.showPreview();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
releasePreviewForContextMenu(): void {
|
|
161
|
+
if (!this.previewPinnedForContextMenu) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
this.previewPinnedForContextMenu = false;
|
|
165
|
+
if (!this.hovered && !this.focused) {
|
|
166
|
+
this.hidePreview();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
get isFocused(): bool {
|
|
171
|
+
return this.focused;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private showPreview(): void {
|
|
175
|
+
const previousOwner = NavLink.activePreviewOwner;
|
|
176
|
+
if (previousOwner !== null && previousOwner !== this) {
|
|
177
|
+
previousOwner.previewVisible = false;
|
|
178
|
+
}
|
|
179
|
+
const bytes = Uint8Array.wrap(String.UTF8.encode(this.hrefValue, false));
|
|
180
|
+
fui_show_url_preview(bytes.length > 0 ? bytes.dataStart : 0, <u32>bytes.length);
|
|
181
|
+
this.previewVisible = true;
|
|
182
|
+
NavLink.activePreviewOwner = this;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private hidePreview(): void {
|
|
186
|
+
if (!this.previewVisible && NavLink.activePreviewOwner !== this) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
this.previewVisible = false;
|
|
190
|
+
if (NavLink.activePreviewOwner !== this) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
NavLink.activePreviewOwner = null;
|
|
194
|
+
fui_hide_url_preview();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
private syncFocusChrome(): void {
|
|
198
|
+
if (this.disposed) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (this.focused && keyboardFocusVisible.value) {
|
|
202
|
+
FocusAdornerManager.showStandard(this, activeTheme.value.spacing.xs);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
FocusAdornerManager.hideOwner(this);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private track(disposable: Disposable): void {
|
|
209
|
+
this.disposables.push(disposable);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { BorderStyle, FlexDirection, Unit } from "../core/ffi";
|
|
2
|
+
import { Node } from "../core/Node";
|
|
3
|
+
import { throwNullArgument } from "../core/Errors";
|
|
4
|
+
import { FlexBox, Portal } from "../nodes";
|
|
5
|
+
import { PopupPlacement, PopupPresenter } from "./internal/PopupPresenter";
|
|
6
|
+
|
|
7
|
+
export { PopupPlacement } from "./internal/PopupPresenter";
|
|
8
|
+
|
|
9
|
+
export class Popup extends Portal {
|
|
10
|
+
private readonly surfaceNode: FlexBox;
|
|
11
|
+
private readonly presenter!: PopupPresenter;
|
|
12
|
+
private dismissOnBackdropClickValue: bool = true;
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
super();
|
|
16
|
+
this.surfaceNode = new FlexBox()
|
|
17
|
+
.positionAbsolute()
|
|
18
|
+
.flexDirection(FlexDirection.Column);
|
|
19
|
+
this.presenter = new PopupPresenter(this, this.surfaceNode);
|
|
20
|
+
this.positionAbsolute()
|
|
21
|
+
.position(0.0, 0.0)
|
|
22
|
+
.width(100.0, Unit.Percent)
|
|
23
|
+
.height(100.0, Unit.Percent);
|
|
24
|
+
this.presenter.overlayNode.onClickWith(this, (popup) => popup.handleBackdropClick());
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
get isOpen(): bool {
|
|
28
|
+
return this.presenter.isOpen;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get surface(): FlexBox {
|
|
32
|
+
return this.surfaceNode;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
placement(value: PopupPlacement): this {
|
|
36
|
+
this.presenter.placement(value);
|
|
37
|
+
return this;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
edgePadding(value: f32): this {
|
|
41
|
+
this.presenter.edgePadding(value);
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
anchorGap(value: f32): this {
|
|
46
|
+
this.presenter.anchorGap(value);
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
dismissOnBackdropClick(flag: bool = true): this {
|
|
51
|
+
this.dismissOnBackdropClickValue = flag;
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
backdropColor(color: u32): this {
|
|
56
|
+
this.presenter.backdropColor(color);
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
backgroundBlur(sigma: f32): this {
|
|
61
|
+
this.presenter.backgroundBlur(sigma);
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
child(node: Node): this {
|
|
66
|
+
if (node == null) {
|
|
67
|
+
throwNullArgument("Popup.child", "node");
|
|
68
|
+
}
|
|
69
|
+
this.surfaceNode.child(node);
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
children(nodes: Array<Node>): this {
|
|
74
|
+
this.surfaceNode.children(nodes);
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
panelColor(color: u32): this {
|
|
79
|
+
this.surfaceNode.bgColor(color);
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
panelBackgroundBlur(sigma: f32): this {
|
|
84
|
+
this.surfaceNode.backgroundBlur(sigma);
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
panelCornerRadius(radius: f32): this {
|
|
89
|
+
this.surfaceNode.cornerRadius(radius);
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
panelBorder(width: f32, color: u32, style: BorderStyle = BorderStyle.Solid): this {
|
|
94
|
+
this.surfaceNode.border(width, color, style);
|
|
95
|
+
return this;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
panelShadow(color: u32, offsetX: f32, offsetY: f32, blurSigma: f32, spread: f32 = 0.0): this {
|
|
99
|
+
this.surfaceNode.dropShadow(color, offsetX, offsetY, blurSigma, spread);
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
showAnchored(anchorX: f32, anchorY: f32, anchorWidth: f32, anchorHeight: f32, width: f32, height: f32): void {
|
|
104
|
+
this.surfaceNode.width(width, Unit.Pixel);
|
|
105
|
+
this.surfaceNode.height(height, Unit.Pixel);
|
|
106
|
+
this.presenter.showAnchored(anchorX, anchorY, anchorWidth, anchorHeight, width, height);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
showAtPoint(x: f32, y: f32, width: f32, height: f32): void {
|
|
110
|
+
this.surfaceNode.width(width, Unit.Pixel);
|
|
111
|
+
this.surfaceNode.height(height, Unit.Pixel);
|
|
112
|
+
this.presenter.showAtPoint(x, y, width, height);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
hide(): void {
|
|
116
|
+
this.presenter.hide();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
dispose(): void {
|
|
120
|
+
this.presenter.dispose();
|
|
121
|
+
super.dispose();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private handleBackdropClick(): void {
|
|
125
|
+
if (this.dismissOnBackdropClickValue) {
|
|
126
|
+
this.hide();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { BorderStyle } from "../core/ffi";
|
|
2
|
+
import { HandlerAction } from "../core/Action";
|
|
3
|
+
import { Disposable, disposeAll } from "../core/Disposable";
|
|
4
|
+
import { Theme, activeTheme } from "../core/Theme";
|
|
5
|
+
import { warn } from "../core/Logger";
|
|
6
|
+
import { FlexBox } from "../nodes";
|
|
7
|
+
|
|
8
|
+
function clamp(value: f32, min: f32, max: f32): f32 {
|
|
9
|
+
if (value < min) {
|
|
10
|
+
return min;
|
|
11
|
+
}
|
|
12
|
+
if (value > max) {
|
|
13
|
+
return max;
|
|
14
|
+
}
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class ProgressBar extends FlexBox {
|
|
19
|
+
private readonly fillNode: FlexBox;
|
|
20
|
+
private readonly disposables: Array<Disposable> = new Array<Disposable>();
|
|
21
|
+
private disposed: bool = false;
|
|
22
|
+
private minValue: f32 = 0.0;
|
|
23
|
+
private maxValue: f32 = 100.0;
|
|
24
|
+
private currentValue: f32 = 0.0;
|
|
25
|
+
private lengthValue: f32 = 220.0;
|
|
26
|
+
private thicknessValue: f32 = 14.0;
|
|
27
|
+
private trackColorValue: u32 = 0;
|
|
28
|
+
private fillColorValue: u32 = 0;
|
|
29
|
+
private trackColorOverridden: bool = false;
|
|
30
|
+
private fillColorOverridden: bool = false;
|
|
31
|
+
private cornerRadiusOverridden: bool = false;
|
|
32
|
+
|
|
33
|
+
constructor(value: f32 = 0.0) {
|
|
34
|
+
super();
|
|
35
|
+
this.fillNode = new FlexBox();
|
|
36
|
+
this.child(this.fillNode);
|
|
37
|
+
this.track(activeTheme.addAction(new HandlerAction<ProgressBar, Theme>(this, (bar: ProgressBar, _theme: Theme): void => {
|
|
38
|
+
bar.handleThemeChanged();
|
|
39
|
+
})));
|
|
40
|
+
this.setValue(value);
|
|
41
|
+
this.handleThemeChanged();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get valueNow(): f32 {
|
|
45
|
+
return this.currentValue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
min(value: f32): this {
|
|
49
|
+
this.minValue = value;
|
|
50
|
+
if (this.maxValue < value) {
|
|
51
|
+
this.maxValue = value;
|
|
52
|
+
}
|
|
53
|
+
this.setValue(this.currentValue);
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
max(value: f32): this {
|
|
58
|
+
this.maxValue = value;
|
|
59
|
+
if (this.minValue > value) {
|
|
60
|
+
this.minValue = value;
|
|
61
|
+
}
|
|
62
|
+
this.setValue(this.currentValue);
|
|
63
|
+
return this;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
value(value: f32): this {
|
|
67
|
+
this.setValue(value);
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
length(value: f32): this {
|
|
72
|
+
if (value <= 0.0) {
|
|
73
|
+
warn("Layout", "ProgressBar.length() received " + value.toString() + "; clamping to 1.0.");
|
|
74
|
+
}
|
|
75
|
+
this.lengthValue = value > 0.0 ? value : 1.0;
|
|
76
|
+
this.syncGeometry();
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
thickness(value: f32): this {
|
|
81
|
+
if (value <= 0.0) {
|
|
82
|
+
warn("Layout", "ProgressBar.thickness() received " + value.toString() + "; clamping to 1.0.");
|
|
83
|
+
}
|
|
84
|
+
this.thicknessValue = value > 0.0 ? value : 1.0;
|
|
85
|
+
if (!this.cornerRadiusOverridden) {
|
|
86
|
+
super.cornerRadius(this.thicknessValue * 0.5);
|
|
87
|
+
}
|
|
88
|
+
this.syncGeometry();
|
|
89
|
+
this.syncVisualState();
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
trackColor(color: u32): this {
|
|
94
|
+
this.trackColorOverridden = true;
|
|
95
|
+
this.trackColorValue = color;
|
|
96
|
+
this.syncVisualState();
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
fillColor(color: u32): this {
|
|
101
|
+
this.fillColorOverridden = true;
|
|
102
|
+
this.fillColorValue = color;
|
|
103
|
+
this.syncVisualState();
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
cornerRadius(radius: f32): this {
|
|
108
|
+
this.cornerRadiusOverridden = true;
|
|
109
|
+
super.cornerRadius(radius);
|
|
110
|
+
this.fillNode.cornerRadius(radius);
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
dispose(): void {
|
|
115
|
+
this.disposeControl();
|
|
116
|
+
super.dispose();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private normalizeValue(value: f32): f32 {
|
|
120
|
+
return clamp(value, this.minValue, this.maxValue);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private setValue(value: f32): void {
|
|
124
|
+
this.currentValue = this.normalizeValue(value);
|
|
125
|
+
this.syncGeometry();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private syncGeometry(): void {
|
|
129
|
+
const range = this.maxValue - this.minValue;
|
|
130
|
+
const fraction = range > 0.0 ? clamp((this.currentValue - this.minValue) / range, 0.0, 1.0) : 0.0;
|
|
131
|
+
const fillLength = this.lengthValue * fraction;
|
|
132
|
+
this.width(this.lengthValue);
|
|
133
|
+
this.height(this.thicknessValue);
|
|
134
|
+
this.fillNode.width(fillLength);
|
|
135
|
+
this.fillNode.height(this.thicknessValue);
|
|
136
|
+
this.semanticValueRange(this.currentValue, this.minValue, this.maxValue);
|
|
137
|
+
this.syncSemanticLabel();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private syncSemanticLabel(): void {
|
|
141
|
+
this.setDefaultSemanticLabel(
|
|
142
|
+
"Progress bar, value " + this.currentValue.toString() +
|
|
143
|
+
", range " + this.minValue.toString() +
|
|
144
|
+
" to " + this.maxValue.toString(),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private syncVisualState(): void {
|
|
149
|
+
const theme = activeTheme.value;
|
|
150
|
+
const trackColor = this.trackColorOverridden ? this.trackColorValue : theme.colors.scrollbarTrack;
|
|
151
|
+
const fillColor = this.fillColorOverridden ? this.fillColorValue : theme.colors.accent;
|
|
152
|
+
if (!this.cornerRadiusOverridden) {
|
|
153
|
+
super.cornerRadius(this.thicknessValue * 0.5);
|
|
154
|
+
this.fillNode.cornerRadius(this.thicknessValue * 0.5);
|
|
155
|
+
}
|
|
156
|
+
super.bgColor(trackColor);
|
|
157
|
+
super.border(1.0, theme.colors.border, BorderStyle.Solid);
|
|
158
|
+
this.fillNode.bgColor(fillColor);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private handleThemeChanged(): void {
|
|
162
|
+
if (this.disposed) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
this.syncVisualState();
|
|
166
|
+
this.syncGeometry();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private track(disposable: Disposable): void {
|
|
170
|
+
this.disposables.push(disposable);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private disposeControl(): void {
|
|
174
|
+
if (this.disposed) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
this.disposed = true;
|
|
178
|
+
disposeAll(this.disposables);
|
|
179
|
+
}
|
|
180
|
+
}
|