@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.
Files changed (118) hide show
  1. package/browser/routed-app-conventions.ts +1 -1
  2. package/browser/src/common-harness.ts +1 -27
  3. package/browser/src/index.ts +19 -26
  4. package/browser/src/routed-harness.ts +1 -194
  5. package/browser/src/shared-browser.ts +6 -0
  6. package/package.json +23 -13
  7. package/scripts/build.sh +47 -46
  8. package/scripts/framework-host-services.ts +1 -1
  9. package/scripts/generate-host-events.ts +15 -241
  10. package/scripts/generate-host-services.ts +13 -261
  11. package/scripts/hostgen/assemblyscript-host-events.ts +165 -0
  12. package/scripts/hostgen/assemblyscript-host-services.ts +207 -0
  13. package/scripts/hostgen/common.ts +73 -0
  14. package/scripts/hostgen/registry.ts +159 -0
  15. package/src/Fui.ts +65 -5
  16. package/src/FuiExports.ts +12 -1
  17. package/src/bindings/ui.ts +157 -8
  18. package/src/controls/Button.ts +98 -71
  19. package/src/controls/Checkbox.ts +23 -21
  20. package/src/controls/ComboBox.ts +1201 -0
  21. package/src/controls/ContextMenu.ts +279 -165
  22. package/src/controls/Dialog.ts +91 -17
  23. package/src/controls/Dropdown.ts +101 -333
  24. package/src/controls/NavLink.ts +29 -6
  25. package/src/controls/Popup.ts +10 -5
  26. package/src/controls/ProgressBar.ts +1 -2
  27. package/src/controls/RadioButton.ts +34 -13
  28. package/src/controls/RadioGroup.ts +32 -19
  29. package/src/controls/Slider.ts +21 -23
  30. package/src/controls/Switch.ts +34 -17
  31. package/src/controls/index.ts +2 -1
  32. package/src/controls/internal/ButtonPresenter.ts +5 -2
  33. package/src/controls/internal/CheckboxIndicatorPresenter.ts +10 -10
  34. package/src/controls/internal/DropdownChevronPresenter.ts +3 -3
  35. package/src/controls/internal/DropdownFieldPresenter.ts +6 -6
  36. package/src/controls/internal/DropdownOptionRowPresenter.ts +5 -4
  37. package/src/controls/internal/PressableLabeledControl.ts +4 -7
  38. package/src/controls/internal/RadioIndicatorPresenter.ts +7 -7
  39. package/src/controls/internal/SelectablePopupList.ts +463 -0
  40. package/src/controls/internal/SliderPresenter.ts +5 -5
  41. package/src/controls/internal/SwitchIndicatorPresenter.ts +63 -17
  42. package/src/controls/internal/TextInputCore.ts +307 -41
  43. package/src/controls/internal/TextInputPresenter.ts +2 -2
  44. package/src/controls/templating.ts +10 -0
  45. package/src/core/Application.ts +17 -8
  46. package/src/core/Bitmap.ts +192 -3
  47. package/src/core/ContextMenuManager.ts +128 -44
  48. package/src/core/DebugApi.ts +17 -2
  49. package/src/core/DragGesture.ts +8 -7
  50. package/src/core/EventRouter.ts +332 -12
  51. package/src/core/Fetch.ts +15 -7
  52. package/src/core/FetchFfi.ts +1 -15
  53. package/src/core/File.ts +75 -34
  54. package/src/core/FocusAdornerManager.ts +3 -3
  55. package/src/core/FocusVisibility.ts +7 -0
  56. package/src/core/FrameScheduler.ts +104 -5
  57. package/src/core/ImageSampling.ts +43 -0
  58. package/src/core/Logger.ts +2 -0
  59. package/src/core/MobileTextSelectionToolbarManager.ts +901 -0
  60. package/src/core/Node.ts +1039 -97
  61. package/src/core/Platform.ts +16 -16
  62. package/src/core/SelectionHandleAdornerManager.ts +697 -0
  63. package/src/core/Theme.ts +34 -20
  64. package/src/core/ToolTipManager.ts +3 -3
  65. package/src/core/Typography.ts +411 -19
  66. package/src/core/Worker.ts +69 -33
  67. package/src/core/event_exports.ts +123 -5
  68. package/src/core/ffi.ts +5 -865
  69. package/src/core/generated/FetchHostAbi.ts +10 -0
  70. package/src/core/generated/FrameworkHostServices.ts +1 -1
  71. package/src/core/generated/HostAbi.ts +220 -0
  72. package/src/core/generated/UiAbi.ts +356 -0
  73. package/src/core/generated/UiEnums.ts +186 -0
  74. package/src/drawing/DrawCallback.ts +17 -1
  75. package/src/drawing/DrawContext.ts +140 -32
  76. package/src/nodes/CustomDrawable.ts +8 -1
  77. package/src/nodes/FlexBox.ts +40 -11
  78. package/src/nodes/Grid.ts +5 -5
  79. package/src/nodes/Image.ts +20 -1
  80. package/src/nodes/RichText.ts +36 -25
  81. package/src/nodes/ScrollBar.ts +2 -1
  82. package/src/nodes/ScrollBox.ts +7 -2
  83. package/src/nodes/ScrollView.ts +17 -6
  84. package/src/nodes/Svg.ts +18 -1
  85. package/src/nodes/Text.ts +1 -1
  86. package/src/nodes/TextCore.ts +115 -20
  87. package/src/nodes/index.ts +1 -1
  88. package/src/text/DynamicTextLayout.ts +275 -0
  89. package/src/text/TextLayout.ts +308 -0
  90. package/src/worker/Worker.ts +3 -36
  91. package/src/worker/WorkerJob.ts +5 -9
  92. package/src/worker/ffi.ts +0 -6
  93. package/browser/src/common-harness/host-imports.ts +0 -447
  94. package/browser/src/common-harness/interop.ts +0 -39
  95. package/browser/src/common-harness/managed-harness-bitmap-host.ts +0 -92
  96. package/browser/src/common-harness/managed-harness-canvas-host.ts +0 -72
  97. package/browser/src/common-harness/managed-harness-fetch-host.ts +0 -201
  98. package/browser/src/common-harness/managed-harness-file-host.ts +0 -1066
  99. package/browser/src/common-harness/managed-harness-file-payloads.ts +0 -143
  100. package/browser/src/common-harness/managed-harness-file-types.ts +0 -71
  101. package/browser/src/common-harness/managed-harness-session.ts +0 -15
  102. package/browser/src/common-harness/managed-harness.ts +0 -1472
  103. package/browser/src/common-harness/managed-history.ts +0 -168
  104. package/browser/src/common-harness/persisted-restore-policy.ts +0 -50
  105. package/browser/src/common-harness/persisted-ui-state-controller.ts +0 -309
  106. package/browser/src/common-harness/text-session-bridge.ts +0 -452
  107. package/browser/src/common-harness/types.ts +0 -206
  108. package/browser/src/common-harness/ui-chrome.ts +0 -206
  109. package/browser/src/common-harness/ui-imports.ts +0 -559
  110. package/browser/src/common-harness/wasm-module-cache.ts +0 -47
  111. package/browser/src/file-processing-worker.ts +0 -89
  112. package/browser/src/host-events.ts +0 -118
  113. package/browser/src/host-services.ts +0 -483
  114. package/browser/src/persisted-ui-state.ts +0 -206
  115. package/browser/src/routed-app-conventions.ts +0 -292
  116. package/browser/src/worker-bootstrap.ts +0 -916
  117. package/browser/src/worker-manager.ts +0 -234
  118. package/browser/src/worker-types.ts +0 -68
