@effindomv2/fui-as 0.1.36 → 0.1.39
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/browser/routed-app-conventions.ts +1 -1
- package/browser/src/common-harness.ts +1 -27
- package/browser/src/index.ts +19 -26
- package/browser/src/routed-harness.ts +1 -194
- package/browser/src/shared-browser.ts +6 -0
- package/package.json +26 -16
- package/scripts/build.sh +51 -47
- package/scripts/framework-host-services.ts +1 -1
- package/scripts/generate-host-events.ts +15 -241
- package/scripts/generate-host-services.ts +13 -261
- package/scripts/hostgen/assemblyscript-host-events.ts +165 -0
- package/scripts/hostgen/assemblyscript-host-services.ts +207 -0
- package/scripts/hostgen/common.ts +73 -0
- package/scripts/hostgen/registry.ts +159 -0
- package/src/Fui.ts +70 -4
- package/src/FuiExports.ts +13 -1
- package/src/bindings/ui.ts +165 -8
- package/src/controls/Button.ts +98 -71
- package/src/controls/Checkbox.ts +23 -21
- package/src/controls/ComboBox.ts +1201 -0
- package/src/controls/ContextMenu.ts +279 -165
- package/src/controls/Dialog.ts +91 -17
- package/src/controls/Dropdown.ts +101 -333
- package/src/controls/NavLink.ts +29 -6
- package/src/controls/Popup.ts +10 -5
- package/src/controls/ProgressBar.ts +1 -2
- package/src/controls/RadioButton.ts +34 -13
- package/src/controls/RadioGroup.ts +32 -19
- package/src/controls/Slider.ts +21 -23
- package/src/controls/Switch.ts +34 -17
- package/src/controls/index.ts +2 -1
- package/src/controls/internal/ButtonPresenter.ts +5 -2
- package/src/controls/internal/CheckboxIndicatorPresenter.ts +10 -10
- package/src/controls/internal/DropdownChevronPresenter.ts +3 -3
- package/src/controls/internal/DropdownFieldPresenter.ts +6 -6
- package/src/controls/internal/DropdownOptionRowPresenter.ts +5 -4
- package/src/controls/internal/PressableLabeledControl.ts +4 -7
- package/src/controls/internal/RadioIndicatorPresenter.ts +7 -7
- package/src/controls/internal/SelectablePopupList.ts +463 -0
- package/src/controls/internal/SliderPresenter.ts +5 -5
- package/src/controls/internal/SwitchIndicatorPresenter.ts +63 -17
- package/src/controls/internal/TextInputCore.ts +307 -41
- package/src/controls/internal/TextInputPresenter.ts +2 -2
- package/src/controls/templating.ts +10 -0
- package/src/core/Application.ts +19 -9
- package/src/core/Bitmap.ts +219 -1
- package/src/core/ContextMenuManager.ts +128 -44
- package/src/core/DebugApi.ts +17 -2
- package/src/core/DragGesture.ts +8 -7
- package/src/core/EventRouter.ts +332 -12
- package/src/core/Fetch.ts +15 -7
- package/src/core/FetchFfi.ts +1 -15
- package/src/core/File.ts +75 -34
- package/src/core/FocusAdornerManager.ts +3 -3
- package/src/core/FocusVisibility.ts +7 -0
- package/src/core/FrameScheduler.ts +126 -0
- package/src/core/ImageSampling.ts +43 -0
- package/src/core/Logger.ts +2 -0
- package/src/core/MobileTextSelectionToolbarManager.ts +901 -0
- package/src/core/Node.ts +1039 -97
- package/src/core/Platform.ts +16 -16
- package/src/core/SelectionHandleAdornerManager.ts +697 -0
- package/src/core/Theme.ts +34 -20
- package/src/core/ToolTipManager.ts +3 -3
- package/src/core/Typography.ts +411 -19
- package/src/core/Worker.ts +69 -33
- package/src/core/event_exports.ts +123 -5
- package/src/core/ffi.ts +5 -746
- package/src/core/generated/FetchHostAbi.ts +10 -0
- package/src/core/generated/FrameworkHostServices.ts +1 -1
- package/src/core/generated/HostAbi.ts +220 -0
- package/src/core/generated/UiAbi.ts +356 -0
- package/src/core/generated/UiEnums.ts +186 -0
- package/src/drawing/DrawCallback.ts +34 -0
- package/src/drawing/DrawContext.ts +189 -0
- package/src/drawing/Paint.ts +34 -0
- package/src/drawing/Path.ts +40 -0
- package/src/nodes/CustomDrawable.ts +61 -0
- package/src/nodes/FlexBox.ts +57 -11
- package/src/nodes/Grid.ts +5 -5
- package/src/nodes/Image.ts +20 -1
- package/src/nodes/RichText.ts +36 -25
- package/src/nodes/ScrollBar.ts +2 -1
- package/src/nodes/ScrollBox.ts +7 -2
- package/src/nodes/ScrollView.ts +17 -6
- package/src/nodes/Svg.ts +18 -1
- package/src/nodes/Text.ts +1 -1
- package/src/nodes/TextCore.ts +115 -20
- package/src/nodes/index.ts +3 -1
- package/src/text/DynamicTextLayout.ts +275 -0
- package/src/text/TextLayout.ts +308 -0
- package/src/worker/Worker.ts +3 -36
- package/src/worker/WorkerJob.ts +5 -9
- package/src/worker/ffi.ts +0 -6
- package/browser/src/common-harness/host-imports.ts +0 -447
- package/browser/src/common-harness/interop.ts +0 -39
- package/browser/src/common-harness/managed-harness-bitmap-host.ts +0 -92
- package/browser/src/common-harness/managed-harness-fetch-host.ts +0 -201
- package/browser/src/common-harness/managed-harness-file-host.ts +0 -1066
- package/browser/src/common-harness/managed-harness-file-payloads.ts +0 -143
- package/browser/src/common-harness/managed-harness-file-types.ts +0 -71
- package/browser/src/common-harness/managed-harness-session.ts +0 -15
- package/browser/src/common-harness/managed-harness.ts +0 -1451
- package/browser/src/common-harness/managed-history.ts +0 -168
- package/browser/src/common-harness/persisted-restore-policy.ts +0 -50
- package/browser/src/common-harness/persisted-ui-state-controller.ts +0 -309
- package/browser/src/common-harness/text-session-bridge.ts +0 -452
- package/browser/src/common-harness/types.ts +0 -205
- package/browser/src/common-harness/ui-chrome.ts +0 -206
- package/browser/src/common-harness/ui-imports.ts +0 -553
- package/browser/src/common-harness/wasm-module-cache.ts +0 -47
- package/browser/src/file-processing-worker.ts +0 -89
- package/browser/src/host-events.ts +0 -118
- package/browser/src/host-services.ts +0 -483
- package/browser/src/persisted-ui-state.ts +0 -206
- package/browser/src/routed-app-conventions.ts +0 -292
- package/browser/src/worker-bootstrap.ts +0 -916
- package/browser/src/worker-manager.ts +0 -234
- package/browser/src/worker-types.ts +0 -68
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import * as ui from "../../bindings/ui";
|
|
2
|
+
import { CursorStyle, FlexDirection, HandleValue, PointerEventType, SemanticRole, Unit } from "../../core/ffi";
|
|
3
|
+
import { warn } from "../../core/Logger";
|
|
4
|
+
import { PointerEventArgs } from "../../core/Node";
|
|
5
|
+
import { Theme, activeTheme } from "../../core/Theme";
|
|
6
|
+
import { FlexBox, Portal, ScrollBarVisibility, ScrollBox } from "../../nodes";
|
|
7
|
+
import { DropdownSizing } from "../ControlSizing";
|
|
8
|
+
import { DropdownColors } from "../DropdownColors";
|
|
9
|
+
import { getControlTemplates } from "../ControlTemplateSet";
|
|
10
|
+
import {
|
|
11
|
+
createDefaultDropdownOptionRowPresenter,
|
|
12
|
+
DropdownOptionRowPresenter,
|
|
13
|
+
DropdownOptionRowTemplate,
|
|
14
|
+
DropdownOptionRowVisualState,
|
|
15
|
+
} from "./DropdownOptionRowPresenter";
|
|
16
|
+
import { PopupPresenter } from "./PopupPresenter";
|
|
17
|
+
|
|
18
|
+
const PANEL_EDGE_PADDING: f32 = 8.0;
|
|
19
|
+
const OPTION_HEIGHT: f32 = 34.0;
|
|
20
|
+
export const SELECTABLE_POPUP_LIST_PANEL_PADDING: f32 = 4.0;
|
|
21
|
+
const UNLIMITED_VISIBLE_ITEMS: i32 = 0;
|
|
22
|
+
|
|
23
|
+
export interface SelectablePopupListOwner {
|
|
24
|
+
getPopupListItemCount(): i32;
|
|
25
|
+
getPopupListItemLabel(index: i32): string;
|
|
26
|
+
isPopupListItemSelected(index: i32): bool;
|
|
27
|
+
isPopupListEnabled(): bool;
|
|
28
|
+
popupListHighlightIndex(index: i32): void;
|
|
29
|
+
popupListActivateIndex(index: i32): void;
|
|
30
|
+
popupListPointerDown(index: i32): void;
|
|
31
|
+
popupListPointerUp(index: i32): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function createOptionRowPresenter(template: DropdownOptionRowTemplate | null, sizing: DropdownSizing | null = null): DropdownOptionRowPresenter {
|
|
35
|
+
if (template !== null) {
|
|
36
|
+
return template.create(sizing);
|
|
37
|
+
}
|
|
38
|
+
const templateSet = getControlTemplates();
|
|
39
|
+
const appTemplate = templateSet !== null ? templateSet.dropdownOptionRow : null;
|
|
40
|
+
return appTemplate === null
|
|
41
|
+
? createDefaultDropdownOptionRowPresenter(sizing)
|
|
42
|
+
: appTemplate.create(sizing);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
class SelectablePopupListOptionNode extends FlexBox {
|
|
46
|
+
private presenter: DropdownOptionRowPresenter;
|
|
47
|
+
private owner: SelectablePopupListOwner | null = null;
|
|
48
|
+
private slotIndex: i32 = -1;
|
|
49
|
+
private currentLabel: string = "";
|
|
50
|
+
|
|
51
|
+
constructor(template: DropdownOptionRowTemplate | null, sizing: DropdownSizing | null) {
|
|
52
|
+
super();
|
|
53
|
+
this.presenter = createOptionRowPresenter(template, sizing);
|
|
54
|
+
this.semanticRole(SemanticRole.ListItem);
|
|
55
|
+
this.width(100.0, Unit.Percent);
|
|
56
|
+
this.cursor(CursorStyle.Pointer);
|
|
57
|
+
this.focusable(false);
|
|
58
|
+
this.requireInteractive();
|
|
59
|
+
this.bindPresenterPointerEvents();
|
|
60
|
+
this.child(this.presenter.root);
|
|
61
|
+
this.syncPresenterLayout();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
bindOwner(owner: SelectablePopupListOwner, slotIndex: i32): this {
|
|
65
|
+
this.owner = owner;
|
|
66
|
+
this.slotIndex = slotIndex;
|
|
67
|
+
return this;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
label(label: string): this {
|
|
71
|
+
this.currentLabel = label;
|
|
72
|
+
this.semanticLabel(label);
|
|
73
|
+
this.presenter.labelNode.text(label);
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
template(template: DropdownOptionRowTemplate | null, sizing: DropdownSizing | null): void {
|
|
78
|
+
const previousPresenter = this.presenter;
|
|
79
|
+
const nextPresenter = createOptionRowPresenter(template, sizing);
|
|
80
|
+
this.presenter = nextPresenter;
|
|
81
|
+
this.removeChildNode(previousPresenter.root);
|
|
82
|
+
this.addChildNode(nextPresenter.root);
|
|
83
|
+
previousPresenter.root.dispose();
|
|
84
|
+
nextPresenter.labelNode.text(this.currentLabel);
|
|
85
|
+
this.bindPresenterPointerEvents();
|
|
86
|
+
this.syncPresenterLayout();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
get rowHeight(): f32 {
|
|
90
|
+
return this.presenter.metrics.height;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
applyTheme(theme: Theme, highlighted: bool, selected: bool, enabled: bool, colors: DropdownColors | null): void {
|
|
94
|
+
this.semanticSelected(selected);
|
|
95
|
+
this.semanticDisabled(!enabled);
|
|
96
|
+
this.presenter.apply(theme, new DropdownOptionRowVisualState(highlighted, selected, enabled), colors);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
_handlePointerEvent(eventType: PointerEventType, x: f32, y: f32, modifiers: u32 = 0): void {
|
|
100
|
+
super._handlePointerEvent(eventType, x, y, modifiers);
|
|
101
|
+
const owner = this.owner;
|
|
102
|
+
if (owner === null) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (eventType == PointerEventType.Enter) {
|
|
106
|
+
owner.popupListHighlightIndex(this.slotIndex);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (eventType == PointerEventType.Down) {
|
|
110
|
+
this.capturePointer();
|
|
111
|
+
owner.popupListPointerDown(this.slotIndex);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (eventType == PointerEventType.Up) {
|
|
115
|
+
this.releasePointer();
|
|
116
|
+
owner.popupListPointerUp(this.slotIndex);
|
|
117
|
+
owner.popupListActivateIndex(this.slotIndex);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (eventType == PointerEventType.Cancel) {
|
|
121
|
+
this.releasePointer();
|
|
122
|
+
owner.popupListPointerUp(this.slotIndex);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private bindPresenterPointerEvents(): void {
|
|
127
|
+
this.presenter.root.cursor(CursorStyle.Pointer);
|
|
128
|
+
this.presenter.labelNode.cursor(CursorStyle.Pointer);
|
|
129
|
+
this.presenter.root
|
|
130
|
+
.onPointerEnterWith(this, (node, event): void => node.handleNestedPointerEnter(event))
|
|
131
|
+
.onPointerDownWith(this, (node, event): void => node.handleNestedPointerDown(event))
|
|
132
|
+
.onPointerUpWith(this, (node, event): void => node.handleNestedPointerUp(event))
|
|
133
|
+
.onPointerCancelWith(this, (node, event): void => node.handleNestedPointerCancel(event));
|
|
134
|
+
this.presenter.labelNode
|
|
135
|
+
.onPointerEnterWith(this, (node, event): void => node.handleNestedPointerEnter(event))
|
|
136
|
+
.onPointerDownWith(this, (node, event): void => node.handleNestedPointerDown(event))
|
|
137
|
+
.onPointerUpWith(this, (node, event): void => node.handleNestedPointerUp(event))
|
|
138
|
+
.onPointerCancelWith(this, (node, event): void => node.handleNestedPointerCancel(event));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private handleNestedPointerEnter(event: PointerEventArgs): void {
|
|
142
|
+
const owner = this.owner;
|
|
143
|
+
if (owner !== null) {
|
|
144
|
+
owner.popupListHighlightIndex(this.slotIndex);
|
|
145
|
+
}
|
|
146
|
+
event.handled = true;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private handleNestedPointerDown(event: PointerEventArgs): void {
|
|
150
|
+
const owner = this.owner;
|
|
151
|
+
if (owner !== null) {
|
|
152
|
+
this.capturePointer();
|
|
153
|
+
owner.popupListPointerDown(this.slotIndex);
|
|
154
|
+
}
|
|
155
|
+
event.handled = true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private handleNestedPointerUp(event: PointerEventArgs): void {
|
|
159
|
+
const owner = this.owner;
|
|
160
|
+
if (owner !== null) {
|
|
161
|
+
this.releasePointer();
|
|
162
|
+
owner.popupListPointerUp(this.slotIndex);
|
|
163
|
+
owner.popupListActivateIndex(this.slotIndex);
|
|
164
|
+
}
|
|
165
|
+
event.handled = true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private handleNestedPointerCancel(event: PointerEventArgs): void {
|
|
169
|
+
const owner = this.owner;
|
|
170
|
+
if (owner !== null) {
|
|
171
|
+
this.releasePointer();
|
|
172
|
+
owner.popupListPointerUp(this.slotIndex);
|
|
173
|
+
}
|
|
174
|
+
event.handled = true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private syncPresenterLayout(): void {
|
|
178
|
+
this.height(this.presenter.metrics.height, Unit.Pixel);
|
|
179
|
+
this.presenter.root.fillSize();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export class SelectablePopupList {
|
|
184
|
+
readonly root: Portal;
|
|
185
|
+
readonly panelNode: FlexBox;
|
|
186
|
+
readonly popupPresenter: PopupPresenter;
|
|
187
|
+
readonly popupScrollBox: ScrollBox;
|
|
188
|
+
readonly optionsHost: FlexBox;
|
|
189
|
+
|
|
190
|
+
private readonly optionNodes: Array<SelectablePopupListOptionNode> = new Array<SelectablePopupListOptionNode>();
|
|
191
|
+
private optionRowTemplateValue: DropdownOptionRowTemplate | null = null;
|
|
192
|
+
private sizingValue: DropdownSizing | null = null;
|
|
193
|
+
private colorsValue: DropdownColors | null = null;
|
|
194
|
+
private highlightedIndexValue: i32 = -1;
|
|
195
|
+
private maxVisibleItemsValue: i32 = UNLIMITED_VISIBLE_ITEMS;
|
|
196
|
+
private popupWidthValue: f32 = 0.0;
|
|
197
|
+
|
|
198
|
+
constructor(private readonly owner: SelectablePopupListOwner) {
|
|
199
|
+
const root = new Portal()
|
|
200
|
+
.positionAbsolute()
|
|
201
|
+
.position(0.0, 0.0)
|
|
202
|
+
.width(100.0, Unit.Percent)
|
|
203
|
+
.height(100.0, Unit.Percent) as Portal;
|
|
204
|
+
const popupScrollBox = new ScrollBox()
|
|
205
|
+
.scrollEnabledX(false)
|
|
206
|
+
.scrollEnabledY(true)
|
|
207
|
+
.horizontalScrollbarVisibility(ScrollBarVisibility.Never)
|
|
208
|
+
.verticalScrollbarVisibility(ScrollBarVisibility.Auto);
|
|
209
|
+
const optionsHost = new FlexBox()
|
|
210
|
+
.flexDirection(FlexDirection.Column);
|
|
211
|
+
const panelNode = new FlexBox()
|
|
212
|
+
.positionAbsolute()
|
|
213
|
+
.flexDirection(FlexDirection.Column);
|
|
214
|
+
// Dropdown and combobox popups should add list semantics without replacing
|
|
215
|
+
// the trigger/editor subtree from the active semantic tree.
|
|
216
|
+
const popupPresenter = new PopupPresenter(root, panelNode, null);
|
|
217
|
+
optionsHost.semanticRole(SemanticRole.List);
|
|
218
|
+
optionsHost.semanticLabel("Dropdown options");
|
|
219
|
+
popupScrollBox.child(optionsHost);
|
|
220
|
+
panelNode.child(popupScrollBox);
|
|
221
|
+
this.root = root;
|
|
222
|
+
this.panelNode = panelNode;
|
|
223
|
+
this.popupPresenter = popupPresenter;
|
|
224
|
+
this.popupScrollBox = popupScrollBox;
|
|
225
|
+
this.optionsHost = optionsHost;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
get isOpen(): bool {
|
|
229
|
+
return this.popupPresenter.isOpen;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
get highlightedIndex(): i32 {
|
|
233
|
+
return this.highlightedIndexValue;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
maxVisibleItems(count: i32): void {
|
|
237
|
+
if (count <= 0) {
|
|
238
|
+
warn("Layout", "Dropdown.maxVisibleItems() received " + count.toString() + "; using unlimited visible items.");
|
|
239
|
+
}
|
|
240
|
+
this.maxVisibleItemsValue = count > 0 ? count : UNLIMITED_VISIBLE_ITEMS;
|
|
241
|
+
this.refreshPanelLayout();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
popupWidth(value: f32): void {
|
|
245
|
+
if (value <= 0.0) {
|
|
246
|
+
warn("Layout", "Dropdown.popupWidth() received " + value.toString() + "; clamping to 0.0.");
|
|
247
|
+
}
|
|
248
|
+
this.popupWidthValue = value > 0.0 ? value : 0.0;
|
|
249
|
+
this.refreshPanelLayout();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
sizing(sizing: DropdownSizing | null): void {
|
|
253
|
+
this.sizingValue = sizing;
|
|
254
|
+
for (let index = 0; index < this.optionNodes.length; ++index) {
|
|
255
|
+
unchecked(this.optionNodes[index]).template(this.optionRowTemplateValue, this.sizingValue);
|
|
256
|
+
}
|
|
257
|
+
this.refreshPanelLayout();
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
colors(colors: DropdownColors | null): void {
|
|
261
|
+
this.colorsValue = colors;
|
|
262
|
+
this.syncOptionVisuals();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
optionRowTemplate(template: DropdownOptionRowTemplate | null): void {
|
|
266
|
+
this.optionRowTemplateValue = template;
|
|
267
|
+
for (let index = 0; index < this.optionNodes.length; ++index) {
|
|
268
|
+
unchecked(this.optionNodes[index]).template(template, this.sizingValue);
|
|
269
|
+
}
|
|
270
|
+
this.refreshPanelLayout();
|
|
271
|
+
this.syncOptionVisuals();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
open(triggerX: f32, triggerY: f32, triggerWidth: f32, triggerHeight: f32, initialHighlightIndex: i32): bool {
|
|
275
|
+
if (this.isOpen || this.owner.getPopupListItemCount() == 0 || this.root.builtHandle == <u64>HandleValue.Invalid) {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
this.ensureOptionNodes();
|
|
279
|
+
this.rebuildPanel();
|
|
280
|
+
this.setHighlightedIndex(initialHighlightIndex);
|
|
281
|
+
this.positionPanel(triggerX, triggerY, triggerWidth, triggerHeight);
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
refreshOpen(triggerX: f32, triggerY: f32, triggerWidth: f32, triggerHeight: f32, highlightedIndex: i32): void {
|
|
286
|
+
if (!this.isOpen) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
this.ensureOptionNodes();
|
|
290
|
+
this.rebuildPanel();
|
|
291
|
+
const count = this.owner.getPopupListItemCount();
|
|
292
|
+
let nextHighlight = highlightedIndex;
|
|
293
|
+
if (nextHighlight >= count) {
|
|
294
|
+
nextHighlight = count > 0 ? count - 1 : -1;
|
|
295
|
+
}
|
|
296
|
+
if (nextHighlight < 0 && count > 0) {
|
|
297
|
+
nextHighlight = 0;
|
|
298
|
+
}
|
|
299
|
+
this.setHighlightedIndex(nextHighlight);
|
|
300
|
+
this.positionPanel(triggerX, triggerY, triggerWidth, triggerHeight);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
close(): void {
|
|
304
|
+
this.popupPresenter.hide();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
dispose(): void {
|
|
308
|
+
this.popupPresenter.dispose();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
clear(): void {
|
|
312
|
+
this.close();
|
|
313
|
+
this.highlightedIndexValue = -1;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
setHighlightedIndex(index: i32): void {
|
|
317
|
+
this.highlightedIndexValue = index;
|
|
318
|
+
this.syncOptionVisuals();
|
|
319
|
+
this.ensureHighlightedVisible();
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
highlightIndex(index: i32): void {
|
|
323
|
+
if (index < 0 || index >= this.owner.getPopupListItemCount() || this.highlightedIndexValue == index) {
|
|
324
|
+
if (index < 0 || index >= this.owner.getPopupListItemCount()) {
|
|
325
|
+
warn("Layout", "Dropdown.highlightIndex() received " + index.toString() + " outside the available item range.");
|
|
326
|
+
}
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
this.highlightedIndexValue = index;
|
|
330
|
+
this.syncOptionVisuals();
|
|
331
|
+
this.ensureHighlightedVisible();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
moveHighlight(delta: i32): void {
|
|
335
|
+
const count = this.owner.getPopupListItemCount();
|
|
336
|
+
if (count == 0) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
let nextIndex = this.highlightedIndexValue;
|
|
340
|
+
if (nextIndex < 0) {
|
|
341
|
+
nextIndex = 0;
|
|
342
|
+
}
|
|
343
|
+
nextIndex += delta;
|
|
344
|
+
if (nextIndex < 0) {
|
|
345
|
+
nextIndex = count - 1;
|
|
346
|
+
} else if (nextIndex >= count) {
|
|
347
|
+
nextIndex = 0;
|
|
348
|
+
}
|
|
349
|
+
this.highlightIndex(nextIndex);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
refreshPanelLayout(): void {
|
|
353
|
+
const count = this.owner.getPopupListItemCount();
|
|
354
|
+
this.optionsHost.width(100.0, Unit.Percent);
|
|
355
|
+
this.optionsHost.height(<f32>count * this.resolveOptionRowHeight(), Unit.Pixel);
|
|
356
|
+
this.popupScrollBox.width(100.0, Unit.Percent);
|
|
357
|
+
this.popupScrollBox.height(
|
|
358
|
+
<f32>Math.max(0.0, this.resolveViewportClampedPanelOuterHeight() - (SELECTABLE_POPUP_LIST_PANEL_PADDING * 2.0)),
|
|
359
|
+
Unit.Pixel,
|
|
360
|
+
);
|
|
361
|
+
if (this.isOpen) {
|
|
362
|
+
this.ensureHighlightedVisible();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
positionPanel(triggerX: f32, triggerY: f32, triggerWidth: f32, triggerHeight: f32): void {
|
|
367
|
+
const popupWidth = this.resolvePopupWidth(triggerWidth);
|
|
368
|
+
const panelHeight = this.resolveViewportClampedPanelOuterHeight();
|
|
369
|
+
this.panelNode.width(popupWidth, Unit.Pixel);
|
|
370
|
+
this.panelNode.height(panelHeight, Unit.Pixel);
|
|
371
|
+
this.popupScrollBox.width(100.0, Unit.Percent);
|
|
372
|
+
this.popupScrollBox.height(<f32>Math.max(0.0, panelHeight - (SELECTABLE_POPUP_LIST_PANEL_PADDING * 2.0)), Unit.Pixel);
|
|
373
|
+
this.popupPresenter.showAnchored(triggerX, triggerY, triggerWidth, triggerHeight, popupWidth, panelHeight);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
syncOptionVisuals(): void {
|
|
377
|
+
this.ensureOptionNodes();
|
|
378
|
+
const theme = activeTheme.value;
|
|
379
|
+
const count = this.owner.getPopupListItemCount();
|
|
380
|
+
for (let index = 0; index < count; ++index) {
|
|
381
|
+
unchecked(this.optionNodes[index]).applyTheme(
|
|
382
|
+
theme,
|
|
383
|
+
index == this.highlightedIndexValue,
|
|
384
|
+
this.owner.isPopupListItemSelected(index),
|
|
385
|
+
this.owner.isPopupListEnabled(),
|
|
386
|
+
this.colorsValue,
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
private rebuildPanel(): void {
|
|
392
|
+
for (let index = 0; index < this.optionNodes.length; ++index) {
|
|
393
|
+
this.optionsHost.removeChildNode(unchecked(this.optionNodes[index]));
|
|
394
|
+
}
|
|
395
|
+
const count = this.owner.getPopupListItemCount();
|
|
396
|
+
for (let index = 0; index < count; ++index) {
|
|
397
|
+
const optionNode = unchecked(this.optionNodes[index]);
|
|
398
|
+
optionNode.label(this.owner.getPopupListItemLabel(index));
|
|
399
|
+
this.optionsHost.addChildNode(optionNode);
|
|
400
|
+
}
|
|
401
|
+
this.refreshPanelLayout();
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
private ensureOptionNodes(): void {
|
|
405
|
+
const count = this.owner.getPopupListItemCount();
|
|
406
|
+
while (this.optionNodes.length < count) {
|
|
407
|
+
const optionNode = new SelectablePopupListOptionNode(this.optionRowTemplateValue, this.sizingValue).bindOwner(this.owner, this.optionNodes.length);
|
|
408
|
+
this.optionNodes.push(optionNode);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
private resolveOptionRowHeight(): f32 {
|
|
413
|
+
if (this.optionNodes.length == 0) {
|
|
414
|
+
const sizing = this.sizingValue;
|
|
415
|
+
if (sizing !== null && sizing.hasOptionHeight && this.optionRowTemplateValue === null) {
|
|
416
|
+
return sizing.optionHeightPx;
|
|
417
|
+
}
|
|
418
|
+
return OPTION_HEIGHT;
|
|
419
|
+
}
|
|
420
|
+
return unchecked(this.optionNodes[0]).rowHeight;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
private resolveVisibleItemCount(): i32 {
|
|
424
|
+
const count = this.owner.getPopupListItemCount();
|
|
425
|
+
if (this.maxVisibleItemsValue <= 0 || count <= this.maxVisibleItemsValue) {
|
|
426
|
+
return count;
|
|
427
|
+
}
|
|
428
|
+
return this.maxVisibleItemsValue;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private resolvePanelOuterHeight(): f32 {
|
|
432
|
+
return <f32>this.resolveVisibleItemCount() * this.resolveOptionRowHeight() + (SELECTABLE_POPUP_LIST_PANEL_PADDING * 2.0);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
private resolveViewportClampedPanelOuterHeight(): f32 {
|
|
436
|
+
const maxHeight = <f32>Math.max(PANEL_EDGE_PADDING, ui.getViewportHeight() - (PANEL_EDGE_PADDING * 2.0));
|
|
437
|
+
return <f32>Math.min(this.resolvePanelOuterHeight(), maxHeight);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
private resolvePopupWidth(triggerWidth: f32): f32 {
|
|
441
|
+
return this.popupWidthValue > 0.0 ? this.popupWidthValue : triggerWidth;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
private ensureHighlightedVisible(): void {
|
|
445
|
+
if (!this.isOpen || this.highlightedIndexValue < 0) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const visibleHeight = <f32>Math.max(0.0, this.resolveViewportClampedPanelOuterHeight() - (SELECTABLE_POPUP_LIST_PANEL_PADDING * 2.0));
|
|
449
|
+
if (visibleHeight <= 0.0) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
const rowHeight = this.resolveOptionRowHeight();
|
|
453
|
+
const itemTop = <f32>this.highlightedIndexValue * rowHeight;
|
|
454
|
+
const itemBottom = itemTop + rowHeight;
|
|
455
|
+
let nextOffset = this.popupScrollBox.scrollState.offsetY.value;
|
|
456
|
+
if (itemTop < nextOffset) {
|
|
457
|
+
nextOffset = itemTop;
|
|
458
|
+
} else if (itemBottom > nextOffset + visibleHeight) {
|
|
459
|
+
nextOffset = itemBottom - visibleHeight;
|
|
460
|
+
}
|
|
461
|
+
this.popupScrollBox.setRuntimeScrollOffset(0.0, nextOffset);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Orientation, Unit } from "../../core/ffi";
|
|
2
2
|
import { Theme } from "../../core/Theme";
|
|
3
3
|
import { FlexBox } from "../../nodes";
|
|
4
4
|
import { SliderSizing } from "../ControlSizing";
|
|
@@ -81,7 +81,7 @@ export abstract class SliderPresenter {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
export abstract class SliderTemplate {
|
|
84
|
-
abstract create(): SliderPresenter;
|
|
84
|
+
abstract create(sizing?: SliderSizing | null): SliderPresenter;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
class DefaultSliderPresenter extends SliderPresenter {
|
|
@@ -184,13 +184,13 @@ class DefaultSliderPresenter extends SliderPresenter {
|
|
|
184
184
|
.cornerRadius(thumbSize * 0.5);
|
|
185
185
|
const thumbColor = colors !== null && colors.hasThumb ? colors.thumbColor : accent;
|
|
186
186
|
this.thumbNode.bgColor(thumbColor);
|
|
187
|
-
this.thumbNode.border(1.0, theme.colors.surface
|
|
187
|
+
this.thumbNode.border(1.0, theme.colors.surface);
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
class DefaultSliderTemplate extends SliderTemplate {
|
|
192
|
-
create(): SliderPresenter {
|
|
193
|
-
return
|
|
192
|
+
create(sizing: SliderSizing | null = null): SliderPresenter {
|
|
193
|
+
return createDefaultSliderPresenter(sizing);
|
|
194
194
|
}
|
|
195
195
|
}
|
|
196
196
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AlignItems, Unit } from "../../core/ffi";
|
|
2
2
|
import { Theme } from "../../core/Theme";
|
|
3
3
|
import { FlexBox } from "../../nodes";
|
|
4
|
+
import { LabeledControlSizing } from "../ControlSizing";
|
|
4
5
|
import { LabeledControlColors } from "../LabeledControlColors";
|
|
5
6
|
import {
|
|
6
7
|
PressableIndicatorMetrics,
|
|
@@ -29,25 +30,62 @@ export abstract class SwitchIndicatorPresenter extends PressableIndicatorPresent
|
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
export abstract class SwitchIndicatorTemplate {
|
|
32
|
-
abstract create(): SwitchIndicatorPresenter;
|
|
33
|
+
abstract create(sizing?: LabeledControlSizing | null): SwitchIndicatorPresenter;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
class SwitchIndicatorMetrics extends PressableIndicatorMetrics {
|
|
37
|
+
constructor(
|
|
38
|
+
readonly trackWidth: f32,
|
|
39
|
+
readonly trackHeight: f32,
|
|
40
|
+
readonly thumbSize: f32,
|
|
41
|
+
readonly thumbX: f32,
|
|
42
|
+
readonly thumbCheckedX: f32,
|
|
43
|
+
readonly thumbY: f32,
|
|
44
|
+
) {
|
|
45
|
+
super(trackWidth, trackHeight);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const DEFAULT_SWITCH_METRICS = new SwitchIndicatorMetrics(44.0, 26.0, 20.0, 3.0, 21.0, 2.0);
|
|
50
|
+
|
|
51
|
+
function resolveSwitchMetrics(sizing: LabeledControlSizing | null): SwitchIndicatorMetrics {
|
|
52
|
+
if (sizing === null || !sizing.hasIndicatorSize) {
|
|
53
|
+
return DEFAULT_SWITCH_METRICS;
|
|
54
|
+
}
|
|
55
|
+
const scale = sizing.indicatorSizePx / DEFAULT_SWITCH_METRICS.trackHeight;
|
|
56
|
+
const trackWidth = DEFAULT_SWITCH_METRICS.trackWidth * scale;
|
|
57
|
+
const trackHeight = DEFAULT_SWITCH_METRICS.trackHeight * scale;
|
|
58
|
+
const thumbSize = DEFAULT_SWITCH_METRICS.thumbSize * scale;
|
|
59
|
+
const thumbX = DEFAULT_SWITCH_METRICS.thumbX * scale;
|
|
60
|
+
const thumbY = DEFAULT_SWITCH_METRICS.thumbY * scale;
|
|
61
|
+
return new SwitchIndicatorMetrics(
|
|
62
|
+
trackWidth,
|
|
63
|
+
trackHeight,
|
|
64
|
+
thumbSize,
|
|
65
|
+
thumbX,
|
|
66
|
+
trackWidth - thumbSize - thumbX,
|
|
67
|
+
thumbY,
|
|
68
|
+
);
|
|
33
69
|
}
|
|
34
70
|
|
|
35
71
|
class DefaultSwitchIndicatorPresenter extends SwitchIndicatorPresenter {
|
|
72
|
+
private readonly geometry: SwitchIndicatorMetrics;
|
|
36
73
|
private readonly thumbNode: FlexBox;
|
|
37
74
|
|
|
38
|
-
constructor() {
|
|
75
|
+
constructor(metrics: SwitchIndicatorMetrics = DEFAULT_SWITCH_METRICS) {
|
|
39
76
|
const root = new FlexBox()
|
|
40
|
-
.width(
|
|
41
|
-
.height(
|
|
77
|
+
.width(metrics.trackWidth, Unit.Pixel)
|
|
78
|
+
.height(metrics.trackHeight, Unit.Pixel)
|
|
42
79
|
.clipToBounds(true);
|
|
43
|
-
super(root,
|
|
80
|
+
super(root, metrics);
|
|
81
|
+
this.geometry = metrics;
|
|
44
82
|
const thumbNode = new FlexBox()
|
|
45
83
|
.positionAbsolute()
|
|
46
|
-
.position(
|
|
47
|
-
.width(
|
|
48
|
-
.height(
|
|
84
|
+
.position(metrics.thumbX, metrics.thumbY)
|
|
85
|
+
.width(metrics.thumbSize, Unit.Pixel)
|
|
86
|
+
.height(metrics.thumbSize, Unit.Pixel);
|
|
49
87
|
this.thumbNode = thumbNode;
|
|
50
|
-
root.alignItems(
|
|
88
|
+
root.alignItems(AlignItems.Center).child(thumbNode);
|
|
51
89
|
}
|
|
52
90
|
|
|
53
91
|
apply(theme: Theme, state: SwitchIndicatorVisualState, colors: LabeledControlColors | null = null): void {
|
|
@@ -62,20 +100,28 @@ class DefaultSwitchIndicatorPresenter extends SwitchIndicatorPresenter {
|
|
|
62
100
|
const borderColor = colors !== null && colors.hasBorder
|
|
63
101
|
? colors.borderColor
|
|
64
102
|
: (state.checked ? trackColor : theme.colors.border);
|
|
65
|
-
this.
|
|
66
|
-
this.root.
|
|
103
|
+
const geometry = this.geometry;
|
|
104
|
+
this.root.cornerRadius(geometry.trackHeight * 0.5);
|
|
105
|
+
this.root.border(1.0, borderColor);
|
|
67
106
|
this.root.bgColor(trackColor);
|
|
68
|
-
this.thumbNode
|
|
69
|
-
|
|
107
|
+
this.thumbNode
|
|
108
|
+
.position(state.checked ? geometry.thumbCheckedX : geometry.thumbX, geometry.thumbY)
|
|
109
|
+
.width(geometry.thumbSize, Unit.Pixel)
|
|
110
|
+
.height(geometry.thumbSize, Unit.Pixel);
|
|
111
|
+
this.thumbNode.cornerRadius(geometry.thumbSize * 0.5);
|
|
70
112
|
this.thumbNode.bgColor(colors !== null && colors.hasBackground ? colors.backgroundColor : theme.colors.surface);
|
|
71
|
-
this.thumbNode.border(1.0, borderColor
|
|
113
|
+
this.thumbNode.border(1.0, borderColor);
|
|
72
114
|
}
|
|
73
115
|
}
|
|
74
116
|
|
|
75
117
|
class DefaultSwitchIndicatorTemplate extends SwitchIndicatorTemplate {
|
|
76
|
-
create(): SwitchIndicatorPresenter {
|
|
77
|
-
return
|
|
118
|
+
create(sizing: LabeledControlSizing | null = null): SwitchIndicatorPresenter {
|
|
119
|
+
return createDefaultSwitchIndicatorPresenter(sizing);
|
|
78
120
|
}
|
|
79
121
|
}
|
|
80
122
|
|
|
81
123
|
export const defaultSwitchIndicatorTemplate = new DefaultSwitchIndicatorTemplate();
|
|
124
|
+
|
|
125
|
+
export function createDefaultSwitchIndicatorPresenter(sizing: LabeledControlSizing | null = null): SwitchIndicatorPresenter {
|
|
126
|
+
return new DefaultSwitchIndicatorPresenter(resolveSwitchMetrics(sizing));
|
|
127
|
+
}
|