@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,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
|
+
}
|