@@ -1,13 +1,12 @@
1
1
  import * as ui from "../bindings/ui";
2
2
  import { HandlerAction } from "../core/Action";
3
- import { Callback2, Handler2 } from "../core/BoundCallback";
3
+ import { Callback1, Handler1 } from "../core/BoundCallback";
4
4
  import { Disposable, disposeAll } from "../core/Disposable";
5
5
  import { EventRouter, GlobalKeyHandler } from "../core/EventRouter";
6
6
  import { FocusAdornerManager } from "../core/FocusAdornerManager";
7
7
  import { keyboardFocusVisible } from "../core/FocusVisibility";
8
8
  import {
9
9
  AlignItems,
10
- BorderStyle,
11
10
  CursorStyle,
12
11
  FlexDirection,
13
12
  KeyEventType,
@@ -17,11 +16,11 @@ import {
17
16
  } from "../core/ffi";
18
17
  import { Theme, activeTheme } from "../core/Theme";
19
18
  import { warn } from "../core/Logger";
20
- import { Node } from "../core/Node";
19
+ import { DropdownChangedEventArgs, Node } from "../core/Node";
21
20
  import { PersistedInt32Codec, PersistedValueState } from "../core/PersistedState";
22
21
  import { registerScrollHook } from "../core/ScrollHooks";
23
- import { FlexBox, Portal, ScrollBarVisibility, ScrollBox, ScrollView } from "../nodes";
24
- import { bind2 } from "../core/bind";
22
+ import { FlexBox, ScrollView } from "../nodes";
23
+ import { bind1 } from "../core/bind";
25
24
  import { DropdownSizing } from "./ControlSizing";
26
25
  import { DropdownColors } from "./DropdownColors";
27
26
  import { getControlTemplates } from "./ControlTemplateSet";
@@ -37,18 +36,13 @@ import {
37
36
  DropdownFieldTemplate,
38
37
  DropdownFieldVisualState,
39
38
  } from "./internal/DropdownFieldPresenter";
39
+ import { DropdownOptionRowTemplate } from "./internal/DropdownOptionRowPresenter";
40
40
  import {
41
- createDefaultDropdownOptionRowPresenter,
42
- DropdownOptionRowPresenter,
43
- DropdownOptionRowTemplate,
44
- DropdownOptionRowVisualState,
45
- } from "./internal/DropdownOptionRowPresenter";
46
- import { PopupPresenter } from "./internal/PopupPresenter";
47
-
48
- const PANEL_EDGE_PADDING: f32 = 8.0;
49
- const OPTION_HEIGHT: f32 = 34.0;
50
- const PANEL_PADDING: f32 = 4.0;
51
- const UNLIMITED_VISIBLE_ITEMS: i32 = 0;
41
+ SELECTABLE_POPUP_LIST_PANEL_PADDING,
42
+ SelectablePopupList,
43
+ SelectablePopupListOwner,
44
+ } from "./internal/SelectablePopupList";
45
+
52
46
  const DEFAULT_PANEL_BACKGROUND_BLUR_SIGMA: f32 = 10.0;
