@effindomv2/fui-as 0.1.37 → 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 +23 -13
- package/scripts/build.sh +47 -46
- 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 +65 -5
- package/src/FuiExports.ts +12 -1
- package/src/bindings/ui.ts +157 -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 +17 -8
- package/src/core/Bitmap.ts +192 -3
- 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 +104 -5
- 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 -865
- 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 +17 -1
- package/src/drawing/DrawContext.ts +140 -32
- package/src/nodes/CustomDrawable.ts +8 -1
- package/src/nodes/FlexBox.ts +40 -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 +1 -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-canvas-host.ts +0 -72
- 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 -1472
- 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 -206
- package/browser/src/common-harness/ui-chrome.ts +0 -206
- package/browser/src/common-harness/ui-imports.ts +0 -559
- 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,1201 @@
|
|
|
1
|
+
import * as ui from "../bindings/ui";
|
|
2
|
+
import { HandlerAction } from "../core/Action";
|
|
3
|
+
import { Callback1, Handler1 } from "../core/BoundCallback";
|
|
4
|
+
import { Disposable, disposeAll } from "../core/Disposable";
|
|
5
|
+
import { EventRouter, GlobalKeyHandler } from "../core/EventRouter";
|
|
6
|
+
import { FocusAdornerManager } from "../core/FocusAdornerManager";
|
|
7
|
+
import { keyboardFocusVisible } from "../core/FocusVisibility";
|
|
8
|
+
import { afterNextCommitWith, markNeedsCommit } from "../core/FrameScheduler";
|
|
9
|
+
import {
|
|
10
|
+
AlignItems,
|
|
11
|
+
CursorStyle,
|
|
12
|
+
FlexDirection,
|
|
13
|
+
JustifyContent,
|
|
14
|
+
KeyModifier,
|
|
15
|
+
KeyEventType,
|
|
16
|
+
PointerEventType,
|
|
17
|
+
SemanticRole,
|
|
18
|
+
Unit,
|
|
19
|
+
} from "../core/ffi";
|
|
20
|
+
import { warn } from "../core/Logger";
|
|
21
|
+
import { ComboBoxChangedEventArgs, Node, TextChangedEventArgs } from "../core/Node";
|
|
22
|
+
import { Theme, activeTheme } from "../core/Theme";
|
|
23
|
+
import { bind1, bind2 } from "../core/bind";
|
|
24
|
+
import { registerScrollHook } from "../core/ScrollHooks";
|
|
25
|
+
import { FlexBox, ScrollView } from "../nodes";
|
|
26
|
+
import { TextInput } from "./TextInput";
|
|
27
|
+
import { TextInputColors } from "./TextInputColors";
|
|
28
|
+
import { TextInputPresenter, TextInputTemplate, TextInputVisualState } from "./internal/TextInputPresenter";
|
|
29
|
+
import { DropdownSizing } from "./ControlSizing";
|
|
30
|
+
import { DropdownColors } from "./DropdownColors";
|
|
31
|
+
import {
|
|
32
|
+
createDefaultDropdownChevronPresenter,
|
|
33
|
+
DropdownChevronPresenter,
|
|
34
|
+
DropdownChevronTemplate,
|
|
35
|
+
DropdownChevronVisualState,
|
|
36
|
+
} from "./internal/DropdownChevronPresenter";
|
|
37
|
+
import { DropdownOptionRowTemplate } from "./internal/DropdownOptionRowPresenter";
|
|
38
|
+
import {
|
|
39
|
+
SELECTABLE_POPUP_LIST_PANEL_PADDING,
|
|
40
|
+
SelectablePopupList,
|
|
41
|
+
SelectablePopupListOwner,
|
|
42
|
+
} from "./internal/SelectablePopupList";
|
|
43
|
+
|
|
44
|
+
const DEFAULT_PANEL_BACKGROUND_BLUR_SIGMA: f32 = 10.0;
|
|
45
|
+
|
|
46
|
+
function stringsEqualIgnoreCase(left: string, right: string): bool {
|
|
47
|
+
return left.toLowerCase() == right.toLowerCase();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function stringContainsIgnoreCase(value: string, query: string): bool {
|
|
51
|
+
return value.toLowerCase().includes(query.toLowerCase());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function stringStartsWithIgnoreCase(value: string, query: string): bool {
|
|
55
|
+
return value.toLowerCase().startsWith(query.toLowerCase());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function readCodePoint(text: string, index: i32): u32 {
|
|
59
|
+
const first = <u32>text.charCodeAt(index);
|
|
60
|
+
if (first < 0xD800 || first > 0xDBFF || index + 1 >= text.length) {
|
|
61
|
+
return first;
|
|
62
|
+
}
|
|
63
|
+
const second = <u32>text.charCodeAt(index + 1);
|
|
64
|
+
if (second < 0xDC00 || second > 0xDFFF) {
|
|
65
|
+
return first;
|
|
66
|
+
}
|
|
67
|
+
return 0x10000 + ((first - 0xD800) << 10) + (second - 0xDC00);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function codePointStringLength(codePoint: u32): i32 {
|
|
71
|
+
return codePoint > 0xFFFF ? 2 : 1;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function codePointCount(text: string): u32 {
|
|
75
|
+
let count: u32 = 0;
|
|
76
|
+
let cursor = 0;
|
|
77
|
+
while (cursor < text.length) {
|
|
78
|
+
const codePoint = readCodePoint(text, cursor);
|
|
79
|
+
cursor += codePointStringLength(codePoint);
|
|
80
|
+
count += 1;
|
|
81
|
+
}
|
|
82
|
+
return count;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function createChevronPresenter(template: DropdownChevronTemplate | null, sizing: DropdownSizing | null = null): DropdownChevronPresenter {
|
|
86
|
+
if (template !== null) {
|
|
87
|
+
return template.create(sizing);
|
|
88
|
+
}
|
|
89
|
+
return createDefaultDropdownChevronPresenter(sizing);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function handleComboBoxDeferredBlurClose(comboBox: ComboBox): void {
|
|
93
|
+
comboBox.fireDeferredBlurClose();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
class ComboBoxEditorPresenter extends TextInputPresenter {
|
|
97
|
+
apply(_theme: Theme, state: TextInputVisualState, _colors: TextInputColors | null = null): void {
|
|
98
|
+
const editableCursor = state.enabled ? CursorStyle.Text : CursorStyle.Default;
|
|
99
|
+
this.host
|
|
100
|
+
.bgColor(0x00000000)
|
|
101
|
+
.cornerRadius(0.0)
|
|
102
|
+
.border(0.0, 0x00000000)
|
|
103
|
+
.padding(0.0, 0.0, 0.0, 0.0)
|
|
104
|
+
.alignItems(AlignItems.Center)
|
|
105
|
+
.cursor(editableCursor);
|
|
106
|
+
this.host.opacity(state.enabled ? 1.0 : 0.6);
|
|
107
|
+
this.editorHost.cursor(editableCursor);
|
|
108
|
+
this.placeholderHost
|
|
109
|
+
.position(0.0, 0.0)
|
|
110
|
+
.width(100.0, Unit.Percent)
|
|
111
|
+
.cursor(editableCursor);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
class ComboBoxEditorTemplate extends TextInputTemplate {
|
|
116
|
+
create(): TextInputPresenter {
|
|
117
|
+
return new ComboBoxEditorPresenter();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function resolveTextInputColors(colors: DropdownColors | null, theme: Theme): TextInputColors | null {
|
|
122
|
+
if (colors === null) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const inputColors = new TextInputColors();
|
|
126
|
+
if (colors.hasBackground) {
|
|
127
|
+
inputColors.background(colors.backgroundColor);
|
|
128
|
+
}
|
|
129
|
+
if (colors.hasTextPrimary) {
|
|
130
|
+
inputColors.textPrimary(colors.textPrimaryColor);
|
|
131
|
+
}
|
|
132
|
+
if (colors.hasPlaceholder) {
|
|
133
|
+
inputColors.placeholder(colors.placeholderColor);
|
|
134
|
+
}
|
|
135
|
+
if (colors.hasBorder) {
|
|
136
|
+
inputColors.border(colors.borderColor);
|
|
137
|
+
}
|
|
138
|
+
if (colors.hasAccent) {
|
|
139
|
+
inputColors.accent(colors.accentColor).caret(colors.accentColor);
|
|
140
|
+
} else {
|
|
141
|
+
inputColors.caret(theme.colors.accent);
|
|
142
|
+
}
|
|
143
|
+
return inputColors;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export enum ComboBoxFilterMode {
|
|
147
|
+
None = 0,
|
|
148
|
+
StartsWith = 1,
|
|
149
|
+
Contains = 2,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export enum ComboBoxCommitMode {
|
|
153
|
+
KeepText = 0,
|
|
154
|
+
RevertToSelection = 1,
|
|
155
|
+
SelectExactMatch = 2,
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export class ComboBoxItem {
|
|
159
|
+
constructor(readonly value: string) {}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export class ComboBox extends FlexBox implements GlobalKeyHandler, SelectablePopupListOwner {
|
|
163
|
+
private static activeInstance: ComboBox | null = null;
|
|
164
|
+
private static scrollHookRegistered: bool = false;
|
|
165
|
+
|
|
166
|
+
private editor: TextInput = changetype<TextInput>(0);
|
|
167
|
+
private chevronHost: FlexBox = changetype<FlexBox>(0);
|
|
168
|
+
private chevronPresenter: DropdownChevronPresenter = changetype<DropdownChevronPresenter>(0);
|
|
169
|
+
private popupList: SelectablePopupList = changetype<SelectablePopupList>(0);
|
|
170
|
+
private readonly itemsValue: Array<ComboBoxItem> = new Array<ComboBoxItem>();
|
|
171
|
+
private readonly filteredIndices: Array<i32> = new Array<i32>();
|
|
172
|
+
private readonly disposables: Array<Disposable> = new Array<Disposable>();
|
|
173
|
+
private disposed: bool = false;
|
|
174
|
+
private openState: bool = false;
|
|
175
|
+
private popupPointerPressedState: bool = false;
|
|
176
|
+
private pointerPressedState: bool = false;
|
|
177
|
+
private hoveredState: bool = false;
|
|
178
|
+
private focusedState: bool = false;
|
|
179
|
+
private wrapperFocusedState: bool = false;
|
|
180
|
+
private editorFocusedState: bool = false;
|
|
181
|
+
private deferredBlurClosePendingState: bool = false;
|
|
182
|
+
private allowCustomValue: bool = true;
|
|
183
|
+
private autoCompleteValue: bool = false;
|
|
184
|
+
private openOnFocusValue: bool = false;
|
|
185
|
+
private staysOpenOnEditValue: bool = true;
|
|
186
|
+
private filterModeValue: ComboBoxFilterMode = ComboBoxFilterMode.Contains;
|
|
187
|
+
private commitModeValue: ComboBoxCommitMode = ComboBoxCommitMode.KeepText;
|
|
188
|
+
private keyFilterToken: u32 = 0;
|
|
189
|
+
private selectedIndexValue: i32 = -1;
|
|
190
|
+
private committedSelectedIndexValue: i32 = -1;
|
|
191
|
+
private highlightedIndexValue: i32 = -1;
|
|
192
|
+
private textValue: string = "";
|
|
193
|
+
private chevronTemplateValue: DropdownChevronTemplate | null = null;
|
|
194
|
+
private sizingValue: DropdownSizing | null = null;
|
|
195
|
+
private colorsValue: DropdownColors | null = null;
|
|
196
|
+
private popupPanelColorValue: u32 = 0x00000000;
|
|
197
|
+
private popupPanelBackgroundBlurSigmaValue: f32 = DEFAULT_PANEL_BACKGROUND_BLUR_SIGMA;
|
|
198
|
+
private popupPanelColorOverridden: bool = false;
|
|
199
|
+
private popupPanelBackgroundBlurOverridden: bool = false;
|
|
200
|
+
private suppressEditorChanged: bool = false;
|
|
201
|
+
private lastAutoCompleteTextValue: string = "";
|
|
202
|
+
private changedCallback: ((event: ComboBoxChangedEventArgs<ComboBoxItem>) => void) | null = null;
|
|
203
|
+
private changedBinding: Callback1<ComboBoxChangedEventArgs<ComboBoxItem>> | null = null;
|
|
204
|
+
private textChangedCallback: ((event: TextChangedEventArgs) => void) | null = null;
|
|
205
|
+
private textChangedBinding: Callback1<TextChangedEventArgs> | null = null;
|
|
206
|
+
|
|
207
|
+
constructor(text: string = "") {
|
|
208
|
+
super();
|
|
209
|
+
ComboBox.ensureScrollHook();
|
|
210
|
+
const editor = new TextInput(text);
|
|
211
|
+
const chevronPresenter = createChevronPresenter(null, null);
|
|
212
|
+
const chevronHost = new FlexBox()
|
|
213
|
+
.width(32.0, Unit.Pixel)
|
|
214
|
+
.height(100.0, Unit.Percent)
|
|
215
|
+
.alignItems(AlignItems.Center)
|
|
216
|
+
.justifyContent(JustifyContent.Center);
|
|
217
|
+
const popupList = new SelectablePopupList(this);
|
|
218
|
+
this.editor = editor;
|
|
219
|
+
this.chevronPresenter = chevronPresenter;
|
|
220
|
+
this.chevronHost = chevronHost;
|
|
221
|
+
this.popupList = popupList;
|
|
222
|
+
this.textValue = text;
|
|
223
|
+
this.rebuildFilteredIndices();
|
|
224
|
+
popupList.popupPresenter.overlayNode.onPointerClickWith(this, (comboBox, _event) => comboBox.close());
|
|
225
|
+
editor
|
|
226
|
+
.template(new ComboBoxEditorTemplate())
|
|
227
|
+
.fillWidth();
|
|
228
|
+
editor
|
|
229
|
+
.onChangedWith(this, (comboBox, event): void => comboBox.handleEditorTextChanged(event.text))
|
|
230
|
+
.onFocusChangedWith(this, (comboBox, event): void => comboBox.handleEditorFocusChanged(event.focused));
|
|
231
|
+
editor.editorNode.onKeyDownWith(this, (comboBox, event): void => {
|
|
232
|
+
if (comboBox.handleEditorKeyDown(event.key, event.modifiers)) {
|
|
233
|
+
event.handled = true;
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
editor.editorNode._editorCommandKeys();
|
|
237
|
+
chevronHost.child(chevronPresenter.root);
|
|
238
|
+
chevronHost
|
|
239
|
+
.onPointerClickWith(this, (comboBox, _event): void => comboBox.toggleFromChevron())
|
|
240
|
+
.onPointerEnterWith(this, (comboBox, _event): void => {
|
|
241
|
+
comboBox.hoveredState = true;
|
|
242
|
+
comboBox.handleThemeChanged();
|
|
243
|
+
})
|
|
244
|
+
.onPointerLeaveWith(this, (comboBox, _event): void => {
|
|
245
|
+
comboBox.pointerPressedState = false;
|
|
246
|
+
comboBox.hoveredState = false;
|
|
247
|
+
comboBox.handleThemeChanged();
|
|
248
|
+
})
|
|
249
|
+
.onPointerDownWith(this, (comboBox, _event): void => {
|
|
250
|
+
comboBox.pointerPressedState = true;
|
|
251
|
+
comboBox.focusEditorFromChevron();
|
|
252
|
+
comboBox.handleThemeChanged();
|
|
253
|
+
})
|
|
254
|
+
.onPointerUpWith(this, (comboBox, _event): void => {
|
|
255
|
+
comboBox.pointerPressedState = false;
|
|
256
|
+
comboBox.handleThemeChanged();
|
|
257
|
+
});
|
|
258
|
+
this.semanticRole(SemanticRole.ComboBox);
|
|
259
|
+
this.focusable(true);
|
|
260
|
+
this.requireInteractive();
|
|
261
|
+
this.reflectSemanticDisabledFromEnabled();
|
|
262
|
+
this.cursor(CursorStyle.Text);
|
|
263
|
+
this.flexDirection(FlexDirection.Row);
|
|
264
|
+
this.alignItems(AlignItems.Center);
|
|
265
|
+
this.child(editor);
|
|
266
|
+
this.child(chevronHost);
|
|
267
|
+
this.child(this.popupList.root);
|
|
268
|
+
this.track(activeTheme.addAction(new HandlerAction<ComboBox, Theme>(this, (comboBox: ComboBox, _theme: Theme): void => {
|
|
269
|
+
comboBox.handleThemeChanged();
|
|
270
|
+
})));
|
|
271
|
+
this.track(keyboardFocusVisible.addAction(new HandlerAction<ComboBox, bool>(this, (comboBox: ComboBox, _visible: bool): void => {
|
|
272
|
+
comboBox.syncFocusChrome(activeTheme.value);
|
|
273
|
+
})));
|
|
274
|
+
this.semanticExpanded(false);
|
|
275
|
+
this.setDefaultSemanticLabel("Combo box");
|
|
276
|
+
this.handleThemeChanged();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
get selectedIndex(): i32 {
|
|
280
|
+
return this.selectedIndexValue;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
get value(): string {
|
|
284
|
+
return this.textValue;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
get filteredCount(): i32 {
|
|
288
|
+
return this.filteredIndices.length;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
get highlightedIndex(): i32 {
|
|
292
|
+
return this.highlightedIndexValue;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
get isOpen(): bool {
|
|
296
|
+
return this.openState;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
items(items: Array<string>): this {
|
|
300
|
+
this.close();
|
|
301
|
+
this.itemsValue.length = 0;
|
|
302
|
+
for (let index = 0; index < items.length; ++index) {
|
|
303
|
+
this.itemsValue.push(new ComboBoxItem(unchecked(items[index])));
|
|
304
|
+
}
|
|
305
|
+
this.syncSelectionFromText();
|
|
306
|
+
this.rebuildFilteredIndices();
|
|
307
|
+
this.popupList.refreshPanelLayout();
|
|
308
|
+
this.syncOptionVisuals();
|
|
309
|
+
this.syncSemanticLabel();
|
|
310
|
+
return this;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
text(text: string): this {
|
|
314
|
+
return this.setText(text, false);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
placeholder(value: string): this {
|
|
318
|
+
this.editor.placeholder(value);
|
|
319
|
+
this.syncSemanticLabel();
|
|
320
|
+
return this;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
allowCustom(flag: bool = true): this {
|
|
324
|
+
this.allowCustomValue = flag;
|
|
325
|
+
this.syncSelectionFromText();
|
|
326
|
+
return this;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
autoComplete(flag: bool = true): this {
|
|
330
|
+
this.autoCompleteValue = flag;
|
|
331
|
+
return this;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
filterMode(mode: ComboBoxFilterMode): this {
|
|
335
|
+
this.filterModeValue = mode;
|
|
336
|
+
this.rebuildFilteredIndices();
|
|
337
|
+
this.refreshPopupAfterFilter();
|
|
338
|
+
this.syncOptionVisuals();
|
|
339
|
+
return this;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
commitMode(mode: ComboBoxCommitMode): this {
|
|
343
|
+
this.commitModeValue = mode;
|
|
344
|
+
return this;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
openOnFocus(flag: bool = true): this {
|
|
348
|
+
this.openOnFocusValue = flag;
|
|
349
|
+
return this;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
staysOpenOnEdit(flag: bool = true): this {
|
|
353
|
+
this.staysOpenOnEditValue = flag;
|
|
354
|
+
return this;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
maxVisibleItems(count: i32): this {
|
|
358
|
+
this.popupList.maxVisibleItems(count);
|
|
359
|
+
return this;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
popupWidth(value: f32): this {
|
|
363
|
+
this.popupList.popupWidth(value);
|
|
364
|
+
return this;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
popupPanelColor(color: u32): this {
|
|
368
|
+
this.popupPanelColorOverridden = true;
|
|
369
|
+
this.popupPanelColorValue = color;
|
|
370
|
+
this.popupList.panelNode.bgColor(color);
|
|
371
|
+
return this;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
popupPanelBackgroundBlur(sigma: f32): this {
|
|
375
|
+
this.popupPanelBackgroundBlurOverridden = true;
|
|
376
|
+
if (sigma < 0.0) {
|
|
377
|
+
warn("Layout", "ComboBox.popupPanelBackgroundBlur() received " + sigma.toString() + "; clamping to 0.0.");
|
|
378
|
+
}
|
|
379
|
+
this.popupPanelBackgroundBlurSigmaValue = sigma >= 0.0 ? sigma : 0.0;
|
|
380
|
+
this.popupList.panelNode.backgroundBlur(this.popupPanelBackgroundBlurSigmaValue);
|
|
381
|
+
return this;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
sizing(sizing: DropdownSizing | null): this {
|
|
385
|
+
this.sizingValue = sizing;
|
|
386
|
+
const previousPresenter = this.chevronPresenter;
|
|
387
|
+
const nextPresenter = createChevronPresenter(this.chevronTemplateValue, sizing);
|
|
388
|
+
this.chevronPresenter = nextPresenter;
|
|
389
|
+
this.chevronHost.removeChildNode(previousPresenter.root);
|
|
390
|
+
this.chevronHost.addChildNode(nextPresenter.root);
|
|
391
|
+
previousPresenter.root.dispose();
|
|
392
|
+
this.popupList.sizing(sizing);
|
|
393
|
+
this.handleThemeChanged();
|
|
394
|
+
return this;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
colors(colors: DropdownColors | null): this {
|
|
398
|
+
this.colorsValue = colors;
|
|
399
|
+
this.popupList.colors(colors);
|
|
400
|
+
this.handleThemeChanged();
|
|
401
|
+
return this;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
chevronTemplate(template: DropdownChevronTemplate | null): this {
|
|
405
|
+
this.chevronTemplateValue = template;
|
|
406
|
+
const previousPresenter = this.chevronPresenter;
|
|
407
|
+
const nextPresenter = createChevronPresenter(template, this.sizingValue);
|
|
408
|
+
this.chevronPresenter = nextPresenter;
|
|
409
|
+
this.chevronHost.removeChildNode(previousPresenter.root);
|
|
410
|
+
this.chevronHost.addChildNode(nextPresenter.root);
|
|
411
|
+
previousPresenter.root.dispose();
|
|
412
|
+
this.handleThemeChanged();
|
|
413
|
+
return this;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
optionRowTemplate(template: DropdownOptionRowTemplate | null): this {
|
|
417
|
+
this.close();
|
|
418
|
+
this.popupList.optionRowTemplate(template);
|
|
419
|
+
return this;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
selectIndex(index: i32): this {
|
|
423
|
+
this.setSelectedIndex(index, false);
|
|
424
|
+
return this;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
onChanged(callback: ((event: ComboBoxChangedEventArgs<ComboBoxItem>) => void) | null): this {
|
|
428
|
+
this.changedCallback = callback;
|
|
429
|
+
this.changedBinding = null;
|
|
430
|
+
return this;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
bindChanged<Owner>(owner: Owner, handler: Handler1<Owner, ComboBoxChangedEventArgs<ComboBoxItem>>): this {
|
|
434
|
+
this.changedCallback = null;
|
|
435
|
+
this.changedBinding = bind1<Owner, ComboBoxChangedEventArgs<ComboBoxItem>>(owner, handler);
|
|
436
|
+
return this;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
onChangedWith<Owner>(owner: Owner, handler: Handler1<Owner, ComboBoxChangedEventArgs<ComboBoxItem>>): this {
|
|
440
|
+
this.bindChanged(owner, handler);
|
|
441
|
+
return this;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
onTextChanged(callback: ((event: TextChangedEventArgs) => void) | null): this {
|
|
445
|
+
this.textChangedCallback = callback;
|
|
446
|
+
this.textChangedBinding = null;
|
|
447
|
+
return this;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
bindTextChanged<Owner>(owner: Owner, handler: Handler1<Owner, TextChangedEventArgs>): this {
|
|
451
|
+
this.textChangedCallback = null;
|
|
452
|
+
this.textChangedBinding = bind1<Owner, TextChangedEventArgs>(owner, handler);
|
|
453
|
+
return this;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
onTextChangedWith<Owner>(owner: Owner, handler: Handler1<Owner, TextChangedEventArgs>): this {
|
|
457
|
+
this.bindTextChanged(owner, handler);
|
|
458
|
+
return this;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
focusNow(): this {
|
|
462
|
+
super.focusNow();
|
|
463
|
+
return this;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
dispose(): void {
|
|
467
|
+
this.close();
|
|
468
|
+
this.disposeControl();
|
|
469
|
+
super.dispose();
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
static hideActiveComboBox(): void {
|
|
473
|
+
const comboBox = ComboBox.activeInstance;
|
|
474
|
+
if (comboBox !== null) {
|
|
475
|
+
comboBox.close();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
static dismissActiveComboBoxIfTriggerOutOfViewport(): void {
|
|
480
|
+
const comboBox = ComboBox.activeInstance;
|
|
481
|
+
if (comboBox === null) {
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (!comboBox.isTriggerVisibleInViewport()) {
|
|
485
|
+
comboBox.close();
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
private static ensureScrollHook(): void {
|
|
490
|
+
if (ComboBox.scrollHookRegistered) {
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
registerScrollHook((): void => ComboBox.dismissActiveComboBoxIfTriggerOutOfViewport());
|
|
494
|
+
ComboBox.scrollHookRegistered = true;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
handleGlobalKeyEvent(eventType: KeyEventType, key: string, modifiers: u32): bool {
|
|
498
|
+
if (!this.openState || modifiers != 0 || eventType != KeyEventType.Down) {
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
if (key == "Escape") {
|
|
502
|
+
this.close();
|
|
503
|
+
return true;
|
|
504
|
+
}
|
|
505
|
+
if (key == "Enter") {
|
|
506
|
+
this.selectHighlighted();
|
|
507
|
+
return true;
|
|
508
|
+
}
|
|
509
|
+
if (key == "Home") {
|
|
510
|
+
this.highlightIndex(0);
|
|
511
|
+
return true;
|
|
512
|
+
}
|
|
513
|
+
if (key == "End") {
|
|
514
|
+
this.highlightIndex(this.filteredIndices.length - 1);
|
|
515
|
+
return true;
|
|
516
|
+
}
|
|
517
|
+
if (key == "ArrowDown") {
|
|
518
|
+
this.moveHighlight(1);
|
|
519
|
+
return true;
|
|
520
|
+
}
|
|
521
|
+
if (key == "ArrowUp") {
|
|
522
|
+
this.moveHighlight(-1);
|
|
523
|
+
return true;
|
|
524
|
+
}
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
_handlePointerEvent(eventType: PointerEventType, x: f32, y: f32, modifiers: u32 = 0): void {
|
|
529
|
+
super._handlePointerEvent(eventType, x, y, modifiers);
|
|
530
|
+
if (!this.isEnabled) {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
if (eventType == PointerEventType.Enter) {
|
|
534
|
+
this.hoveredState = true;
|
|
535
|
+
this.handleThemeChanged();
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (eventType == PointerEventType.Leave) {
|
|
539
|
+
this.pointerPressedState = false;
|
|
540
|
+
this.hoveredState = false;
|
|
541
|
+
this.handleThemeChanged();
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
if (eventType == PointerEventType.Down) {
|
|
545
|
+
this.pointerPressedState = true;
|
|
546
|
+
this.editor.focusNow();
|
|
547
|
+
this.handleThemeChanged();
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
if (eventType == PointerEventType.Up && this.pointerPressedState) {
|
|
551
|
+
this.pointerPressedState = false;
|
|
552
|
+
this.toggleOpen();
|
|
553
|
+
this.handleThemeChanged();
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
private toggleFromChevron(): void {
|
|
558
|
+
if (!this.isEnabled) {
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
this.focusEditorFromChevron();
|
|
562
|
+
this.toggleOpen();
|
|
563
|
+
this.handleThemeChanged();
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
private focusEditorFromChevron(): void {
|
|
567
|
+
this.editor.caretToEnd();
|
|
568
|
+
this.editor.focusNow();
|
|
569
|
+
this.editor.caretToEnd();
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
private toggleOpen(): void {
|
|
573
|
+
if (this.openState) {
|
|
574
|
+
this.close();
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
this.open();
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
_handleKeyEvent(eventType: KeyEventType, key: string, modifiers: u32): bool {
|
|
581
|
+
const callbackHandled = super._handleKeyEvent(eventType, key, modifiers);
|
|
582
|
+
if (!this.isEnabled || modifiers != 0 || eventType != KeyEventType.Down) {
|
|
583
|
+
return callbackHandled;
|
|
584
|
+
}
|
|
585
|
+
if (!this.openState && (key == "Enter" || key == " " || key == "ArrowDown")) {
|
|
586
|
+
this.open();
|
|
587
|
+
return true;
|
|
588
|
+
}
|
|
589
|
+
if (!this.openState && key == "ArrowUp") {
|
|
590
|
+
this.open();
|
|
591
|
+
this.moveHighlight(-1);
|
|
592
|
+
return true;
|
|
593
|
+
}
|
|
594
|
+
return callbackHandled;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
_handleFocusChanged(focused: bool): void {
|
|
598
|
+
super._handleFocusChanged(focused);
|
|
599
|
+
this.wrapperFocusedState = focused;
|
|
600
|
+
if (!focused && !this.openState) {
|
|
601
|
+
this.pointerPressedState = false;
|
|
602
|
+
}
|
|
603
|
+
this.syncFocusedState();
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
protected _onEffectiveEnabledChanged(_isEnabled: bool): void {
|
|
607
|
+
this.editor.enabled(this.isEnabled);
|
|
608
|
+
if (!this.isEnabled) {
|
|
609
|
+
this.pointerPressedState = false;
|
|
610
|
+
this.hoveredState = false;
|
|
611
|
+
this.close();
|
|
612
|
+
}
|
|
613
|
+
this.handleThemeChanged();
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
private handleEditorKeyDown(key: string, modifiers: u32): bool {
|
|
617
|
+
if (!this.isEnabled || modifiers != 0) {
|
|
618
|
+
return this.isTextNavigationKey(key, modifiers);
|
|
619
|
+
}
|
|
620
|
+
if (key == "ArrowDown") {
|
|
621
|
+
if (!this.openState) {
|
|
622
|
+
this.open();
|
|
623
|
+
} else {
|
|
624
|
+
this.moveHighlight(1);
|
|
625
|
+
}
|
|
626
|
+
return true;
|
|
627
|
+
}
|
|
628
|
+
if (key == "ArrowUp") {
|
|
629
|
+
if (!this.openState) {
|
|
630
|
+
this.open();
|
|
631
|
+
}
|
|
632
|
+
this.moveHighlight(-1);
|
|
633
|
+
return true;
|
|
634
|
+
}
|
|
635
|
+
if (key == "Enter" && this.openState) {
|
|
636
|
+
this.selectHighlighted();
|
|
637
|
+
return true;
|
|
638
|
+
}
|
|
639
|
+
if (key == "Escape" && this.openState) {
|
|
640
|
+
this.close();
|
|
641
|
+
return true;
|
|
642
|
+
}
|
|
643
|
+
return this.isTextNavigationKey(key, modifiers);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
private isTextNavigationKey(key: string, modifiers: u32): bool {
|
|
647
|
+
const nonShiftModifiers = modifiers & (<u32>KeyModifier.Ctrl | <u32>KeyModifier.Alt | <u32>KeyModifier.Meta);
|
|
648
|
+
return nonShiftModifiers == 0 &&
|
|
649
|
+
(
|
|
650
|
+
key == "ArrowLeft" ||
|
|
651
|
+
key == "ArrowRight" ||
|
|
652
|
+
key == "ArrowUp" ||
|
|
653
|
+
key == "ArrowDown" ||
|
|
654
|
+
key == "Home" ||
|
|
655
|
+
key == "End" ||
|
|
656
|
+
key == "PageUp" ||
|
|
657
|
+
key == "PageDown"
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
private handleEditorTextChanged(value: string): void {
|
|
662
|
+
if (this.suppressEditorChanged) {
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
let nextValue = value;
|
|
666
|
+
let selectionStart: u32 = 0;
|
|
667
|
+
let selectionEnd: u32 = 0;
|
|
668
|
+
const deletingText = value.length < this.textValue.length;
|
|
669
|
+
const shouldAutoComplete = this.autoCompleteValue &&
|
|
670
|
+
value.length > 0 &&
|
|
671
|
+
!deletingText &&
|
|
672
|
+
value != this.lastAutoCompleteTextValue;
|
|
673
|
+
this.lastAutoCompleteTextValue = "";
|
|
674
|
+
if (shouldAutoComplete) {
|
|
675
|
+
const autoCompleteIndex = this.findAutoCompleteMatch(value);
|
|
676
|
+
if (autoCompleteIndex >= 0) {
|
|
677
|
+
const completedValue = unchecked(this.itemsValue[autoCompleteIndex]).value;
|
|
678
|
+
if (completedValue.length > value.length) {
|
|
679
|
+
nextValue = completedValue;
|
|
680
|
+
selectionStart = codePointCount(value);
|
|
681
|
+
selectionEnd = codePointCount(completedValue);
|
|
682
|
+
this.lastAutoCompleteTextValue = value;
|
|
683
|
+
this.suppressEditorChanged = true;
|
|
684
|
+
this.editor.text(completedValue);
|
|
685
|
+
this.editor.selectionRange(selectionStart, selectionEnd);
|
|
686
|
+
this.suppressEditorChanged = false;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
this.textValue = nextValue;
|
|
691
|
+
this.syncSelectionFromText();
|
|
692
|
+
this.rebuildFilteredIndices();
|
|
693
|
+
if (this.filteredIndices.length == 0) {
|
|
694
|
+
this.close();
|
|
695
|
+
} else if (this.staysOpenOnEditValue) {
|
|
696
|
+
this.highlightedIndexValue = 0;
|
|
697
|
+
if (this.openState) {
|
|
698
|
+
this.refreshOpenPopup();
|
|
699
|
+
} else {
|
|
700
|
+
this.open();
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
this.refreshPopupAfterFilter();
|
|
704
|
+
this.syncOptionVisuals();
|
|
705
|
+
this.syncSemanticLabel();
|
|
706
|
+
this.emitTextChanged(nextValue);
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
private handleEditorFocusChanged(focused: bool): void {
|
|
710
|
+
this.editorFocusedState = focused;
|
|
711
|
+
if (focused && this.openOnFocusValue) {
|
|
712
|
+
this.open();
|
|
713
|
+
}
|
|
714
|
+
if (!focused && !this.openState) {
|
|
715
|
+
this.commitCurrentText();
|
|
716
|
+
this.pointerPressedState = false;
|
|
717
|
+
}
|
|
718
|
+
this.syncFocusedState();
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
private syncFocusedState(): void {
|
|
722
|
+
const nextFocused = this.wrapperFocusedState || this.editorFocusedState;
|
|
723
|
+
if (!nextFocused && !this.popupPointerPressedState) {
|
|
724
|
+
this.scheduleDeferredBlurClose();
|
|
725
|
+
}
|
|
726
|
+
if (this.focusedState == nextFocused) {
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
this.focusedState = nextFocused;
|
|
730
|
+
this.handleThemeChanged();
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
private scheduleDeferredBlurClose(): void {
|
|
734
|
+
if (this.deferredBlurClosePendingState) {
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
this.deferredBlurClosePendingState = true;
|
|
738
|
+
afterNextCommitWith<ComboBox>(this, handleComboBoxDeferredBlurClose);
|
|
739
|
+
markNeedsCommit();
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
fireDeferredBlurClose(): void {
|
|
743
|
+
this.deferredBlurClosePendingState = false;
|
|
744
|
+
const nextFocused = this.wrapperFocusedState || this.editorFocusedState;
|
|
745
|
+
if (!nextFocused && !this.popupPointerPressedState) {
|
|
746
|
+
this.pointerPressedState = false;
|
|
747
|
+
this.close();
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
private setText(value: string, emit: bool): this {
|
|
752
|
+
if (this.textValue == value) {
|
|
753
|
+
return this;
|
|
754
|
+
}
|
|
755
|
+
this.textValue = value;
|
|
756
|
+
this.suppressEditorChanged = true;
|
|
757
|
+
this.editor.text(value);
|
|
758
|
+
this.suppressEditorChanged = false;
|
|
759
|
+
this.syncSelectionFromText();
|
|
760
|
+
this.rebuildFilteredIndices();
|
|
761
|
+
this.refreshPopupAfterFilter();
|
|
762
|
+
this.syncOptionVisuals();
|
|
763
|
+
this.syncSemanticLabel();
|
|
764
|
+
if (emit) {
|
|
765
|
+
this.emitTextChanged(value);
|
|
766
|
+
}
|
|
767
|
+
return this;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
private setSelectedIndex(index: i32, emit: bool): void {
|
|
771
|
+
if (index == -1) {
|
|
772
|
+
this.selectedIndexValue = -1;
|
|
773
|
+
this.committedSelectedIndexValue = -1;
|
|
774
|
+
this.highlightedIndexValue = -1;
|
|
775
|
+
this.popupList.setHighlightedIndex(-1);
|
|
776
|
+
this.syncSemanticLabel();
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
if (this.itemsValue.length == 0) {
|
|
780
|
+
if (index != -1) {
|
|
781
|
+
warn("Layout", "ComboBox.selectIndex() received " + index.toString() + " before any items were assigned.");
|
|
782
|
+
}
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
const clampedIndex = index < 0
|
|
786
|
+
? 0
|
|
787
|
+
: (index >= this.itemsValue.length ? this.itemsValue.length - 1 : index);
|
|
788
|
+
if (clampedIndex != index) {
|
|
789
|
+
warn(
|
|
790
|
+
"Layout",
|
|
791
|
+
"ComboBox.selectIndex() received " +
|
|
792
|
+
index.toString() +
|
|
793
|
+
"; clamping to " +
|
|
794
|
+
clampedIndex.toString() +
|
|
795
|
+
".",
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
const changed = this.selectedIndexValue != clampedIndex;
|
|
799
|
+
this.selectedIndexValue = clampedIndex;
|
|
800
|
+
this.committedSelectedIndexValue = clampedIndex;
|
|
801
|
+
const item = unchecked(this.itemsValue[clampedIndex]);
|
|
802
|
+
this.setText(item.value, false);
|
|
803
|
+
this.editor.caretToEnd();
|
|
804
|
+
this.rebuildFilteredIndices();
|
|
805
|
+
const visibleIndex = this.findVisibleIndexForSourceIndex(clampedIndex);
|
|
806
|
+
this.highlightedIndexValue = visibleIndex;
|
|
807
|
+
this.popupList.setHighlightedIndex(visibleIndex);
|
|
808
|
+
this.syncSemanticLabel();
|
|
809
|
+
if (emit && changed) {
|
|
810
|
+
this.requestSemanticAnnouncement();
|
|
811
|
+
this.emitSelectionChanged();
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
private syncSelectionFromText(): void {
|
|
816
|
+
const exactIndex = this.findExactTextMatch(this.textValue);
|
|
817
|
+
if (exactIndex >= 0) {
|
|
818
|
+
this.selectedIndexValue = exactIndex;
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
if (this.allowCustomValue) {
|
|
822
|
+
this.selectedIndexValue = -1;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
private commitCurrentText(): void {
|
|
827
|
+
if (this.commitModeValue == ComboBoxCommitMode.KeepText) {
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
830
|
+
if (this.commitModeValue == ComboBoxCommitMode.SelectExactMatch) {
|
|
831
|
+
const exactIndex = this.findExactTextMatch(this.textValue);
|
|
832
|
+
if (exactIndex >= 0) {
|
|
833
|
+
this.setSelectedIndex(exactIndex, true);
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
if (this.commitModeValue == ComboBoxCommitMode.RevertToSelection) {
|
|
838
|
+
if (this.committedSelectedIndexValue >= 0 && this.committedSelectedIndexValue < this.itemsValue.length) {
|
|
839
|
+
this.setText(unchecked(this.itemsValue[this.committedSelectedIndexValue]).value, true);
|
|
840
|
+
this.editor.caretToEnd();
|
|
841
|
+
this.selectedIndexValue = this.committedSelectedIndexValue;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
private emitSelectionChanged(): void {
|
|
847
|
+
if (this.selectedIndexValue < 0 || this.selectedIndexValue >= this.itemsValue.length) {
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
const item = unchecked(this.itemsValue[this.selectedIndexValue]);
|
|
851
|
+
const event = new ComboBoxChangedEventArgs<ComboBoxItem>(item, this.selectedIndexValue);
|
|
852
|
+
const callback = this.changedCallback;
|
|
853
|
+
if (callback !== null) {
|
|
854
|
+
callback(event);
|
|
855
|
+
}
|
|
856
|
+
const binding = this.changedBinding;
|
|
857
|
+
if (binding !== null) {
|
|
858
|
+
binding.invoke(event);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
private emitTextChanged(value: string): void {
|
|
863
|
+
const event = new TextChangedEventArgs(value);
|
|
864
|
+
const callback = this.textChangedCallback;
|
|
865
|
+
if (callback !== null) {
|
|
866
|
+
callback(event);
|
|
867
|
+
}
|
|
868
|
+
const binding = this.textChangedBinding;
|
|
869
|
+
if (binding !== null) {
|
|
870
|
+
binding.invoke(event);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
private open(): void {
|
|
875
|
+
if (this.openState || this.filteredIndices.length == 0 || this.builtHandle == 0) {
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
const initialHighlight = this.selectedIndexValue >= 0
|
|
879
|
+
? this.findVisibleIndexForSourceIndex(this.selectedIndexValue)
|
|
880
|
+
: 0;
|
|
881
|
+
this.popupList.setHighlightedIndex(initialHighlight);
|
|
882
|
+
this.highlightedIndexValue = this.popupList.highlightedIndex;
|
|
883
|
+
const bounds = this.tryGetViewportBounds();
|
|
884
|
+
if (bounds !== null) {
|
|
885
|
+
if (!this.popupList.open(unchecked(bounds[0]), unchecked(bounds[1]), unchecked(bounds[2]), unchecked(bounds[3]), initialHighlight)) {
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
this.highlightedIndexValue = this.popupList.highlightedIndex;
|
|
889
|
+
}
|
|
890
|
+
this.openState = true;
|
|
891
|
+
ComboBox.activeInstance = this;
|
|
892
|
+
this.semanticExpanded(true);
|
|
893
|
+
this.requestSemanticAnnouncement();
|
|
894
|
+
if (this.keyFilterToken == 0) {
|
|
895
|
+
this.keyFilterToken = EventRouter.pushKeyFilter(this);
|
|
896
|
+
}
|
|
897
|
+
this.handleThemeChanged();
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
private close(): void {
|
|
901
|
+
if (!this.openState && !this.popupList.isOpen) {
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
this.deferredBlurClosePendingState = false;
|
|
905
|
+
this.popupPointerPressedState = false;
|
|
906
|
+
this.popupList.close();
|
|
907
|
+
this.openState = false;
|
|
908
|
+
if (ComboBox.activeInstance === this) {
|
|
909
|
+
ComboBox.activeInstance = null;
|
|
910
|
+
}
|
|
911
|
+
this.semanticExpanded(false);
|
|
912
|
+
this.requestSemanticAnnouncement();
|
|
913
|
+
if (this.keyFilterToken != 0) {
|
|
914
|
+
EventRouter.removeKeyFilter(this.keyFilterToken);
|
|
915
|
+
this.keyFilterToken = 0;
|
|
916
|
+
}
|
|
917
|
+
this.commitCurrentText();
|
|
918
|
+
this.handleThemeChanged();
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
private tryGetViewportBounds(): Float32Array | null {
|
|
922
|
+
if (this.builtHandle == 0) {
|
|
923
|
+
return null;
|
|
924
|
+
}
|
|
925
|
+
return ui.tryGetBounds(this.builtHandle);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
private findContainingScrollView(): ScrollView | null {
|
|
929
|
+
let current: Node | null = this.parentNode;
|
|
930
|
+
while (current !== null) {
|
|
931
|
+
if (current instanceof ScrollView) {
|
|
932
|
+
return current as ScrollView;
|
|
933
|
+
}
|
|
934
|
+
current = current.parentNode;
|
|
935
|
+
}
|
|
936
|
+
return null;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
private isTriggerVisibleInViewport(): bool {
|
|
940
|
+
const bounds = this.tryGetViewportBounds();
|
|
941
|
+
if (bounds === null) {
|
|
942
|
+
return true;
|
|
943
|
+
}
|
|
944
|
+
const x = unchecked(bounds[0]);
|
|
945
|
+
const y = unchecked(bounds[1]);
|
|
946
|
+
const width = unchecked(bounds[2]);
|
|
947
|
+
const height = unchecked(bounds[3]);
|
|
948
|
+
if (width <= 0.0 || height <= 0.0) {
|
|
949
|
+
return true;
|
|
950
|
+
}
|
|
951
|
+
const right = x + width;
|
|
952
|
+
const bottom = y + height;
|
|
953
|
+
const scrollView = this.findContainingScrollView();
|
|
954
|
+
if (scrollView === null) {
|
|
955
|
+
const viewportWidth = ui.getViewportWidth();
|
|
956
|
+
const viewportHeight = ui.getViewportHeight();
|
|
957
|
+
return right > 0.0 && bottom > 0.0 && x < viewportWidth && y < viewportHeight;
|
|
958
|
+
}
|
|
959
|
+
const scrollViewBounds = ui.tryGetBounds(scrollView.builtHandle);
|
|
960
|
+
if (scrollViewBounds === null) {
|
|
961
|
+
const viewportWidth = ui.getViewportWidth();
|
|
962
|
+
const viewportHeight = ui.getViewportHeight();
|
|
963
|
+
return right > 0.0 && bottom > 0.0 && x < viewportWidth && y < viewportHeight;
|
|
964
|
+
}
|
|
965
|
+
const svX = unchecked(scrollViewBounds[0]);
|
|
966
|
+
const svY = unchecked(scrollViewBounds[1]);
|
|
967
|
+
const svWidth = unchecked(scrollViewBounds[2]);
|
|
968
|
+
const svHeight = unchecked(scrollViewBounds[3]);
|
|
969
|
+
return right > svX && bottom > svY && x < svX + svWidth && y < svY + svHeight;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
private highlightIndex(index: i32): void {
|
|
973
|
+
this.popupList.highlightIndex(index);
|
|
974
|
+
this.highlightedIndexValue = this.popupList.highlightedIndex;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
private moveHighlight(delta: i32): void {
|
|
978
|
+
this.popupList.moveHighlight(delta);
|
|
979
|
+
this.highlightedIndexValue = this.popupList.highlightedIndex;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
private refreshPopupAfterFilter(): void {
|
|
983
|
+
if (this.openState) {
|
|
984
|
+
this.refreshOpenPopup();
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
this.popupList.refreshPanelLayout();
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
private refreshOpenPopup(): void {
|
|
991
|
+
const bounds = this.tryGetViewportBounds();
|
|
992
|
+
if (bounds === null) {
|
|
993
|
+
this.popupList.refreshPanelLayout();
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
this.popupList.refreshOpen(
|
|
997
|
+
unchecked(bounds[0]),
|
|
998
|
+
unchecked(bounds[1]),
|
|
999
|
+
unchecked(bounds[2]),
|
|
1000
|
+
unchecked(bounds[3]),
|
|
1001
|
+
this.highlightedIndexValue,
|
|
1002
|
+
);
|
|
1003
|
+
this.highlightedIndexValue = this.popupList.highlightedIndex;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
private selectHighlighted(): void {
|
|
1007
|
+
if (this.highlightedIndexValue < 0 || this.highlightedIndexValue >= this.filteredIndices.length) {
|
|
1008
|
+
return;
|
|
1009
|
+
}
|
|
1010
|
+
this.setSelectedIndex(unchecked(this.filteredIndices[this.highlightedIndexValue]), true);
|
|
1011
|
+
this.close();
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
private rebuildFilteredIndices(): void {
|
|
1015
|
+
this.filteredIndices.length = 0;
|
|
1016
|
+
for (let index = 0; index < this.itemsValue.length; ++index) {
|
|
1017
|
+
if (this.shouldIncludeItem(unchecked(this.itemsValue[index]))) {
|
|
1018
|
+
this.filteredIndices.push(index);
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
if (this.highlightedIndexValue >= this.filteredIndices.length) {
|
|
1022
|
+
this.highlightedIndexValue = this.filteredIndices.length > 0 ? this.filteredIndices.length - 1 : -1;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
private shouldIncludeItem(item: ComboBoxItem): bool {
|
|
1027
|
+
if (this.filterModeValue == ComboBoxFilterMode.None || this.textValue.length == 0) {
|
|
1028
|
+
return true;
|
|
1029
|
+
}
|
|
1030
|
+
if (this.filterModeValue == ComboBoxFilterMode.StartsWith) {
|
|
1031
|
+
return stringStartsWithIgnoreCase(item.value, this.textValue);
|
|
1032
|
+
}
|
|
1033
|
+
return stringContainsIgnoreCase(item.value, this.textValue);
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
private findAutoCompleteMatch(text: string): i32 {
|
|
1037
|
+
for (let index = 0; index < this.itemsValue.length; ++index) {
|
|
1038
|
+
if (stringStartsWithIgnoreCase(unchecked(this.itemsValue[index]).value, text)) {
|
|
1039
|
+
return index;
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
return -1;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
private findExactTextMatch(text: string): i32 {
|
|
1046
|
+
for (let index = 0; index < this.itemsValue.length; ++index) {
|
|
1047
|
+
const item = unchecked(this.itemsValue[index]);
|
|
1048
|
+
if (stringsEqualIgnoreCase(item.value, text)) {
|
|
1049
|
+
return index;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
return -1;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
private findVisibleIndexForSourceIndex(sourceIndex: i32): i32 {
|
|
1056
|
+
for (let index = 0; index < this.filteredIndices.length; ++index) {
|
|
1057
|
+
if (unchecked(this.filteredIndices[index]) == sourceIndex) {
|
|
1058
|
+
return index;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
return this.filteredIndices.length > 0 ? 0 : -1;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
private handleThemeChanged(): void {
|
|
1065
|
+
if (this.disposed) {
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
const theme = activeTheme.value;
|
|
1069
|
+
if (!this.popupPanelColorOverridden) {
|
|
1070
|
+
this.popupPanelColorValue = theme.contextMenu.panelBackground;
|
|
1071
|
+
}
|
|
1072
|
+
if (!this.popupPanelBackgroundBlurOverridden) {
|
|
1073
|
+
this.popupPanelBackgroundBlurSigmaValue = DEFAULT_PANEL_BACKGROUND_BLUR_SIGMA;
|
|
1074
|
+
}
|
|
1075
|
+
const sizing = this.sizingValue;
|
|
1076
|
+
const fieldHeight = sizing !== null && sizing.hasFieldHeight ? sizing.fieldHeightPx : 32.0;
|
|
1077
|
+
const chevronBoxSize = sizing !== null && sizing.hasChevronBoxSize ? sizing.chevronBoxSizePx : 32.0;
|
|
1078
|
+
const fieldFontSize = sizing !== null && sizing.hasFieldFontSize ? sizing.fieldFontSizePx : theme.fonts.sizeBody;
|
|
1079
|
+
const fieldBorderWidth: f32 = 2.0;
|
|
1080
|
+
const fieldContentHeight = <f32>Math.max(0.0, fieldHeight - (fieldBorderWidth * 2.0));
|
|
1081
|
+
this.cursor(this.isEnabled ? CursorStyle.Text : CursorStyle.Default);
|
|
1082
|
+
this.cornerRadius(0.0);
|
|
1083
|
+
this.border(0.0, 0x00000000);
|
|
1084
|
+
this.padding(0.0, 0.0, 0.0, 0.0);
|
|
1085
|
+
this.bgColor(0x00000000);
|
|
1086
|
+
this.opacity(this.isEnabled ? 1.0 : 0.6);
|
|
1087
|
+
const colors = this.colorsValue;
|
|
1088
|
+
const fieldBackground = colors !== null && colors.hasBackground ? colors.backgroundColor : theme.colors.surface;
|
|
1089
|
+
const fieldBorderColor = colors !== null && colors.hasBorder ? colors.borderColor : theme.colors.border;
|
|
1090
|
+
this.height(fieldHeight, Unit.Pixel);
|
|
1091
|
+
this.cornerRadius(theme.spacing.sm);
|
|
1092
|
+
this.border(fieldBorderWidth, fieldBorderColor);
|
|
1093
|
+
this.padding(16.0, 0.0, 8.0, 0.0);
|
|
1094
|
+
this.bgColor(fieldBackground);
|
|
1095
|
+
this.editor.height(fieldContentHeight, Unit.Pixel);
|
|
1096
|
+
this.editor
|
|
1097
|
+
.fontSize(fieldFontSize)
|
|
1098
|
+
.lineHeight(fieldContentHeight)
|
|
1099
|
+
.colors(resolveTextInputColors(this.colorsValue, theme));
|
|
1100
|
+
this.chevronHost
|
|
1101
|
+
.width(chevronBoxSize, Unit.Pixel)
|
|
1102
|
+
.height(fieldContentHeight, Unit.Pixel)
|
|
1103
|
+
.alignItems(AlignItems.Center)
|
|
1104
|
+
.justifyContent(JustifyContent.Center)
|
|
1105
|
+
.cursor(this.isEnabled ? CursorStyle.Pointer : CursorStyle.Default);
|
|
1106
|
+
this.chevronPresenter.apply(theme, new DropdownChevronVisualState(this.openState, this.hoveredState, this.isEnabled));
|
|
1107
|
+
this.popupList.panelNode
|
|
1108
|
+
.padding(
|
|
1109
|
+
SELECTABLE_POPUP_LIST_PANEL_PADDING,
|
|
1110
|
+
SELECTABLE_POPUP_LIST_PANEL_PADDING,
|
|
1111
|
+
SELECTABLE_POPUP_LIST_PANEL_PADDING,
|
|
1112
|
+
SELECTABLE_POPUP_LIST_PANEL_PADDING,
|
|
1113
|
+
)
|
|
1114
|
+
.cornerRadius(theme.spacing.sm)
|
|
1115
|
+
.bgColor(this.popupPanelColorValue)
|
|
1116
|
+
.border(1.0, theme.contextMenu.panelBorderColor)
|
|
1117
|
+
.backgroundBlur(this.popupPanelBackgroundBlurSigmaValue)
|
|
1118
|
+
.dropShadow(
|
|
1119
|
+
theme.contextMenu.panelShadowColor,
|
|
1120
|
+
0.0,
|
|
1121
|
+
theme.contextMenu.shadowOffsetY,
|
|
1122
|
+
theme.contextMenu.shadowBlur,
|
|
1123
|
+
theme.contextMenu.shadowSpread,
|
|
1124
|
+
);
|
|
1125
|
+
this.popupList.popupScrollBox.bgColor(0x00000000);
|
|
1126
|
+
this.syncOptionVisuals();
|
|
1127
|
+
this.syncFocusChrome(theme);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
private syncOptionVisuals(): void {
|
|
1131
|
+
this.popupList.syncOptionVisuals();
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
private syncSemanticLabel(): void {
|
|
1135
|
+
if (this.textValue.length > 0) {
|
|
1136
|
+
this.setDefaultSemanticLabel(this.textValue);
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
this.setDefaultSemanticLabel("Combo box");
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
private syncFocusChrome(theme: Theme): void {
|
|
1143
|
+
if (this.focusedState && this.isEnabled && keyboardFocusVisible.value) {
|
|
1144
|
+
FocusAdornerManager.showStandard(this, theme.spacing.sm);
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
FocusAdornerManager.hideOwner(this);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
private track(disposable: Disposable): void {
|
|
1151
|
+
this.disposables.push(disposable);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
private disposeControl(): void {
|
|
1155
|
+
if (this.disposed) {
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
this.disposed = true;
|
|
1159
|
+
this.popupList.dispose();
|
|
1160
|
+
disposeAll(this.disposables);
|
|
1161
|
+
FocusAdornerManager.hideOwner(this);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
getPopupListItemCount(): i32 {
|
|
1165
|
+
return this.filteredIndices.length;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
getPopupListItemLabel(index: i32): string {
|
|
1169
|
+
return unchecked(this.itemsValue[unchecked(this.filteredIndices[index])]).value;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
isPopupListItemSelected(index: i32): bool {
|
|
1173
|
+
if (index < 0 || index >= this.filteredIndices.length) {
|
|
1174
|
+
return false;
|
|
1175
|
+
}
|
|
1176
|
+
return unchecked(this.filteredIndices[index]) == this.selectedIndexValue;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
isPopupListEnabled(): bool {
|
|
1180
|
+
return this.isEnabled;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
popupListHighlightIndex(index: i32): void {
|
|
1184
|
+
this.highlightIndex(index);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
popupListActivateIndex(index: i32): void {
|
|
1188
|
+
this.highlightIndex(index);
|
|
1189
|
+
this.selectHighlighted();
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
popupListPointerDown(_index: i32): void {
|
|
1193
|
+
this.popupPointerPressedState = true;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
popupListPointerUp(_index: i32): void {
|
|
1197
|
+
this.popupPointerPressedState = false;
|
|
1198
|
+
this.syncFocusedState();
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
}
|