@effindomv2/fui-as 0.1.0
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/LICENSE.md +7 -0
- package/browser/src/common-harness/host-imports.ts +430 -0
- package/browser/src/common-harness/interop.ts +39 -0
- package/browser/src/common-harness/managed-harness-bitmap-host.ts +92 -0
- package/browser/src/common-harness/managed-harness-fetch-host.ts +201 -0
- package/browser/src/common-harness/managed-harness-file-host.ts +1101 -0
- package/browser/src/common-harness/managed-harness-file-payloads.ts +143 -0
- package/browser/src/common-harness/managed-harness-file-types.ts +106 -0
- package/browser/src/common-harness/managed-harness-session.ts +15 -0
- package/browser/src/common-harness/managed-harness.ts +1323 -0
- package/browser/src/common-harness/managed-history.ts +168 -0
- package/browser/src/common-harness/persisted-restore-policy.ts +50 -0
- package/browser/src/common-harness/persisted-ui-state-controller.ts +309 -0
- package/browser/src/common-harness/text-session-bridge.ts +452 -0
- package/browser/src/common-harness/types.ts +205 -0
- package/browser/src/common-harness/ui-chrome.ts +191 -0
- package/browser/src/common-harness/ui-imports.ts +529 -0
- package/browser/src/common-harness/wasm-module-cache.ts +47 -0
- package/browser/src/common-harness.ts +27 -0
- package/browser/src/file-processing-worker.ts +89 -0
- package/browser/src/host-events.ts +97 -0
- package/browser/src/host-services.ts +203 -0
- package/browser/src/index.ts +62 -0
- package/browser/src/persisted-ui-state.ts +206 -0
- package/browser/src/routed-harness.ts +198 -0
- package/browser/src/worker-bootstrap.ts +483 -0
- package/browser/src/worker-manager.ts +230 -0
- package/browser/src/worker-types.ts +50 -0
- package/package.json +89 -0
- package/scripts/build-demo-as.sh +91 -0
- package/scripts/build.sh +325 -0
- package/scripts/generate-host-events.ts +175 -0
- package/scripts/generate-host-services.ts +157 -0
- package/src/Fui.ts +205 -0
- package/src/FuiExports.ts +55 -0
- package/src/FuiPrimitives.ts +15 -0
- package/src/FuiWorker.ts +3 -0
- package/src/FuiWorkerExports.ts +6 -0
- package/src/bindings/ui.ts +531 -0
- package/src/color.ts +86 -0
- package/src/controls/AntiSelectionArea.ts +23 -0
- package/src/controls/Button.ts +750 -0
- package/src/controls/Checkbox.ts +181 -0
- package/src/controls/ContextMenu.ts +885 -0
- package/src/controls/ControlTemplateSet.ts +37 -0
- package/src/controls/Dialog.ts +355 -0
- package/src/controls/Dropdown.ts +856 -0
- package/src/controls/Form.ts +110 -0
- package/src/controls/NavLink.ts +211 -0
- package/src/controls/Popup.ts +129 -0
- package/src/controls/ProgressBar.ts +180 -0
- package/src/controls/RadioButton.ts +135 -0
- package/src/controls/RadioGroup.ts +244 -0
- package/src/controls/SelectionArea.ts +75 -0
- package/src/controls/Slider.ts +471 -0
- package/src/controls/Switch.ts +132 -0
- package/src/controls/TextArea.ts +20 -0
- package/src/controls/TextInput.ts +7 -0
- package/src/controls/index.ts +18 -0
- package/src/controls/internal/ButtonPresenter.ts +95 -0
- package/src/controls/internal/CheckboxIndicatorPresenter.ts +93 -0
- package/src/controls/internal/DropdownChevronPresenter.ts +67 -0
- package/src/controls/internal/DropdownFieldPresenter.ts +110 -0
- package/src/controls/internal/DropdownOptionRowPresenter.ts +82 -0
- package/src/controls/internal/PopupPresenter.ts +198 -0
- package/src/controls/internal/PressableIndicatorPresenter.ts +32 -0
- package/src/controls/internal/PressableLabeledControl.ts +221 -0
- package/src/controls/internal/RadioIndicatorPresenter.ts +73 -0
- package/src/controls/internal/SliderPresenter.ts +157 -0
- package/src/controls/internal/SwitchIndicatorPresenter.ts +72 -0
- package/src/controls/internal/TextInputCore.ts +695 -0
- package/src/controls/internal/TextInputPresenter.ts +72 -0
- package/src/controls/templating.ts +54 -0
- package/src/core/Action.ts +94 -0
- package/src/core/Actions.ts +37 -0
- package/src/core/Animation.ts +412 -0
- package/src/core/Application.ts +328 -0
- package/src/core/Assets.ts +264 -0
- package/src/core/AttachedProperties.ts +32 -0
- package/src/core/Bitmap.ts +70 -0
- package/src/core/BoundCallback.ts +104 -0
- package/src/core/Callbacks.ts +17 -0
- package/src/core/ContextMenuManager.ts +466 -0
- package/src/core/DebugApi.ts +30 -0
- package/src/core/Disposable.ts +10 -0
- package/src/core/DragDropManager.ts +179 -0
- package/src/core/DragGesture.ts +184 -0
- package/src/core/DynamicAssetIds.ts +24 -0
- package/src/core/Errors.ts +48 -0
- package/src/core/EventRouter.ts +408 -0
- package/src/core/ExternalDropManager.ts +122 -0
- package/src/core/Fetch.ts +264 -0
- package/src/core/FetchFfi.ts +15 -0
- package/src/core/File.ts +1002 -0
- package/src/core/FocusAdornerManager.ts +263 -0
- package/src/core/FocusVisibility.ts +36 -0
- package/src/core/FrameScheduler.ts +28 -0
- package/src/core/KeyboardScroll.ts +161 -0
- package/src/core/KeyboardScrollTracker.ts +386 -0
- package/src/core/Logger.ts +80 -0
- package/src/core/Navigation.ts +13 -0
- package/src/core/Node.ts +1708 -0
- package/src/core/PersistedState.ts +102 -0
- package/src/core/PersistedUiState.ts +142 -0
- package/src/core/Platform.ts +219 -0
- package/src/core/Signal.ts +89 -0
- package/src/core/Theme.ts +365 -0
- package/src/core/Timers.ts +129 -0
- package/src/core/ToolTip.ts +122 -0
- package/src/core/ToolTipManager.ts +459 -0
- package/src/core/Transitions.ts +34 -0
- package/src/core/Typography.ts +204 -0
- package/src/core/Worker.ts +196 -0
- package/src/core/bind.ts +37 -0
- package/src/core/event_exports.ts +596 -0
- package/src/core/ffi.ts +728 -0
- package/src/host-services/runtime.ts +25 -0
- package/src/nodes/FlexBox.ts +789 -0
- package/src/nodes/GradientStop.ts +9 -0
- package/src/nodes/Grid.ts +183 -0
- package/src/nodes/Image.ts +189 -0
- package/src/nodes/Portal.ts +14 -0
- package/src/nodes/RichText.ts +312 -0
- package/src/nodes/ScrollBar.ts +570 -0
- package/src/nodes/ScrollBox.ts +415 -0
- package/src/nodes/ScrollState.ts +10 -0
- package/src/nodes/ScrollView.ts +511 -0
- package/src/nodes/Svg.ts +142 -0
- package/src/nodes/Text.ts +145 -0
- package/src/nodes/TextCore.ts +558 -0
- package/src/nodes/VirtualList.ts +431 -0
- package/src/nodes/helpers.ts +25 -0
- package/src/nodes/index.ts +14 -0
- package/src/tsconfig.json +7 -0
- package/src/worker/Worker.ts +169 -0
- package/src/worker/WorkerJob.ts +65 -0
- package/src/worker/ffi.ts +23 -0
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
import * as ui from "../../bindings/ui";
|
|
2
|
+
import { HandlerAction } from "../../core/Action";
|
|
3
|
+
import { Callback1, Callback2, Handler1, Handler2 } from "../../core/BoundCallback";
|
|
4
|
+
import { Disposable, disposeAll } from "../../core/Disposable";
|
|
5
|
+
import { FocusAdornerManager } from "../../core/FocusAdornerManager";
|
|
6
|
+
import { keyboardFocusVisible } from "../../core/FocusVisibility";
|
|
7
|
+
import { Node } from "../../core/Node";
|
|
8
|
+
import {
|
|
9
|
+
BorderStyle,
|
|
10
|
+
CursorStyle,
|
|
11
|
+
HandleValue,
|
|
12
|
+
PointerEventType,
|
|
13
|
+
SemanticRole,
|
|
14
|
+
TextVerticalAlign,
|
|
15
|
+
Unit,
|
|
16
|
+
} from "../../core/ffi";
|
|
17
|
+
import { Theme, activeTheme } from "../../core/Theme";
|
|
18
|
+
import { FontFamily } from "../../core/Typography";
|
|
19
|
+
import { PersistedStringCodec, PersistedValueState } from "../../core/PersistedState";
|
|
20
|
+
import { FlexBox, ScrollBarVisibility, ScrollBox, TextCore } from "../../nodes";
|
|
21
|
+
import { bind1, bind2 } from "../../core/bind";
|
|
22
|
+
import { ScrollState } from "../../nodes/ScrollState";
|
|
23
|
+
import { ScrollView } from "../../nodes/ScrollView";
|
|
24
|
+
import { getControlTemplates } from "../ControlTemplateSet";
|
|
25
|
+
import {
|
|
26
|
+
defaultTextInputTemplate,
|
|
27
|
+
TextInputPresenter,
|
|
28
|
+
TextInputTemplate,
|
|
29
|
+
TextInputVisualState,
|
|
30
|
+
} from "./TextInputPresenter";
|
|
31
|
+
|
|
32
|
+
const UNLIMITED_TEXT_LENGTH: i32 = 2147483647;
|
|
33
|
+
|
|
34
|
+
function clampSelection(position: u32, textLength: i32): u32 {
|
|
35
|
+
const limit = textLength <= 0 ? <u32>0 : <u32>textLength;
|
|
36
|
+
return position > limit ? limit : position;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function utf8ByteLength(text: string): u32 {
|
|
40
|
+
const bytes = Uint8Array.wrap(String.UTF8.encode(text, false));
|
|
41
|
+
return <u32>bytes.length;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class TextInputProfile {
|
|
45
|
+
constructor(
|
|
46
|
+
readonly maxLines: i32,
|
|
47
|
+
readonly multiline: bool,
|
|
48
|
+
readonly wrapsByDefault: bool,
|
|
49
|
+
readonly defaultSemanticLabel: string,
|
|
50
|
+
) {}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const singleLineTextInputProfile = new TextInputProfile(1, false, false, "Text input");
|
|
54
|
+
export const multilineTextAreaProfile = new TextInputProfile(0, true, true, "Text area");
|
|
55
|
+
const TEXT_INPUT_PERSISTED_CODEC = new PersistedStringCodec();
|
|
56
|
+
|
|
57
|
+
class PersistedTextInputState extends PersistedValueState<TextInputCore, string> {
|
|
58
|
+
constructor() {
|
|
59
|
+
super("text-input-value", TEXT_INPUT_PERSISTED_CODEC, 1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
protected shouldCaptureValue(node: TextInputCore): bool {
|
|
63
|
+
return !node.isPassword;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
protected captureValue(node: TextInputCore): string {
|
|
67
|
+
return node.value;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
protected restoreValue(node: TextInputCore, value: string): void {
|
|
71
|
+
if (node.isPassword) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
node._applyPersistedText(value);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const TEXT_INPUT_PERSISTED_STATE = new PersistedTextInputState();
|
|
79
|
+
|
|
80
|
+
class TextInputEditorText extends TextCore {
|
|
81
|
+
private readonly owner: TextInputCore;
|
|
82
|
+
|
|
83
|
+
constructor(owner: TextInputCore, text: string) {
|
|
84
|
+
super(text);
|
|
85
|
+
this.owner = owner;
|
|
86
|
+
this.semanticRole(SemanticRole.Textbox);
|
|
87
|
+
this.focusable(true);
|
|
88
|
+
this.reflectSemanticDisabledFromEnabled();
|
|
89
|
+
this.selectable();
|
|
90
|
+
this.editable();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
_handleFocusChanged(focused: bool): void {
|
|
94
|
+
super._handleFocusChanged(focused);
|
|
95
|
+
this.owner.handleEditorFocusChanged(focused);
|
|
96
|
+
}
|
|
97
|
+
_handleTextChanged(text: string): void {
|
|
98
|
+
super._handleTextChanged(text);
|
|
99
|
+
this.owner.handleEditorTextChanged(text);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
_handleTextReplaced(start: u32, end: u32, text: string): void {
|
|
103
|
+
super._handleTextReplaced(start, end, text);
|
|
104
|
+
this.owner.handleEditorTextChanged(this.content);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
_handleSelectionChanged(start: u32, end: u32): void {
|
|
108
|
+
super._handleSelectionChanged(start, end);
|
|
109
|
+
this.owner.handleEditorSelectionChanged(start, end);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
class TextInputPlaceholderHost extends FlexBox {
|
|
114
|
+
private readonly owner: TextInputCore;
|
|
115
|
+
|
|
116
|
+
constructor(owner: TextInputCore, placeholderText: TextCore) {
|
|
117
|
+
super();
|
|
118
|
+
this.owner = owner;
|
|
119
|
+
this.positionAbsolute();
|
|
120
|
+
this.clipToBounds(false);
|
|
121
|
+
this.child(placeholderText);
|
|
122
|
+
this.requireInteractive();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
_handlePointerEvent(eventType: PointerEventType, x: f32, y: f32, modifiers: u32 = 0): void {
|
|
126
|
+
super._handlePointerEvent(eventType, x, y, modifiers);
|
|
127
|
+
if (eventType != PointerEventType.Down) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
this.owner.handleShellPointerDown();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
class TextInputEditorViewport extends ScrollView {
|
|
135
|
+
private readonly owner: TextInputCore;
|
|
136
|
+
|
|
137
|
+
constructor(owner: TextInputCore) {
|
|
138
|
+
super();
|
|
139
|
+
this.owner = owner;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
_handlePointerEvent(eventType: PointerEventType, x: f32, y: f32, modifiers: u32 = 0): void {
|
|
143
|
+
super._handlePointerEvent(eventType, x, y, modifiers);
|
|
144
|
+
if (eventType == PointerEventType.Down) {
|
|
145
|
+
this.owner.handleViewportPointerDown();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export class TextInputCore extends FlexBox {
|
|
151
|
+
protected editorText: TextCore = changetype<TextCore>(0);
|
|
152
|
+
|
|
153
|
+
private readonly profile: TextInputProfile;
|
|
154
|
+
private presenter: TextInputPresenter = changetype<TextInputPresenter>(0);
|
|
155
|
+
private editorScrollBox: ScrollBox | null = null;
|
|
156
|
+
private placeholderHost: FlexBox = changetype<FlexBox>(0);
|
|
157
|
+
private placeholderText: TextCore = changetype<TextCore>(0);
|
|
158
|
+
private readonly disposables: Array<Disposable> = new Array<Disposable>();
|
|
159
|
+
private disposed: bool = false;
|
|
160
|
+
private placeholderAttached: bool = false;
|
|
161
|
+
private textValue: string;
|
|
162
|
+
private placeholderValue: string = "";
|
|
163
|
+
private selectionStartValue: u32 = 0;
|
|
164
|
+
private selectionEndValue: u32 = 0;
|
|
165
|
+
private focusedState: bool = false;
|
|
166
|
+
private readOnlyValue: bool = false;
|
|
167
|
+
private passwordValue: bool = false;
|
|
168
|
+
private maxCharsValue: i32 = UNLIMITED_TEXT_LENGTH;
|
|
169
|
+
private wrappingValue: bool;
|
|
170
|
+
private verticalScrollbarVisibilityValue: ScrollBarVisibility = ScrollBarVisibility.Auto;
|
|
171
|
+
private horizontalScrollbarVisibilityValue: ScrollBarVisibility = ScrollBarVisibility.Auto;
|
|
172
|
+
private fontFamilyOverride: FontFamily | null = null;
|
|
173
|
+
private fontSizeOverride: f32 = 0.0;
|
|
174
|
+
private hasFontSizeOverride: bool = false;
|
|
175
|
+
private changedCb: ((text: string) => void) | null = null;
|
|
176
|
+
private changedBinding: Callback1<string> | null = null;
|
|
177
|
+
private selectionChangedCb: ((start: u32, end: u32) => void) | null = null;
|
|
178
|
+
private selectionChangedBinding: Callback2<u32, u32> | null = null;
|
|
179
|
+
private focusChangedListener: ((focused: bool) => void) | null = null;
|
|
180
|
+
private textInputFocusChangedBinding: Callback1<bool> | null = null;
|
|
181
|
+
private templateValue: TextInputTemplate | null = null;
|
|
182
|
+
|
|
183
|
+
constructor(profile: TextInputProfile, text: string = "") {
|
|
184
|
+
super();
|
|
185
|
+
this.profile = profile;
|
|
186
|
+
this.textValue = text;
|
|
187
|
+
this.wrappingValue = profile.wrapsByDefault;
|
|
188
|
+
this.editorText = new TextInputEditorText(this, text);
|
|
189
|
+
this.placeholderText = new TextCore("");
|
|
190
|
+
this.placeholderHost = new TextInputPlaceholderHost(this, this.placeholderText);
|
|
191
|
+
if (profile.multiline) {
|
|
192
|
+
const scrollBox = new ScrollBox(new ScrollState(), new TextInputEditorViewport(this))
|
|
193
|
+
.child(this.editorText) as ScrollBox;
|
|
194
|
+
this.editorScrollBox = scrollBox;
|
|
195
|
+
this.child(scrollBox);
|
|
196
|
+
} else {
|
|
197
|
+
this.child(this.editorText);
|
|
198
|
+
}
|
|
199
|
+
this.presenter = this.createPresenter(null);
|
|
200
|
+
this.presenter.bind(this, this.editorHostNode(), this.placeholderHost);
|
|
201
|
+
|
|
202
|
+
this.track(activeTheme.addAction(new HandlerAction<TextInputCore, Theme>(this, (control: TextInputCore, theme: Theme): void => {
|
|
203
|
+
control.handleThemeChanged(theme);
|
|
204
|
+
})));
|
|
205
|
+
this.track(keyboardFocusVisible.addAction(new HandlerAction<TextInputCore, bool>(this, (control: TextInputCore, _visible: bool): void => {
|
|
206
|
+
control.syncFocusChrome(activeTheme.value);
|
|
207
|
+
})));
|
|
208
|
+
|
|
209
|
+
this.requireInteractive();
|
|
210
|
+
this.syncSemanticLabel();
|
|
211
|
+
this.syncEditorLimits();
|
|
212
|
+
this.syncEditorEditability();
|
|
213
|
+
this.syncThemeState(activeTheme.value);
|
|
214
|
+
this.syncPlaceholderVisibility();
|
|
215
|
+
this.persistState(TEXT_INPUT_PERSISTED_STATE);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
get isSelectionBarrier(): bool {
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
get value(): string {
|
|
223
|
+
return this.textValue;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
get selectionStart(): u32 {
|
|
227
|
+
return this.selectionStartValue;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
get selectionEnd(): u32 {
|
|
231
|
+
return this.selectionEndValue;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
get isFocused(): bool {
|
|
235
|
+
return this.focusedState;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
get isReadOnly(): bool {
|
|
239
|
+
return this.readOnlyValue;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
get isPassword(): bool {
|
|
243
|
+
return this.passwordValue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
get editorNode(): TextCore {
|
|
247
|
+
return this.editorText;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
template(template: TextInputTemplate | null): this {
|
|
251
|
+
this.templateValue = template;
|
|
252
|
+
this.presenter = this.createPresenter(template);
|
|
253
|
+
this.presenter.bind(this, this.editorHostNode(), this.placeholderHost);
|
|
254
|
+
this.syncThemeState(activeTheme.value);
|
|
255
|
+
return this;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
text(value: string): this {
|
|
259
|
+
this.textValue = value;
|
|
260
|
+
this.clampSelectionToText();
|
|
261
|
+
this.editorText.text(value);
|
|
262
|
+
this.syncSemanticLabel();
|
|
263
|
+
this.syncPlaceholderVisibility();
|
|
264
|
+
return this;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
placeholder(value: string): this {
|
|
268
|
+
this.placeholderValue = value;
|
|
269
|
+
this.placeholderText.text(value);
|
|
270
|
+
this.syncSemanticLabel();
|
|
271
|
+
this.syncPlaceholderVisibility();
|
|
272
|
+
return this;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
maxChars(limit: i32): this {
|
|
276
|
+
this.maxCharsValue = limit < 0 ? UNLIMITED_TEXT_LENGTH : limit;
|
|
277
|
+
this.syncEditorLimits();
|
|
278
|
+
return this;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
readOnly(flag: bool = true): this {
|
|
282
|
+
this.readOnlyValue = flag;
|
|
283
|
+
this.syncEditorEditability();
|
|
284
|
+
return this;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
password(flag: bool = true): this {
|
|
288
|
+
this.passwordValue = flag;
|
|
289
|
+
this.editorText.obscured(flag);
|
|
290
|
+
this.syncSemanticLabel();
|
|
291
|
+
return this;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
onChanged(cb: (text: string) => void): this {
|
|
295
|
+
this.changedCb = cb;
|
|
296
|
+
this.changedBinding = null;
|
|
297
|
+
return this;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
bindChanged<Owner>(owner: Owner, handler: Handler1<Owner, string>): this {
|
|
301
|
+
this.changedCb = null;
|
|
302
|
+
this.changedBinding = bind1<Owner, string>(owner, handler);
|
|
303
|
+
return this;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
onChangedWith<Owner>(owner: Owner, handler: Handler1<Owner, string>): this {
|
|
307
|
+
this.bindChanged(owner, handler);
|
|
308
|
+
return this;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
onTextChanged(cb: (text: string) => void): this {
|
|
312
|
+
this.onChanged(cb);
|
|
313
|
+
return this;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
onSelectionChanged(cb: (start: u32, end: u32) => void): this {
|
|
317
|
+
this.selectionChangedCb = cb;
|
|
318
|
+
this.selectionChangedBinding = null;
|
|
319
|
+
return this;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
bindSelectionChanged<Owner>(owner: Owner, handler: Handler2<Owner, u32, u32>): this {
|
|
323
|
+
this.selectionChangedCb = null;
|
|
324
|
+
this.selectionChangedBinding = bind2<Owner, u32, u32>(owner, handler);
|
|
325
|
+
return this;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
onSelectionChangedWith<Owner>(owner: Owner, handler: Handler2<Owner, u32, u32>): this {
|
|
329
|
+
this.bindSelectionChanged(owner, handler);
|
|
330
|
+
return this;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
onFocusChanged(cb: (focused: bool) => void): this {
|
|
334
|
+
this.focusChangedListener = cb;
|
|
335
|
+
this.textInputFocusChangedBinding = null;
|
|
336
|
+
return this;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
bindFocusChanged<Owner>(owner: Owner, handler: Handler1<Owner, bool>): this {
|
|
340
|
+
this.focusChangedListener = null;
|
|
341
|
+
this.textInputFocusChangedBinding = bind1<Owner, bool>(owner, handler);
|
|
342
|
+
return this;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
onFocusChangedWith<Owner>(owner: Owner, handler: Handler1<Owner, bool>): this {
|
|
346
|
+
this.bindFocusChanged(owner, handler);
|
|
347
|
+
return this;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
semanticLabel(label: string): this {
|
|
351
|
+
this.editorText.semanticLabel(label);
|
|
352
|
+
return this;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
fontFamily(family: FontFamily | null): this {
|
|
356
|
+
this.fontFamilyOverride = family;
|
|
357
|
+
this.syncThemeState(activeTheme.value);
|
|
358
|
+
return this;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
fontSize(size: f32): this {
|
|
362
|
+
this.fontSizeOverride = size;
|
|
363
|
+
this.hasFontSizeOverride = size > 0.0;
|
|
364
|
+
this.syncThemeState(activeTheme.value);
|
|
365
|
+
return this;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
lineHeight(px: f32): this {
|
|
369
|
+
this.editorText.lineHeight(px);
|
|
370
|
+
this.placeholderText.lineHeight(px);
|
|
371
|
+
return this;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
focusNow(): this {
|
|
375
|
+
this.focusEditor();
|
|
376
|
+
return this;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
wrapping(flag: bool = true): this {
|
|
380
|
+
if (this.wrappingValue == flag) {
|
|
381
|
+
return this;
|
|
382
|
+
}
|
|
383
|
+
this.wrappingValue = flag;
|
|
384
|
+
this.syncEditorWrapping();
|
|
385
|
+
this.syncScrollChromeState();
|
|
386
|
+
this.syncThemeState(activeTheme.value);
|
|
387
|
+
return this;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
verticalScrollbarVisibility(mode: ScrollBarVisibility): this {
|
|
391
|
+
this.verticalScrollbarVisibilityValue = mode;
|
|
392
|
+
this.syncScrollChromeState();
|
|
393
|
+
return this;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
horizontalScrollbarVisibility(mode: ScrollBarVisibility): this {
|
|
397
|
+
this.horizontalScrollbarVisibilityValue = mode;
|
|
398
|
+
this.syncScrollChromeState();
|
|
399
|
+
return this;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
protected get isMultilineProfile(): bool {
|
|
403
|
+
return this.profile.multiline;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
protected get scrollBoxNode(): ScrollBox | null {
|
|
407
|
+
return this.editorScrollBox;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
build(): u64 {
|
|
411
|
+
if (this.builtHandle != <u64>HandleValue.Invalid) {
|
|
412
|
+
return this.builtHandle;
|
|
413
|
+
}
|
|
414
|
+
const handle = super.build();
|
|
415
|
+
ui.setSelectionAreaBarrier(handle, true);
|
|
416
|
+
return handle;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
dispose(): void {
|
|
420
|
+
this.disposeControl();
|
|
421
|
+
if (!this.placeholderAttached) {
|
|
422
|
+
this.placeholderHost.dispose();
|
|
423
|
+
}
|
|
424
|
+
super.dispose();
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
_handlePointerEvent(eventType: PointerEventType, x: f32, y: f32, modifiers: u32 = 0): void {
|
|
428
|
+
super._handlePointerEvent(eventType, x, y, modifiers);
|
|
429
|
+
if (eventType != PointerEventType.Down || !this.isEnabled) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
this.handleShellPointerDown();
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
protected _onEffectiveEnabledChanged(_isEnabled: bool): void {
|
|
436
|
+
this.syncEditorEditability();
|
|
437
|
+
this.syncThemeState(activeTheme.value);
|
|
438
|
+
this.syncPlaceholderVisibility();
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
_applyPersistedText(value: string): void {
|
|
442
|
+
if (this.textValue == value) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
this.text(value);
|
|
446
|
+
this.emitTextChanged(value);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
private handleThemeChanged(theme: Theme): void {
|
|
450
|
+
if (this.disposed) {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
this.syncThemeState(theme);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
private syncThemeState(theme: Theme): void {
|
|
457
|
+
const resolvedFontFamily = this.fontFamilyOverride !== null
|
|
458
|
+
? changetype<FontFamily>(this.fontFamilyOverride)
|
|
459
|
+
: theme.fonts.bodyFamily;
|
|
460
|
+
const resolvedFontSize = this.hasFontSizeOverride ? this.fontSizeOverride : theme.fonts.sizeBody;
|
|
461
|
+
const lineHeight = resolvedFontSize + theme.spacing.sm;
|
|
462
|
+
const editableCursor = this.isEnabled ? CursorStyle.Text : CursorStyle.Default;
|
|
463
|
+
this.presenter.apply(theme, this.createVisualState());
|
|
464
|
+
|
|
465
|
+
const textVerticalAlign = this.profile.multiline ? TextVerticalAlign.Top : TextVerticalAlign.Center;
|
|
466
|
+
this.editorText
|
|
467
|
+
.width(this.shouldEditorTrackViewportWidth() ? 100.0 : 0.0, this.shouldEditorTrackViewportWidth() ? Unit.Percent : Unit.Auto)
|
|
468
|
+
.height(this.profile.multiline ? 0.0 : lineHeight, this.profile.multiline ? Unit.Auto : Unit.Pixel)
|
|
469
|
+
.fontFamily(resolvedFontFamily)
|
|
470
|
+
.fontSize(resolvedFontSize)
|
|
471
|
+
.verticalAlign(textVerticalAlign)
|
|
472
|
+
.textColor(this.isEnabled ? theme.colors.textPrimary : theme.colors.textMuted)
|
|
473
|
+
.caretColor(theme.colors.accent);
|
|
474
|
+
this.syncEditorWrapping();
|
|
475
|
+
|
|
476
|
+
this.placeholderHost
|
|
477
|
+
.width(100.0, Unit.Percent)
|
|
478
|
+
.height(this.profile.multiline ? 0.0 : lineHeight, this.profile.multiline ? Unit.Auto : Unit.Pixel)
|
|
479
|
+
.cursor(editableCursor);
|
|
480
|
+
this.placeholderText
|
|
481
|
+
.width(100.0, Unit.Percent)
|
|
482
|
+
.height(this.profile.multiline ? 0.0 : lineHeight, this.profile.multiline ? Unit.Auto : Unit.Pixel)
|
|
483
|
+
.fontFamily(resolvedFontFamily)
|
|
484
|
+
.fontSize(resolvedFontSize)
|
|
485
|
+
.verticalAlign(textVerticalAlign)
|
|
486
|
+
.textColor(theme.colors.textMuted);
|
|
487
|
+
const scrollBox = this.editorScrollBox;
|
|
488
|
+
if (scrollBox !== null) {
|
|
489
|
+
scrollBox.cursor(CursorStyle.Default);
|
|
490
|
+
scrollBox.viewport.cursor(editableCursor);
|
|
491
|
+
scrollBox.width(100.0, Unit.Percent);
|
|
492
|
+
scrollBox.height(100.0, Unit.Percent);
|
|
493
|
+
scrollBox.flexGrow(1.0);
|
|
494
|
+
this.syncScrollChromeState();
|
|
495
|
+
}
|
|
496
|
+
this.syncFocusChrome(theme);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
handleEditorFocusChanged(focused: bool): void {
|
|
500
|
+
if (this.focusedState == focused) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
this.focusedState = focused;
|
|
504
|
+
this.syncFocusChrome(activeTheme.value);
|
|
505
|
+
this.syncPlaceholderVisibility();
|
|
506
|
+
const callback = this.focusChangedListener;
|
|
507
|
+
if (callback !== null) {
|
|
508
|
+
callback(focused);
|
|
509
|
+
}
|
|
510
|
+
const binding = this.textInputFocusChangedBinding;
|
|
511
|
+
if (binding !== null) {
|
|
512
|
+
binding.invoke(focused);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
handleEditorTextChanged(value: string): void {
|
|
517
|
+
this.textValue = value;
|
|
518
|
+
this.clampSelectionToText();
|
|
519
|
+
this.syncSemanticLabel();
|
|
520
|
+
this.syncPlaceholderVisibility();
|
|
521
|
+
this.emitTextChanged(value);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
private emitTextChanged(value: string): void {
|
|
525
|
+
const callback = this.changedCb;
|
|
526
|
+
if (callback !== null) {
|
|
527
|
+
callback(value);
|
|
528
|
+
}
|
|
529
|
+
const binding = this.changedBinding;
|
|
530
|
+
if (binding !== null) {
|
|
531
|
+
binding.invoke(value);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
handleEditorSelectionChanged(start: u32, end: u32): void {
|
|
536
|
+
this.selectionStartValue = start;
|
|
537
|
+
this.selectionEndValue = end;
|
|
538
|
+
const callback = this.selectionChangedCb;
|
|
539
|
+
if (callback !== null) {
|
|
540
|
+
callback(start, end);
|
|
541
|
+
}
|
|
542
|
+
const binding = this.selectionChangedBinding;
|
|
543
|
+
if (binding !== null) {
|
|
544
|
+
binding.invoke(start, end);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
private syncEditorLimits(): void {
|
|
549
|
+
this.editorText.textLimits(this.maxCharsValue, this.profile.maxLines);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
private syncEditorEditability(): void {
|
|
553
|
+
this.editorText.selectable(this.isEnabled);
|
|
554
|
+
this.editorText.editable(!this.readOnlyValue && this.isEnabled);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
private syncSemanticLabel(): void {
|
|
558
|
+
if (this.placeholderValue.length > 0) {
|
|
559
|
+
this.editorText._defaultSemanticLabel(this.placeholderValue);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
if (this.readOnlyValue && this.textValue.length > 0) {
|
|
563
|
+
this.editorText._defaultSemanticLabel(this.textValue);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
this.editorText._defaultSemanticLabel(this.passwordValue ? "Password input" : this.profile.defaultSemanticLabel);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
private syncPlaceholderVisibility(): void {
|
|
570
|
+
const shouldShow = this.textValue.length == 0 && this.placeholderValue.length > 0;
|
|
571
|
+
this.placeholderText.text(shouldShow ? this.placeholderValue : "");
|
|
572
|
+
if (shouldShow) {
|
|
573
|
+
if (!this.placeholderAttached) {
|
|
574
|
+
this.placeholderAttached = true;
|
|
575
|
+
this.child(this.placeholderHost);
|
|
576
|
+
}
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (!this.placeholderAttached) {
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
this.placeholderAttached = false;
|
|
583
|
+
this.removeChildNode(this.placeholderHost);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
private clampSelectionToText(): void {
|
|
587
|
+
const length = this.textValue.length;
|
|
588
|
+
this.selectionStartValue = clampSelection(this.selectionStartValue, length);
|
|
589
|
+
this.selectionEndValue = clampSelection(this.selectionEndValue, length);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
focusEditor(): void {
|
|
593
|
+
if (!this.isEnabled) {
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
this.editorText.focusNow();
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
handleShellPointerDown(): void {
|
|
600
|
+
if (!this.isEnabled) {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
if (!this.profile.multiline) {
|
|
604
|
+
this.focusEditor();
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
this.handleViewportPointerDown();
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
handleViewportPointerDown(): void {
|
|
611
|
+
if (!this.isEnabled) {
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
const handle = this.editorText.builtHandle;
|
|
615
|
+
if (handle == <u64>HandleValue.Invalid) {
|
|
616
|
+
this.focusEditor();
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const documentEnd = utf8ByteLength(this.textValue);
|
|
620
|
+
ui.setTextSelectionRange(handle, documentEnd, documentEnd);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
private syncEditorWrapping(): void {
|
|
624
|
+
this.editorText.wrapping(this.wrappingValue);
|
|
625
|
+
this.placeholderText.wrapping(this.wrappingValue);
|
|
626
|
+
if (this.profile.multiline) {
|
|
627
|
+
this.editorText.width(this.shouldEditorTrackViewportWidth() ? 100.0 : 0.0, this.shouldEditorTrackViewportWidth() ? Unit.Percent : Unit.Auto);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
private syncScrollChromeState(): void {
|
|
632
|
+
const scrollBox = this.editorScrollBox;
|
|
633
|
+
if (scrollBox === null) {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
const allowHorizontalScroll = !this.wrappingValue;
|
|
637
|
+
scrollBox.scrollEnabledX(allowHorizontalScroll);
|
|
638
|
+
scrollBox.scrollEnabledY(true);
|
|
639
|
+
scrollBox.verticalScrollbarVisibility(this.verticalScrollbarVisibilityValue);
|
|
640
|
+
scrollBox.horizontalScrollbarVisibility(
|
|
641
|
+
allowHorizontalScroll
|
|
642
|
+
? this.horizontalScrollbarVisibilityValue
|
|
643
|
+
: ScrollBarVisibility.Never,
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
private shouldEditorTrackViewportWidth(): bool {
|
|
648
|
+
return !this.profile.multiline || this.wrappingValue;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
private track(disposable: Disposable): void {
|
|
652
|
+
this.disposables.push(disposable);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
private disposeControl(): void {
|
|
656
|
+
if (this.disposed) {
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
this.disposed = true;
|
|
660
|
+
disposeAll(this.disposables);
|
|
661
|
+
FocusAdornerManager.hideOwner(this);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
private syncFocusChrome(theme: Theme): void {
|
|
665
|
+
if (this.focusedState && this.isEnabled && keyboardFocusVisible.value) {
|
|
666
|
+
FocusAdornerManager.showStandard(this, theme.spacing.sm);
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
FocusAdornerManager.hideOwner(this);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
private createPresenter(template: TextInputTemplate | null): TextInputPresenter {
|
|
673
|
+
if (template !== null) {
|
|
674
|
+
return template.create();
|
|
675
|
+
}
|
|
676
|
+
const templateSet = getControlTemplates();
|
|
677
|
+
const appTemplate = templateSet !== null
|
|
678
|
+
? (this.profile.multiline ? templateSet.textArea : templateSet.textInput)
|
|
679
|
+
: null;
|
|
680
|
+
return (appTemplate === null ? defaultTextInputTemplate : appTemplate).create();
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
private editorHostNode(): Node {
|
|
684
|
+
const scrollBox = this.editorScrollBox;
|
|
685
|
+
return scrollBox === null ? this.editorText : scrollBox;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
private createVisualState(): TextInputVisualState {
|
|
689
|
+
return new TextInputVisualState(
|
|
690
|
+
this.profile.multiline,
|
|
691
|
+
this.isEnabled,
|
|
692
|
+
this.wrappingValue,
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
}
|