53
47
  const DROPDOWN_PERSISTED_CODEC = new PersistedInt32Codec();
54
48
 
@@ -78,115 +72,31 @@ const DROPDOWN_PERSISTED_STATE = new PersistedDropdownState();
78
72
 
79
73
  function createFieldPresenter(template: DropdownFieldTemplate | null, sizing: DropdownSizing | null = null): DropdownFieldPresenter {
80
74
  if (template !== null) {
81
- return template.create();
75
+ return template.create(sizing);
82
76
  }
83
77
  const templateSet = getControlTemplates();
84
78
  const appTemplate = templateSet !== null ? templateSet.dropdownField : null;
85
79
  return appTemplate === null
86
80
  ? createDefaultDropdownFieldPresenter(sizing)
87
- : appTemplate.create();
81
+ : appTemplate.create(sizing);
88
82
  }
89
83
 
90
84
  function createChevronPresenter(template: DropdownChevronTemplate | null, sizing: DropdownSizing | null = null): DropdownChevronPresenter {
91
85
  if (template !== null) {
92
- return template.create();
86
+ return template.create(sizing);
93
87
  }
94
88
  const templateSet = getControlTemplates();
95
89
  const appTemplate = templateSet !== null ? templateSet.dropdownChevron : null;
96
90
  return appTemplate === null
97
91
  ? createDefaultDropdownChevronPresenter(sizing)
98
- : appTemplate.create();
99
- }
100
-
101
- function createOptionRowPresenter(template: DropdownOptionRowTemplate | null, sizing: DropdownSizing | null = null): DropdownOptionRowPresenter {
102
- if (template !== null) {
103
- return template.create();
104
- }
105
- const templateSet = getControlTemplates();
106
- const appTemplate = templateSet !== null ? templateSet.dropdownOptionRow : null;
107
- return appTemplate === null
108
- ? createDefaultDropdownOptionRowPresenter(sizing)
109
- : appTemplate.create();
92
+ : appTemplate.create(sizing);
110
93
  }
111
94
 
112
95
  export class DropdownItem {
113
96
  constructor(readonly value: string, readonly label: string = value) {}
114
97
  }
115
98
 
