@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.
Files changed (137) hide show
  1. package/LICENSE.md +7 -0
  2. package/browser/src/common-harness/host-imports.ts +430 -0
  3. package/browser/src/common-harness/interop.ts +39 -0
  4. package/browser/src/common-harness/managed-harness-bitmap-host.ts +92 -0
  5. package/browser/src/common-harness/managed-harness-fetch-host.ts +201 -0
  6. package/browser/src/common-harness/managed-harness-file-host.ts +1101 -0
  7. package/browser/src/common-harness/managed-harness-file-payloads.ts +143 -0
  8. package/browser/src/common-harness/managed-harness-file-types.ts +106 -0
  9. package/browser/src/common-harness/managed-harness-session.ts +15 -0
  10. package/browser/src/common-harness/managed-harness.ts +1323 -0
  11. package/browser/src/common-harness/managed-history.ts +168 -0
  12. package/browser/src/common-harness/persisted-restore-policy.ts +50 -0
  13. package/browser/src/common-harness/persisted-ui-state-controller.ts +309 -0
  14. package/browser/src/common-harness/text-session-bridge.ts +452 -0
  15. package/browser/src/common-harness/types.ts +205 -0
  16. package/browser/src/common-harness/ui-chrome.ts +191 -0
  17. package/browser/src/common-harness/ui-imports.ts +529 -0
  18. package/browser/src/common-harness/wasm-module-cache.ts +47 -0
  19. package/browser/src/common-harness.ts +27 -0
  20. package/browser/src/file-processing-worker.ts +89 -0
  21. package/browser/src/host-events.ts +97 -0
  22. package/browser/src/host-services.ts +203 -0
  23. package/browser/src/index.ts +62 -0
  24. package/browser/src/persisted-ui-state.ts +206 -0
  25. package/browser/src/routed-harness.ts +198 -0
  26. package/browser/src/worker-bootstrap.ts +483 -0
  27. package/browser/src/worker-manager.ts +230 -0
  28. package/browser/src/worker-types.ts +50 -0
  29. package/package.json +89 -0
  30. package/scripts/build-demo-as.sh +91 -0
  31. package/scripts/build.sh +325 -0
  32. package/scripts/generate-host-events.ts +175 -0
  33. package/scripts/generate-host-services.ts +157 -0
  34. package/src/Fui.ts +205 -0
  35. package/src/FuiExports.ts +55 -0
  36. package/src/FuiPrimitives.ts +15 -0
  37. package/src/FuiWorker.ts +3 -0
  38. package/src/FuiWorkerExports.ts +6 -0
  39. package/src/bindings/ui.ts +531 -0
  40. package/src/color.ts +86 -0
  41. package/src/controls/AntiSelectionArea.ts +23 -0
  42. package/src/controls/Button.ts +750 -0
  43. package/src/controls/Checkbox.ts +181 -0
  44. package/src/controls/ContextMenu.ts +885 -0
  45. package/src/controls/ControlTemplateSet.ts +37 -0
  46. package/src/controls/Dialog.ts +355 -0
  47. package/src/controls/Dropdown.ts +856 -0
  48. package/src/controls/Form.ts +110 -0
  49. package/src/controls/NavLink.ts +211 -0
  50. package/src/controls/Popup.ts +129 -0
  51. package/src/controls/ProgressBar.ts +180 -0
  52. package/src/controls/RadioButton.ts +135 -0
  53. package/src/controls/RadioGroup.ts +244 -0
  54. package/src/controls/SelectionArea.ts +75 -0
  55. package/src/controls/Slider.ts +471 -0
  56. package/src/controls/Switch.ts +132 -0
  57. package/src/controls/TextArea.ts +20 -0
  58. package/src/controls/TextInput.ts +7 -0
  59. package/src/controls/index.ts +18 -0
  60. package/src/controls/internal/ButtonPresenter.ts +95 -0
  61. package/src/controls/internal/CheckboxIndicatorPresenter.ts +93 -0
  62. package/src/controls/internal/DropdownChevronPresenter.ts +67 -0
  63. package/src/controls/internal/DropdownFieldPresenter.ts +110 -0
  64. package/src/controls/internal/DropdownOptionRowPresenter.ts +82 -0
  65. package/src/controls/internal/PopupPresenter.ts +198 -0
  66. package/src/controls/internal/PressableIndicatorPresenter.ts +32 -0
  67. package/src/controls/internal/PressableLabeledControl.ts +221 -0
  68. package/src/controls/internal/RadioIndicatorPresenter.ts +73 -0
  69. package/src/controls/internal/SliderPresenter.ts +157 -0
  70. package/src/controls/internal/SwitchIndicatorPresenter.ts +72 -0
  71. package/src/controls/internal/TextInputCore.ts +695 -0
  72. package/src/controls/internal/TextInputPresenter.ts +72 -0
  73. package/src/controls/templating.ts +54 -0
  74. package/src/core/Action.ts +94 -0
  75. package/src/core/Actions.ts +37 -0
  76. package/src/core/Animation.ts +412 -0
  77. package/src/core/Application.ts +328 -0
  78. package/src/core/Assets.ts +264 -0
  79. package/src/core/AttachedProperties.ts +32 -0
  80. package/src/core/Bitmap.ts +70 -0
  81. package/src/core/BoundCallback.ts +104 -0
  82. package/src/core/Callbacks.ts +17 -0
  83. package/src/core/ContextMenuManager.ts +466 -0
  84. package/src/core/DebugApi.ts +30 -0
  85. package/src/core/Disposable.ts +10 -0
  86. package/src/core/DragDropManager.ts +179 -0
  87. package/src/core/DragGesture.ts +184 -0
  88. package/src/core/DynamicAssetIds.ts +24 -0
  89. package/src/core/Errors.ts +48 -0
  90. package/src/core/EventRouter.ts +408 -0
  91. package/src/core/ExternalDropManager.ts +122 -0
  92. package/src/core/Fetch.ts +264 -0
  93. package/src/core/FetchFfi.ts +15 -0
  94. package/src/core/File.ts +1002 -0
  95. package/src/core/FocusAdornerManager.ts +263 -0
  96. package/src/core/FocusVisibility.ts +36 -0
  97. package/src/core/FrameScheduler.ts +28 -0
  98. package/src/core/KeyboardScroll.ts +161 -0
  99. package/src/core/KeyboardScrollTracker.ts +386 -0
  100. package/src/core/Logger.ts +80 -0
  101. package/src/core/Navigation.ts +13 -0
  102. package/src/core/Node.ts +1708 -0
  103. package/src/core/PersistedState.ts +102 -0
  104. package/src/core/PersistedUiState.ts +142 -0
  105. package/src/core/Platform.ts +219 -0
  106. package/src/core/Signal.ts +89 -0
  107. package/src/core/Theme.ts +365 -0
  108. package/src/core/Timers.ts +129 -0
  109. package/src/core/ToolTip.ts +122 -0
  110. package/src/core/ToolTipManager.ts +459 -0
  111. package/src/core/Transitions.ts +34 -0
  112. package/src/core/Typography.ts +204 -0
  113. package/src/core/Worker.ts +196 -0
  114. package/src/core/bind.ts +37 -0
  115. package/src/core/event_exports.ts +596 -0
  116. package/src/core/ffi.ts +728 -0
  117. package/src/host-services/runtime.ts +25 -0
  118. package/src/nodes/FlexBox.ts +789 -0
  119. package/src/nodes/GradientStop.ts +9 -0
  120. package/src/nodes/Grid.ts +183 -0
  121. package/src/nodes/Image.ts +189 -0
  122. package/src/nodes/Portal.ts +14 -0
  123. package/src/nodes/RichText.ts +312 -0
  124. package/src/nodes/ScrollBar.ts +570 -0
  125. package/src/nodes/ScrollBox.ts +415 -0
  126. package/src/nodes/ScrollState.ts +10 -0
  127. package/src/nodes/ScrollView.ts +511 -0
  128. package/src/nodes/Svg.ts +142 -0
  129. package/src/nodes/Text.ts +145 -0
  130. package/src/nodes/TextCore.ts +558 -0
  131. package/src/nodes/VirtualList.ts +431 -0
  132. package/src/nodes/helpers.ts +25 -0
  133. package/src/nodes/index.ts +14 -0
  134. package/src/tsconfig.json +7 -0
  135. package/src/worker/Worker.ts +169 -0
  136. package/src/worker/WorkerJob.ts +65 -0
  137. 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
+ }