@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.
Files changed (119) 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 +26 -16
  7. package/scripts/build.sh +51 -47
  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 +70 -4
  16. package/src/FuiExports.ts +13 -1
  17. package/src/bindings/ui.ts +165 -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 +19 -9
  46. package/src/core/Bitmap.ts +219 -1
  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 +126 -0
  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 -746
  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 +34 -0
  75. package/src/drawing/DrawContext.ts +189 -0
  76. package/src/drawing/Paint.ts +34 -0
  77. package/src/drawing/Path.ts +40 -0
  78. package/src/nodes/CustomDrawable.ts +61 -0
  79. package/src/nodes/FlexBox.ts +57 -11
  80. package/src/nodes/Grid.ts +5 -5
  81. package/src/nodes/Image.ts +20 -1
  82. package/src/nodes/RichText.ts +36 -25
  83. package/src/nodes/ScrollBar.ts +2 -1
  84. package/src/nodes/ScrollBox.ts +7 -2
  85. package/src/nodes/ScrollView.ts +17 -6
  86. package/src/nodes/Svg.ts +18 -1
  87. package/src/nodes/Text.ts +1 -1
  88. package/src/nodes/TextCore.ts +115 -20
  89. package/src/nodes/index.ts +3 -1
  90. package/src/text/DynamicTextLayout.ts +275 -0
  91. package/src/text/TextLayout.ts +308 -0
  92. package/src/worker/Worker.ts +3 -36
  93. package/src/worker/WorkerJob.ts +5 -9
  94. package/src/worker/ffi.ts +0 -6
  95. package/browser/src/common-harness/host-imports.ts +0 -447
  96. package/browser/src/common-harness/interop.ts +0 -39
  97. package/browser/src/common-harness/managed-harness-bitmap-host.ts +0 -92
  98. package/browser/src/common-harness/managed-harness-fetch-host.ts +0 -201
  99. package/browser/src/common-harness/managed-harness-file-host.ts +0 -1066
  100. package/browser/src/common-harness/managed-harness-file-payloads.ts +0 -143
  101. package/browser/src/common-harness/managed-harness-file-types.ts +0 -71
  102. package/browser/src/common-harness/managed-harness-session.ts +0 -15
  103. package/browser/src/common-harness/managed-harness.ts +0 -1451
  104. package/browser/src/common-harness/managed-history.ts +0 -168
  105. package/browser/src/common-harness/persisted-restore-policy.ts +0 -50
  106. package/browser/src/common-harness/persisted-ui-state-controller.ts +0 -309
  107. package/browser/src/common-harness/text-session-bridge.ts +0 -452
  108. package/browser/src/common-harness/types.ts +0 -205
  109. package/browser/src/common-harness/ui-chrome.ts +0 -206
  110. package/browser/src/common-harness/ui-imports.ts +0 -553
  111. package/browser/src/common-harness/wasm-module-cache.ts +0 -47
  112. package/browser/src/file-processing-worker.ts +0 -89
  113. package/browser/src/host-events.ts +0 -118
  114. package/browser/src/host-services.ts +0 -483
  115. package/browser/src/persisted-ui-state.ts +0 -206
  116. package/browser/src/routed-app-conventions.ts +0 -292
  117. package/browser/src/worker-bootstrap.ts +0 -916
  118. package/browser/src/worker-manager.ts +0 -234
  119. package/browser/src/worker-types.ts +0 -68