116
- class DropdownOptionNode extends FlexBox {
117
- private presenter: DropdownOptionRowPresenter;
118
- private owner: Dropdown | null = null;
119
- private slotIndex: i32 = -1;
120
- private currentLabel: string = "";
121
-
122
- constructor(template: DropdownOptionRowTemplate | null, sizing: DropdownSizing | null) {
123
- super();
124
- this.presenter = createOptionRowPresenter(template, sizing);
125
- this.semanticRole(SemanticRole.ListItem);
126
- this.width(100.0, Unit.Percent);
127
- this.cursor(CursorStyle.Pointer);
128
- this.focusable(false);
129
- this.requireInteractive();
130
- this.child(this.presenter.root);
131
- this.syncPresenterLayout();
132
- }
133
-
134
- bindOwner(owner: Dropdown, slotIndex: i32): this {
135
- this.owner = owner;
136
- this.slotIndex = slotIndex;
137
- return this;
138
- }
139
-
140
- label(label: string): this {
141
- this.currentLabel = label;
142
- this.semanticLabel(label);
143
- this.presenter.labelNode.text(label);
144
- return this;
145
- }
146
-
147
- template(template: DropdownOptionRowTemplate | null, sizing: DropdownSizing | null): void {
148
- const previousPresenter = this.presenter;
149
- const nextPresenter = createOptionRowPresenter(template, sizing);
150
- this.presenter = nextPresenter;
151
- this.removeChildNode(previousPresenter.root);
152
- this.addChildNode(nextPresenter.root);
153
- previousPresenter.root.dispose();
154
- nextPresenter.labelNode.text(this.currentLabel);
155
- this.syncPresenterLayout();
156
- }
157
-
158
- get rowHeight(): f32 {
159
- return this.presenter.metrics.height;
160
- }
161
-
162
- applyTheme(theme: Theme, highlighted: bool, selected: bool, enabled: bool, colors: DropdownColors | null): void {
163
- this.semanticSelected(selected);
164
- this.semanticDisabled(!enabled);
165
- this.presenter.apply(theme, new DropdownOptionRowVisualState(highlighted, selected, enabled), colors);
166
- }
167
-
168
- _handlePointerEvent(eventType: PointerEventType, x: f32, y: f32, modifiers: u32 = 0): void {
169
- super._handlePointerEvent(eventType, x, y, modifiers);
170
- const owner = this.owner;
171
- if (owner === null) {
172
- return;
173
- }
174
- if (eventType == PointerEventType.Enter) {
175
- owner.highlightIndex(this.slotIndex);
176
- return;
177
- }
178
- if (eventType == PointerEventType.Up) {
179
- owner.selectHighlighted();
180
- }
181
- }
182
-
183
- private syncPresenterLayout(): void {
184
- this.height(this.presenter.metrics.height, Unit.Pixel);
185
- this.presenter.root.fillSize();
186
- }
187
- }
188
-
189
- export class Dropdown extends FlexBox implements GlobalKeyHandler {
99
+ export class Dropdown extends FlexBox implements GlobalKeyHandler, SelectablePopupListOwner {
190
100
  private static activeInstance: Dropdown | null = null;
191
101
  private static scrollHookRegistered: bool = false;
192
102
 
@@ -195,14 +105,9 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
195
105
  private optionRowTemplateValue: DropdownOptionRowTemplate | null = null;
196
106
  private sizingValue: DropdownSizing | null = null;
197
107
  private colorsValue: DropdownColors | null = null;
198
- private fieldPresenter: DropdownFieldPresenter;
199
- private chevronPresenter: DropdownChevronPresenter;
200
- private readonly popupRoot: Portal;
201
- private readonly panelNode: FlexBox;
202
- private readonly popupPresenter: PopupPresenter;
203
- private readonly popupScrollBox: ScrollBox;
204
- private readonly optionsHost: FlexBox;
205
- private readonly optionNodes: Array<DropdownOptionNode> = new Array<DropdownOptionNode>();
108
+ private fieldPresenter: DropdownFieldPresenter = changetype<DropdownFieldPresenter>(0);
109
+ private chevronPresenter: DropdownChevronPresenter = changetype<DropdownChevronPresenter>(0);
110
+ private popupList: SelectablePopupList = changetype<SelectablePopupList>(0);
206
111
  private readonly itemsValue: Array<DropdownItem> = new Array<DropdownItem>();
207
112
  private readonly disposables: Array<Disposable> = new Array<Disposable>();
208
113
  private disposed: bool = false;
@@ -213,48 +118,23 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
213
118
  private keyFilterToken: u32 = 0;
214
119
  private selectedIndexValue: i32 = -1;
215
120
  private highlightedIndexValue: i32 = -1;
216
- private maxVisibleItemsValue: i32 = UNLIMITED_VISIBLE_ITEMS;
217
- private popupWidthValue: f32 = 0.0;
218
121
  private popupPanelColorValue: u32 = 0x00000000;
219
122
  private popupPanelBackgroundBlurSigmaValue: f32 = DEFAULT_PANEL_BACKGROUND_BLUR_SIGMA;
220
123
  private popupPanelColorOverridden: bool = false;
221
124
  private popupPanelBackgroundBlurOverridden: bool = false;
222
- private changedCallback: ((item: DropdownItem, index: i32) => void) | null = null;
223
- private changedBinding: Callback2<DropdownItem, i32> | null = null;
125
+ private changedCallback: ((event: DropdownChangedEventArgs<DropdownItem>) => void) | null = null;
126
+ private changedBinding: Callback1<DropdownChangedEventArgs<DropdownItem>> | null = null;
224
127
 
225
128
  constructor() {
226
129
  super();
227
130
  Dropdown.ensureScrollHook();
228
131
  const fieldPresenter = createFieldPresenter(null, null);
229
132
  const chevronPresenter = createChevronPresenter(null, null);
230
- const popupRoot = new Portal()
231
- .positionAbsolute()
232
- .position(0.0, 0.0)
233
- .width(100.0, Unit.Percent)
234
- .height(100.0, Unit.Percent) as Portal;
235
- const popupScrollBox = new ScrollBox()
236
- .scrollEnabledX(false)
237
- .scrollEnabledY(true)
238
- .horizontalScrollbarVisibility(ScrollBarVisibility.Never)
239
- .verticalScrollbarVisibility(ScrollBarVisibility.Auto);
240
- const optionsHost = new FlexBox()
241
- .flexDirection(FlexDirection.Column);
242
- const panelNode = new FlexBox()
243
- .positionAbsolute()
244
- .flexDirection(FlexDirection.Column);
245
- const popupPresenter = new PopupPresenter(popupRoot, panelNode);
133
+ const popupList = new SelectablePopupList(this);
246
134
  this.fieldPresenter = fieldPresenter;
247
135
  this.chevronPresenter = chevronPresenter;
248
- this.popupRoot = popupRoot;
249
- this.panelNode = panelNode;
250
- this.popupPresenter = popupPresenter;
251
- this.popupScrollBox = popupScrollBox;
252
- this.optionsHost = optionsHost;
253
- popupPresenter.overlayNode.onClickWith(this, (dropdown) => dropdown.close());
254
- optionsHost.semanticRole(SemanticRole.List);
255
- optionsHost.semanticLabel("Dropdown options");
256
- popupScrollBox.child(optionsHost);
257
- panelNode.child(popupScrollBox);
136
+ this.popupList = popupList;
137
+ popupList.popupPresenter.overlayNode.onPointerClickWith(this, (dropdown, _event) => dropdown.close());
258
138
  this.semanticRole(SemanticRole.ComboBox);
259
139
  this.focusable(true);
260
140
  this.requireInteractive();
@@ -265,7 +145,7 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
265
145
  fieldPresenter.root.fillWidth();
266
146
  fieldPresenter.chevronHost.child(chevronPresenter.root);
267
147
  this.child(fieldPresenter.root);
268
- this.child(this.popupRoot);
148
+ this.child(this.popupList.root);
269
149
  this.track(activeTheme.addAction(new HandlerAction<Dropdown, Theme>(this, (dropdown: Dropdown, _theme: Theme): void => {
270
150
  dropdown.handleThemeChanged();
271
151
  })));
@@ -292,51 +172,43 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
292
172
  } else if (this.selectedIndexValue < 0 && this.itemsValue.length > 0) {
293
173
  this.selectedIndexValue = 0;
294
174
  }
295
- this.ensureOptionNodes();
175
+ this.popupList.refreshPanelLayout();
296
176
  this.syncValueLabel();
297
177
  this.handleThemeChanged();
298
178
  return this;
299
179
  }
300
180
 
301
- onChanged(callback: ((item: DropdownItem, index: i32) => void) | null): this {
181
+ onChanged(callback: ((event: DropdownChangedEventArgs<DropdownItem>) => void) | null): this {
302
182
  this.changedCallback = callback;
303
183
  this.changedBinding = null;
304
184
  return this;
305
185
  }
306
186
 
307
- bindChanged<Owner>(owner: Owner, handler: Handler2<Owner, DropdownItem, i32>): this {
187
+ bindChanged<Owner>(owner: Owner, handler: Handler1<Owner, DropdownChangedEventArgs<DropdownItem>>): this {
308
188
  this.changedCallback = null;
309
- this.changedBinding = bind2<Owner, DropdownItem, i32>(owner, handler);
189
+ this.changedBinding = bind1<Owner, DropdownChangedEventArgs<DropdownItem>>(owner, handler);
310
190
  return this;
311
191
  }
312
192
 
313
- onChangedWith<Owner>(owner: Owner, handler: Handler2<Owner, DropdownItem, i32>): this {
193
+ onChangedWith<Owner>(owner: Owner, handler: Handler1<Owner, DropdownChangedEventArgs<DropdownItem>>): this {
314
194
  this.bindChanged(owner, handler);
315
195
  return this;
316
196
  }
317
197
 
318
198
  maxVisibleItems(count: i32): this {
319
- if (count <= 0) {
320
- warn("Layout", "Dropdown.maxVisibleItems() received " + count.toString() + "; using unlimited visible items.");
321
- }
322
- this.maxVisibleItemsValue = count > 0 ? count : UNLIMITED_VISIBLE_ITEMS;
323
- this.refreshPanelLayout();
199
+ this.popupList.maxVisibleItems(count);
324
200
  return this;
325
201
  }
326
202
 
327
203
  popupWidth(value: f32): this {
328
- if (value <= 0.0) {
329
- warn("Layout", "Dropdown.popupWidth() received " + value.toString() + "; clamping to 0.0.");
330
- }
331
- this.popupWidthValue = value > 0.0 ? value : 0.0;
332
- this.refreshPanelLayout();
204
+ this.popupList.popupWidth(value);
333
205
  return this;
334
206
  }
335
207
 
336
208
  popupPanelColor(color: u32): this {
337
209
  this.popupPanelColorOverridden = true;
338
210
  this.popupPanelColorValue = color;
339
- this.panelNode.bgColor(color);
211
+ this.popupList.panelNode.bgColor(color);
340
212
  return this;
341
213
  }
342
214
 
@@ -346,7 +218,7 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
346
218
  warn("Layout", "Dropdown.popupPanelBackgroundBlur() received " + sigma.toString() + "; clamping to 0.0.");
347
219
  }
348
220
  this.popupPanelBackgroundBlurSigmaValue = sigma >= 0.0 ? sigma : 0.0;
349
- this.panelNode.backgroundBlur(this.popupPanelBackgroundBlurSigmaValue);
221
+ this.popupList.panelNode.backgroundBlur(this.popupPanelBackgroundBlurSigmaValue);
350
222
  return this;
351
223
  }
352
224
 
@@ -366,12 +238,7 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
366
238
  this.fieldPresenter.chevronHost.addChildNode(nextPresenter.root);
367
239
  previousPresenter.root.dispose();
368
240
  }
369
- if (this.usesDefaultOptionRowPresenter()) {
370
- for (let index = 0; index < this.optionNodes.length; ++index) {
371
- unchecked(this.optionNodes[index]).template(this.optionRowTemplateValue, this.sizingValue);
372
- }
373
- this.refreshPanelLayout();
374
- }
241
+ this.popupList.sizing(this.sizingValue);
375
242
  this.syncValueLabel();
376
243
  this.handleThemeChanged();
377
244
  return this;
@@ -379,6 +246,7 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
379
246
 
380
247
  colors(colors: DropdownColors | null): this {
381
248
  this.colorsValue = colors;
249
+ this.popupList.colors(colors);
382
250
  this.handleThemeChanged();
383
251
  return this;
384
252
  }
@@ -410,11 +278,7 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
410
278
  optionRowTemplate(template: DropdownOptionRowTemplate | null): this {
411
279
  this.close();
412
280
  this.optionRowTemplateValue = template;
413
- for (let index = 0; index < this.optionNodes.length; ++index) {
414
- unchecked(this.optionNodes[index]).template(template, this.sizingValue);
415
- }
416
- this.refreshPanelLayout();
417
- this.syncOptionVisuals();
281
+ this.popupList.optionRowTemplate(template);
418
282
  return this;
419
283
  }
420
284
 
@@ -459,15 +323,8 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
459
323
  }
