@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,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AlignItems,
|
|
3
|
+
BorderStyle,
|
|
4
|
+
FlexDirection,
|
|
5
|
+
JustifyContent,
|
|
6
|
+
} from "../../core/ffi";
|
|
7
|
+
import { Theme } from "../../core/Theme";
|
|
8
|
+
import { FontStyle, FontWeight } from "../../core/Typography";
|
|
9
|
+
import { FlexBox, TextCore } from "../../nodes";
|
|
10
|
+
|
|
11
|
+
export class ButtonVisualState {
|
|
12
|
+
constructor(
|
|
13
|
+
readonly hovered: bool,
|
|
14
|
+
readonly pressed: bool,
|
|
15
|
+
readonly focused: bool,
|
|
16
|
+
readonly enabled: bool,
|
|
17
|
+
) {}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export abstract class ButtonPresenter {
|
|
21
|
+
private hostValue: FlexBox | null = null;
|
|
22
|
+
|
|
23
|
+
protected constructor(
|
|
24
|
+
private readonly contentRootValue: FlexBox,
|
|
25
|
+
private readonly labelNodeValue: TextCore,
|
|
26
|
+
) {}
|
|
27
|
+
|
|
28
|
+
bindHost(host: FlexBox): this {
|
|
29
|
+
this.hostValue = host;
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get contentRoot(): FlexBox {
|
|
34
|
+
return this.contentRootValue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get labelNode(): TextCore {
|
|
38
|
+
return this.labelNodeValue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
protected get host(): FlexBox {
|
|
42
|
+
return changetype<FlexBox>(this.hostValue);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
abstract apply(theme: Theme, state: ButtonVisualState): void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export abstract class ButtonTemplate {
|
|
49
|
+
abstract create(): ButtonPresenter;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
class DefaultButtonPresenter extends ButtonPresenter {
|
|
53
|
+
constructor() {
|
|
54
|
+
const labelNode = new TextCore("");
|
|
55
|
+
const contentRoot = new FlexBox()
|
|
56
|
+
.flexDirection(FlexDirection.Row)
|
|
57
|
+
.alignItems(AlignItems.Center)
|
|
58
|
+
.justifyContent(JustifyContent.Center)
|
|
59
|
+
.child(labelNode);
|
|
60
|
+
super(contentRoot, labelNode);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
apply(theme: Theme, state: ButtonVisualState): void {
|
|
64
|
+
const background = state.pressed
|
|
65
|
+
? theme.colors.accentPressed
|
|
66
|
+
: (state.hovered ? theme.colors.accentHovered : theme.colors.accent);
|
|
67
|
+
this.host
|
|
68
|
+
.flexDirection(FlexDirection.Row)
|
|
69
|
+
.justifyContent(JustifyContent.Center)
|
|
70
|
+
.alignItems(AlignItems.Center)
|
|
71
|
+
.cornerRadius(theme.spacing.sm)
|
|
72
|
+
.border(1.0, theme.colors.border, BorderStyle.Solid)
|
|
73
|
+
.padding(theme.spacing.md, theme.spacing.sm, theme.spacing.md, theme.spacing.sm)
|
|
74
|
+
.dropShadow(0x00000000, 0.0, 0.0, 0.0, 0.0)
|
|
75
|
+
.bgColor(background);
|
|
76
|
+
this.contentRoot
|
|
77
|
+
.flexDirection(FlexDirection.Row)
|
|
78
|
+
.alignItems(AlignItems.Center)
|
|
79
|
+
.justifyContent(JustifyContent.Center);
|
|
80
|
+
this.labelNode
|
|
81
|
+
.fontFamily(theme.fonts.bodyFamily)
|
|
82
|
+
.fontWeight(FontWeight.Regular)
|
|
83
|
+
.fontStyle(FontStyle.Normal)
|
|
84
|
+
.fontSize(theme.fonts.sizeBody)
|
|
85
|
+
.textColor(theme.colors.textPrimary);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
class DefaultButtonTemplate extends ButtonTemplate {
|
|
90
|
+
create(): ButtonPresenter {
|
|
91
|
+
return new DefaultButtonPresenter();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export const defaultButtonTemplate = new DefaultButtonTemplate();
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { BorderStyle, SemanticCheckedState, Unit } from "../../core/ffi";
|
|
2
|
+
import { Theme } from "../../core/Theme";
|
|
3
|
+
import { FlexBox, Svg } from "../../nodes";
|
|
4
|
+
import {
|
|
5
|
+
PressableIndicatorMetrics,
|
|
6
|
+
PressableIndicatorPresenter,
|
|
7
|
+
PressableIndicatorVisualState,
|
|
8
|
+
} from "./PressableIndicatorPresenter";
|
|
9
|
+
|
|
10
|
+
const CHECKBOX_CHECK_SVG_URL = "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 14 14'><path d='M3.25 8.25 6.35 11.35 12.75 4.95' fill='none' stroke='%23000000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/></svg>";
|
|
11
|
+
|
|
12
|
+
export class CheckboxIndicatorVisualState extends PressableIndicatorVisualState {
|
|
13
|
+
constructor(
|
|
14
|
+
readonly checkedState: SemanticCheckedState,
|
|
15
|
+
hovered: bool,
|
|
16
|
+
pressed: bool,
|
|
17
|
+
focused: bool,
|
|
18
|
+
enabled: bool,
|
|
19
|
+
) {
|
|
20
|
+
super(hovered, pressed, focused, enabled);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export abstract class CheckboxIndicatorPresenter extends PressableIndicatorPresenter {
|
|
25
|
+
protected constructor(root: FlexBox, metrics: PressableIndicatorMetrics) {
|
|
26
|
+
super(root, metrics);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
abstract apply(theme: Theme, state: CheckboxIndicatorVisualState): void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export abstract class CheckboxIndicatorTemplate {
|
|
33
|
+
abstract create(): CheckboxIndicatorPresenter;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
class DefaultCheckboxIndicatorPresenter extends CheckboxIndicatorPresenter {
|
|
37
|
+
private readonly markNode: Svg;
|
|
38
|
+
|
|
39
|
+
constructor() {
|
|
40
|
+
const root = new FlexBox()
|
|
41
|
+
.width(20.0, Unit.Pixel)
|
|
42
|
+
.height(20.0, Unit.Pixel)
|
|
43
|
+
.alignItems(1)
|
|
44
|
+
.justifyContent(1);
|
|
45
|
+
super(root, new PressableIndicatorMetrics(20.0, 20.0));
|
|
46
|
+
const markHost = new FlexBox()
|
|
47
|
+
.width(100.0, Unit.Percent)
|
|
48
|
+
.height(100.0, Unit.Percent)
|
|
49
|
+
.alignItems(1)
|
|
50
|
+
.justifyContent(1);
|
|
51
|
+
const markNode = new Svg();
|
|
52
|
+
markNode
|
|
53
|
+
.width(16.0, Unit.Pixel)
|
|
54
|
+
.height(16.0, Unit.Pixel);
|
|
55
|
+
this.markNode = markNode;
|
|
56
|
+
markHost.child(markNode);
|
|
57
|
+
root.child(markHost);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
apply(theme: Theme, state: CheckboxIndicatorVisualState): void {
|
|
61
|
+
let background = theme.colors.surface;
|
|
62
|
+
let borderColor = theme.colors.border;
|
|
63
|
+
let markVisible = false;
|
|
64
|
+
let markColor = theme.colors.textPrimary;
|
|
65
|
+
if (state.checkedState == SemanticCheckedState.True || state.checkedState == SemanticCheckedState.Mixed) {
|
|
66
|
+
background = state.pressed
|
|
67
|
+
? theme.colors.accentPressed
|
|
68
|
+
: (state.hovered ? theme.colors.accentHovered : theme.colors.accent);
|
|
69
|
+
borderColor = background;
|
|
70
|
+
markVisible = state.checkedState == SemanticCheckedState.True;
|
|
71
|
+
markColor = theme.colors.surface;
|
|
72
|
+
} else if (state.hovered) {
|
|
73
|
+
background = theme.colors.background;
|
|
74
|
+
}
|
|
75
|
+
this.root.cornerRadius(theme.spacing.xs);
|
|
76
|
+
this.root.border(1.0, borderColor, BorderStyle.Solid);
|
|
77
|
+
this.root.bgColor(background);
|
|
78
|
+
if (markVisible) {
|
|
79
|
+
this.markNode.source(CHECKBOX_CHECK_SVG_URL);
|
|
80
|
+
} else {
|
|
81
|
+
this.markNode.clearSource();
|
|
82
|
+
}
|
|
83
|
+
this.markNode.tint(markColor);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
class DefaultCheckboxIndicatorTemplate extends CheckboxIndicatorTemplate {
|
|
88
|
+
create(): CheckboxIndicatorPresenter {
|
|
89
|
+
return new DefaultCheckboxIndicatorPresenter();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const defaultCheckboxIndicatorTemplate = new DefaultCheckboxIndicatorTemplate();
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { AlignItems, JustifyContent, Unit } from "../../core/ffi";
|
|
2
|
+
import { Theme } from "../../core/Theme";
|
|
3
|
+
import { FlexBox, Svg } from "../../nodes";
|
|
4
|
+
|
|
5
|
+
const DROPDOWN_CHEVRON_COLLAPSED_SVG = "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'><path d='M3 4.5 6 7.5 9 4.5' fill='none' stroke='%23000000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/></svg>";
|
|
6
|
+
const DROPDOWN_CHEVRON_EXPANDED_SVG = "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'><path d='M3 7.5 6 4.5 9 7.5' fill='none' stroke='%23000000' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/></svg>";
|
|
7
|
+
|
|
8
|
+
export class DropdownChevronVisualState {
|
|
9
|
+
constructor(
|
|
10
|
+
readonly open: bool,
|
|
11
|
+
readonly hovered: bool,
|
|
12
|
+
readonly enabled: bool,
|
|
13
|
+
) {}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export abstract class DropdownChevronPresenter {
|
|
17
|
+
protected constructor(
|
|
18
|
+
private readonly rootValue: FlexBox,
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
get root(): FlexBox {
|
|
22
|
+
return this.rootValue;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
abstract apply(theme: Theme, state: DropdownChevronVisualState): void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export abstract class DropdownChevronTemplate {
|
|
29
|
+
abstract create(): DropdownChevronPresenter;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class DefaultDropdownChevronPresenter extends DropdownChevronPresenter {
|
|
33
|
+
private readonly iconNode: Svg;
|
|
34
|
+
|
|
35
|
+
constructor() {
|
|
36
|
+
const root = new FlexBox()
|
|
37
|
+
.width(100.0, Unit.Percent)
|
|
38
|
+
.height(100.0, Unit.Percent)
|
|
39
|
+
.alignItems(AlignItems.Center)
|
|
40
|
+
.justifyContent(JustifyContent.Center);
|
|
41
|
+
const iconNode = new Svg()
|
|
42
|
+
.width(12.0, Unit.Pixel)
|
|
43
|
+
.height(12.0, Unit.Pixel) as Svg;
|
|
44
|
+
root.child(iconNode);
|
|
45
|
+
super(root);
|
|
46
|
+
this.iconNode = iconNode;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
apply(theme: Theme, state: DropdownChevronVisualState): void {
|
|
50
|
+
this.root
|
|
51
|
+
.width(100.0, Unit.Percent)
|
|
52
|
+
.height(100.0, Unit.Percent)
|
|
53
|
+
.alignItems(AlignItems.Center)
|
|
54
|
+
.justifyContent(JustifyContent.Center);
|
|
55
|
+
this.iconNode
|
|
56
|
+
.source(state.open ? DROPDOWN_CHEVRON_EXPANDED_SVG : DROPDOWN_CHEVRON_COLLAPSED_SVG)
|
|
57
|
+
.tint(!state.enabled ? theme.colors.textMuted : (state.hovered ? theme.colors.textPrimary : theme.colors.textMuted));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class DefaultDropdownChevronTemplate extends DropdownChevronTemplate {
|
|
62
|
+
create(): DropdownChevronPresenter {
|
|
63
|
+
return new DefaultDropdownChevronPresenter();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const defaultDropdownChevronTemplate = new DefaultDropdownChevronTemplate();
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AlignItems,
|
|
3
|
+
BorderStyle,
|
|
4
|
+
FlexDirection,
|
|
5
|
+
JustifyContent,
|
|
6
|
+
TextVerticalAlign,
|
|
7
|
+
Unit,
|
|
8
|
+
} from "../../core/ffi";
|
|
9
|
+
import { Theme } from "../../core/Theme";
|
|
10
|
+
import { FlexBox, Text } from "../../nodes";
|
|
11
|
+
|
|
12
|
+
const DEFAULT_CHEVRON_BOX_SIZE: f32 = 16.0;
|
|
13
|
+
|
|
14
|
+
export class DropdownFieldVisualState {
|
|
15
|
+
constructor(
|
|
16
|
+
readonly open: bool,
|
|
17
|
+
readonly focused: bool,
|
|
18
|
+
readonly enabled: bool,
|
|
19
|
+
readonly pressed: bool,
|
|
20
|
+
readonly selectedLabel: string,
|
|
21
|
+
) {}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export abstract class DropdownFieldPresenter {
|
|
25
|
+
protected constructor(
|
|
26
|
+
private readonly rootValue: FlexBox,
|
|
27
|
+
private readonly valueHostValue: FlexBox,
|
|
28
|
+
private readonly valueNodeValue: Text,
|
|
29
|
+
private readonly chevronHostValue: FlexBox,
|
|
30
|
+
) {}
|
|
31
|
+
|
|
32
|
+
get root(): FlexBox {
|
|
33
|
+
return this.rootValue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get valueHost(): FlexBox {
|
|
37
|
+
return this.valueHostValue;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get valueNode(): Text {
|
|
41
|
+
return this.valueNodeValue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get chevronHost(): FlexBox {
|
|
45
|
+
return this.chevronHostValue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
abstract apply(theme: Theme, state: DropdownFieldVisualState): void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export abstract class DropdownFieldTemplate {
|
|
52
|
+
abstract create(): DropdownFieldPresenter;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
class DefaultDropdownFieldPresenter extends DropdownFieldPresenter {
|
|
56
|
+
constructor() {
|
|
57
|
+
const valueNode = new Text("")
|
|
58
|
+
.selectable(false)
|
|
59
|
+
.width(100.0, Unit.Percent)
|
|
60
|
+
.maxLines(1)
|
|
61
|
+
.wrapping(false) as Text;
|
|
62
|
+
valueNode
|
|
63
|
+
.overflowFade(true, false)
|
|
64
|
+
.verticalAlign(TextVerticalAlign.Center);
|
|
65
|
+
const valueHost = new FlexBox()
|
|
66
|
+
.width(0.0, Unit.Pixel)
|
|
67
|
+
.flexGrow(1.0)
|
|
68
|
+
.child(valueNode) as FlexBox;
|
|
69
|
+
const chevronHost = new FlexBox()
|
|
70
|
+
.width(DEFAULT_CHEVRON_BOX_SIZE, Unit.Pixel)
|
|
71
|
+
.height(DEFAULT_CHEVRON_BOX_SIZE, Unit.Pixel)
|
|
72
|
+
.alignItems(AlignItems.Center)
|
|
73
|
+
.justifyContent(JustifyContent.Center);
|
|
74
|
+
const root = new FlexBox()
|
|
75
|
+
.flexDirection(FlexDirection.Row)
|
|
76
|
+
.alignItems(AlignItems.Center)
|
|
77
|
+
.child(valueHost)
|
|
78
|
+
.child(chevronHost);
|
|
79
|
+
super(root, valueHost, valueNode, chevronHost);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
apply(theme: Theme, state: DropdownFieldVisualState): void {
|
|
83
|
+
this.root
|
|
84
|
+
.flexDirection(FlexDirection.Row)
|
|
85
|
+
.alignItems(AlignItems.Center)
|
|
86
|
+
.cornerRadius(theme.spacing.sm)
|
|
87
|
+
.border(2.0, theme.colors.border, BorderStyle.Solid)
|
|
88
|
+
.padding(theme.spacing.md, theme.spacing.sm, theme.spacing.md, theme.spacing.sm)
|
|
89
|
+
.bgColor(state.pressed && state.enabled ? theme.colors.background : theme.colors.surface);
|
|
90
|
+
this.valueHost
|
|
91
|
+
.width(0.0, Unit.Pixel)
|
|
92
|
+
.flexGrow(1.0);
|
|
93
|
+
this.valueNode
|
|
94
|
+
.font(theme.fonts.body, theme.fonts.sizeBody)
|
|
95
|
+
.textColor(state.enabled ? theme.colors.textPrimary : theme.colors.textMuted);
|
|
96
|
+
this.chevronHost
|
|
97
|
+
.width(DEFAULT_CHEVRON_BOX_SIZE, Unit.Pixel)
|
|
98
|
+
.height(DEFAULT_CHEVRON_BOX_SIZE, Unit.Pixel)
|
|
99
|
+
.alignItems(AlignItems.Center)
|
|
100
|
+
.justifyContent(JustifyContent.Center);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
class DefaultDropdownFieldTemplate extends DropdownFieldTemplate {
|
|
105
|
+
create(): DropdownFieldPresenter {
|
|
106
|
+
return new DefaultDropdownFieldPresenter();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const defaultDropdownFieldTemplate = new DefaultDropdownFieldTemplate();
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { AlignItems, Unit } from "../../core/ffi";
|
|
2
|
+
import { Theme } from "../../core/Theme";
|
|
3
|
+
import { FlexBox, Text } from "../../nodes";
|
|
4
|
+
|
|
5
|
+
export class DropdownOptionRowMetrics {
|
|
6
|
+
constructor(
|
|
7
|
+
readonly height: f32,
|
|
8
|
+
) {}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class DropdownOptionRowVisualState {
|
|
12
|
+
constructor(
|
|
13
|
+
readonly highlighted: bool,
|
|
14
|
+
readonly selected: bool,
|
|
15
|
+
readonly enabled: bool,
|
|
16
|
+
) {}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export abstract class DropdownOptionRowPresenter {
|
|
20
|
+
protected constructor(
|
|
21
|
+
private readonly rootValue: FlexBox,
|
|
22
|
+
private readonly labelNodeValue: Text,
|
|
23
|
+
private readonly metricsValue: DropdownOptionRowMetrics,
|
|
24
|
+
) {}
|
|
25
|
+
|
|
26
|
+
get root(): FlexBox {
|
|
27
|
+
return this.rootValue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get labelNode(): Text {
|
|
31
|
+
return this.labelNodeValue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get metrics(): DropdownOptionRowMetrics {
|
|
35
|
+
return this.metricsValue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
abstract apply(theme: Theme, state: DropdownOptionRowVisualState): void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export abstract class DropdownOptionRowTemplate {
|
|
42
|
+
abstract create(): DropdownOptionRowPresenter;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class DefaultDropdownOptionRowPresenter extends DropdownOptionRowPresenter {
|
|
46
|
+
constructor() {
|
|
47
|
+
const labelNode = new Text("")
|
|
48
|
+
.selectable(false)
|
|
49
|
+
.width(100.0, Unit.Percent)
|
|
50
|
+
.maxLines(1)
|
|
51
|
+
.wrapping(false) as Text;
|
|
52
|
+
labelNode.overflowFade(true, false);
|
|
53
|
+
const root = new FlexBox()
|
|
54
|
+
.width(100.0, Unit.Percent)
|
|
55
|
+
.height(100.0, Unit.Percent)
|
|
56
|
+
.alignItems(AlignItems.Center)
|
|
57
|
+
.child(labelNode);
|
|
58
|
+
super(root, labelNode, new DropdownOptionRowMetrics(34.0));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
apply(theme: Theme, state: DropdownOptionRowVisualState): void {
|
|
62
|
+
this.root
|
|
63
|
+
.padding(10.0, 6.0, 10.0, 6.0)
|
|
64
|
+
.cornerRadius(theme.spacing.xs)
|
|
65
|
+
.bgColor(state.highlighted ? theme.contextMenu.item.hoverBackground : 0x00000000);
|
|
66
|
+
this.labelNode
|
|
67
|
+
.font(theme.fonts.body, theme.fonts.sizeBody)
|
|
68
|
+
.textColor(
|
|
69
|
+
!state.enabled
|
|
70
|
+
? theme.colors.textMuted
|
|
71
|
+
: (state.selected ? theme.colors.accent : theme.colors.textPrimary),
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
class DefaultDropdownOptionRowTemplate extends DropdownOptionRowTemplate {
|
|
77
|
+
create(): DropdownOptionRowPresenter {
|
|
78
|
+
return new DefaultDropdownOptionRowPresenter();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const defaultDropdownOptionRowTemplate = new DefaultDropdownOptionRowTemplate();
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import * as ui from "../../bindings/ui";
|
|
2
|
+
import { HandleValue, Unit } from "../../core/ffi";
|
|
3
|
+
import { warn } from "../../core/Logger";
|
|
4
|
+
import { FlexBox, Portal } from "../../nodes";
|
|
5
|
+
|
|
6
|
+
export enum PopupPlacement {
|
|
7
|
+
Auto = 0,
|
|
8
|
+
Bottom = 1,
|
|
9
|
+
Top = 2,
|
|
10
|
+
Overlap = 3,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class PopupPresenter {
|
|
14
|
+
readonly overlayNode: FlexBox;
|
|
15
|
+
private openState: bool = false;
|
|
16
|
+
private semanticScopeToken: u32 = 0;
|
|
17
|
+
private edgePaddingValue: f32 = 8.0;
|
|
18
|
+
private anchorGapValue: f32 = 4.0;
|
|
19
|
+
private placementValue: PopupPlacement = PopupPlacement.Auto;
|
|
20
|
+
private backdropColorValue: u32 = 0x00000000;
|
|
21
|
+
private backgroundBlurSigmaValue: f32 = 0.0;
|
|
22
|
+
private surfaceXValue: f32 = 0.0;
|
|
23
|
+
private surfaceYValue: f32 = 0.0;
|
|
24
|
+
|
|
25
|
+
constructor(
|
|
26
|
+
private readonly root: Portal,
|
|
27
|
+
private readonly surfaceNode: FlexBox,
|
|
28
|
+
private readonly semanticScopeNode: FlexBox | null = surfaceNode,
|
|
29
|
+
) {
|
|
30
|
+
this.overlayNode = new FlexBox()
|
|
31
|
+
.positionAbsolute()
|
|
32
|
+
.position(0.0, 0.0)
|
|
33
|
+
.width(100.0, Unit.Percent)
|
|
34
|
+
.height(100.0, Unit.Percent)
|
|
35
|
+
.child(surfaceNode);
|
|
36
|
+
this.surfaceNode.positionAbsolute();
|
|
37
|
+
this.applyBackdropStyle();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get isOpen(): bool {
|
|
41
|
+
return this.openState;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get surfaceX(): f32 {
|
|
45
|
+
return this.surfaceXValue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get surfaceY(): f32 {
|
|
49
|
+
return this.surfaceYValue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
placement(value: PopupPlacement): this {
|
|
53
|
+
this.placementValue = value;
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
edgePadding(value: f32): this {
|
|
58
|
+
if (value < 0.0) {
|
|
59
|
+
warn("Layout", "PopupPresenter.edgePadding() received " + value.toString() + "; clamping to 0.0.");
|
|
60
|
+
}
|
|
61
|
+
this.edgePaddingValue = value >= 0.0 ? value : 0.0;
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
anchorGap(value: f32): this {
|
|
66
|
+
if (value < 0.0) {
|
|
67
|
+
warn("Layout", "PopupPresenter.anchorGap() received " + value.toString() + "; clamping to 0.0.");
|
|
68
|
+
}
|
|
69
|
+
this.anchorGapValue = value >= 0.0 ? value : 0.0;
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
backdropColor(color: u32): this {
|
|
74
|
+
this.backdropColorValue = color;
|
|
75
|
+
this.applyBackdropStyle();
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
backgroundBlur(sigma: f32): this {
|
|
80
|
+
if (sigma < 0.0) {
|
|
81
|
+
warn("Layout", "PopupPresenter.backgroundBlur() received " + sigma.toString() + "; clamping to 0.0.");
|
|
82
|
+
}
|
|
83
|
+
this.backgroundBlurSigmaValue = sigma >= 0.0 ? sigma : 0.0;
|
|
84
|
+
this.applyBackdropStyle();
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
syncOverlayBounds(): void {
|
|
89
|
+
const popupBounds = this.root.builtHandle != <u64>HandleValue.Invalid
|
|
90
|
+
? ui.tryGetBounds(this.root.builtHandle)
|
|
91
|
+
: null;
|
|
92
|
+
const overlayX = popupBounds !== null ? -unchecked(popupBounds[0]) : 0.0;
|
|
93
|
+
const overlayY = popupBounds !== null ? -unchecked(popupBounds[1]) : 0.0;
|
|
94
|
+
this.overlayNode
|
|
95
|
+
.position(overlayX, overlayY)
|
|
96
|
+
.width(ui.getViewportWidth(), Unit.Pixel)
|
|
97
|
+
.height(ui.getViewportHeight(), Unit.Pixel);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
showAnchored(
|
|
101
|
+
anchorX: f32,
|
|
102
|
+
anchorY: f32,
|
|
103
|
+
anchorWidth: f32,
|
|
104
|
+
anchorHeight: f32,
|
|
105
|
+
surfaceWidth: f32,
|
|
106
|
+
surfaceHeight: f32,
|
|
107
|
+
placement: PopupPlacement = this.placementValue,
|
|
108
|
+
): void {
|
|
109
|
+
if (this.root.builtHandle == <u64>HandleValue.Invalid) {
|
|
110
|
+
warn("Layout", "PopupPresenter.showAnchored() was called before the root was built.");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.syncOverlayBounds();
|
|
114
|
+
const clampedWidth = surfaceWidth > 0.0 ? surfaceWidth : 1.0;
|
|
115
|
+
const clampedHeight = surfaceHeight > 0.0 ? surfaceHeight : 1.0;
|
|
116
|
+
const maxX = <f32>Math.max(this.edgePaddingValue, ui.getViewportWidth() - clampedWidth - this.edgePaddingValue);
|
|
117
|
+
const maxY = <f32>Math.max(this.edgePaddingValue, ui.getViewportHeight() - clampedHeight - this.edgePaddingValue);
|
|
118
|
+
const belowY = anchorY + anchorHeight + this.anchorGapValue;
|
|
119
|
+
const aboveY = anchorY - clampedHeight - this.anchorGapValue;
|
|
120
|
+
const fitsBelow = belowY <= maxY;
|
|
121
|
+
const fitsAbove = aboveY >= this.edgePaddingValue;
|
|
122
|
+
let panelY = belowY;
|
|
123
|
+
if (placement == PopupPlacement.Top) {
|
|
124
|
+
panelY = aboveY;
|
|
125
|
+
} else if (placement == PopupPlacement.Overlap) {
|
|
126
|
+
panelY = anchorY;
|
|
127
|
+
} else if (placement == PopupPlacement.Auto) {
|
|
128
|
+
if (!fitsBelow && fitsAbove) {
|
|
129
|
+
panelY = aboveY;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
this.setSurfacePosition(
|
|
133
|
+
<f32>Math.max(this.edgePaddingValue, Math.min(anchorX, maxX)),
|
|
134
|
+
<f32>Math.max(this.edgePaddingValue, Math.min(panelY, maxY)),
|
|
135
|
+
);
|
|
136
|
+
this.attach();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
showAtPoint(x: f32, y: f32, surfaceWidth: f32, surfaceHeight: f32): void {
|
|
140
|
+
if (this.root.builtHandle == <u64>HandleValue.Invalid) {
|
|
141
|
+
warn("Layout", "PopupPresenter.showAtPoint() was called before the root was built.");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
this.syncOverlayBounds();
|
|
145
|
+
const clampedWidth = surfaceWidth > 0.0 ? surfaceWidth : 1.0;
|
|
146
|
+
const clampedHeight = surfaceHeight > 0.0 ? surfaceHeight : 1.0;
|
|
147
|
+
const maxX = <f32>Math.max(this.edgePaddingValue, ui.getViewportWidth() - clampedWidth - this.edgePaddingValue);
|
|
148
|
+
const maxY = <f32>Math.max(this.edgePaddingValue, ui.getViewportHeight() - clampedHeight - this.edgePaddingValue);
|
|
149
|
+
this.setSurfacePosition(
|
|
150
|
+
<f32>Math.max(this.edgePaddingValue, Math.min(x, maxX)),
|
|
151
|
+
<f32>Math.max(this.edgePaddingValue, Math.min(y, maxY)),
|
|
152
|
+
);
|
|
153
|
+
this.attach();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
hide(): void {
|
|
157
|
+
if (!this.openState && this.overlayNode.parentNode === null) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
this.root.removeChildNode(this.overlayNode);
|
|
161
|
+
this.openState = false;
|
|
162
|
+
if (this.semanticScopeToken != 0) {
|
|
163
|
+
ui.removeSemanticScope(this.semanticScopeToken);
|
|
164
|
+
this.semanticScopeToken = 0;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
dispose(): void {
|
|
169
|
+
this.hide();
|
|
170
|
+
if (this.overlayNode.builtHandle != <u64>HandleValue.Invalid) {
|
|
171
|
+
this.overlayNode.dispose();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private attach(): void {
|
|
176
|
+
this.root.addChildNode(this.overlayNode);
|
|
177
|
+
this.openState = true;
|
|
178
|
+
const semanticScopeNode = this.semanticScopeNode;
|
|
179
|
+
if (
|
|
180
|
+
this.semanticScopeToken == 0 &&
|
|
181
|
+
semanticScopeNode !== null &&
|
|
182
|
+
semanticScopeNode.builtHandle != <u64>HandleValue.Invalid
|
|
183
|
+
) {
|
|
184
|
+
this.semanticScopeToken = ui.pushSemanticScope(semanticScopeNode.builtHandle);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private applyBackdropStyle(): void {
|
|
189
|
+
this.overlayNode.bgColor(this.backdropColorValue);
|
|
190
|
+
this.overlayNode.backgroundBlur(this.backgroundBlurSigmaValue);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private setSurfacePosition(x: f32, y: f32): void {
|
|
194
|
+
this.surfaceXValue = x;
|
|
195
|
+
this.surfaceYValue = y;
|
|
196
|
+
this.surfaceNode.position(x, y);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { FlexBox } from "../../nodes";
|
|
2
|
+
|
|
3
|
+
export class PressableIndicatorMetrics {
|
|
4
|
+
constructor(
|
|
5
|
+
readonly width: f32,
|
|
6
|
+
readonly height: f32,
|
|
7
|
+
) {}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class PressableIndicatorVisualState {
|
|
11
|
+
constructor(
|
|
12
|
+
readonly hovered: bool,
|
|
13
|
+
readonly pressed: bool,
|
|
14
|
+
readonly focused: bool,
|
|
15
|
+
readonly enabled: bool,
|
|
16
|
+
) {}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export abstract class PressableIndicatorPresenter {
|
|
20
|
+
protected constructor(
|
|
21
|
+
private readonly rootValue: FlexBox,
|
|
22
|
+
private readonly metricsValue: PressableIndicatorMetrics,
|
|
23
|
+
) {}
|
|
24
|
+
|
|
25
|
+
get root(): FlexBox {
|
|
26
|
+
return this.rootValue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get metrics(): PressableIndicatorMetrics {
|
|
30
|
+
return this.metricsValue;
|
|
31
|
+
}
|
|
32
|
+
}
|