@effindomv2/fui-as 0.2.0 → 0.2.2-alpha1
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/src/index.ts +0 -1
- package/package.json +3 -2
- package/scripts/update-upstream-versions.mjs +27 -0
- package/src/Fui.ts +6 -4
- package/src/FuiExports.ts +1 -0
- package/src/controls/Button.ts +1 -4
- package/src/controls/Checkbox.ts +2 -11
- package/src/controls/Dropdown.ts +4 -21
- package/src/controls/RadioButton.ts +2 -11
- package/src/controls/Slider.ts +2 -11
- package/src/controls/Switch.ts +1 -4
- package/src/controls/TabView.ts +345 -0
- package/src/controls/index.ts +6 -0
- package/src/controls/internal/SelectablePopupList.ts +1 -6
- package/src/controls/internal/TextInputCore.ts +1 -6
- package/src/controls/templating.ts +0 -6
- package/src/core/Application.ts +10 -23
- package/src/core/Bitmap.ts +6 -6
- package/src/core/DebugApi.ts +5 -0
- package/src/core/EventRouter.ts +51 -0
- package/src/core/Node.ts +15 -0
- package/src/core/RetainedView.ts +72 -0
- package/src/core/event_exports.ts +15 -1
- package/src/core/generated/HostAbi.ts +3 -0
- package/src/core/generated/UiEnums.ts +3 -0
- package/src/controls/ControlTemplateSet.ts +0 -37
package/browser/src/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effindomv2/fui-as",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2-alpha1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "AGPL-3.0-only OR LicenseRef-EffinDom-Commercial",
|
|
6
6
|
"description": "EffinDom v2 AssemblyScript frontend framework SDK and browser harness",
|
|
@@ -89,6 +89,7 @@
|
|
|
89
89
|
"LICENSES"
|
|
90
90
|
],
|
|
91
91
|
"scripts": {
|
|
92
|
+
"update:upstream": "node scripts/update-upstream-versions.mjs",
|
|
92
93
|
"guard:runtime-dependency": "bash scripts/check-runtime-dependency.sh",
|
|
93
94
|
"generate:abi": "mkdir -p build && esbuild ../abi/generate.ts --bundle --format=esm --platform=node --target=node20 --packages=external --outfile=build/generate-abi.mjs && node build/generate-abi.mjs fui-as-ui && node build/generate-abi.mjs fui-as-host && node build/generate-abi.mjs fui-as-enums",
|
|
94
95
|
"check:abi": "mkdir -p build && esbuild ../abi/generate.ts --bundle --format=esm --platform=node --target=node20 --packages=external --outfile=build/generate-abi.mjs && node build/generate-abi.mjs fui-as-ui --check && node build/generate-abi.mjs fui-as-host --check && node build/generate-abi.mjs fui-as-enums --check",
|
|
@@ -105,7 +106,7 @@
|
|
|
105
106
|
"typecheck": "npm run guard:runtime-dependency && npm run check:abi && tsc -p tsconfig.json --noEmit"
|
|
106
107
|
},
|
|
107
108
|
"dependencies": {
|
|
108
|
-
"@effindomv2/runtime": "0.2.
|
|
109
|
+
"@effindomv2/runtime": "0.2.10"
|
|
109
110
|
},
|
|
110
111
|
"devDependencies": {
|
|
111
112
|
"@as-pect/assembly": "9.0.0",
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const packageDirectory = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
7
|
+
const packagePath = join(packageDirectory, 'package.json');
|
|
8
|
+
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
9
|
+
const versionPattern = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
10
|
+
|
|
11
|
+
const runtimeVersion = execFileSync(
|
|
12
|
+
npm,
|
|
13
|
+
['view', '@effindomv2/runtime', 'dist-tags.latest'],
|
|
14
|
+
{ encoding: 'utf8' },
|
|
15
|
+
).trim();
|
|
16
|
+
if (!versionPattern.test(runtimeVersion)) {
|
|
17
|
+
throw new Error(`npm returned an invalid EffinDOM runtime version: ${JSON.stringify(runtimeVersion)}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const packageJson = JSON.parse(readFileSync(packagePath, 'utf8'));
|
|
21
|
+
packageJson.dependencies['@effindomv2/runtime'] = runtimeVersion;
|
|
22
|
+
writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`);
|
|
23
|
+
execFileSync(npm, ['install', '--package-lock-only', '--ignore-scripts'], {
|
|
24
|
+
cwd: packageDirectory,
|
|
25
|
+
stdio: 'inherit',
|
|
26
|
+
});
|
|
27
|
+
console.log(`Pinned @effindomv2/runtime@${runtimeVersion}.`);
|
package/src/Fui.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export {
|
|
2
2
|
Application,
|
|
3
3
|
ApplicationRegistration,
|
|
4
|
+
PageZoomMode,
|
|
4
5
|
ManagedApplicationController,
|
|
5
6
|
createApplication,
|
|
6
7
|
createManagedApplication,
|
|
@@ -44,6 +45,7 @@ export {
|
|
|
44
45
|
export { Fetch, FetchErrorEventArgs, FetchRequest, FetchResponse } from "./core/Fetch";
|
|
45
46
|
export { log } from "./core/Logger";
|
|
46
47
|
export { disposeAll, Disposable } from "./core/Disposable";
|
|
48
|
+
export { retainedView, RetainedView, RetainedViewLifecycleHandler } from "./core/RetainedView";
|
|
47
49
|
export { cancelAllTimers, cancelTimer, hasTimer, scheduleTimer } from "./core/Timers";
|
|
48
50
|
export { LoadedEventArgs, LoadedHandler, onLoaded, onLoadedWith } from "./core/FrameScheduler";
|
|
49
51
|
export { SetBackgroundAction, SetTextAction } from "./core/Actions";
|
|
@@ -217,12 +219,10 @@ export {
|
|
|
217
219
|
CheckboxIndicatorTemplate,
|
|
218
220
|
CheckboxIndicatorVisualState,
|
|
219
221
|
Checkbox,
|
|
220
|
-
clearControlTemplates,
|
|
221
222
|
ComboBox,
|
|
222
223
|
ComboBoxCommitMode,
|
|
223
224
|
ComboBoxFilterMode,
|
|
224
225
|
ComboBoxItem,
|
|
225
|
-
ControlTemplateSet,
|
|
226
226
|
ContextMenu,
|
|
227
227
|
Dialog,
|
|
228
228
|
DropdownSizing,
|
|
@@ -241,7 +241,6 @@ export {
|
|
|
241
241
|
DropdownOptionRowTemplate,
|
|
242
242
|
DropdownOptionRowVisualState,
|
|
243
243
|
Form,
|
|
244
|
-
getControlTemplates,
|
|
245
244
|
MenuItem,
|
|
246
245
|
NavLink,
|
|
247
246
|
LabeledControlSizing,
|
|
@@ -265,13 +264,16 @@ export {
|
|
|
265
264
|
SwitchIndicatorTemplate,
|
|
266
265
|
SwitchIndicatorVisualState,
|
|
267
266
|
Switch,
|
|
267
|
+
TabContentFactory,
|
|
268
|
+
TabItem,
|
|
269
|
+
TabSelectionChangedEventArgs,
|
|
270
|
+
TabView,
|
|
268
271
|
TextArea,
|
|
269
272
|
TextInputPresenter,
|
|
270
273
|
TextInput,
|
|
271
274
|
TextInputTemplate,
|
|
272
275
|
TextInputColors,
|
|
273
276
|
TextInputVisualState,
|
|
274
|
-
useControlTemplates,
|
|
275
277
|
} from "./controls";
|
|
276
278
|
export {
|
|
277
279
|
Border,
|
package/src/FuiExports.ts
CHANGED
package/src/controls/Button.ts
CHANGED
|
@@ -22,7 +22,6 @@ import { FontFamily, FontStyle, FontWeight } from "../core/Typography";
|
|
|
22
22
|
import { Border, FlexBox, TextCore } from "../nodes";
|
|
23
23
|
import { ClickEventArgs, HoverChangedEventArgs, PointerEventArgs } from "../core/Node";
|
|
24
24
|
import { ButtonColors } from "./ButtonColors";
|
|
25
|
-
import { getControlTemplates } from "./ControlTemplateSet";
|
|
26
25
|
import {
|
|
27
26
|
ButtonPresenter,
|
|
28
27
|
ButtonTemplate,
|
|
@@ -642,9 +641,7 @@ export class Button extends FlexBox {
|
|
|
642
641
|
if (template !== null) {
|
|
643
642
|
return template.create();
|
|
644
643
|
}
|
|
645
|
-
|
|
646
|
-
const appTemplate = templateSet !== null ? templateSet.button : null;
|
|
647
|
-
return (appTemplate === null ? defaultButtonTemplate : appTemplate).create();
|
|
644
|
+
return defaultButtonTemplate.create();
|
|
648
645
|
}
|
|
649
646
|
|
|
650
647
|
private replacePresenter(nextPresenter: ButtonPresenter): void {
|
package/src/controls/Checkbox.ts
CHANGED
|
@@ -4,7 +4,6 @@ import { CheckboxChangedEventArgs, ClickEventArgs } from "../core/Node";
|
|
|
4
4
|
import { PersistedInt32Codec, PersistedValueState } from "../core/PersistedState";
|
|
5
5
|
import { bind1 } from "../core/bind";
|
|
6
6
|
import { LabeledControlSizing } from "./ControlSizing";
|
|
7
|
-
import { getControlTemplates } from "./ControlTemplateSet";
|
|
8
7
|
import { activeTheme } from "../core/Theme";
|
|
9
8
|
import { PressableLabeledControl } from "./internal/PressableLabeledControl";
|
|
10
9
|
import {
|
|
@@ -45,11 +44,7 @@ function createIndicatorPresenter(template: CheckboxIndicatorTemplate | null, si
|
|
|
45
44
|
if (template !== null) {
|
|
46
45
|
return template.create(sizing);
|
|
47
46
|
}
|
|
48
|
-
|
|
49
|
-
const appTemplate = templateSet !== null ? templateSet.checkboxIndicator : null;
|
|
50
|
-
return appTemplate === null
|
|
51
|
-
? createDefaultCheckboxIndicatorPresenter(sizing)
|
|
52
|
-
: appTemplate.create(sizing);
|
|
47
|
+
return createDefaultCheckboxIndicatorPresenter(sizing);
|
|
53
48
|
}
|
|
54
49
|
|
|
55
50
|
export class Checkbox extends PressableLabeledControl {
|
|
@@ -214,11 +209,7 @@ export class Checkbox extends PressableLabeledControl {
|
|
|
214
209
|
}
|
|
215
210
|
|
|
216
211
|
private usesDefaultIndicatorPresenter(): bool {
|
|
217
|
-
|
|
218
|
-
return false;
|
|
219
|
-
}
|
|
220
|
-
const templateSet = getControlTemplates();
|
|
221
|
-
return templateSet === null || templateSet.checkboxIndicator === null;
|
|
212
|
+
return this.templateOverride === null;
|
|
222
213
|
}
|
|
223
214
|
|
|
224
215
|
}
|
package/src/controls/Dropdown.ts
CHANGED
|
@@ -23,7 +23,6 @@ import { FlexBox, ScrollView } from "../nodes";
|
|
|
23
23
|
import { bind1 } from "../core/bind";
|
|
24
24
|
import { DropdownSizing } from "./ControlSizing";
|
|
25
25
|
import { DropdownColors } from "./DropdownColors";
|
|
26
|
-
import { getControlTemplates } from "./ControlTemplateSet";
|
|
27
26
|
import {
|
|
28
27
|
createDefaultDropdownChevronPresenter,
|
|
29
28
|
DropdownChevronPresenter,
|
|
@@ -74,22 +73,14 @@ function createFieldPresenter(template: DropdownFieldTemplate | null, sizing: Dr
|
|
|
74
73
|
if (template !== null) {
|
|
75
74
|
return template.create(sizing);
|
|
76
75
|
}
|
|
77
|
-
|
|
78
|
-
const appTemplate = templateSet !== null ? templateSet.dropdownField : null;
|
|
79
|
-
return appTemplate === null
|
|
80
|
-
? createDefaultDropdownFieldPresenter(sizing)
|
|
81
|
-
: appTemplate.create(sizing);
|
|
76
|
+
return createDefaultDropdownFieldPresenter(sizing);
|
|
82
77
|
}
|
|
83
78
|
|
|
84
79
|
function createChevronPresenter(template: DropdownChevronTemplate | null, sizing: DropdownSizing | null = null): DropdownChevronPresenter {
|
|
85
80
|
if (template !== null) {
|
|
86
81
|
return template.create(sizing);
|
|
87
82
|
}
|
|
88
|
-
|
|
89
|
-
const appTemplate = templateSet !== null ? templateSet.dropdownChevron : null;
|
|
90
|
-
return appTemplate === null
|
|
91
|
-
? createDefaultDropdownChevronPresenter(sizing)
|
|
92
|
-
: appTemplate.create(sizing);
|
|
83
|
+
return createDefaultDropdownChevronPresenter(sizing);
|
|
93
84
|
}
|
|
94
85
|
|
|
95
86
|
export class DropdownItem {
|
|
@@ -722,19 +713,11 @@ export class Dropdown extends FlexBox implements GlobalKeyHandler, SelectablePop
|
|
|
722
713
|
}
|
|
723
714
|
|
|
724
715
|
private usesDefaultFieldPresenter(): bool {
|
|
725
|
-
|
|
726
|
-
return false;
|
|
727
|
-
}
|
|
728
|
-
const templateSet = getControlTemplates();
|
|
729
|
-
return templateSet === null || templateSet.dropdownField === null;
|
|
716
|
+
return this.fieldTemplateValue === null;
|
|
730
717
|
}
|
|
731
718
|
|
|
732
719
|
private usesDefaultChevronPresenter(): bool {
|
|
733
|
-
|
|
734
|
-
return false;
|
|
735
|
-
}
|
|
736
|
-
const templateSet = getControlTemplates();
|
|
737
|
-
return templateSet === null || templateSet.dropdownChevron === null;
|
|
720
|
+
return this.chevronTemplateValue === null;
|
|
738
721
|
}
|
|
739
722
|
|
|
740
723
|
getPopupListItemCount(): i32 {
|
|
@@ -5,7 +5,6 @@ import { activeTheme } from "../core/Theme";
|
|
|
5
5
|
import { bind1 } from "../core/bind";
|
|
6
6
|
import { LabeledControlSizing } from "./ControlSizing";
|
|
7
7
|
import { PressableLabeledControl } from "./internal/PressableLabeledControl";
|
|
8
|
-
import { getControlTemplates } from "./ControlTemplateSet";
|
|
9
8
|
import { RadioGroup } from "./RadioGroup";
|
|
10
9
|
import {
|
|
11
10
|
createDefaultRadioIndicatorPresenter,
|
|
@@ -18,11 +17,7 @@ function createIndicatorPresenter(template: RadioIndicatorTemplate | null, sizin
|
|
|
18
17
|
if (template !== null) {
|
|
19
18
|
return template.create(sizing);
|
|
20
19
|
}
|
|
21
|
-
|
|
22
|
-
const appTemplate = templateSet !== null ? templateSet.radioIndicator : null;
|
|
23
|
-
return appTemplate === null
|
|
24
|
-
? createDefaultRadioIndicatorPresenter(sizing)
|
|
25
|
-
: appTemplate.create(sizing);
|
|
20
|
+
return createDefaultRadioIndicatorPresenter(sizing);
|
|
26
21
|
}
|
|
27
22
|
|
|
28
23
|
export class RadioButton extends PressableLabeledControl {
|
|
@@ -188,10 +183,6 @@ export class RadioButton extends PressableLabeledControl {
|
|
|
188
183
|
}
|
|
189
184
|
|
|
190
185
|
private usesDefaultIndicatorPresenter(): bool {
|
|
191
|
-
|
|
192
|
-
return false;
|
|
193
|
-
}
|
|
194
|
-
const templateSet = getControlTemplates();
|
|
195
|
-
return templateSet === null || templateSet.radioIndicator === null;
|
|
186
|
+
return this.templateOverride === null;
|
|
196
187
|
}
|
|
197
188
|
}
|
package/src/controls/Slider.ts
CHANGED
|
@@ -21,7 +21,6 @@ import { FlexBox } from "../nodes";
|
|
|
21
21
|
import { bind1 } from "../core/bind";
|
|
22
22
|
import { SliderSizing } from "./ControlSizing";
|
|
23
23
|
import { SliderColors } from "./SliderColors";
|
|
24
|
-
import { getControlTemplates } from "./ControlTemplateSet";
|
|
25
24
|
import {
|
|
26
25
|
createDefaultSliderPresenter,
|
|
27
26
|
SliderPresenter,
|
|
@@ -68,11 +67,7 @@ function createSliderPresenter(template: SliderTemplate | null, sizing: SliderSi
|
|
|
68
67
|
if (template !== null) {
|
|
69
68
|
return template.create(sizing);
|
|
70
69
|
}
|
|
71
|
-
|
|
72
|
-
const appTemplate = templateSet !== null ? templateSet.slider : null;
|
|
73
|
-
return appTemplate === null
|
|
74
|
-
? createDefaultSliderPresenter(sizing)
|
|
75
|
-
: appTemplate.create(sizing);
|
|
70
|
+
return createDefaultSliderPresenter(sizing);
|
|
76
71
|
}
|
|
77
72
|
|
|
78
73
|
export class Slider extends FlexBox implements DragGestureHost {
|
|
@@ -472,11 +467,7 @@ export class Slider extends FlexBox implements DragGestureHost {
|
|
|
472
467
|
}
|
|
473
468
|
|
|
474
469
|
private usesDefaultPresenter(): bool {
|
|
475
|
-
|
|
476
|
-
return false;
|
|
477
|
-
}
|
|
478
|
-
const templateSet = getControlTemplates();
|
|
479
|
-
return templateSet === null || templateSet.slider === null;
|
|
470
|
+
return this.templateOverride === null;
|
|
480
471
|
}
|
|
481
472
|
|
|
482
473
|
private track(disposable: Disposable): void {
|
package/src/controls/Switch.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { PersistedBoolCodec, PersistedValueState } from "../core/PersistedState"
|
|
|
5
5
|
import { bind1 } from "../core/bind";
|
|
6
6
|
import { activeTheme } from "../core/Theme";
|
|
7
7
|
import { LabeledControlSizing } from "./ControlSizing";
|
|
8
|
-
import { getControlTemplates } from "./ControlTemplateSet";
|
|
9
8
|
import { PressableLabeledControl } from "./internal/PressableLabeledControl";
|
|
10
9
|
import {
|
|
11
10
|
defaultSwitchIndicatorTemplate,
|
|
@@ -36,9 +35,7 @@ function createIndicatorPresenter(template: SwitchIndicatorTemplate | null, sizi
|
|
|
36
35
|
if (template !== null) {
|
|
37
36
|
return template.create(sizing);
|
|
38
37
|
}
|
|
39
|
-
|
|
40
|
-
const appTemplate = templateSet !== null ? templateSet.switchIndicator : null;
|
|
41
|
-
return (appTemplate === null ? defaultSwitchIndicatorTemplate : appTemplate).create(sizing);
|
|
38
|
+
return defaultSwitchIndicatorTemplate.create(sizing);
|
|
42
39
|
}
|
|
43
40
|
|
|
44
41
|
export class Switch extends PressableLabeledControl {
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
import { Callback1, Handler1 } from "../core/BoundCallback";
|
|
2
|
+
import { throwNullArgument } from "../core/Errors";
|
|
3
|
+
import { FlexDirection, SemanticRole } from "../core/ffi";
|
|
4
|
+
import { warn } from "../core/Logger";
|
|
5
|
+
import { retainedView, RetainedView } from "../core/RetainedView";
|
|
6
|
+
import { bind1 } from "../core/bind";
|
|
7
|
+
import { FlexBox } from "../nodes";
|
|
8
|
+
|
|
9
|
+
const NO_PENDING_SELECTION: i32 = -2;
|
|
10
|
+
|
|
11
|
+
export type TabContentFactory = () => RetainedView;
|
|
12
|
+
|
|
13
|
+
export class TabSelectionChangedEventArgs {
|
|
14
|
+
constructor(
|
|
15
|
+
readonly previousIndex: i32,
|
|
16
|
+
readonly selectedIndex: i32,
|
|
17
|
+
readonly previousItem: TabItem | null,
|
|
18
|
+
readonly selectedItem: TabItem | null,
|
|
19
|
+
) {}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class TabItem {
|
|
23
|
+
private labelValue: string;
|
|
24
|
+
private descriptionValue: string = "";
|
|
25
|
+
private enabledValue: bool = true;
|
|
26
|
+
private factoryValue: TabContentFactory | null;
|
|
27
|
+
private preparedViewValue: RetainedView | null = null;
|
|
28
|
+
private retainedViewValue: RetainedView | null = null;
|
|
29
|
+
private ownerValue: TabView | null = null;
|
|
30
|
+
|
|
31
|
+
constructor(label: string, content: TabContentFactory | null = null) {
|
|
32
|
+
this.labelValue = label;
|
|
33
|
+
this.factoryValue = content;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get labelText(): string { return this.labelValue; }
|
|
37
|
+
get descriptionText(): string { return this.descriptionValue; }
|
|
38
|
+
get isEnabled(): bool { return this.enabledValue; }
|
|
39
|
+
get isMaterialized(): bool { return this.retainedViewValue !== null; }
|
|
40
|
+
|
|
41
|
+
label(value: string): this {
|
|
42
|
+
this.labelValue = value;
|
|
43
|
+
const owner = this.ownerValue;
|
|
44
|
+
if (owner !== null) changetype<TabView>(owner)._itemMetadataChanged(this);
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
description(value: string): this {
|
|
49
|
+
this.descriptionValue = value;
|
|
50
|
+
const owner = this.ownerValue;
|
|
51
|
+
if (owner !== null) changetype<TabView>(owner)._itemMetadataChanged(this);
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
enabled(flag: bool): this {
|
|
56
|
+
if (this.enabledValue == flag) return this;
|
|
57
|
+
this.enabledValue = flag;
|
|
58
|
+
const owner = this.ownerValue;
|
|
59
|
+
if (owner !== null) changetype<TabView>(owner)._itemEnabledChanged(this);
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
content(factory: TabContentFactory): this {
|
|
64
|
+
if (this.isMaterialized) {
|
|
65
|
+
warn("Lifecycle", "TabItem.content() cannot replace content after the tab has been materialized.");
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
this.preparedViewValue = null;
|
|
69
|
+
this.factoryValue = factory;
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
contentView(view: RetainedView): this {
|
|
74
|
+
if (this.isMaterialized) {
|
|
75
|
+
warn("Lifecycle", "TabItem.contentView() cannot replace content after the tab has been materialized.");
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
this.preparedViewValue = view;
|
|
79
|
+
this.factoryValue = null;
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_bindOwner(owner: TabView | null): void { this.ownerValue = owner; }
|
|
84
|
+
|
|
85
|
+
_resolveView(): RetainedView {
|
|
86
|
+
const retained = this.retainedViewValue;
|
|
87
|
+
if (retained !== null) return changetype<RetainedView>(retained);
|
|
88
|
+
const prepared = this.preparedViewValue;
|
|
89
|
+
let resolved: RetainedView;
|
|
90
|
+
if (prepared !== null) {
|
|
91
|
+
resolved = changetype<RetainedView>(prepared);
|
|
92
|
+
} else {
|
|
93
|
+
const factory = this.factoryValue;
|
|
94
|
+
resolved = factory === null
|
|
95
|
+
? retainedView(new FlexBox().fillSize())
|
|
96
|
+
: changetype<TabContentFactory>(factory)();
|
|
97
|
+
}
|
|
98
|
+
this.preparedViewValue = null;
|
|
99
|
+
this.retainedViewValue = resolved;
|
|
100
|
+
return resolved;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
_existingView(): RetainedView | null { return this.retainedViewValue; }
|
|
104
|
+
|
|
105
|
+
_disposeContent(): void {
|
|
106
|
+
const retained = this.retainedViewValue;
|
|
107
|
+
if (retained !== null) changetype<RetainedView>(retained).dispose();
|
|
108
|
+
const prepared = this.preparedViewValue;
|
|
109
|
+
if (prepared !== null && prepared !== retained) changetype<RetainedView>(prepared).dispose();
|
|
110
|
+
this.retainedViewValue = null;
|
|
111
|
+
this.preparedViewValue = null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export class TabView extends FlexBox {
|
|
116
|
+
private readonly itemsValue: Array<TabItem> = new Array<TabItem>();
|
|
117
|
+
private selectedIndexValue: i32 = -1;
|
|
118
|
+
private selectingValue: bool = false;
|
|
119
|
+
private pendingSelectionValue: i32 = NO_PENDING_SELECTION;
|
|
120
|
+
private pendingEmitValue: bool = false;
|
|
121
|
+
private changedCallback: ((event: TabSelectionChangedEventArgs) => void) | null = null;
|
|
122
|
+
private changedBinding: Callback1<TabSelectionChangedEventArgs> | null = null;
|
|
123
|
+
readonly contentPresenter: FlexBox;
|
|
124
|
+
|
|
125
|
+
constructor(items: Array<TabItem> = new Array<TabItem>()) {
|
|
126
|
+
super();
|
|
127
|
+
this.contentPresenter = new FlexBox().fillSize() as FlexBox;
|
|
128
|
+
this.contentPresenter.semanticRole(SemanticRole.TabPanel);
|
|
129
|
+
this.contentPresenter.semanticLabel("Tab panel");
|
|
130
|
+
this.flexDirection(FlexDirection.Column);
|
|
131
|
+
this.addChildNode(this.contentPresenter);
|
|
132
|
+
this.items(items);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
get selectedIndex(): i32 { return this.selectedIndexValue; }
|
|
136
|
+
get selectedItem(): TabItem | null {
|
|
137
|
+
return this.selectedIndexValue >= 0 && this.selectedIndexValue < this.itemsValue.length
|
|
138
|
+
? unchecked(this.itemsValue[this.selectedIndexValue])
|
|
139
|
+
: null;
|
|
140
|
+
}
|
|
141
|
+
get itemCount(): i32 { return this.itemsValue.length; }
|
|
142
|
+
|
|
143
|
+
itemAt(index: i32): TabItem | null {
|
|
144
|
+
return index >= 0 && index < this.itemsValue.length ? unchecked(this.itemsValue[index]) : null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
items(items: Array<TabItem>): this {
|
|
148
|
+
this.clearItems();
|
|
149
|
+
for (let index = 0; index < items.length; ++index) this.addItem(unchecked(items[index]));
|
|
150
|
+
return this;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
addItem(item: TabItem): this {
|
|
154
|
+
if (item == null) throwNullArgument("TabView.addItem", "item");
|
|
155
|
+
if (this.indexOfItem(item) >= 0) return this;
|
|
156
|
+
item._bindOwner(this);
|
|
157
|
+
const index = this.itemsValue.length;
|
|
158
|
+
this.itemsValue.push(item);
|
|
159
|
+
if (this.selectedIndexValue < 0 && item.isEnabled) this.selectIndexInternal(index, false, null);
|
|
160
|
+
return this;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
removeItem(item: TabItem): this {
|
|
164
|
+
const index = this.indexOfItem(item);
|
|
165
|
+
if (index >= 0) this.removeItemAt(index);
|
|
166
|
+
return this;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
removeItemAt(index: i32): this {
|
|
170
|
+
if (index < 0 || index >= this.itemsValue.length) return this;
|
|
171
|
+
const removed = unchecked(this.itemsValue[index]);
|
|
172
|
+
const wasSelected = index == this.selectedIndexValue;
|
|
173
|
+
if (wasSelected) this.detachActiveView();
|
|
174
|
+
removed._bindOwner(null);
|
|
175
|
+
for (let cursor = index; cursor < this.itemsValue.length - 1; ++cursor) {
|
|
176
|
+
unchecked(this.itemsValue[cursor] = unchecked(this.itemsValue[cursor + 1]));
|
|
177
|
+
}
|
|
178
|
+
this.itemsValue.length -= 1;
|
|
179
|
+
if (wasSelected) {
|
|
180
|
+
const previousIndex = this.selectedIndexValue;
|
|
181
|
+
this.selectedIndexValue = -1;
|
|
182
|
+
const replacement = this.findReplacementIndex(index);
|
|
183
|
+
if (replacement >= 0) this.selectIndexInternal(replacement, true, removed, previousIndex);
|
|
184
|
+
else this.emitChanged(previousIndex, -1, removed, null);
|
|
185
|
+
} else if (this.selectedIndexValue > index) {
|
|
186
|
+
this.selectedIndexValue -= 1;
|
|
187
|
+
}
|
|
188
|
+
removed._disposeContent();
|
|
189
|
+
return this;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
clearItems(): this {
|
|
193
|
+
const previousIndex = this.selectedIndexValue;
|
|
194
|
+
const previousItem = this.selectedItem;
|
|
195
|
+
this.detachActiveView();
|
|
196
|
+
for (let index = 0; index < this.itemsValue.length; ++index) {
|
|
197
|
+
const item = unchecked(this.itemsValue[index]);
|
|
198
|
+
item._bindOwner(null);
|
|
199
|
+
item._disposeContent();
|
|
200
|
+
}
|
|
201
|
+
this.itemsValue.length = 0;
|
|
202
|
+
this.selectedIndexValue = -1;
|
|
203
|
+
if (previousItem !== null) this.emitChanged(previousIndex, -1, previousItem, null);
|
|
204
|
+
return this;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
selectIndex(index: i32): this {
|
|
208
|
+
this.selectIndexInternal(index, true, null);
|
|
209
|
+
return this;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
onSelectionChanged(callback: ((event: TabSelectionChangedEventArgs) => void) | null): this {
|
|
213
|
+
this.changedCallback = callback;
|
|
214
|
+
this.changedBinding = null;
|
|
215
|
+
return this;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
bindSelectionChanged<Owner>(owner: Owner, handler: Handler1<Owner, TabSelectionChangedEventArgs>): this {
|
|
219
|
+
this.changedCallback = null;
|
|
220
|
+
this.changedBinding = bind1<Owner, TabSelectionChangedEventArgs>(owner, handler);
|
|
221
|
+
return this;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
onSelectionChangedWith<Owner>(owner: Owner, handler: Handler1<Owner, TabSelectionChangedEventArgs>): this {
|
|
225
|
+
return this.bindSelectionChanged(owner, handler);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
_itemEnabledChanged(item: TabItem): void {
|
|
229
|
+
const index = this.indexOfItem(item);
|
|
230
|
+
if (index < 0) return;
|
|
231
|
+
if (!item.isEnabled && index == this.selectedIndexValue) {
|
|
232
|
+
const previousIndex = this.selectedIndexValue;
|
|
233
|
+
this.detachActiveView();
|
|
234
|
+
this.selectedIndexValue = -1;
|
|
235
|
+
const replacement = this.findReplacementIndex(index);
|
|
236
|
+
if (replacement >= 0) this.selectIndexInternal(replacement, true, item, previousIndex);
|
|
237
|
+
else this.emitChanged(previousIndex, -1, item, null);
|
|
238
|
+
} else if (item.isEnabled && this.selectedIndexValue < 0) {
|
|
239
|
+
this.selectIndexInternal(index, true, null);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
_itemMetadataChanged(item: TabItem): void {
|
|
244
|
+
const index = this.indexOfItem(item);
|
|
245
|
+
if (index < 0) return;
|
|
246
|
+
if (index == this.selectedIndexValue) this.contentPresenter.semanticLabel(item.labelText + " tab panel");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
dispose(): void {
|
|
250
|
+
this.detachActiveView();
|
|
251
|
+
for (let index = 0; index < this.itemsValue.length; ++index) {
|
|
252
|
+
const item = unchecked(this.itemsValue[index]);
|
|
253
|
+
item._bindOwner(null);
|
|
254
|
+
item._disposeContent();
|
|
255
|
+
}
|
|
256
|
+
super.dispose();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private selectIndexInternal(
|
|
260
|
+
index: i32,
|
|
261
|
+
emit: bool,
|
|
262
|
+
previousItemOverride: TabItem | null,
|
|
263
|
+
previousIndexOverride: i32 = -1,
|
|
264
|
+
): void {
|
|
265
|
+
if (!this.isEnabledIndex(index)) return;
|
|
266
|
+
if (this.selectingValue) {
|
|
267
|
+
this.pendingSelectionValue = index;
|
|
268
|
+
this.pendingEmitValue = emit;
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
this.selectingValue = true;
|
|
272
|
+
let requestedIndex = index;
|
|
273
|
+
let requestedEmit = emit;
|
|
274
|
+
let overrideItem = previousItemOverride;
|
|
275
|
+
let overrideIndex = previousIndexOverride;
|
|
276
|
+
let iterations = 0;
|
|
277
|
+
while (requestedIndex >= 0 && this.isEnabledIndex(requestedIndex)) {
|
|
278
|
+
const previousIndex = overrideItem !== null ? overrideIndex : this.selectedIndexValue;
|
|
279
|
+
const previousItem = overrideItem !== null ? overrideItem : this.selectedItem;
|
|
280
|
+
overrideItem = null;
|
|
281
|
+
overrideIndex = -1;
|
|
282
|
+
if (this.selectedIndexValue != requestedIndex) {
|
|
283
|
+
this.detachActiveView();
|
|
284
|
+
this.selectedIndexValue = requestedIndex;
|
|
285
|
+
const item = unchecked(this.itemsValue[requestedIndex]);
|
|
286
|
+
const view = item._resolveView();
|
|
287
|
+
this.contentPresenter.addChildNode(view.root);
|
|
288
|
+
this.contentPresenter.semanticLabel(item.labelText + " tab panel");
|
|
289
|
+
view.activate();
|
|
290
|
+
if (requestedEmit) this.emitChanged(previousIndex, requestedIndex, previousItem, item);
|
|
291
|
+
}
|
|
292
|
+
if (this.pendingSelectionValue == NO_PENDING_SELECTION) break;
|
|
293
|
+
requestedIndex = this.pendingSelectionValue;
|
|
294
|
+
requestedEmit = this.pendingEmitValue;
|
|
295
|
+
this.pendingSelectionValue = NO_PENDING_SELECTION;
|
|
296
|
+
iterations += 1;
|
|
297
|
+
if (iterations > this.itemsValue.length + 8) {
|
|
298
|
+
warn("Lifecycle", "TabView stopped a re-entrant selection loop.");
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
this.pendingSelectionValue = NO_PENDING_SELECTION;
|
|
303
|
+
this.selectingValue = false;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private detachActiveView(): void {
|
|
307
|
+
const item = this.selectedItem;
|
|
308
|
+
if (item === null) return;
|
|
309
|
+
const view = changetype<TabItem>(item)._existingView();
|
|
310
|
+
if (view === null) return;
|
|
311
|
+
const retained = changetype<RetainedView>(view);
|
|
312
|
+
retained.deactivate();
|
|
313
|
+
this.contentPresenter.removeChildNode(retained.root);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private emitChanged(
|
|
317
|
+
previousIndex: i32,
|
|
318
|
+
selectedIndex: i32,
|
|
319
|
+
previousItem: TabItem | null,
|
|
320
|
+
selectedItem: TabItem | null,
|
|
321
|
+
): void {
|
|
322
|
+
const event = new TabSelectionChangedEventArgs(previousIndex, selectedIndex, previousItem, selectedItem);
|
|
323
|
+
const callback = this.changedCallback;
|
|
324
|
+
if (callback !== null) callback(event);
|
|
325
|
+
const binding = this.changedBinding;
|
|
326
|
+
if (binding !== null) binding.invoke(event);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private indexOfItem(item: TabItem): i32 {
|
|
330
|
+
for (let index = 0; index < this.itemsValue.length; ++index) {
|
|
331
|
+
if (unchecked(this.itemsValue[index]) === item) return index;
|
|
332
|
+
}
|
|
333
|
+
return -1;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private isEnabledIndex(index: i32): bool {
|
|
337
|
+
return index >= 0 && index < this.itemsValue.length && unchecked(this.itemsValue[index]).isEnabled;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private findReplacementIndex(index: i32): i32 {
|
|
341
|
+
for (let cursor = index; cursor < this.itemsValue.length; ++cursor) if (this.isEnabledIndex(cursor)) return cursor;
|
|
342
|
+
for (let cursor = index - 1; cursor >= 0; --cursor) if (this.isEnabledIndex(cursor)) return cursor;
|
|
343
|
+
return -1;
|
|
344
|
+
}
|
|
345
|
+
}
|
package/src/controls/index.ts
CHANGED
|
@@ -20,6 +20,12 @@ export { AntiSelectionArea } from "./AntiSelectionArea";
|
|
|
20
20
|
export { SelectionArea } from "./SelectionArea";
|
|
21
21
|
export { Slider } from "./Slider";
|
|
22
22
|
export { Switch } from "./Switch";
|
|
23
|
+
export {
|
|
24
|
+
TabContentFactory,
|
|
25
|
+
TabItem,
|
|
26
|
+
TabSelectionChangedEventArgs,
|
|
27
|
+
TabView,
|
|
28
|
+
} from "./TabView";
|
|
23
29
|
export { TextArea } from "./TextArea";
|
|
24
30
|
export { TextInput } from "./TextInput";
|
|
25
31
|
export * from "./templating";
|
|
@@ -6,7 +6,6 @@ import { Theme, activeTheme } from "../../core/Theme";
|
|
|
6
6
|
import { FlexBox, Portal, ScrollBarVisibility, ScrollBox } from "../../nodes";
|
|
7
7
|
import { DropdownSizing } from "../ControlSizing";
|
|
8
8
|
import { DropdownColors } from "../DropdownColors";
|
|
9
|
-
import { getControlTemplates } from "../ControlTemplateSet";
|
|
10
9
|
import {
|
|
11
10
|
createDefaultDropdownOptionRowPresenter,
|
|
12
11
|
DropdownOptionRowPresenter,
|
|
@@ -35,11 +34,7 @@ function createOptionRowPresenter(template: DropdownOptionRowTemplate | null, si
|
|
|
35
34
|
if (template !== null) {
|
|
36
35
|
return template.create(sizing);
|
|
37
36
|
}
|
|
38
|
-
|
|
39
|
-
const appTemplate = templateSet !== null ? templateSet.dropdownOptionRow : null;
|
|
40
|
-
return appTemplate === null
|
|
41
|
-
? createDefaultDropdownOptionRowPresenter(sizing)
|
|
42
|
-
: appTemplate.create(sizing);
|
|
37
|
+
return createDefaultDropdownOptionRowPresenter(sizing);
|
|
43
38
|
}
|
|
44
39
|
|
|
45
40
|
class SelectablePopupListOptionNode extends FlexBox {
|
|
@@ -22,7 +22,6 @@ import { FlexBox, ScrollBarVisibility, ScrollBox, TextCore } from "../../nodes";
|
|
|
22
22
|
import { bind1 } from "../../core/bind";
|
|
23
23
|
import { ScrollState } from "../../nodes/ScrollState";
|
|
24
24
|
import { ScrollView } from "../../nodes/ScrollView";
|
|
25
|
-
import { getControlTemplates } from "../ControlTemplateSet";
|
|
26
25
|
import { TextInputColors } from "../TextInputColors";
|
|
27
26
|
import {
|
|
28
27
|
defaultTextInputTemplate,
|
|
@@ -973,11 +972,7 @@ export class TextInputCore extends FlexBox {
|
|
|
973
972
|
if (template !== null) {
|
|
974
973
|
return template.create();
|
|
975
974
|
}
|
|
976
|
-
|
|
977
|
-
const appTemplate = templateSet !== null
|
|
978
|
-
? (this.profile.multiline ? templateSet.textArea : templateSet.textInput)
|
|
979
|
-
: null;
|
|
980
|
-
return (appTemplate === null ? defaultTextInputTemplate : appTemplate).create();
|
|
975
|
+
return defaultTextInputTemplate.create();
|
|
981
976
|
}
|
|
982
977
|
|
|
983
978
|
private editorHostNode(): Node {
|
|
@@ -5,12 +5,6 @@ export {
|
|
|
5
5
|
createDefaultButtonPresenter,
|
|
6
6
|
defaultButtonTemplate,
|
|
7
7
|
} from "./internal/ButtonPresenter";
|
|
8
|
-
export {
|
|
9
|
-
ControlTemplateSet,
|
|
10
|
-
clearControlTemplates,
|
|
11
|
-
getControlTemplates,
|
|
12
|
-
useControlTemplates,
|
|
13
|
-
} from "./ControlTemplateSet";
|
|
14
8
|
export { PressableIndicatorMetrics } from "./internal/PressableIndicatorPresenter";
|
|
15
9
|
export {
|
|
16
10
|
CheckboxIndicatorPresenter,
|
package/src/core/Application.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { SelectionHandleAdornerManager } from "./SelectionHandleAdornerManager";
|
|
|
10
10
|
import { ToolTipManager } from "./ToolTipManager";
|
|
11
11
|
import * as ui from "../bindings/ui";
|
|
12
12
|
import { flushCommit, fireLoadedCallbacks, markNeedsCommit, resetCommitState } from "./FrameScheduler";
|
|
13
|
-
import { fui_set_application_caption, HandleValue, Unit } from "./ffi";
|
|
13
|
+
import { fui_set_application_caption, fui_set_page_zoom_enabled, HandleValue, Unit } from "./ffi";
|
|
14
14
|
import { Node } from "./Node";
|
|
15
15
|
import { cancelAllTimers } from "./Timers";
|
|
16
16
|
import {
|
|
@@ -23,12 +23,16 @@ import {
|
|
|
23
23
|
useSystemTheme,
|
|
24
24
|
} from "./Theme";
|
|
25
25
|
import { disposeAllWorkers } from "./Worker";
|
|
26
|
-
import { ControlTemplateSet, clearControlTemplates, getControlTemplates, useControlTemplates } from "../controls/ControlTemplateSet";
|
|
27
26
|
|
|
28
27
|
let mountedRoot: Node | null = null;
|
|
29
28
|
let mountedShell: FlexBox | null = null;
|
|
30
29
|
let exportedApplication: Application<Node> | null = null;
|
|
31
30
|
|
|
31
|
+
export enum PageZoomMode {
|
|
32
|
+
Disabled = 0,
|
|
33
|
+
Enabled = 1,
|
|
34
|
+
}
|
|
35
|
+
|
|
32
36
|
function createEmptyPage(): Node {
|
|
33
37
|
return new FlexBox().width(100.0, Unit.Percent).height(100.0, Unit.Percent);
|
|
34
38
|
}
|
|
@@ -78,8 +82,6 @@ export class ApplicationRegistration {
|
|
|
78
82
|
private customThemeOnRegister: Theme | null = null;
|
|
79
83
|
private accentColorOnRegister: u32 = 0;
|
|
80
84
|
private hasAccentColorOnRegister: bool = false;
|
|
81
|
-
private controlTemplatesOnRegister: ControlTemplateSet | null = null;
|
|
82
|
-
private hasControlTemplatesOnRegister: bool = false;
|
|
83
85
|
|
|
84
86
|
page(buildPage: () => Node): this {
|
|
85
87
|
this.buildPageFn = buildPage;
|
|
@@ -108,15 +110,8 @@ export class ApplicationRegistration {
|
|
|
108
110
|
return this;
|
|
109
111
|
}
|
|
110
112
|
|
|
111
|
-
controlTemplates(templates: ControlTemplateSet | null): this {
|
|
112
|
-
this.controlTemplatesOnRegister = templates;
|
|
113
|
-
this.hasControlTemplatesOnRegister = true;
|
|
114
|
-
return this;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
113
|
register(): Application<Node> {
|
|
118
114
|
const app = createApplication(this.buildPageFn);
|
|
119
|
-
app.useControlTemplates(this.hasControlTemplatesOnRegister ? this.controlTemplatesOnRegister : null);
|
|
120
115
|
if (this.useSystemThemeOnRegister) {
|
|
121
116
|
app.useSystemTheme();
|
|
122
117
|
return app;
|
|
@@ -208,18 +203,6 @@ export class Application<TPage> {
|
|
|
208
203
|
return activeTheme.value;
|
|
209
204
|
}
|
|
210
205
|
|
|
211
|
-
useControlTemplates(templates: ControlTemplateSet | null): ControlTemplateSet | null {
|
|
212
|
-
return useControlTemplates(templates);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
clearControlTemplates(): void {
|
|
216
|
-
clearControlTemplates();
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
getControlTemplates(): ControlTemplateSet | null {
|
|
220
|
-
return getControlTemplates();
|
|
221
|
-
}
|
|
222
|
-
|
|
223
206
|
static mount(root: Node): Node {
|
|
224
207
|
const previousShell = mountedShell;
|
|
225
208
|
if (previousShell !== null) {
|
|
@@ -252,6 +235,10 @@ export class Application<TPage> {
|
|
|
252
235
|
fui_set_application_caption(bytes.length > 0 ? bytes.dataStart : 0, <u32>bytes.length);
|
|
253
236
|
}
|
|
254
237
|
|
|
238
|
+
static pageZoom(value: PageZoomMode): void {
|
|
239
|
+
fui_set_page_zoom_enabled(value === PageZoomMode.Enabled);
|
|
240
|
+
}
|
|
241
|
+
|
|
255
242
|
static unmount(): void {
|
|
256
243
|
const previous = mountedShell;
|
|
257
244
|
if (previous === null) {
|
package/src/core/Bitmap.ts
CHANGED
|
@@ -147,20 +147,20 @@ export class Bitmap implements Disposable {
|
|
|
147
147
|
*
|
|
148
148
|
* @param node A built Text or RichText node sized to match this bitmap's dimensions.
|
|
149
149
|
*/
|
|
150
|
-
render(node: Node, x: f32 = 0.0, y: f32 = 0.0, scale: f32 = 1.0):
|
|
150
|
+
render(node: Node, x: f32 = 0.0, y: f32 = 0.0, scale: f32 = 1.0): bool {
|
|
151
151
|
this.ensureNotDisposed("Bitmap.render");
|
|
152
152
|
const handle = node.builtHandle;
|
|
153
|
-
if (handle == <u64>0) return;
|
|
154
|
-
ui.renderNodeToRgba(handle, this.width, this.height, this.pixelPtr(), <u32>this.pixelBytes.length, scale, x, y);
|
|
153
|
+
if (handle == <u64>0) return false;
|
|
154
|
+
return ui.renderNodeToRgba(handle, this.width, this.height, this.pixelPtr(), <u32>this.pixelBytes.length, scale, x, y) != 0;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
renderTextLayout(layout: TextLayout, x: f32 = 0.0, y: f32 = 0.0, scale: f32 = 1.0):
|
|
157
|
+
renderTextLayout(layout: TextLayout, x: f32 = 0.0, y: f32 = 0.0, scale: f32 = 1.0): bool {
|
|
158
158
|
this.ensureNotDisposed("Bitmap.renderTextLayout");
|
|
159
159
|
if (!layout.isReady) {
|
|
160
160
|
error("TextLayout", "Bitmap.renderTextLayout() called before the TextLayout was ready; register onReady/onReadyWith and render after the callback.");
|
|
161
|
-
return;
|
|
161
|
+
return false;
|
|
162
162
|
}
|
|
163
|
-
this.render(layout._drawNode(), x, y, scale);
|
|
163
|
+
return this.render(layout._drawNode(), x, y, scale);
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
/** Queue text shaping for a built text node before the next CommitFrame. */
|
package/src/core/DebugApi.ts
CHANGED
package/src/core/EventRouter.ts
CHANGED
|
@@ -95,6 +95,11 @@ export class EventRouter {
|
|
|
95
95
|
width: f32 = 0.0,
|
|
96
96
|
height: f32 = 0.0,
|
|
97
97
|
clickCount: i32 = 0,
|
|
98
|
+
isPrimary: bool = true,
|
|
99
|
+
tangentialPressure: f32 = 0.0,
|
|
100
|
+
tiltX: f32 = 0.0,
|
|
101
|
+
tiltY: f32 = 0.0,
|
|
102
|
+
twist: f32 = 0.0,
|
|
98
103
|
): bool {
|
|
99
104
|
showKeyboardFocusForPointerEvent(eventType);
|
|
100
105
|
SelectionHandleAdornerManager.recordPointerEvent(eventType, pointerType);
|
|
@@ -145,6 +150,11 @@ export class EventRouter {
|
|
|
145
150
|
width,
|
|
146
151
|
height,
|
|
147
152
|
clickCount,
|
|
153
|
+
isPrimary,
|
|
154
|
+
tangentialPressure,
|
|
155
|
+
tiltX,
|
|
156
|
+
tiltY,
|
|
157
|
+
twist,
|
|
148
158
|
);
|
|
149
159
|
Node._dispatchPointerEventWithArgs(capturedNode, event);
|
|
150
160
|
this.bubblePointerEvent(changetype<Node>(capturedNode).parentNode, event);
|
|
@@ -174,6 +184,11 @@ export class EventRouter {
|
|
|
174
184
|
width,
|
|
175
185
|
height,
|
|
176
186
|
clickCount,
|
|
187
|
+
isPrimary,
|
|
188
|
+
tangentialPressure,
|
|
189
|
+
tiltX,
|
|
190
|
+
tiltY,
|
|
191
|
+
twist,
|
|
177
192
|
);
|
|
178
193
|
if (SelectionHandleAdornerManager.routeActiveHandleDragEvent(handleDragEvent)) {
|
|
179
194
|
DragDropManager.handlePointerEvent(pointedNode, eventType, x, y, modifiers);
|
|
@@ -218,6 +233,11 @@ export class EventRouter {
|
|
|
218
233
|
width,
|
|
219
234
|
height,
|
|
220
235
|
clickCount,
|
|
236
|
+
isPrimary,
|
|
237
|
+
tangentialPressure,
|
|
238
|
+
tiltX,
|
|
239
|
+
tiltY,
|
|
240
|
+
twist,
|
|
221
241
|
);
|
|
222
242
|
Node._dispatchPointerEventWithArgs(node, event);
|
|
223
243
|
this.bubblePointerEvent(changetype<Node>(node).parentNode, event);
|
|
@@ -571,6 +591,37 @@ export class EventRouter {
|
|
|
571
591
|
}
|
|
572
592
|
}
|
|
573
593
|
|
|
594
|
+
static deactivateSubtree(root: Node): void {
|
|
595
|
+
const captured = this.resolveNode(this.capturedPointerHandle);
|
|
596
|
+
if (captured !== null && this.belongsToSubtree(changetype<Node>(captured), root)) {
|
|
597
|
+
this.capturedPointerHandle = <u64>HandleValue.Invalid;
|
|
598
|
+
}
|
|
599
|
+
if (this.focusedNode !== null && this.belongsToSubtree(changetype<Node>(this.focusedNode), root)) {
|
|
600
|
+
this.focusedNode = null;
|
|
601
|
+
ui.requestFocus(<u64>HandleValue.Invalid);
|
|
602
|
+
}
|
|
603
|
+
for (let index = this.hoverStack.length - 1; index >= 0; --index) {
|
|
604
|
+
if (this.belongsToSubtree(unchecked(this.hoverStack[index]), root)) this.removeHoverAt(index);
|
|
605
|
+
}
|
|
606
|
+
this.selectionCursorHandle = <u64>HandleValue.Invalid;
|
|
607
|
+
DragDropManager.reset();
|
|
608
|
+
ExternalDropManager.reset();
|
|
609
|
+
ContextMenuManager.hideActiveMenu();
|
|
610
|
+
ToolTipManager.clear();
|
|
611
|
+
SelectionHandleAdornerManager.clear();
|
|
612
|
+
MobileTextSelectionToolbarManager.clear();
|
|
613
|
+
this.applyCurrentCursor();
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
private static belongsToSubtree(node: Node, root: Node): bool {
|
|
617
|
+
let current: Node | null = node;
|
|
618
|
+
while (current !== null) {
|
|
619
|
+
if (current === root) return true;
|
|
620
|
+
current = changetype<Node>(current).parentNode;
|
|
621
|
+
}
|
|
622
|
+
return false;
|
|
623
|
+
}
|
|
624
|
+
|
|
574
625
|
static reset(): void {
|
|
575
626
|
this.nodes.clear();
|
|
576
627
|
this.generations.clear();
|
package/src/core/Node.ts
CHANGED
|
@@ -239,6 +239,11 @@ export class PointerEventArgs {
|
|
|
239
239
|
readonly width: f32;
|
|
240
240
|
readonly height: f32;
|
|
241
241
|
readonly clickCount: i32;
|
|
242
|
+
readonly isPrimary: bool;
|
|
243
|
+
readonly tangentialPressure: f32;
|
|
244
|
+
readonly tiltX: f32;
|
|
245
|
+
readonly tiltY: f32;
|
|
246
|
+
readonly twist: f32;
|
|
242
247
|
x: f32;
|
|
243
248
|
y: f32;
|
|
244
249
|
handled: bool = false;
|
|
@@ -256,6 +261,11 @@ export class PointerEventArgs {
|
|
|
256
261
|
width: f32 = 0.0,
|
|
257
262
|
height: f32 = 0.0,
|
|
258
263
|
clickCount: i32 = 0,
|
|
264
|
+
isPrimary: bool = true,
|
|
265
|
+
tangentialPressure: f32 = 0.0,
|
|
266
|
+
tiltX: f32 = 0.0,
|
|
267
|
+
tiltY: f32 = 0.0,
|
|
268
|
+
twist: f32 = 0.0,
|
|
259
269
|
) {
|
|
260
270
|
this.eventType = eventType;
|
|
261
271
|
this.sceneX = sceneX;
|
|
@@ -271,6 +281,11 @@ export class PointerEventArgs {
|
|
|
271
281
|
this.width = width;
|
|
272
282
|
this.height = height;
|
|
273
283
|
this.clickCount = clickCount;
|
|
284
|
+
this.isPrimary = isPrimary;
|
|
285
|
+
this.tangentialPressure = tangentialPressure;
|
|
286
|
+
this.tiltX = tiltX;
|
|
287
|
+
this.tiltY = tiltY;
|
|
288
|
+
this.twist = twist;
|
|
274
289
|
}
|
|
275
290
|
}
|
|
276
291
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Disposable, disposeAll } from "./Disposable";
|
|
2
|
+
import { EventRouter } from "./EventRouter";
|
|
3
|
+
import { Node } from "./Node";
|
|
4
|
+
|
|
5
|
+
export type RetainedViewLifecycleHandler = (view: RetainedView) => void;
|
|
6
|
+
|
|
7
|
+
export class RetainedView implements Disposable {
|
|
8
|
+
private readonly retainedResources: Array<Disposable> = new Array<Disposable>();
|
|
9
|
+
private readonly activationHandlers: Array<RetainedViewLifecycleHandler> = new Array<RetainedViewLifecycleHandler>();
|
|
10
|
+
private readonly deactivationHandlers: Array<RetainedViewLifecycleHandler> = new Array<RetainedViewLifecycleHandler>();
|
|
11
|
+
private readonly disposalHandlers: Array<RetainedViewLifecycleHandler> = new Array<RetainedViewLifecycleHandler>();
|
|
12
|
+
private activeValue: bool = false;
|
|
13
|
+
private disposedValue: bool = false;
|
|
14
|
+
|
|
15
|
+
constructor(readonly root: Node) {}
|
|
16
|
+
|
|
17
|
+
retain(resource: Disposable): this {
|
|
18
|
+
if (this.disposedValue) {
|
|
19
|
+
resource.dispose();
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
this.retainedResources.push(resource);
|
|
23
|
+
return this;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
onActivate(handler: RetainedViewLifecycleHandler): this {
|
|
27
|
+
this.activationHandlers.push(handler);
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
onDeactivate(handler: RetainedViewLifecycleHandler): this {
|
|
32
|
+
this.deactivationHandlers.push(handler);
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
onDispose(handler: RetainedViewLifecycleHandler): this {
|
|
37
|
+
this.disposalHandlers.push(handler);
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get isActive(): bool { return this.activeValue; }
|
|
42
|
+
get isDisposed(): bool { return this.disposedValue; }
|
|
43
|
+
|
|
44
|
+
activate(): void {
|
|
45
|
+
if (this.disposedValue || this.activeValue) return;
|
|
46
|
+
this.activeValue = true;
|
|
47
|
+
for (let i = 0; i < this.activationHandlers.length; ++i) unchecked(this.activationHandlers[i])(this);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
deactivate(): void {
|
|
51
|
+
if (this.disposedValue || !this.activeValue) return;
|
|
52
|
+
EventRouter.deactivateSubtree(this.root);
|
|
53
|
+
this.activeValue = false;
|
|
54
|
+
for (let i = 0; i < this.deactivationHandlers.length; ++i) unchecked(this.deactivationHandlers[i])(this);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
dispose(): void {
|
|
58
|
+
if (this.disposedValue) return;
|
|
59
|
+
this.deactivate();
|
|
60
|
+
this.disposedValue = true;
|
|
61
|
+
for (let i = 0; i < this.disposalHandlers.length; ++i) unchecked(this.disposalHandlers[i])(this);
|
|
62
|
+
disposeAll(this.retainedResources);
|
|
63
|
+
this.root.dispose();
|
|
64
|
+
this.activationHandlers.length = 0;
|
|
65
|
+
this.deactivationHandlers.length = 0;
|
|
66
|
+
this.disposalHandlers.length = 0;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function retainedView(root: Node): RetainedView {
|
|
71
|
+
return new RetainedView(root);
|
|
72
|
+
}
|
|
@@ -28,7 +28,7 @@ import { handleRouteChanged } from "./Navigation";
|
|
|
28
28
|
import { handleSystemAccentColorChanged, handleSystemDarkModeChanged } from "./Theme";
|
|
29
29
|
import { handleTimer } from "./Timers";
|
|
30
30
|
import { handleWorkerComplete, handleWorkerError, handleWorkerProgress } from "./Worker";
|
|
31
|
-
import { tickAnimations } from "./Animation";
|
|
31
|
+
import { getAnimationManager, tickAnimations } from "./Animation";
|
|
32
32
|
import { ExternalDragEventType } from "./ExternalDropManager";
|
|
33
33
|
import {
|
|
34
34
|
describeHandle,
|
|
@@ -74,6 +74,11 @@ export function __fui_on_pointer_event_with_metadata(
|
|
|
74
74
|
width: f32,
|
|
75
75
|
height: f32,
|
|
76
76
|
clickCount: i32,
|
|
77
|
+
isPrimary: bool,
|
|
78
|
+
tangentialPressure: f32,
|
|
79
|
+
tiltX: f32,
|
|
80
|
+
tiltY: f32,
|
|
81
|
+
twist: f32,
|
|
77
82
|
): bool {
|
|
78
83
|
log(
|
|
79
84
|
"Event",
|
|
@@ -106,6 +111,11 @@ export function __fui_on_pointer_event_with_metadata(
|
|
|
106
111
|
width,
|
|
107
112
|
height,
|
|
108
113
|
clickCount,
|
|
114
|
+
isPrimary,
|
|
115
|
+
tangentialPressure,
|
|
116
|
+
tiltX,
|
|
117
|
+
tiltY,
|
|
118
|
+
twist,
|
|
109
119
|
);
|
|
110
120
|
}
|
|
111
121
|
|
|
@@ -191,6 +201,10 @@ export function __fui_on_frame(timestampMs: f64): void {
|
|
|
191
201
|
tickAnimations(timestampMs);
|
|
192
202
|
}
|
|
193
203
|
|
|
204
|
+
export function __fui_needs_animation_frame(): bool {
|
|
205
|
+
return getAnimationManager().hasActiveAnimations();
|
|
206
|
+
}
|
|
207
|
+
|
|
194
208
|
export function __fui_on_timer(timerId: u32): void {
|
|
195
209
|
log("Event", "timer fired id=" + timerId.toString());
|
|
196
210
|
handleTimer(timerId);
|
|
@@ -18,6 +18,9 @@ export declare function get_device_pixel_ratio(): f32;
|
|
|
18
18
|
@external("fui_host", "fui_set_application_caption")
|
|
19
19
|
export declare function fui_set_application_caption(captionPtr: usize, captionLen: u32): void;
|
|
20
20
|
|
|
21
|
+
@external("fui_host", "fui_set_page_zoom_enabled")
|
|
22
|
+
export declare function fui_set_page_zoom_enabled(enabled: bool): void;
|
|
23
|
+
|
|
21
24
|
@external("fui_host", "fui_set_pointer_capture")
|
|
22
25
|
export declare function fui_set_pointer_capture(handle: u64): void;
|
|
23
26
|
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { ButtonTemplate } from "./internal/ButtonPresenter";
|
|
2
|
-
import { CheckboxIndicatorTemplate } from "./internal/CheckboxIndicatorPresenter";
|
|
3
|
-
import { RadioIndicatorTemplate } from "./internal/RadioIndicatorPresenter";
|
|
4
|
-
import { SwitchIndicatorTemplate } from "./internal/SwitchIndicatorPresenter";
|
|
5
|
-
import { SliderTemplate } from "./internal/SliderPresenter";
|
|
6
|
-
import { DropdownFieldTemplate } from "./internal/DropdownFieldPresenter";
|
|
7
|
-
import { DropdownChevronTemplate } from "./internal/DropdownChevronPresenter";
|
|
8
|
-
import { DropdownOptionRowTemplate } from "./internal/DropdownOptionRowPresenter";
|
|
9
|
-
import { TextInputTemplate } from "./internal/TextInputPresenter";
|
|
10
|
-
|
|
11
|
-
export class ControlTemplateSet {
|
|
12
|
-
button: ButtonTemplate | null = null;
|
|
13
|
-
checkboxIndicator: CheckboxIndicatorTemplate | null = null;
|
|
14
|
-
radioIndicator: RadioIndicatorTemplate | null = null;
|
|
15
|
-
switchIndicator: SwitchIndicatorTemplate | null = null;
|
|
16
|
-
slider: SliderTemplate | null = null;
|
|
17
|
-
dropdownField: DropdownFieldTemplate | null = null;
|
|
18
|
-
dropdownChevron: DropdownChevronTemplate | null = null;
|
|
19
|
-
dropdownOptionRow: DropdownOptionRowTemplate | null = null;
|
|
20
|
-
textInput: TextInputTemplate | null = null;
|
|
21
|
-
textArea: TextInputTemplate | null = null;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
let activeControlTemplatesValue: ControlTemplateSet | null = null;
|
|
25
|
-
|
|
26
|
-
export function getControlTemplates(): ControlTemplateSet | null {
|
|
27
|
-
return activeControlTemplatesValue;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function useControlTemplates(templates: ControlTemplateSet | null): ControlTemplateSet | null {
|
|
31
|
-
activeControlTemplatesValue = templates;
|
|
32
|
-
return activeControlTemplatesValue;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function clearControlTemplates(): void {
|
|
36
|
-
activeControlTemplatesValue = null;
|
|
37
|
-
}
|