460
324
 
461
325
  highlightIndex(index: i32): void {
462
- if (index < 0 || index >= this.itemsValue.length || this.highlightedIndexValue == index) {
463
- if (index < 0 || index >= this.itemsValue.length) {
464
- warn("Layout", "Dropdown.highlightIndex() received " + index.toString() + " outside the available item range.");
465
- }
466
- return;
467
- }
468
- this.highlightedIndexValue = index;
469
- this.syncOptionVisuals();
470
- this.ensureHighlightedVisible();
326
+ this.popupList.highlightIndex(index);
327
+ this.highlightedIndexValue = this.popupList.highlightedIndex;
471
328
  }
472
329
 
473
330
  selectHighlighted(): void {
@@ -583,6 +440,7 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
583
440
  if (index == -1) {
584
441
  this.selectedIndexValue = -1;
585
442
  this.highlightedIndexValue = -1;
443
+ this.popupList.setHighlightedIndex(-1);
586
444
  this.syncValueLabel();
587
445
  this.handleThemeChanged();
588
446
  return;
@@ -609,6 +467,7 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
609
467
  const changed = this.selectedIndexValue != clampedIndex;
610
468
  this.selectedIndexValue = clampedIndex;
611
469
  this.highlightedIndexValue = clampedIndex;
470
+ this.popupList.setHighlightedIndex(clampedIndex);
612
471
  this.syncValueLabel();
613
472
  this.handleThemeChanged();
614
473
  if (emit && changed) {
@@ -622,13 +481,14 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
622
481
  return;
623
482
  }
624
483
  const item = unchecked(this.itemsValue[this.selectedIndexValue]);
484
+ const event = new DropdownChangedEventArgs<DropdownItem>(item, this.selectedIndexValue);
625
485
  const callback = this.changedCallback;
626
486
  if (callback !== null) {
627
- callback(item, this.selectedIndexValue);
487
+ callback(event);
628
488
  }
629
489
  const binding = this.changedBinding;
630
490
  if (binding !== null) {
631
- binding.invoke(item, this.selectedIndexValue);
491
+ binding.invoke(event);
632
492
  }
633
493
  }
634
494
 
@@ -636,24 +496,22 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
636
496
  if (this.openState || this.itemsValue.length == 0 || this.builtHandle == 0) {
637
497
  return;
638
498
  }
639
- this.ensureOptionNodes();
640
- this.rebuildPanel();
499
+ const initialHighlight = this.selectedIndexValue >= 0
500
+ ? this.selectedIndexValue
501
+ : (this.itemsValue.length > 0 ? 0 : -1);
502
+ this.popupList.setHighlightedIndex(initialHighlight);
503
+ this.highlightedIndexValue = this.popupList.highlightedIndex;
641
504
  const bounds = this.tryGetViewportBounds();
642
505
  if (bounds !== null) {
643
- const width = unchecked(bounds[2]);
644
- const height = unchecked(bounds[3]);
645
- this.positionPanel(unchecked(bounds[0]), unchecked(bounds[1]), width, height);
506
+ if (!this.popupList.open(unchecked(bounds[0]), unchecked(bounds[1]), unchecked(bounds[2]), unchecked(bounds[3]), initialHighlight)) {
507
+ return;
508
+ }
509
+ this.highlightedIndexValue = this.popupList.highlightedIndex;
646
510
  }
647
511
  this.openState = true;
648
512
  Dropdown.activeInstance = this;
649
513
  this.semanticExpanded(true);
650
514
  this.requestSemanticAnnouncement();
651
- if (this.selectedIndexValue >= 0) {
652
- this.highlightedIndexValue = this.selectedIndexValue;
653
- } else if (this.itemsValue.length > 0) {
654
- this.highlightedIndexValue = 0;
655
- }
656
- this.syncOptionVisuals();
657
515
  if (this.keyFilterToken == 0) {
658
516
  this.keyFilterToken = EventRouter.pushKeyFilter(this);
659
517
  }
@@ -717,21 +575,11 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
717
575
  return right > svX && bottom > svY && x < svRight && y < svBottom;
718
576
  }