@@ -0,0 +1,901 @@
1
+ import { FlexBox, Portal, ScrollBarVisibility, ScrollBox, TextCore } from "../nodes";
2
+ import { ContextMenuAction, MenuItem, runContextMenuAction } from "../controls/ContextMenu";
3
+ import { TextInputCore } from "../controls/internal/TextInputCore";
4
+ import { PointerClickEventArgs, Node, PointerEventArgs } from "./Node";
5
+ import {
6
+ AlignItems,
7
+ FlexDirection,
8
+ HandleValue,
9
+ JustifyContent,
10
+ PointerEventType,
11
+ SemanticRole,
12
+ TextOverflow,
13
+ Unit,
14
+ Visibility,
15
+ } from "./ffi";
16
+ import { activeTheme } from "./Theme";
17
+ import * as ui from "../bindings/ui";
18
+
19
+ const TOOLBAR_MARGIN: f32 = 8.0;
20
+ const EDGE_MARGIN: f32 = 8.0;
21
+ const READONLY_BUTTON_WIDTH: f32 = 96.0;
22
+ const EDITABLE_BUTTON_WIDTH: f32 = 72.0;
23
+ const OVERFLOW_BUTTON_WIDTH: f32 = 44.0;
24
+ const VERTICAL_MENU_WIDTH: f32 = 168.0;
25
+ const VERTICAL_MENU_MAX_HEIGHT: f32 = 184.0;
26
+ const TOOLBAR_HORIZONTAL_PADDING: f32 = 4.0;
27
+ const MAX_HORIZONTAL_ACTIONS: i32 = 3;
28
+ const OVERFLOW_SLOT: i32 = -1;
29
+ const BACK_SLOT: i32 = -2;
30
+ const OVERFLOW_LABEL = "More";
31
+
32
+ function handleFirstButtonClick(_event: PointerClickEventArgs): void {
33
+ MobileTextSelectionToolbarManager.activateToolbarSlot(0);
34
+ }
35
+
36
+ function handleSecondButtonClick(_event: PointerClickEventArgs): void {
37
+ MobileTextSelectionToolbarManager.activateToolbarSlot(1);
38
+ }
39
+
40
+ function handleThirdButtonClick(_event: PointerClickEventArgs): void {
41
+ MobileTextSelectionToolbarManager.activateToolbarSlot(2);
42
+ }
43
+
44
+ function handleFourthButtonClick(_event: PointerClickEventArgs): void {
45
+ MobileTextSelectionToolbarManager.activateToolbarSlot(3);
46
+ }
47
+
48
+ function handleFifthButtonClick(_event: PointerClickEventArgs): void {
49
+ MobileTextSelectionToolbarManager.activateToolbarSlot(4);
50
+ }
51
+
52
+ function handleSixthButtonClick(_event: PointerClickEventArgs): void {
53
+ MobileTextSelectionToolbarManager.activateToolbarSlot(5);
54
+ }
55
+
56
+ function handleOverflowButtonClick(_event: PointerClickEventArgs): void {
57
+ MobileTextSelectionToolbarManager.activateToolbarSlot(OVERFLOW_SLOT);
58
+ }
59
+
60
+ function handleBackButtonClick(_event: PointerClickEventArgs): void {
61
+ MobileTextSelectionToolbarManager.activateToolbarSlot(BACK_SLOT);
62
+ }
63
+
64
+ class ToolbarButton extends FlexBox {
65
+ private labelNode: TextCore | null = null;
66
+ private readonly dotNodes: Array<FlexBox> = new Array<FlexBox>();
67
+ private readonly slot: i32;
68
+
69
+ constructor(label: string, width: f32, click: (event: PointerClickEventArgs) => void, slot: i32) {
70
+ super();
71
+ this.slot = slot;
72
+ const childNode = slot == OVERFLOW_SLOT
73
+ ? this.createOverflowIcon()
74
+ : this.createLabel(label);
75
+ this.width(width, Unit.Pixel)
76
+ .height(activeTheme.value.contextMenu.item.height, Unit.Pixel)
77
+ .alignItems(AlignItems.Center)
78
+ .justifyContent(JustifyContent.Center)
79
+ .padding(
80
+ activeTheme.value.contextMenu.item.paddingLeft,
81
+ activeTheme.value.contextMenu.item.paddingTop,
82
+ activeTheme.value.contextMenu.item.paddingRight,
83
+ activeTheme.value.contextMenu.item.paddingBottom,
84
+ )
85
+ .cornerRadius(activeTheme.value.contextMenu.item.cornerRadius)
86
+ .child(childNode)
87
+ .semanticRole(SemanticRole.Button)
88
+ .semanticLabel(label)
89
+ .preserveSelectionOnPointerDown(true)
90
+ .onPointerClick(click);
91
+ }
92
+
93
+ _handlePointerEvent(eventType: PointerEventType, x: f32, y: f32, modifiers: u32 = 0): void {
94
+ if (eventType == PointerEventType.Up) {
95
+ MobileTextSelectionToolbarManager.activateToolbarSlot(this.slot);
96
+ return;
97
+ }
98
+ super._handlePointerEvent(eventType, x, y, modifiers);
99
+ }
100
+
101
+ _handleBubbledPointerEvent(event: PointerEventArgs): bool {
102
+ if (event.eventType == PointerEventType.Up) {
103
+ MobileTextSelectionToolbarManager.activateToolbarSlot(this.slot);
104
+ event.handled = true;
105
+ return true;
106
+ }
107
+ return super._handleBubbledPointerEvent(event);
108
+ }
109
+
110
+ label(value: string): void {
111
+ const labelNode = this.labelNode;
112
+ if (labelNode !== null) {
113
+ labelNode.text(value);
114
+ }
115
+ this.semanticLabel(value);
116
+ }
117
+
118
+ applyMenuStyle(): void {
119
+ this.height(activeTheme.value.contextMenu.item.height, Unit.Pixel);
120
+ this.padding(
121
+ activeTheme.value.contextMenu.item.paddingLeft,
122
+ activeTheme.value.contextMenu.item.paddingTop,
123
+ activeTheme.value.contextMenu.item.paddingRight,
124
+ activeTheme.value.contextMenu.item.paddingBottom,
125
+ );
126
+ this.cornerRadius(activeTheme.value.contextMenu.item.cornerRadius);
127
+ this.bgColor(activeTheme.value.contextMenu.item.background);
128
+ const labelNode = this.labelNode;
129
+ if (labelNode !== null) {
130
+ labelNode
131
+ .fontFamily(activeTheme.value.contextMenu.item.fontFamily)
132
+ .fontSize(activeTheme.value.contextMenu.item.fontSize)
133
+ .textColor(activeTheme.value.contextMenu.item.textColor);
134
+ }
135
+ for (let index = 0; index < this.dotNodes.length; ++index) {
136
+ unchecked(this.dotNodes[index]).bgColor(activeTheme.value.contextMenu.item.textColor);
137
+ }
138
+ }
139
+
140
+ private createLabel(label: string): TextCore {
141
+ const labelNode = new TextCore(label)
142
+ .fontFamily(activeTheme.value.contextMenu.item.fontFamily)
143
+ .fontSize(activeTheme.value.contextMenu.item.fontSize)
144
+ .textColor(activeTheme.value.contextMenu.item.textColor)
145
+ .overflow(TextOverflow.Ellipsis)
146
+ .selectable(false) as TextCore;
147
+ labelNode
148
+ .preserveSelectionOnPointerDown(true)
149
+ .onPointerUpWith<ToolbarButton>(this, (button: ToolbarButton, event: PointerEventArgs): void => {
150
+ MobileTextSelectionToolbarManager.activateToolbarSlot(button.slot);
151
+ event.handled = true;
152
+ });
153
+ this.labelNode = labelNode;
154
+ return labelNode;
155
+ }
156
+
157
+ private createOverflowIcon(): FlexBox {
158
+ this.labelNode = null;
159
+ const children = new Array<Node>();
160
+ for (let index = 0; index < 3; ++index) {
161
+ const dot = new FlexBox()
162
+ .width(3.0, Unit.Pixel)
163
+ .height(3.0, Unit.Pixel)
164
+ .margin(0.0, 1.25, 0.0, 1.25)
165
+ .cornerRadius(2.0)
166
+ .bgColor(activeTheme.value.contextMenu.item.textColor) as FlexBox;
167
+ this.dotNodes.push(dot);
168
+ children.push(dot);
169
+ }
170
+ const icon = new FlexBox()
171
+ .width(16.0, Unit.Pixel)
172
+ .height(activeTheme.value.contextMenu.item.height, Unit.Pixel)
173
+ .flexDirection(FlexDirection.Column)
174
+ .alignItems(AlignItems.Center)
175
+ .justifyContent(JustifyContent.Center)
176
+ .children(children)
177
+ .preserveSelectionOnPointerDown(true) as FlexBox;
178
+ icon.onPointerUpWith<ToolbarButton>(this, (button: ToolbarButton, event: PointerEventArgs): void => {
179
+ MobileTextSelectionToolbarManager.activateToolbarSlot(button.slot);
180
+ event.handled = true;
181
+ });
182
+ return icon;
183
+ }
184
+ }
185
+
186
+ function makeSeparator(): FlexBox {
187
+ return new FlexBox()
188
+ .width(1.0, Unit.Pixel)
189
+ .height(activeTheme.value.contextMenu.item.height - 10.0, Unit.Pixel)
190
+ .bgColor(activeTheme.value.contextMenu.separatorColor) as FlexBox;
191
+ }
192
+
193
+ function normalizeStart(start: u32, end: u32): u32 {
194
+ return start < end ? start : end;
195
+ }
196
+
197
+ function normalizeEnd(start: u32, end: u32): u32 {
198
+ return start < end ? end : start;
199
+ }
200
+
201
+ export class MobileTextSelectionToolbarManager {
202
+ private static hostRoot: Portal | null = null;
203
+ private static panel: FlexBox | null = null;
204
+ private static overflowPanel: FlexBox | null = null;
205
+ private static overflowScrollBox: ScrollBox | null = null;
206
+ private static overflowContent: FlexBox | null = null;
207
+ private static readonly buttons: Array<ToolbarButton> = new Array<ToolbarButton>();
208
+ private static readonly overflowButtons: Array<ToolbarButton> = new Array<ToolbarButton>();
209
+ private static readonly separators: Array<FlexBox> = new Array<FlexBox>();
210
+ private static readonly overflowSeparators: Array<FlexBox> = new Array<FlexBox>();
211
+ private static readonly activeItems: Array<MenuItem> = new Array<MenuItem>();
212
+ private static activeHandle: u64 = 0;
213
+ private static activeStart: u32 = 0;
214
+ private static activeEnd: u32 = 0;
215
+ private static activeCrossSelectionText: string = "";
216
+ private static activeCrossSelectionSelectAllTarget: u64 = 0;
217
+ private static pendingCrossSelectionTextHandle: u64 = 0;
218
+ private static hiddenForHandleDrag: bool = false;
219
+ private static overflowVisible: bool = false;
220
+ private static horizontalItemCount: i32 = 0;
221
+ private static lastPanelX: f32 = EDGE_MARGIN;
222
+ private static lastPanelY: f32 = EDGE_MARGIN;
223
+ private static lastPanelWidth: f32 = 0.0;
224
+
225
+ static createDefaultHost(): Portal {
226
+ const existingHost = this.hostRoot;
227
+ if (existingHost !== null) {
228
+ return existingHost;
229
+ }
230
+ const panel = new FlexBox()
231
+ .positionAbsolute()
232
+ .height(activeTheme.value.contextMenu.item.height + 8.0, Unit.Pixel)
233
+ .flexDirection(FlexDirection.Row)
234
+ .alignItems(AlignItems.Center)
235
+ .padding(TOOLBAR_HORIZONTAL_PADDING, 4.0, TOOLBAR_HORIZONTAL_PADDING, 4.0)
236
+ .cornerRadius(activeTheme.value.contextMenu.panelCornerRadius)
237
+ .border(1.0, activeTheme.value.contextMenu.panelBorderColor)
238
+ .bgColor(activeTheme.value.contextMenu.panelBackground)
239
+ .backgroundBlur(10.0)
240
+ .dropShadow(
241
+ activeTheme.value.contextMenu.panelShadowColor,
242
+ 0.0,
243
+ activeTheme.value.contextMenu.shadowOffsetY,
244
+ activeTheme.value.contextMenu.shadowBlur,
245
+ activeTheme.value.contextMenu.shadowSpread,
246
+ )
247
+ .preserveSelectionOnPointerDown(true) as FlexBox;
248
+ panel.visibility(Visibility.Collapsed);
249
+
250
+ const overflowContent = new FlexBox()
251
+ .flexDirection(FlexDirection.Column)
252
+ .width(100.0, Unit.Percent)
253
+ .bgColor(0x00000000)
254
+ .preserveSelectionOnPointerDown(true) as FlexBox;
255
+ const overflowScrollBox = new ScrollBox()
256
+ .scrollEnabledX(false)
257
+ .scrollEnabledY(true)
258
+ .verticalScrollbarVisibility(ScrollBarVisibility.Auto)
259
+ .horizontalScrollbarVisibility(ScrollBarVisibility.Never)
260
+ .scrollbarGutter(2.0)
261
+ .width(100.0, Unit.Percent)
262
+ .height(VERTICAL_MENU_MAX_HEIGHT, Unit.Pixel)
263
+ .child(overflowContent)
264
+ .preserveSelectionOnPointerDown(true) as ScrollBox;
265
+ const overflowPanel = new FlexBox()
266
+ .positionAbsolute()
267
+ .width(VERTICAL_MENU_WIDTH, Unit.Pixel)
268
+ .height(VERTICAL_MENU_MAX_HEIGHT, Unit.Pixel)
269
+ .cornerRadius(activeTheme.value.contextMenu.panelCornerRadius)
270
+ .border(1.0, activeTheme.value.contextMenu.panelBorderColor)
271
+ .bgColor(activeTheme.value.contextMenu.panelBackground)
272
+ .backgroundBlur(10.0)
273
+ .dropShadow(
274
+ activeTheme.value.contextMenu.panelShadowColor,
275
+ 0.0,
276
+ activeTheme.value.contextMenu.shadowOffsetY,
277
+ activeTheme.value.contextMenu.shadowBlur,
278
+ activeTheme.value.contextMenu.shadowSpread,
279
+ ) as FlexBox;
280
+ overflowPanel.preserveSelectionOnPointerDown(true);
281
+ overflowPanel.child(overflowScrollBox);
282
+ overflowPanel.visibility(Visibility.Collapsed);
283
+
284
+ this.buttons.length = 0;
285
+ this.buttons.push(new ToolbarButton("Copy", READONLY_BUTTON_WIDTH, handleFirstButtonClick, 0));
286
+ this.buttons.push(new ToolbarButton("Select all", READONLY_BUTTON_WIDTH, handleSecondButtonClick, 1));
287
+ this.buttons.push(new ToolbarButton("Paste", EDITABLE_BUTTON_WIDTH, handleThirdButtonClick, 2));
288
+ this.buttons.push(new ToolbarButton(OVERFLOW_LABEL, OVERFLOW_BUTTON_WIDTH, handleOverflowButtonClick, OVERFLOW_SLOT));
289
+ this.overflowButtons.length = 0;
290
+ this.overflowButtons.push(new ToolbarButton("Select all", VERTICAL_MENU_WIDTH, handleFourthButtonClick, 3));
291
+ this.overflowButtons.push(new ToolbarButton("Extra", VERTICAL_MENU_WIDTH, handleFifthButtonClick, 4));
292
+ this.overflowButtons.push(new ToolbarButton("Extra", VERTICAL_MENU_WIDTH, handleSixthButtonClick, 5));
293
+ this.overflowButtons.push(new ToolbarButton("<", VERTICAL_MENU_WIDTH, handleBackButtonClick, BACK_SLOT));
294
+ this.separators.length = 0;
295
+ this.separators.push(makeSeparator());
296
+ this.separators.push(makeSeparator());
297
+ this.separators.push(makeSeparator());
298
+ this.overflowSeparators.length = 0;
299
+ this.overflowSeparators.push(makeSeparator());
300
+ this.overflowSeparators.push(makeSeparator());
301
+ this.overflowSeparators.push(makeSeparator());
302
+ this.overflowSeparators.push(makeSeparator());
303
+
304
+ const hostChildren = new Array<Node>();
305
+ hostChildren.push(panel);
306
+ hostChildren.push(overflowPanel);
307
+ const hostRoot = new Portal()
308
+ .positionAbsolute()
309
+ .position(0.0, 0.0)
310
+ .width(100.0, Unit.Percent)
311
+ .height(100.0, Unit.Percent)
312
+ .children(hostChildren) as Portal;
313
+ this.hostRoot = hostRoot;
314
+ this.panel = panel;
315
+ this.overflowPanel = overflowPanel;
316
+ this.overflowScrollBox = overflowScrollBox;
317
+ this.overflowContent = overflowContent;
318
+ return hostRoot;
319
+ }
320
+
321
+ static reset(): void {
322
+ const hostRoot = this.hostRoot;
323
+ if (hostRoot !== null) {
324
+ hostRoot.dispose();
325
+ }
326
+ this.hostRoot = null;
327
+ this.panel = null;
328
+ this.overflowPanel = null;
329
+ this.overflowScrollBox = null;
330
+ this.overflowContent = null;
331
+ this.buttons.length = 0;
332
+ this.overflowButtons.length = 0;
333
+ this.separators.length = 0;
334
+ this.overflowSeparators.length = 0;
335
+ this.activeItems.length = 0;
336
+ this.activeHandle = 0;
337
+ this.activeStart = 0;
338
+ this.activeEnd = 0;
339
+ this.activeCrossSelectionText = "";
340
+ this.activeCrossSelectionSelectAllTarget = 0;
341
+ this.pendingCrossSelectionTextHandle = 0;
342
+ this.hiddenForHandleDrag = false;
343
+ this.overflowVisible = false;
344
+ this.horizontalItemCount = 0;
345
+ this.lastPanelX = EDGE_MARGIN;
346
+ this.lastPanelY = EDGE_MARGIN;
347
+ this.lastPanelWidth = 0.0;
348
+ }
349
+
350
+ static clear(): void {
351
+ this.activeHandle = 0;
352
+ this.activeStart = 0;
353
+ this.activeEnd = 0;
354
+ this.activeCrossSelectionText = "";
355
+ this.activeCrossSelectionSelectAllTarget = 0;
356
+ this.activeItems.length = 0;
357
+ this.pendingCrossSelectionTextHandle = 0;
358
+ this.hiddenForHandleDrag = false;
359
+ this.overflowVisible = false;
360
+ this.horizontalItemCount = 0;
361
+ this.hide();
362
+ }
363
+
364
+ static showItemsAt(items: Array<MenuItem>, x: f32, y: f32): bool {
365
+ if (items.length == 0) {
366
+ this.clear();
367
+ return false;
368
+ }
369
+ this.activeHandle = 0;
370
+ this.activeStart = 0;
371
+ this.activeEnd = 0;
372
+ this.activeCrossSelectionText = "";
373
+ this.activeCrossSelectionSelectAllTarget = 0;
374
+ this.pendingCrossSelectionTextHandle = 0;
375
+ this.hiddenForHandleDrag = false;
376
+ this.ensureHost();
377
+ this.activeItems.length = 0;
378
+ for (let index = 0; index < items.length; ++index) {
379
+ this.activeItems.push(unchecked(items[index]));
380
+ }
381
+ this.applyItems(false);
382
+ this.positionAtPoint(x, y, READONLY_BUTTON_WIDTH);
383
+ return true;
384
+ }
385
+
386
+ static setPendingCrossSelectionTextHandle(handle: u64): void {
387
+ this.pendingCrossSelectionTextHandle = handle;
388
+ }
389
+
390
+ static handleSelectionChanged(handle: u64, target: Node, start: u32, end: u32, selectionChromeVisible: bool): void {
391
+ this.activeCrossSelectionText = "";
392
+ const textTarget = this.resolveTextTarget(target);
393
+ const textHandle = textTarget === null ? 0 : changetype<TextCore>(textTarget).builtHandle;
394
+ if (start == end && this.hiddenForHandleDrag && textHandle != 0 && this.activeHandle == textHandle) {
395
+ this.activeStart = start;
396
+ this.activeEnd = end;
397
+ this.hide();
398
+ return;
399
+ }
400
+ if (!selectionChromeVisible || start == end || textTarget === null) {
401
+ this.clear();
402
+ return;
403
+ }
404
+ this.activeHandle = textHandle;
405
+ this.activeStart = start;
406
+ this.activeEnd = end;
407
+ this.ensureHost();
408
+ this.buildItemsForText(textTarget, start, end, "");
409
+ this.positionForTextRange(textHandle, start, end);
410
+ }
411
+
412
+ static handleCrossSelectionChanged(handle: u64, area: Node, text: string, selectionChromeVisible: bool): void {
413
+ if (!selectionChromeVisible || text.length == 0) {
414
+ if (text.length == 0 && this.hiddenForHandleDrag && this.activeHandle == handle) {
415
+ this.activeStart = 0;
416
+ this.activeEnd = 0;
417
+ this.activeCrossSelectionText = "";
418
+ this.hide();
419
+ return;
420
+ }
421
+ this.clear();
422
+ return;
423
+ }
424
+ const previousHandle = this.activeHandle;
425
+ const previousText = this.activeCrossSelectionText;
426
+ const previousSelectAllTarget = this.activeCrossSelectionSelectAllTarget;
427
+ this.activeHandle = handle;
428
+ this.activeStart = 0;
429
+ this.activeEnd = <u32>text.length;
430
+ this.activeCrossSelectionText = text;
431
+ this.ensureHost();
432
+ this.activeItems.length = 0;
433
+ this.activeItems.push(new MenuItem("Copy", ContextMenuAction.CopyCurrentSelection, text));
434
+ let selectAllTarget = this.pendingCrossSelectionTextHandle;
435
+ this.pendingCrossSelectionTextHandle = 0;
436
+ if (selectAllTarget == 0 && previousHandle == handle && previousText == text) {
437
+ selectAllTarget = previousSelectAllTarget;
438
+ }
439
+ if (selectAllTarget == 0) {
440
+ selectAllTarget = handle;
441
+ }
442
+ this.activeCrossSelectionSelectAllTarget = selectAllTarget;
443
+ this.activeItems.push(new MenuItem(
444
+ "Select all",
445
+ ContextMenuAction.SelectAllText,
446
+ null,
447
+ 0,
448
+ null,
449
+ false,
450
+ selectAllTarget,
451
+ ));
452
+ this.applyItems(false);
453
+ this.positionForCrossSelection(handle);
454
+ }
455
+
456
+ static refreshActiveGeometry(selectionChromeVisible: bool): void {
457
+ if (!selectionChromeVisible || this.activeHandle == 0 || this.hiddenForHandleDrag) {
458
+ this.hide();
459
+ return;
460
+ }
461
+ if (this.activeCrossSelectionText.length > 0) {
462
+ this.positionForCrossSelection(this.activeHandle);
463
+ return;
464
+ }
465
+ this.positionForTextRange(this.activeHandle, this.activeStart, this.activeEnd);
466
+ }
467
+
468
+ static hideForHandleDrag(): void {
469
+ this.hiddenForHandleDrag = true;
470
+ this.overflowVisible = false;
471
+ this.hide();
472
+ }
473
+
474
+ static showAfterHandleDrag(selectionChromeVisible: bool): void {
475
+ this.hiddenForHandleDrag = false;
476
+ this.refreshActiveGeometry(selectionChromeVisible);
477
+ }
478
+
479
+ static invokeSlot(slot: i32): void {
480
+ this.activateToolbarSlot(slot);
481
+ }
482
+
483
+ static activateToolbarSlot(slot: i32): void {
484
+ if (slot == OVERFLOW_SLOT) {
485
+ this.showOverflowMenu();
486
+ return;
487
+ }
488
+ if (slot == BACK_SLOT) {
489
+ this.showHorizontalMenu();
490
+ return;
491
+ }
492
+ if (slot < 0 || slot >= this.activeItems.length) {
493
+ return;
494
+ }
495
+ const item = unchecked(this.activeItems[slot]);
496
+ runContextMenuAction(item);
497
+ if (item.action == ContextMenuAction.CopyCurrentSelection) {
498
+ ui.clearCurrentSelection();
499
+ this.clear();
500
+ return;
501
+ }
502
+ if (item.action == ContextMenuAction.SelectAllText) {
503
+ return;
504
+ }
505
+ this.hide();
506
+ }
507
+
508
+ static isVisible(): bool {
509
+ const panel = this.panel;
510
+ return panel !== null && panel.isVisible;
511
+ }
512
+
513
+ static itemCount(): i32 {
514
+ return this.activeItems.length;
515
+ }
516
+
517
+ static itemLabel(index: i32): string {
518
+ if (index < 0 || index >= this.activeItems.length) {
519
+ return "";
520
+ }
521
+ return unchecked(this.activeItems[index]).label;
522
+ }
523
+
524
+ static visibleToolbarItemCount(): i32 {
525
+ return this.horizontalItemCount;
526
+ }
527
+
528
+ static isOverflowVisible(): bool {
529
+ const overflowPanel = this.overflowPanel;
530
+ return overflowPanel !== null && overflowPanel.isVisible;
531
+ }
532
+
533
+ static dismissForOutsidePointerDown(sceneX: f32, sceneY: f32): bool {
534
+ if (this.activeItems.length == 0) {
535
+ return false;
536
+ }
537
+ if (this.pointHitsNode(this.panel, sceneX, sceneY) || this.pointHitsNode(this.overflowPanel, sceneX, sceneY)) {
538
+ return false;
539
+ }
540
+ this.clear();
541
+ return true;
542
+ }
543
+
544
+ static panelNode(): Node | null {
545
+ return this.panel;
546
+ }
547
+
548
+ static overflowPanelNode(): Node | null {
549
+ return this.overflowPanel;
550
+ }
551
+
552
+ private static ensureHost(): void {
553
+ if (this.hostRoot === null) {
554
+ this.createDefaultHost();
555
+ }
556
+ }
557
+
558
+ private static buildItemsForText(target: TextCore, start: u32, end: u32, crossSelectionText: string): void {
559
+ this.activeItems.length = 0;
560
+ const handle = target.builtHandle;
561
+ const parent = target.parentNode;
562
+ const editable = target.isEditableText;
563
+ const selectionStart = start;
564
+ const selectionEnd = end;
565
+ const content = parent instanceof TextInputCore ? changetype<TextInputCore>(parent).value : target.content;
566
+ const selectedText = crossSelectionText.length > 0 ? crossSelectionText : this.resolveSelectedText(content, selectionStart, selectionEnd);
567
+ const selectedPayload = selectedText.length > 0 ? selectedText : null;
568
+ const hasText = content.length > 0;
569
+ if (editable) {
570
+ this.activeItems.push(
571
+ new MenuItem("Cut", ContextMenuAction.CutTextSelection, selectedPayload, 0, null, false, handle, 0, 0, true)
572
+ .withSelectionRange(selectionStart, selectionEnd),
573
+ );
574
+ this.activeItems.push(new MenuItem("Copy", ContextMenuAction.CopyCurrentSelection, selectedPayload, 0, null, false, handle, 0, 0, true));
575
+ this.activeItems.push(new MenuItem("Paste", ContextMenuAction.PasteText, null, 0, null, false, handle, 0, 0, true));
576
+ this.activeItems.push(new MenuItem("Select all", ContextMenuAction.SelectAllText, null, 0, null, !hasText, handle, 0, 0, true));
577
+ this.applyItems(true);
578
+ return;
579
+ }
580
+ this.activeItems.push(new MenuItem("Copy", ContextMenuAction.CopyCurrentSelection, selectedPayload, 0, null, false, handle));
581
+ this.activeItems.push(new MenuItem("Select all", ContextMenuAction.SelectAllText, null, 0, null, !hasText, handle));
582
+ this.applyItems(false);
583
+ }
584
+
585
+ private static resolveSelectedText(content: string, selectionStart: u32, selectionEnd: u32): string {
586
+ if (selectionStart == selectionEnd) {
587
+ return "";
588
+ }
589
+ const start = normalizeStart(selectionStart, selectionEnd);
590
+ const end = normalizeEnd(selectionStart, selectionEnd);
591
+ return content.substring(<i32>start, <i32>end);
592
+ }
593
+
594
+ private static resolveTextTarget(node: Node): TextCore | null {
595
+ if (node instanceof TextInputCore) {
596
+ return changetype<TextInputCore>(node).editorNode;
597
+ }
598
+ if (node instanceof TextCore) {
599
+ return changetype<TextCore>(node);
600
+ }
601
+ return null;
602
+ }
603
+
604
+ private static applyItems(editable: bool): void {
605
+ const panel = this.panel;
606
+ if (panel === null) {
607
+ return;
608
+ }
609
+ const buttonWidth = editable ? EDITABLE_BUTTON_WIDTH : READONLY_BUTTON_WIDTH;
610
+ const children = new Array<Node>();
611
+ const height = activeTheme.value.contextMenu.item.height + 8.0;
612
+ const hasOverflow = this.activeItems.length > MAX_HORIZONTAL_ACTIONS;
613
+ this.horizontalItemCount = hasOverflow ? MAX_HORIZONTAL_ACTIONS + 1 : this.activeItems.length;
614
+ panel
615
+ .height(height, Unit.Pixel)
616
+ .width(this.activeWidthForButtonWidth(buttonWidth, this.activeItems.length, hasOverflow), Unit.Pixel)
617
+ .bgColor(activeTheme.value.contextMenu.panelBackground)
618
+ .backgroundBlur(10.0)
619
+ .cornerRadius(activeTheme.value.contextMenu.panelCornerRadius)
620
+ .border(1.0, activeTheme.value.contextMenu.panelBorderColor)
621
+ .dropShadow(
622
+ activeTheme.value.contextMenu.panelShadowColor,
623
+ 0.0,
624
+ activeTheme.value.contextMenu.shadowOffsetY,
625
+ activeTheme.value.contextMenu.shadowBlur,
626
+ activeTheme.value.contextMenu.shadowSpread,
627
+ );
628
+ const visibleActions = hasOverflow ? MAX_HORIZONTAL_ACTIONS : this.activeItems.length;
629
+ for (let index = 0; index < visibleActions; ++index) {
630
+ if (index > 0) {
631
+ const separator = unchecked(this.separators[index - 1]);
632
+ separator.height(activeTheme.value.contextMenu.item.height - 10.0, Unit.Pixel);
633
+ separator.bgColor(activeTheme.value.contextMenu.separatorColor);
634
+ children.push(separator);
635
+ }
636
+ const button = unchecked(this.buttons[index]);
637
+ button.width(buttonWidth, Unit.Pixel);
638
+ button.applyMenuStyle();
639
+ button.label(unchecked(this.activeItems[index]).label);
640
+ children.push(button);
641
+ }
642
+ if (hasOverflow) {
643
+ const separator = unchecked(this.separators[MAX_HORIZONTAL_ACTIONS - 1]);
644
+ separator.height(activeTheme.value.contextMenu.item.height - 10.0, Unit.Pixel);
645
+ separator.bgColor(activeTheme.value.contextMenu.separatorColor);
646
+ children.push(separator);
647
+ const overflowButton = unchecked(this.buttons[MAX_HORIZONTAL_ACTIONS]);
648
+ overflowButton.width(OVERFLOW_BUTTON_WIDTH, Unit.Pixel);
649
+ overflowButton.applyMenuStyle();
650
+ overflowButton.label(OVERFLOW_LABEL);
651
+ children.push(overflowButton);
652
+ }
653
+ panel.children(children);
654
+ this.applyOverflowItems(buttonWidth);
655
+ this.showHorizontalMenu();
656
+ }
657
+
658
+ private static positionForTextRange(handle: u64, start: u32, end: u32): void {
659
+ if (start == end) {
660
+ this.hide();
661
+ return;
662
+ }
663
+ const rects = ui.getTextRangeRects(handle, normalizeStart(start, end), normalizeEnd(start, end));
664
+ if (rects.length == 0) {
665
+ this.hide();
666
+ return;
667
+ }
668
+ const first = unchecked(rects[0]);
669
+ const last = unchecked(rects[rects.length - 1]);
670
+ this.positionAtSelectionBounds(first.x, first.y, first.height, last.x + last.width, last.y + last.height);
671
+ }
672
+
673
+ private static positionForCrossSelection(handle: u64): void {
674
+ const rects = ui.getCrossSelectionEndpointRects(handle);
675
+ if (rects === null) {
676
+ this.hide();
677
+ return;
678
+ }
679
+ this.positionAtSelectionBounds(
680
+ rects.start.x,
681
+ rects.start.y,
682
+ rects.start.height,
683
+ rects.end.x + rects.end.width,
684
+ rects.end.y + rects.end.height,
685
+ );
686
+ }
687
+
688
+ private static positionAtSelectionBounds(startX: f32, topY: f32, startHeight: f32, endX: f32, bottomY: f32): void {
689
+ const panel = this.panel;
690
+ if (panel === null || this.hiddenForHandleDrag) {
691
+ return;
692
+ }
693
+ const hasOverflow = this.activeItems.length > MAX_HORIZONTAL_ACTIONS;
694
+ const buttonWidth = this.activeItems.length > 2 ? EDITABLE_BUTTON_WIDTH : READONLY_BUTTON_WIDTH;
695
+ const width = this.activeWidthForButtonWidth(buttonWidth, this.activeItems.length, hasOverflow);
696
+ const height = activeTheme.value.contextMenu.item.height + 8.0;
697
+ const viewportWidth = ui.getViewportWidth();
698
+ const viewportHeight = ui.getViewportHeight();
699
+ const maxX = <f32>Math.max(EDGE_MARGIN, viewportWidth - width - EDGE_MARGIN);
700
+ const x = <f32>Math.max(EDGE_MARGIN, Math.min(startX - (width * 0.5), maxX));
701
+ const topCandidate = topY - height - TOOLBAR_MARGIN;
702
+ const bottomCandidate = bottomY + TOOLBAR_MARGIN + 12.0;
703
+ let y = topCandidate;
704
+ if (topCandidate < EDGE_MARGIN) {
705
+ y = bottomCandidate;
706
+ if (bottomCandidate + height > viewportHeight - EDGE_MARGIN) {
707
+ y = topY + (startHeight * 0.5) - (height * 0.5);
708
+ }
709
+ }
710
+ const maxY = <f32>Math.max(EDGE_MARGIN, viewportHeight - height - EDGE_MARGIN);
711
+ y = <f32>Math.max(EDGE_MARGIN, Math.min(y, maxY));
712
+ this.lastPanelX = x;
713
+ this.lastPanelY = y;
714
+ this.lastPanelWidth = width;
715
+ panel.position(x, y);
716
+ if (this.overflowVisible) {
717
+ this.positionOverflowPanel(x, y, width, height);
718
+ } else {
719
+ panel.visibility(Visibility.Normal);
720
+ }
721
+ }
722
+
723
+ private static positionAtPoint(pointX: f32, pointY: f32, buttonWidth: f32): void {
724
+ const panel = this.panel;
725
+ if (panel === null || this.hiddenForHandleDrag || this.activeItems.length == 0) {
726
+ return;
727
+ }
728
+ const hasOverflow = this.activeItems.length > MAX_HORIZONTAL_ACTIONS;
729
+ const width = this.activeWidthForButtonWidth(buttonWidth, this.activeItems.length, hasOverflow);
730
+ const height = activeTheme.value.contextMenu.item.height + 8.0;
731
+ const viewportWidth = ui.getViewportWidth();
732
+ const viewportHeight = ui.getViewportHeight();
733
+ const maxX = <f32>Math.max(EDGE_MARGIN, viewportWidth - width - EDGE_MARGIN);
734
+ let x = pointX - (width * 0.5);
735
+ x = <f32>Math.max(EDGE_MARGIN, Math.min(x, maxX));
736
+ let y = pointY - height - TOOLBAR_MARGIN;
737
+ if (y < EDGE_MARGIN) {
738
+ y = pointY + TOOLBAR_MARGIN;
739
+ }
740
+ const maxY = <f32>Math.max(EDGE_MARGIN, viewportHeight - height - EDGE_MARGIN);
741
+ y = <f32>Math.max(EDGE_MARGIN, Math.min(y, maxY));
742
+ this.lastPanelX = x;
743
+ this.lastPanelY = y;
744
+ this.lastPanelWidth = width;
745
+ panel.position(x, y);
746
+ if (this.overflowVisible) {
747
+ this.positionOverflowPanel(x, y, width, height);
748
+ } else {
749
+ panel.visibility(Visibility.Normal);
750
+ }
751
+ }
752
+
753
+ private static applyOverflowItems(buttonWidth: f32): void {
754
+ const overflowPanel = this.overflowPanel;
755
+ const overflowScrollBox = this.overflowScrollBox;
756
+ const overflowContent = this.overflowContent;
757
+ if (overflowPanel === null || overflowScrollBox === null || overflowContent === null) {
758
+ return;
759
+ }
760
+ const overflowCount = this.activeItems.length > MAX_HORIZONTAL_ACTIONS
761
+ ? this.activeItems.length - MAX_HORIZONTAL_ACTIONS
762
+ : 0;
763
+ const totalRows = overflowCount + 1;
764
+ const contentHeight = (activeTheme.value.contextMenu.item.height * <f32>totalRows) + <f32>(totalRows - 1);
765
+ const maxPanelHeight = <f32>Math.max(
766
+ activeTheme.value.contextMenu.item.height,
767
+ Math.min(VERTICAL_MENU_MAX_HEIGHT, ui.getViewportHeight() - (EDGE_MARGIN * 2.0)),
768
+ );
769
+ const panelHeight = <f32>Math.min(contentHeight, maxPanelHeight);
770
+ overflowPanel
771
+ .width(VERTICAL_MENU_WIDTH, Unit.Pixel)
772
+ .height(panelHeight, Unit.Pixel)
773
+ .bgColor(activeTheme.value.contextMenu.panelBackground)
774
+ .backgroundBlur(10.0)
775
+ .cornerRadius(activeTheme.value.contextMenu.panelCornerRadius)
776
+ .border(1.0, activeTheme.value.contextMenu.panelBorderColor)
777
+ .dropShadow(
778
+ activeTheme.value.contextMenu.panelShadowColor,
779
+ 0.0,
780
+ activeTheme.value.contextMenu.shadowOffsetY,
781
+ activeTheme.value.contextMenu.shadowBlur,
782
+ activeTheme.value.contextMenu.shadowSpread,
783
+ );
784
+ overflowScrollBox.width(100.0, Unit.Percent);
785
+ overflowScrollBox.height(panelHeight, Unit.Pixel);
786
+ overflowScrollBox.scrollContentSize(-1.0, contentHeight);
787
+
788
+ const children = new Array<Node>();
789
+ for (let index = 0; index < overflowCount; ++index) {
790
+ if (index > 0) {
791
+ const separator = unchecked(this.overflowSeparators[index - 1]);
792
+ separator.width(100.0, Unit.Percent);
793
+ separator.height(1.0, Unit.Pixel);
794
+ separator.bgColor(activeTheme.value.contextMenu.separatorColor);
795
+ children.push(separator);
796
+ }
797
+ const itemIndex = MAX_HORIZONTAL_ACTIONS + index;
798
+ const button = unchecked(this.overflowButtons[index]);
799
+ button.width(VERTICAL_MENU_WIDTH, Unit.Pixel);
800
+ button.applyMenuStyle();
801
+ button.label(unchecked(this.activeItems[itemIndex]).label);
802
+ children.push(button);
803
+ }
804
+ if (overflowCount > 0) {
805
+ const separator = unchecked(this.overflowSeparators[overflowCount - 1]);
806
+ separator.width(100.0, Unit.Percent);
807
+ separator.height(1.0, Unit.Pixel);
808
+ separator.bgColor(activeTheme.value.contextMenu.separatorColor);
809
+ children.push(separator);
810
+ }
811
+ const backButton = unchecked(this.overflowButtons[this.overflowButtons.length - 1]);
812
+ backButton.width(VERTICAL_MENU_WIDTH, Unit.Pixel);
813
+ backButton.applyMenuStyle();
814
+ backButton.label("<");
815
+ children.push(backButton);
816
+ overflowContent.children(children);
817
+ if (buttonWidth > 0.0) {
818
+ overflowContent.width(100.0, Unit.Percent);
819
+ }
820
+ }
821
+
822
+ private static showOverflowMenu(): void {
823
+ const panel = this.panel;
824
+ const overflowPanel = this.overflowPanel;
825
+ if (panel === null || overflowPanel === null || this.activeItems.length <= MAX_HORIZONTAL_ACTIONS) {
826
+ return;
827
+ }
828
+ this.overflowVisible = true;
829
+ panel.visibility(Visibility.Collapsed);
830
+ this.positionOverflowPanel(this.lastPanelX, this.lastPanelY, this.lastPanelWidth, activeTheme.value.contextMenu.item.height + 8.0);
831
+ overflowPanel.visibility(Visibility.Normal);
832
+ }
833
+
834
+ private static showHorizontalMenu(): void {
835
+ this.overflowVisible = false;
836
+ const overflowPanel = this.overflowPanel;
837
+ if (overflowPanel !== null) {
838
+ overflowPanel.visibility(Visibility.Collapsed);
839
+ }
840
+ const panel = this.panel;
841
+ if (panel !== null && this.activeItems.length > 0 && !this.hiddenForHandleDrag) {
842
+ panel.visibility(Visibility.Normal);
843
+ }
844
+ }
845
+
846
+ private static positionOverflowPanel(x: f32, y: f32, horizontalWidth: f32, horizontalHeight: f32): void {
847
+ const overflowPanel = this.overflowPanel;
848
+ if (overflowPanel === null) {
849
+ return;
850
+ }
851
+ const viewportWidth = ui.getViewportWidth();
852
+ const viewportHeight = ui.getViewportHeight();
853
+ const panelHeight = <f32>Math.min(VERTICAL_MENU_MAX_HEIGHT, ui.getViewportHeight() - (EDGE_MARGIN * 2.0));
854
+ const maxX = <f32>Math.max(EDGE_MARGIN, viewportWidth - VERTICAL_MENU_WIDTH - EDGE_MARGIN);
855
+ const overflowX = <f32>Math.max(EDGE_MARGIN, Math.min(x + horizontalWidth - VERTICAL_MENU_WIDTH, maxX));
856
+ let overflowY = y + horizontalHeight + 4.0;
857
+ if (overflowY + panelHeight > viewportHeight - EDGE_MARGIN) {
858
+ overflowY = y - panelHeight - 4.0;
859
+ }
860
+ const maxY = <f32>Math.max(EDGE_MARGIN, viewportHeight - panelHeight - EDGE_MARGIN);
861
+ overflowY = <f32>Math.max(EDGE_MARGIN, Math.min(overflowY, maxY));
862
+ overflowPanel.position(overflowX, overflowY);
863
+ }
864
+
865
+ private static pointHitsNode(node: Node | null, sceneX: f32, sceneY: f32): bool {
866
+ if (node === null || !node.isVisible || node.builtHandle == <u64>HandleValue.Invalid) {
867
+ return false;
868
+ }
869
+ const bounds = ui.tryGetBounds(node.builtHandle);
870
+ if (bounds === null) {
871
+ return false;
872
+ }
873
+ const x = unchecked(changetype<Float32Array>(bounds)[0]);
874
+ const y = unchecked(changetype<Float32Array>(bounds)[1]);
875
+ const width = unchecked(changetype<Float32Array>(bounds)[2]);
876
+ const height = unchecked(changetype<Float32Array>(bounds)[3]);
877
+ return sceneX >= x && sceneX <= (x + width) && sceneY >= y && sceneY <= (y + height);
878
+ }
879
+
880
+ private static activeWidthForButtonWidth(buttonWidth: f32, itemCount: i32, hasOverflow: bool): f32 {
881
+ const visibleActions = hasOverflow ? MAX_HORIZONTAL_ACTIONS : itemCount;
882
+ const visibleCount = hasOverflow ? visibleActions + 1 : visibleActions;
883
+ const actionWidth = buttonWidth * <f32>visibleActions;
884
+ const overflowWidth = hasOverflow ? OVERFLOW_BUTTON_WIDTH : 0.0;
885
+ return actionWidth +
886
+ overflowWidth +
887
+ (<f32>(visibleCount - 1) * 1.0) +
888
+ (TOOLBAR_HORIZONTAL_PADDING * 2.0);
889
+ }
890
+
891
+ private static hide(): void {
892
+ const panel = this.panel;
893
+ if (panel !== null) {
894
+ panel.visibility(Visibility.Collapsed);
895
+ }
896
+ const overflowPanel = this.overflowPanel;
897
+ if (overflowPanel !== null) {
898
+ overflowPanel.visibility(Visibility.Collapsed);
899
+ }
900
+ }
901
+ }