719
577
 
720
- private positionPanel(triggerX: f32, triggerY: f32, triggerWidth: f32, triggerHeight: f32): void {
721
- const popupWidth = this.resolvePopupWidth(triggerWidth);
722
- const panelHeight = this.resolveViewportClampedPanelOuterHeight();
723
- this.panelNode.width(popupWidth, Unit.Pixel);
724
- this.panelNode.height(panelHeight, Unit.Pixel);
725
- this.popupScrollBox.width(100.0, Unit.Percent);
726
- this.popupScrollBox.height(<f32>Math.max(0.0, panelHeight - (PANEL_PADDING * 2.0)), Unit.Pixel);
727
- this.popupPresenter.showAnchored(triggerX, triggerY, triggerWidth, triggerHeight, popupWidth, panelHeight);
728
- }
729
-
730
578
  private close(): void {
731
- if (!this.openState && !this.popupPresenter.isOpen) {
579
+ if (!this.openState && !this.popupList.isOpen) {
732
580
  return;
733
581
  }
734
- this.popupPresenter.hide();
582
+ this.popupList.close();
735
583
  this.openState = false;
736
584
  if (Dropdown.activeInstance === this) {
737
585
  Dropdown.activeInstance = null;
@@ -745,18 +593,6 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
745
593
  this.handleThemeChanged();
746
594
  }
747
595
 
748
- private rebuildPanel(): void {
749
- for (let index = 0; index < this.optionNodes.length; ++index) {
750
- this.optionsHost.removeChildNode(unchecked(this.optionNodes[index]));
751
- }
752
- for (let index = 0; index < this.itemsValue.length; ++index) {
753
- const optionNode = unchecked(this.optionNodes[index]);
754
- optionNode.label(unchecked(this.itemsValue[index]).label);
755
- this.optionsHost.addChildNode(optionNode);
756
- }
757
- this.refreshPanelLayout();
758
- }
759
-
760
596
  private syncValueLabel(): void {
761
597
  if (this.selectedIndexValue >= 0 && this.selectedIndexValue < this.itemsValue.length) {
762
598
  const label = unchecked(this.itemsValue[this.selectedIndexValue]).label;
@@ -769,33 +605,15 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
769
605
  }
770
606
 
771
607
  private syncOptionVisuals(): void {
772
- const theme = activeTheme.value;
773
- for (let index = 0; index < this.itemsValue.length; ++index) {
774
- unchecked(this.optionNodes[index]).applyTheme(
775
- theme,
776
- index == this.highlightedIndexValue,
777
- index == this.selectedIndexValue,
778
- this.isEnabled,
779
- this.colorsValue,
780
- );
781
- }
608
+ this.popupList.syncOptionVisuals();
782
609
  }
783
610
 
784
611
  private moveHighlight(delta: i32): void {
785
- if (this.itemsValue.length == 0) {
786
- return;
787
- }
788
- let nextIndex = this.highlightedIndexValue;
789
- if (nextIndex < 0) {
790
- nextIndex = this.selectedIndexValue >= 0 ? this.selectedIndexValue : 0;
612
+ if (this.popupList.highlightedIndex < 0 && this.selectedIndexValue >= 0) {
613
+ this.popupList.setHighlightedIndex(this.selectedIndexValue);
791
614
  }
792
- nextIndex += delta;
793
- if (nextIndex < 0) {
794
- nextIndex = this.itemsValue.length - 1;
795
- } else if (nextIndex >= this.itemsValue.length) {
796
- nextIndex = 0;
797
- }
798
- this.highlightIndex(nextIndex);
615
+ this.popupList.moveHighlight(delta);
616
+ this.highlightedIndexValue = this.popupList.highlightedIndex;
799
617
  }
800
618
 
801
619
  private clearItems(): void {
@@ -803,85 +621,7 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
803
621
  this.itemsValue.length = 0;
804
622
  this.selectedIndexValue = -1;
805
623
  this.highlightedIndexValue = -1;
806
- }
807
-
808
- private ensureOptionNodes(): void {
809
- while (this.optionNodes.length < this.itemsValue.length) {
810
- const optionNode = new DropdownOptionNode(this.optionRowTemplateValue, this.sizingValue).bindOwner(this, this.optionNodes.length);
811
- this.optionNodes.push(optionNode);
812
- }
813
- }
814
-
815
- private resolveOptionRowHeight(): f32 {
816
- if (this.optionNodes.length == 0) {
817
- const sizing = this.sizingValue;
818
- if (
819
- sizing !== null &&
820
- sizing.hasOptionHeight &&
821
- this.usesDefaultOptionRowPresenter()
822
- ) {
823
- return sizing.optionHeightPx;
824
- }
825
- return OPTION_HEIGHT;
826
- }
827
- return unchecked(this.optionNodes[0]).rowHeight;
828
- }
829
-
830
- private resolveVisibleItemCount(): i32 {
831
- if (this.maxVisibleItemsValue <= 0 || this.itemsValue.length <= this.maxVisibleItemsValue) {
832
- return this.itemsValue.length;
833
- }
834
- return this.maxVisibleItemsValue;
835
- }
836
-
837
- private resolvePanelOuterHeight(): f32 {
838
- return <f32>this.resolveVisibleItemCount() * this.resolveOptionRowHeight() + (PANEL_PADDING * 2.0);
839
- }
840
-
841
- private resolveViewportClampedPanelOuterHeight(): f32 {
842
- const maxHeight = <f32>Math.max(PANEL_EDGE_PADDING, ui.getViewportHeight() - (PANEL_EDGE_PADDING * 2.0));
843
- return <f32>Math.min(this.resolvePanelOuterHeight(), maxHeight);
844
- }
845
-
846
- private resolvePopupWidth(triggerWidth: f32): f32 {
847
- return this.popupWidthValue > 0.0 ? this.popupWidthValue : triggerWidth;
848
- }
849
-
850
- private refreshPanelLayout(): void {
851
- this.optionsHost.width(100.0, Unit.Percent);
852
- this.optionsHost.height(<f32>this.itemsValue.length * this.resolveOptionRowHeight(), Unit.Pixel);
853
- this.popupScrollBox.width(100.0, Unit.Percent);
854
- this.popupScrollBox.height(
855
- <f32>Math.max(0.0, this.resolveViewportClampedPanelOuterHeight() - (PANEL_PADDING * 2.0)),
856
- Unit.Pixel,
857
- );
858
- if (this.openState) {
859
- const bounds = this.tryGetViewportBounds();
860
- if (bounds !== null) {
861
- this.positionPanel(unchecked(bounds[0]), unchecked(bounds[1]), unchecked(bounds[2]), unchecked(bounds[3]));
862
- }
863
- this.ensureHighlightedVisible();
864
- }
865
- }
866
-
867
- private ensureHighlightedVisible(): void {
868
- if (!this.openState || this.highlightedIndexValue < 0) {
869
- return;
870
- }
871
- const visibleHeight = <f32>Math.max(0.0, this.resolveViewportClampedPanelOuterHeight() - (PANEL_PADDING * 2.0));
872
- if (visibleHeight <= 0.0) {
873
- return;
874
- }
875
- const rowHeight = this.resolveOptionRowHeight();
876
- const itemTop = <f32>this.highlightedIndexValue * rowHeight;
877
- const itemBottom = itemTop + rowHeight;
878
- let nextOffset = this.popupScrollBox.scrollState.offsetY.value;
879
- if (itemTop < nextOffset) {
880
- nextOffset = itemTop;
881
- } else if (itemBottom > nextOffset + visibleHeight) {
882
- nextOffset = itemBottom - visibleHeight;
883
- }
884
- this.popupScrollBox.setRuntimeScrollOffset(0.0, nextOffset);
624
+ this.popupList.clear();
885
625
  }
886
626
 
887
627
  private handleThemeChanged(): void {
@@ -897,7 +637,7 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
897
637
  }
898
638
  this.cursor(this.isEnabled ? CursorStyle.Pointer : CursorStyle.Default);
899
639
  this.cornerRadius(0.0);
900
- this.border(0.0, 0x00000000, BorderStyle.Solid);
640
+ this.border(0.0, 0x00000000);
901
641
  this.padding(0.0, 0.0, 0.0, 0.0);
902
642
  this.bgColor(0x00000000);
903
643
  this.opacity(this.isEnabled ? 1.0 : 0.6);
@@ -923,11 +663,16 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
923
663
  this.isEnabled,
924
664
  ),
925
665
  );
926
- this.panelNode
927
- .padding(PANEL_PADDING, PANEL_PADDING, PANEL_PADDING, PANEL_PADDING)
666
+ this.popupList.panelNode
667
+ .padding(
668
+ SELECTABLE_POPUP_LIST_PANEL_PADDING,
669
+ SELECTABLE_POPUP_LIST_PANEL_PADDING,
670
+ SELECTABLE_POPUP_LIST_PANEL_PADDING,
671
+ SELECTABLE_POPUP_LIST_PANEL_PADDING,
672
+ )
928
673
  .cornerRadius(theme.spacing.sm)
929
674
  .bgColor(this.popupPanelColorValue)
930
- .border(1.0, theme.contextMenu.panelBorderColor, BorderStyle.Solid)
675
+ .border(1.0, theme.contextMenu.panelBorderColor)
931
676
  .backgroundBlur(this.popupPanelBackgroundBlurSigmaValue)
932
677
  .dropShadow(
933
678
  theme.contextMenu.panelShadowColor,
@@ -936,7 +681,7 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
936
681
  theme.contextMenu.shadowBlur,
937
682
  theme.contextMenu.shadowSpread,
938
683
  );
939
- this.popupScrollBox.bgColor(0x00000000);
684
+ this.popupList.popupScrollBox.bgColor(0x00000000);
940
685
  this.syncOptionVisuals();
941
686
  this.syncFocusChrome(theme);
942
687
  }
@@ -950,7 +695,7 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
950
695
  return;
951
696
  }
952
697
  this.disposed = true;
953
- this.popupPresenter.dispose();
698
+ this.popupList.dispose();
954
699
  disposeAll(this.disposables);
955
700
  FocusAdornerManager.hideOwner(this);
956
701
  }
@@ -971,7 +716,7 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
971
716
  nextFieldPresenter.chevronHost.addChildNode(nextChevronPresenter.root);
972
717
  const children = new Array<Node>();
973
718
  children.push(nextFieldPresenter.root);
974
- children.push(this.popupRoot);
719
+ children.push(this.popupList.root);
975
720
  this.replaceChildren(children);
976
721
  previousFieldRoot.dispose();
977
722
  }
@@ -992,11 +737,34 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler {
992
737
  return templateSet === null || templateSet.dropdownChevron === null;
993
738
  }
994
739
 
995
- private usesDefaultOptionRowPresenter(): bool {
996
- if (this.optionRowTemplateValue !== null) {
997
- return false;
998
- }
999
- const templateSet = getControlTemplates();
1000
- return templateSet === null || templateSet.dropdownOptionRow === null;
740
+ getPopupListItemCount(): i32 {
741
+ return this.itemsValue.length;
742
+ }
743
+
744
+ getPopupListItemLabel(index: i32): string {
745
+ return unchecked(this.itemsValue[index]).label;
746
+ }
747
+
748
+ isPopupListItemSelected(index: i32): bool {
749
+ return index == this.selectedIndexValue;
750
+ }
751
+
752
+ isPopupListEnabled(): bool {
753
+ return this.isEnabled;
754
+ }
755
+
756
+ popupListHighlightIndex(index: i32): void {
757
+ this.highlightIndex(index);
758
+ }
759
+
760
+ popupListActivateIndex(index: i32): void {
761
+ this.highlightIndex(index);
762
+ this.selectHighlighted();
763
+ }
764
+
765
+ popupListPointerDown(_index: i32): void {
766
+ }
767
+
768
+ popupListPointerUp(_index: i32): void {
1001
769
  }
1002
770
  }