drawnui-react 0.1.0-preview.2 → 0.1.0-preview.21
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/README.md +17 -4
- package/SKIPPED.md +33 -27
- package/dist/controls/AnimatedFramesRenderer.d.ts +7 -0
- package/dist/controls/AnimatedFramesRenderer.js +16 -1
- package/dist/controls/DrawnGame.d.ts +40 -0
- package/dist/controls/DrawnGame.js +64 -0
- package/dist/controls/GridStructure.d.ts +16 -2
- package/dist/controls/GridStructure.js +40 -4
- package/dist/controls/SkiaBackdrop.d.ts +8 -2
- package/dist/controls/SkiaBackdrop.js +48 -8
- package/dist/controls/SkiaButton.d.ts +19 -2
- package/dist/controls/SkiaButton.js +41 -11
- package/dist/controls/SkiaCarousel.d.ts +9 -1
- package/dist/controls/SkiaCarousel.js +16 -3
- package/dist/controls/SkiaCheckbox.d.ts +0 -1
- package/dist/controls/SkiaCheckbox.js +0 -6
- package/dist/controls/SkiaDrawer.d.ts +2 -0
- package/dist/controls/SkiaDrawer.js +2 -0
- package/dist/controls/SkiaEditor.d.ts +12 -1
- package/dist/controls/SkiaEditor.js +46 -19
- package/dist/controls/SkiaGif.d.ts +3 -0
- package/dist/controls/SkiaGif.js +12 -2
- package/dist/controls/SkiaImage.js +15 -10
- package/dist/controls/SkiaLabel.d.ts +54 -3
- package/dist/controls/SkiaLabel.js +235 -52
- package/dist/controls/SkiaLabelFps.d.ts +16 -0
- package/dist/controls/SkiaLabelFps.js +41 -0
- package/dist/controls/SkiaLayout.d.ts +64 -7
- package/dist/controls/SkiaLayout.js +316 -19
- package/dist/controls/SkiaLottie.js +4 -2
- package/dist/controls/SkiaProgress.d.ts +2 -1
- package/dist/controls/SkiaProgress.js +6 -3
- package/dist/controls/SkiaRadioButton.js +4 -3
- package/dist/controls/SkiaScroll.d.ts +35 -5
- package/dist/controls/SkiaScroll.js +233 -23
- package/dist/controls/SkiaScrollBar.d.ts +27 -0
- package/dist/controls/SkiaScrollBar.js +51 -2
- package/dist/controls/SkiaShaderCarousel.d.ts +5 -0
- package/dist/controls/SkiaShaderCarousel.js +5 -0
- package/dist/controls/SkiaShape.d.ts +13 -2
- package/dist/controls/SkiaShape.js +50 -10
- package/dist/controls/SkiaSlider.d.ts +20 -2
- package/dist/controls/SkiaSlider.js +59 -5
- package/dist/controls/SkiaSprite.d.ts +1 -0
- package/dist/controls/SkiaSprite.js +4 -0
- package/dist/controls/SkiaSvg.js +1 -1
- package/dist/controls/SkiaSwitch.d.ts +0 -1
- package/dist/controls/SkiaSwitch.js +0 -6
- package/dist/controls/SkiaToggle.d.ts +25 -5
- package/dist/controls/SkiaToggle.js +58 -5
- package/dist/controls/SnappingLayout.d.ts +12 -1
- package/dist/controls/SnappingLayout.js +19 -1
- package/dist/core/Accessibility.d.ts +80 -0
- package/dist/core/Accessibility.js +365 -8
- package/dist/core/Animators.d.ts +37 -1
- package/dist/core/Animators.js +102 -9
- package/dist/core/Canvas.d.ts +108 -7
- package/dist/core/Canvas.js +363 -35
- package/dist/core/Easing.d.ts +12 -1
- package/dist/core/Easing.js +25 -1
- package/dist/core/Gestures.d.ts +48 -2
- package/dist/core/Gestures.js +95 -3
- package/dist/core/Hover.d.ts +47 -0
- package/dist/core/Hover.js +134 -0
- package/dist/core/KeyboardManager.d.ts +13 -0
- package/dist/core/KeyboardManager.js +18 -0
- package/dist/core/ScrollAnimators.js +9 -0
- package/dist/core/SkiaControl.d.ts +216 -7
- package/dist/core/SkiaControl.js +516 -59
- package/dist/core/SkiaEffect.d.ts +17 -0
- package/dist/core/SkiaEffect.js +43 -8
- package/dist/core/SkiaImageManager.js +1 -1
- package/dist/core/Styles.d.ts +51 -0
- package/dist/core/Styles.js +92 -0
- package/dist/core/Super.d.ts +17 -1
- package/dist/core/Super.js +35 -4
- package/dist/core/ViewsAdapter.d.ts +7 -2
- package/dist/core/ViewsAdapter.js +10 -3
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/react/SkiaShell.js +33 -14
- package/dist/react/index.d.ts +15 -4
- package/dist/react/index.js +102 -15
- package/dist/react/reconciler.js +34 -12
- package/dist/vite/index.d.ts +9 -0
- package/dist/vite/index.js +36 -3
- package/package.json +33 -3
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { InputKey } from "./KeyboardManager";
|
|
1
2
|
import type { SkiaControl } from "./SkiaControl";
|
|
2
3
|
import { SKRect } from "./Types";
|
|
3
4
|
/** ARIA role constants (DrawnUi.Models.Aria) for `AccessibilityRole`. */
|
|
@@ -38,10 +39,25 @@ export declare const Aria: {
|
|
|
38
39
|
readonly RoleRegion: "region";
|
|
39
40
|
readonly RoleNavigation: "navigation";
|
|
40
41
|
readonly RoleMain: "main";
|
|
42
|
+
readonly RoleGrid: "grid";
|
|
43
|
+
readonly RoleToolbar: "toolbar";
|
|
44
|
+
readonly RoleRadioGroup: "radiogroup";
|
|
45
|
+
readonly RoleMenuBar: "menubar";
|
|
41
46
|
/** Removes the control from the accessibility tree even when a default role would apply (inner label of a button). */
|
|
42
47
|
readonly RolePresentation: "presentation";
|
|
43
48
|
readonly LivePolite: "polite";
|
|
44
49
|
readonly LiveAssertive: "assertive";
|
|
50
|
+
/**
|
|
51
|
+
* Container roles whose items the arrow keys walk (C# Aria.IsCompositeRole): a layout with one of them is one Tab stop,
|
|
52
|
+
* its current item, and the arrow keys move between its items.
|
|
53
|
+
*/
|
|
54
|
+
readonly IsCompositeRole: (role?: string) => boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Roles a user operates (C# Aria.IsInteractiveRole, as DrawnUi.Rust is_control_role): a node with one of them that
|
|
57
|
+
* cannot take input is read as disabled (aria-disabled). C# also lists listbox and scrollbar; here a listbox is an
|
|
58
|
+
* arrow-key group and groups are never disabled.
|
|
59
|
+
*/
|
|
60
|
+
readonly IsInteractiveRole: (role?: string) => boolean;
|
|
45
61
|
};
|
|
46
62
|
/** One laid-out text line exposed for native selection (AccessibilityTextSelectable): CSS px relative to the node. */
|
|
47
63
|
export interface AccessibilityTextLine {
|
|
@@ -54,6 +70,17 @@ export interface AccessibilityTextLine {
|
|
|
54
70
|
FontWeight: number;
|
|
55
71
|
FontSize: number;
|
|
56
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* The value of a range control (a slider, a progress bar): what a screen reader reads and adjusts (drawnui-cross 6c,
|
|
75
|
+
* C# AccessibilityValue): aria-valuenow / min / max, and aria-valuetext when Text is not empty ("65%", "20 – 80").
|
|
76
|
+
*/
|
|
77
|
+
export interface AccessibilityValue {
|
|
78
|
+
Now: number;
|
|
79
|
+
Min: number;
|
|
80
|
+
Max: number;
|
|
81
|
+
Step: number;
|
|
82
|
+
Text: string;
|
|
83
|
+
}
|
|
57
84
|
/** One entry of the accessibility snapshot (DrawnUi AccessibilityNode). Rect is CSS pixels relative to the canvas. */
|
|
58
85
|
export interface AccessibilityNode {
|
|
59
86
|
Id: number;
|
|
@@ -62,9 +89,13 @@ export interface AccessibilityNode {
|
|
|
62
89
|
Role: string;
|
|
63
90
|
Rect: SKRect;
|
|
64
91
|
CanInteract: boolean;
|
|
92
|
+
/** A role a user operates (Aria.IsInteractiveRole) that cannot take input: aria-disabled, no tab stop. */
|
|
93
|
+
Disabled: boolean;
|
|
65
94
|
IsPressed?: boolean;
|
|
66
95
|
Live?: string;
|
|
67
96
|
Source: SkiaControl;
|
|
97
|
+
/** A range control's value (aria-valuenow / min / max / valuetext); its name stays the app's label. */
|
|
98
|
+
Value?: AccessibilityValue;
|
|
68
99
|
/** Real text lines rendered into the overlay so the browser can select / copy them (opt-in per control). */
|
|
69
100
|
TextLines?: AccessibilityTextLine[];
|
|
70
101
|
}
|
|
@@ -81,6 +112,7 @@ export declare class SkiaAccessibilityManager {
|
|
|
81
112
|
private pending;
|
|
82
113
|
private readonly changed;
|
|
83
114
|
private readonly liveUpdated;
|
|
115
|
+
private readonly keyboardFocusRequested;
|
|
84
116
|
/** Minimum milliseconds between snapshot rebuilds. */
|
|
85
117
|
MinUpdateIntervalMs: number;
|
|
86
118
|
Snapshot: AccessibilityNode[];
|
|
@@ -89,7 +121,54 @@ export declare class SkiaAccessibilityManager {
|
|
|
89
121
|
OnChanged(cb: () => void): () => void;
|
|
90
122
|
/** Fired immediately (bypassing the rate limit) when a live-region node's value changes. */
|
|
91
123
|
OnLiveRegionUpdated(cb: (node: SkiaControl) => void): () => void;
|
|
124
|
+
/**
|
|
125
|
+
* Keyboard focus moved by the manager itself (arrow keys in a group): the overlay moves DOM focus to that node's
|
|
126
|
+
* element (C# KeyboardFocusRequested). Returns the unsubscribe function.
|
|
127
|
+
*/
|
|
128
|
+
OnKeyboardFocusRequested(cb: (node: SkiaControl) => void): () => void;
|
|
92
129
|
NotifyFocused(node?: SkiaControl): void;
|
|
130
|
+
/**
|
|
131
|
+
* Enter / Space from keyboard navigation or a screen reader: activates the node only while a tap could reach it
|
|
132
|
+
* (AccessibilityCanInteract), the rule every head applies.
|
|
133
|
+
*/
|
|
134
|
+
static Activate(node?: SkiaControl): boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Arrow keys, Home / End, PageUp / PageDown for the node in keyboard focus: the control gets them first, only while a
|
|
137
|
+
* pan could reach it (a slider steps its value); keys it does not use move keyboard focus between the items of the
|
|
138
|
+
* group around it (MoveInGroup). True when the key was used.
|
|
139
|
+
*/
|
|
140
|
+
static Key(node: SkiaControl | undefined, key: InputKey): boolean;
|
|
141
|
+
/** Last focused node of each group: where Tab enters the group. */
|
|
142
|
+
private readonly groupFocus;
|
|
143
|
+
private groupRequest?;
|
|
144
|
+
/**
|
|
145
|
+
* The arrow-key group around `control`: its nearest ancestor with a composite role (Aria.IsCompositeRole, e.g.
|
|
146
|
+
* Aria.RoleList on a SkiaStack), and the item of that group holding the control (one of its children, a cell).
|
|
147
|
+
*/
|
|
148
|
+
static TryFindGroup(control: SkiaControl): {
|
|
149
|
+
Group: SkiaControl;
|
|
150
|
+
Item: SkiaControl;
|
|
151
|
+
} | undefined;
|
|
152
|
+
/** Remembers where keyboard focus is inside its group, so the next Tab into the group lands there. */
|
|
153
|
+
private NoteFocus;
|
|
154
|
+
/**
|
|
155
|
+
* Whether Tab / Shift+Tab stop on this node. A group of items is one Tab stop, like a native list: its current item
|
|
156
|
+
* (the one keyboard focus last had there, else the group's first usable node) plus the controls inside that same
|
|
157
|
+
* item; the arrow keys move between items. Every node outside a group is a stop.
|
|
158
|
+
*/
|
|
159
|
+
IsTabStop(node: SkiaControl): boolean;
|
|
160
|
+
/**
|
|
161
|
+
* Moves keyboard focus between the items of the group around `focused`, by item index like a native list: Down / Up in
|
|
162
|
+
* a Column, Right / Left in a Row, all four in a Wrap, a Grid or a Split layout (Up / Down by one row), Home / End to the
|
|
163
|
+
* first / last item, PageDown / PageUp by one viewport of the scroll around it. No wrap at the ends. The target is the
|
|
164
|
+
* item itself when it is an interactive node, else its first interactive node; items the pointer cannot use are
|
|
165
|
+
* skipped. An item not realized (recycled cells) is scrolled in with ScrollToIndex when the group is the scroll's
|
|
166
|
+
* content, and focused once drawn. True when the key belongs to the group.
|
|
167
|
+
*/
|
|
168
|
+
MoveInGroup(focused: SkiaControl, key: InputKey): boolean;
|
|
169
|
+
/** Frame end: focus the requested item once it is drawn, skipping items that cannot take input. */
|
|
170
|
+
private ProcessGroupFocus;
|
|
171
|
+
private FocusFromKeyboard;
|
|
93
172
|
Register(node: SkiaControl): void;
|
|
94
173
|
NotifyUpdated(node: SkiaControl): void;
|
|
95
174
|
ForceRebuildOnNextFrame(): void;
|
|
@@ -100,5 +179,6 @@ export declare class SkiaAccessibilityManager {
|
|
|
100
179
|
* per MinUpdateIntervalMs otherwise (keeps rects in sync with scrolling); notifies only when the snapshot differs.
|
|
101
180
|
*/
|
|
102
181
|
OnFrameEnd(scale: number, canvasWidthPx: number, canvasHeightPx: number, requestFrame: () => void): void;
|
|
182
|
+
private Rebuild;
|
|
103
183
|
private static Same;
|
|
104
184
|
}
|
|
@@ -12,11 +12,33 @@ export const Aria = {
|
|
|
12
12
|
RoleSeparator: "separator", RoleProgressBar: "progressbar", RoleTooltip: "tooltip", RoleDialog: "dialog",
|
|
13
13
|
RoleAlertDialog: "alertdialog", RoleStatus: "status", RoleAlert: "alert", RoleGroup: "group", RoleRegion: "region",
|
|
14
14
|
RoleNavigation: "navigation", RoleMain: "main",
|
|
15
|
+
// containers of items: with one of these roles a layout is an arrow-key group (IsCompositeRole)
|
|
16
|
+
RoleGrid: "grid", RoleToolbar: "toolbar", RoleRadioGroup: "radiogroup", RoleMenuBar: "menubar",
|
|
15
17
|
/** Removes the control from the accessibility tree even when a default role would apply (inner label of a button). */
|
|
16
18
|
RolePresentation: "presentation",
|
|
17
19
|
// live regions
|
|
18
20
|
LivePolite: "polite", LiveAssertive: "assertive",
|
|
21
|
+
/**
|
|
22
|
+
* Container roles whose items the arrow keys walk (C# Aria.IsCompositeRole): a layout with one of them is one Tab stop,
|
|
23
|
+
* its current item, and the arrow keys move between its items.
|
|
24
|
+
*/
|
|
25
|
+
IsCompositeRole(role) {
|
|
26
|
+
return role === "list" || role === "listbox" || role === "grid" || role === "toolbar" || role === "radiogroup"
|
|
27
|
+
|| role === "tablist" || role === "menu" || role === "menubar";
|
|
28
|
+
},
|
|
29
|
+
/**
|
|
30
|
+
* Roles a user operates (C# Aria.IsInteractiveRole, as DrawnUi.Rust is_control_role): a node with one of them that
|
|
31
|
+
* cannot take input is read as disabled (aria-disabled). C# also lists listbox and scrollbar; here a listbox is an
|
|
32
|
+
* arrow-key group and groups are never disabled.
|
|
33
|
+
*/
|
|
34
|
+
IsInteractiveRole(role) {
|
|
35
|
+
return role === "button" || role === "link" || role === "checkbox" || role === "radio" || role === "switch"
|
|
36
|
+
|| role === "slider" || role === "spinbutton" || role === "textbox" || role === "searchbox" || role === "combobox"
|
|
37
|
+
|| role === "option" || role === "tab" || role === "menuitem" || role === "menuitemcheckbox" || role === "menuitemradio";
|
|
38
|
+
},
|
|
19
39
|
};
|
|
40
|
+
const asLayout = (c) => ("ChildrenFactory" in c && "Split" in c ? c : undefined);
|
|
41
|
+
const asScroll = (c) => ("ScrollToIndex" in c && "ViewportOffsetY" in c ? c : undefined);
|
|
20
42
|
/**
|
|
21
43
|
* Mirrors DrawnUi SkiaAccessibilityManager: registry of accessible controls + a rate-limited snapshot
|
|
22
44
|
* (at most one rebuild per MinUpdateIntervalMs, taken at frame end) that the DOM overlay renders.
|
|
@@ -30,6 +52,7 @@ export class SkiaAccessibilityManager {
|
|
|
30
52
|
pending = 0;
|
|
31
53
|
changed = new Set();
|
|
32
54
|
liveUpdated = new Set();
|
|
55
|
+
keyboardFocusRequested = new Set();
|
|
33
56
|
/** Minimum milliseconds between snapshot rebuilds. */
|
|
34
57
|
MinUpdateIntervalMs = 1000;
|
|
35
58
|
Snapshot = [];
|
|
@@ -38,8 +61,194 @@ export class SkiaAccessibilityManager {
|
|
|
38
61
|
OnChanged(cb) { this.changed.add(cb); return () => this.changed.delete(cb); }
|
|
39
62
|
/** Fired immediately (bypassing the rate limit) when a live-region node's value changes. */
|
|
40
63
|
OnLiveRegionUpdated(cb) { this.liveUpdated.add(cb); return () => this.liveUpdated.delete(cb); }
|
|
41
|
-
|
|
42
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Keyboard focus moved by the manager itself (arrow keys in a group): the overlay moves DOM focus to that node's
|
|
66
|
+
* element (C# KeyboardFocusRequested). Returns the unsubscribe function.
|
|
67
|
+
*/
|
|
68
|
+
OnKeyboardFocusRequested(cb) { this.keyboardFocusRequested.add(cb); return () => this.keyboardFocusRequested.delete(cb); }
|
|
69
|
+
NotifyFocused(node) {
|
|
70
|
+
if (this.FocusedNode === node)
|
|
71
|
+
return;
|
|
72
|
+
this.NoteFocus(node);
|
|
73
|
+
this.FocusedNode = node;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Enter / Space from keyboard navigation or a screen reader: activates the node only while a tap could reach it
|
|
77
|
+
* (AccessibilityCanInteract), the rule every head applies.
|
|
78
|
+
*/
|
|
79
|
+
static Activate(node) {
|
|
80
|
+
if (!node || !node.AccessibilityCanInteract)
|
|
81
|
+
return false;
|
|
82
|
+
node.OnAccessibilityActivated();
|
|
83
|
+
node.Superview?.AccessibilityManager.ForceRebuildOnNextFrame(); // a screen reader reads the new state right back
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Arrow keys, Home / End, PageUp / PageDown for the node in keyboard focus: the control gets them first, only while a
|
|
88
|
+
* pan could reach it (a slider steps its value); keys it does not use move keyboard focus between the items of the
|
|
89
|
+
* group around it (MoveInGroup). True when the key was used.
|
|
90
|
+
*/
|
|
91
|
+
static Key(node, key) {
|
|
92
|
+
if (!node)
|
|
93
|
+
return false;
|
|
94
|
+
if (node.CanReceiveGesture("Panning") && node.OnAccessibilityKey(key)) {
|
|
95
|
+
node.Superview?.AccessibilityManager.ForceRebuildOnNextFrame(); // the new value is read right back, not a second later
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
return node.Superview?.AccessibilityManager.MoveInGroup(node, key) ?? false;
|
|
99
|
+
}
|
|
100
|
+
// ---- arrow-key groups (C# SkiaAccessibilityManager, "Arrow-key groups") ----
|
|
101
|
+
/** Last focused node of each group: where Tab enters the group. */
|
|
102
|
+
groupFocus = new WeakMap();
|
|
103
|
+
groupRequest;
|
|
104
|
+
/**
|
|
105
|
+
* The arrow-key group around `control`: its nearest ancestor with a composite role (Aria.IsCompositeRole, e.g.
|
|
106
|
+
* Aria.RoleList on a SkiaStack), and the item of that group holding the control (one of its children, a cell).
|
|
107
|
+
*/
|
|
108
|
+
static TryFindGroup(control) {
|
|
109
|
+
for (let child = control, parent = control.Parent; parent; child = parent, parent = parent.Parent) {
|
|
110
|
+
if (Aria.IsCompositeRole(parent.AccessibilityRole))
|
|
111
|
+
return { Group: parent, Item: child };
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
/** Remembers where keyboard focus is inside its group, so the next Tab into the group lands there. */
|
|
116
|
+
NoteFocus(node) {
|
|
117
|
+
const found = node && SkiaAccessibilityManager.TryFindGroup(node);
|
|
118
|
+
if (found)
|
|
119
|
+
this.groupFocus.set(found.Group, node);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Whether Tab / Shift+Tab stop on this node. A group of items is one Tab stop, like a native list: its current item
|
|
123
|
+
* (the one keyboard focus last had there, else the group's first usable node) plus the controls inside that same
|
|
124
|
+
* item; the arrow keys move between items. Every node outside a group is a stop.
|
|
125
|
+
*/
|
|
126
|
+
IsTabStop(node) {
|
|
127
|
+
const found = SkiaAccessibilityManager.TryFindGroup(node);
|
|
128
|
+
if (!found)
|
|
129
|
+
return true;
|
|
130
|
+
let stopItem;
|
|
131
|
+
const remembered = this.groupFocus.get(found.Group);
|
|
132
|
+
const current = remembered && !remembered.IsDisposed && remembered.AccessibilityCanInteract && this.Snapshot.some((n) => n.Source === remembered)
|
|
133
|
+
? SkiaAccessibilityManager.TryFindGroup(remembered) : undefined;
|
|
134
|
+
if (current && current.Group === found.Group)
|
|
135
|
+
stopItem = current.Item;
|
|
136
|
+
else {
|
|
137
|
+
for (const n of this.Snapshot) {
|
|
138
|
+
if (!n.Source.AccessibilityCanInteract)
|
|
139
|
+
continue;
|
|
140
|
+
const g = SkiaAccessibilityManager.TryFindGroup(n.Source);
|
|
141
|
+
if (g && g.Group === found.Group) {
|
|
142
|
+
stopItem = g.Item;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return !stopItem || stopItem === found.Item;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Moves keyboard focus between the items of the group around `focused`, by item index like a native list: Down / Up in
|
|
151
|
+
* a Column, Right / Left in a Row, all four in a Wrap, a Grid or a Split layout (Up / Down by one row), Home / End to the
|
|
152
|
+
* first / last item, PageDown / PageUp by one viewport of the scroll around it. No wrap at the ends. The target is the
|
|
153
|
+
* item itself when it is an interactive node, else its first interactive node; items the pointer cannot use are
|
|
154
|
+
* skipped. An item not realized (recycled cells) is scrolled in with ScrollToIndex when the group is the scroll's
|
|
155
|
+
* content, and focused once drawn. True when the key belongs to the group.
|
|
156
|
+
*/
|
|
157
|
+
MoveInGroup(focused, key) {
|
|
158
|
+
const found = SkiaAccessibilityManager.TryFindGroup(focused);
|
|
159
|
+
if (!found)
|
|
160
|
+
return false;
|
|
161
|
+
const { Group: group, Item: item } = found;
|
|
162
|
+
const scroll = FindScroll(group), axis = AxisOf(group), count = ItemCount(group);
|
|
163
|
+
const current = this.groupRequest?.Group === group ? this.groupRequest.Index : IndexOf(group, item);
|
|
164
|
+
if (current < 0 || count < 1)
|
|
165
|
+
return false;
|
|
166
|
+
const row = axis === "Both" ? RowLength(group) : 1;
|
|
167
|
+
const vertical = axis !== "Horizontal";
|
|
168
|
+
const page = () => PageSize(scroll, item, vertical) * row;
|
|
169
|
+
let target, step;
|
|
170
|
+
if ((key === "ArrowDown" && axis === "Vertical") || (key === "ArrowRight" && axis !== "Vertical")) {
|
|
171
|
+
target = current + 1;
|
|
172
|
+
step = 1;
|
|
173
|
+
}
|
|
174
|
+
else if ((key === "ArrowUp" && axis === "Vertical") || (key === "ArrowLeft" && axis !== "Vertical")) {
|
|
175
|
+
target = current - 1;
|
|
176
|
+
step = -1;
|
|
177
|
+
}
|
|
178
|
+
else if (key === "ArrowDown" && axis === "Both") {
|
|
179
|
+
target = Math.min(count - 1, current + row);
|
|
180
|
+
step = 1;
|
|
181
|
+
}
|
|
182
|
+
else if (key === "ArrowUp" && axis === "Both") {
|
|
183
|
+
target = Math.max(0, current - row);
|
|
184
|
+
step = -1;
|
|
185
|
+
}
|
|
186
|
+
else if (key === "Home") {
|
|
187
|
+
target = 0;
|
|
188
|
+
step = 1;
|
|
189
|
+
}
|
|
190
|
+
else if (key === "End") {
|
|
191
|
+
target = count - 1;
|
|
192
|
+
step = -1;
|
|
193
|
+
}
|
|
194
|
+
else if (key === "PageDown" && scroll) {
|
|
195
|
+
target = Math.min(count - 1, current + page());
|
|
196
|
+
step = 1;
|
|
197
|
+
}
|
|
198
|
+
else if (key === "PageUp" && scroll) {
|
|
199
|
+
target = Math.max(0, current - page());
|
|
200
|
+
step = -1;
|
|
201
|
+
}
|
|
202
|
+
else
|
|
203
|
+
return false;
|
|
204
|
+
if (target < 0 || target >= count || target === current)
|
|
205
|
+
return true; // at the end of the group nothing happens, the key still belongs to it
|
|
206
|
+
this.groupRequest = { Group: group, Scroll: scroll, Index: target, Step: step, Deadline: performance.now() + 3000, ScrollIssued: false };
|
|
207
|
+
group.Superview?.Update(); // resolved at the end of the next frame, together with the snapshot
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
/** Frame end: focus the requested item once it is drawn, skipping items that cannot take input. */
|
|
211
|
+
ProcessGroupFocus(scale, w, h, requestFrame) {
|
|
212
|
+
const request = this.groupRequest;
|
|
213
|
+
if (!request)
|
|
214
|
+
return;
|
|
215
|
+
const cell = ItemAt(request.Group, request.Index);
|
|
216
|
+
if (cell && IsDrawn(cell)) {
|
|
217
|
+
const node = FindTarget(cell);
|
|
218
|
+
if (node) {
|
|
219
|
+
this.groupRequest = undefined;
|
|
220
|
+
this.FocusFromKeyboard(node, scale, w, h);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
// this item cannot take input: go on to the next one in the same direction
|
|
224
|
+
request.Index += request.Step;
|
|
225
|
+
if (request.Index < 0 || request.Index >= ItemCount(request.Group)) {
|
|
226
|
+
this.groupRequest = undefined;
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
request.Deadline = performance.now() + 3000;
|
|
230
|
+
request.ScrollIssued = false;
|
|
231
|
+
}
|
|
232
|
+
else if (performance.now() > request.Deadline || request.Group.IsDisposed) {
|
|
233
|
+
this.groupRequest = undefined;
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (!request.ScrollIssued) {
|
|
237
|
+
request.ScrollIssued = true;
|
|
238
|
+
const next = ItemAt(request.Group, request.Index);
|
|
239
|
+
if ((!next || !IsDrawn(next)) && request.Scroll && request.Scroll.Content === request.Group) {
|
|
240
|
+
request.Scroll.ScrollToIndex(request.Index, true, request.Step > 0 ? "End" : "Start");
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
requestFrame(); // keep frames coming until the item is drawn
|
|
244
|
+
}
|
|
245
|
+
FocusFromKeyboard(node, scale, w, h) {
|
|
246
|
+
this.Rebuild(scale, w, h); // the overlay looks the node up in the snapshot: make sure it is there now
|
|
247
|
+
this.NoteFocus(node);
|
|
248
|
+
this.FocusedNode = node;
|
|
249
|
+
for (const cb of this.keyboardFocusRequested)
|
|
250
|
+
cb(node);
|
|
251
|
+
}
|
|
43
252
|
Register(node) { this.nodes.add(node); this.dirty = true; }
|
|
44
253
|
NotifyUpdated(node) {
|
|
45
254
|
if (!this.nodes.has(node))
|
|
@@ -49,7 +258,7 @@ export class SkiaAccessibilityManager {
|
|
|
49
258
|
for (const cb of this.liveUpdated)
|
|
50
259
|
cb(node);
|
|
51
260
|
}
|
|
52
|
-
ForceRebuildOnNextFrame() { this.lastRebuild =
|
|
261
|
+
ForceRebuildOnNextFrame() { this.lastRebuild = -Infinity; this.dirty = true; }
|
|
53
262
|
Unregister(node) {
|
|
54
263
|
if (this.nodes.delete(node)) {
|
|
55
264
|
node.OnAccessibilityUnregistered();
|
|
@@ -70,6 +279,7 @@ export class SkiaAccessibilityManager {
|
|
|
70
279
|
* per MinUpdateIntervalMs otherwise (keeps rects in sync with scrolling); notifies only when the snapshot differs.
|
|
71
280
|
*/
|
|
72
281
|
OnFrameEnd(scale, canvasWidthPx, canvasHeightPx, requestFrame) {
|
|
282
|
+
this.ProcessGroupFocus(scale, canvasWidthPx, canvasHeightPx, requestFrame);
|
|
73
283
|
if (this.nodes.size === 0 && this.Snapshot.length === 0)
|
|
74
284
|
return;
|
|
75
285
|
const now = performance.now();
|
|
@@ -80,9 +290,12 @@ export class SkiaAccessibilityManager {
|
|
|
80
290
|
this.pending = window.setTimeout(() => { this.pending = 0; requestFrame(); }, wait);
|
|
81
291
|
return;
|
|
82
292
|
}
|
|
293
|
+
this.Rebuild(scale, canvasWidthPx, canvasHeightPx);
|
|
294
|
+
}
|
|
295
|
+
Rebuild(scale, canvasWidthPx, canvasHeightPx) {
|
|
83
296
|
this.dirty = false;
|
|
84
|
-
this.lastRebuild = now;
|
|
85
|
-
|
|
297
|
+
this.lastRebuild = performance.now();
|
|
298
|
+
let list = [];
|
|
86
299
|
for (const n of this.nodes) {
|
|
87
300
|
if (!n.Superview) {
|
|
88
301
|
this.nodes.delete(n);
|
|
@@ -101,13 +314,30 @@ export class SkiaAccessibilityManager {
|
|
|
101
314
|
if (px.Right < -canvasWidthPx || px.Bottom < -canvasHeightPx || px.Left > 2 * canvasWidthPx || px.Top > 2 * canvasHeightPx)
|
|
102
315
|
continue;
|
|
103
316
|
list.push({
|
|
104
|
-
Id: n.AccessibilityId, Label: n.AccessibilityLabel, Hint: n.AccessibilityHint, Role: n.AccessibilityRole,
|
|
317
|
+
Id: n.AccessibilityId, Label: n.AccessibilityLabel, Hint: n.AccessibilityHint, Role: n.AccessibilityRole, Value: n.AccessibilityValue,
|
|
105
318
|
Rect: new SKRect(px.Left / scale, px.Top / scale, px.Right / scale, px.Bottom / scale),
|
|
106
319
|
CanInteract: n.AccessibilityCanInteract, IsPressed: n.AccessibilityIsPressed, Live: n.AccessibilityLive, Source: n,
|
|
320
|
+
Disabled: !n.AccessibilityCanInteract && Aria.IsInteractiveRole(n.AccessibilityRole),
|
|
107
321
|
TextLines: n.AccessibilityTextSelectable ? n.GetAccessibilityTextLines(scale) : undefined,
|
|
108
322
|
});
|
|
109
323
|
}
|
|
324
|
+
list = SayNamesOnce(list);
|
|
110
325
|
list.sort((a, b) => a.Rect.Top - b.Rect.Top || a.Rect.Left - b.Rect.Left);
|
|
326
|
+
// reading order: nodes whose tops are within half the smaller height form one row, read left to right, so a row of
|
|
327
|
+
// vertically centered controls of different heights keeps its visual order (C# RectComparer + LeftComparer)
|
|
328
|
+
for (let start = 0, i = 1; i <= list.length; i++) {
|
|
329
|
+
if (i < list.length) {
|
|
330
|
+
const first = list[start].Rect, next = list[i].Rect;
|
|
331
|
+
if (next.Top - first.Top < Math.min(first.Height, next.Height) / 2)
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (i - start > 1) {
|
|
335
|
+
const row = list.slice(start, i).sort((a, b) => a.Rect.Left - b.Rect.Left);
|
|
336
|
+
for (let k = 0; k < row.length; k++)
|
|
337
|
+
list[start + k] = row[k];
|
|
338
|
+
}
|
|
339
|
+
start = i;
|
|
340
|
+
}
|
|
111
341
|
if (SkiaAccessibilityManager.Same(list, this.Snapshot))
|
|
112
342
|
return;
|
|
113
343
|
this.Snapshot = list;
|
|
@@ -119,8 +349,8 @@ export class SkiaAccessibilityManager {
|
|
|
119
349
|
return false;
|
|
120
350
|
for (let i = 0; i < a.length; i++) {
|
|
121
351
|
const x = a[i], y = b[i];
|
|
122
|
-
if (x.Source !== y.Source || x.Label !== y.Label || x.Hint !== y.Hint || x.Role !== y.Role || x.CanInteract !== y.CanInteract
|
|
123
|
-
|| x.IsPressed !== y.IsPressed || x.Live !== y.Live
|
|
352
|
+
if (x.Source !== y.Source || x.Label !== y.Label || x.Hint !== y.Hint || x.Role !== y.Role || x.CanInteract !== y.CanInteract || x.Disabled !== y.Disabled
|
|
353
|
+
|| x.IsPressed !== y.IsPressed || x.Live !== y.Live || !SameValue(x.Value, y.Value)
|
|
124
354
|
|| Math.abs(x.Rect.Left - y.Rect.Left) > 0.5 || Math.abs(x.Rect.Top - y.Rect.Top) > 0.5
|
|
125
355
|
|| Math.abs(x.Rect.Right - y.Rect.Right) > 0.5 || Math.abs(x.Rect.Bottom - y.Rect.Bottom) > 0.5)
|
|
126
356
|
return false;
|
|
@@ -138,6 +368,133 @@ export class SkiaAccessibilityManager {
|
|
|
138
368
|
return true;
|
|
139
369
|
}
|
|
140
370
|
}
|
|
371
|
+
/**
|
|
372
|
+
* Each name is said once in the flat overlay (DrawnUi.Rust said_by_child): a text or a heading whose label repeats the
|
|
373
|
+
* label of the node it is in (its nearest ancestor node) is said by one of them. Under an interactive node (a card
|
|
374
|
+
* button) the child is left out, the button keeps its name; under any other node (a group card and its title) the
|
|
375
|
+
* node loses its name and the heading says it. Selectable text (TextLines) always stays.
|
|
376
|
+
*/
|
|
377
|
+
function SayNamesOnce(list) {
|
|
378
|
+
const bySource = new Map(list.map((x) => [x.Source, x]));
|
|
379
|
+
const said = new Set();
|
|
380
|
+
for (const x of list) {
|
|
381
|
+
if (!x.Label || x.TextLines || (x.Role !== Aria.RoleText && x.Role !== Aria.RoleHeading))
|
|
382
|
+
continue;
|
|
383
|
+
let owner;
|
|
384
|
+
for (let p = x.Source.Parent; p && !owner; p = p.Parent)
|
|
385
|
+
owner = bySource.get(p);
|
|
386
|
+
if (!owner || owner.Label !== x.Label)
|
|
387
|
+
continue;
|
|
388
|
+
if (owner.CanInteract)
|
|
389
|
+
said.add(x);
|
|
390
|
+
else
|
|
391
|
+
owner.Label = undefined;
|
|
392
|
+
}
|
|
393
|
+
return said.size ? list.filter((x) => !said.has(x)) : list;
|
|
394
|
+
}
|
|
395
|
+
function SameValue(a, b) {
|
|
396
|
+
return a === b || (!!a && !!b && a.Now === b.Now && a.Min === b.Min && a.Max === b.Max && a.Step === b.Step && a.Text === b.Text);
|
|
397
|
+
}
|
|
398
|
+
// ---- group helpers (duck-typed layouts and scrolls) ----
|
|
399
|
+
/** Children of a control (a layout's Views, a scroll's Content). */
|
|
400
|
+
function ViewsOf(c) {
|
|
401
|
+
if ("Views" in c)
|
|
402
|
+
return c.Views;
|
|
403
|
+
const content = c.Content;
|
|
404
|
+
return content ? [content] : [];
|
|
405
|
+
}
|
|
406
|
+
function FindScroll(group) {
|
|
407
|
+
for (let p = group.Parent; p; p = p.Parent) {
|
|
408
|
+
const s = asScroll(p);
|
|
409
|
+
if (s)
|
|
410
|
+
return s;
|
|
411
|
+
}
|
|
412
|
+
return undefined;
|
|
413
|
+
}
|
|
414
|
+
/** Down / Up in a Column, Right / Left in a Row, all four in a Wrap, a Grid or a Split layout. */
|
|
415
|
+
function AxisOf(group) {
|
|
416
|
+
const l = asLayout(group);
|
|
417
|
+
if (!l)
|
|
418
|
+
return "Vertical";
|
|
419
|
+
if (l.Type === "Column")
|
|
420
|
+
return l.Split <= 1 ? "Vertical" : "Both";
|
|
421
|
+
if (l.Type === "Row")
|
|
422
|
+
return "Horizontal";
|
|
423
|
+
if (l.Type === "Absolute")
|
|
424
|
+
return "Vertical";
|
|
425
|
+
return "Both";
|
|
426
|
+
}
|
|
427
|
+
/** Items per row of a 2D group: Split when set, else the items sharing the row of `item`. */
|
|
428
|
+
/**
|
|
429
|
+
* Items per row of a 2D group: the Split, else the items on the row of the group's first item. C# counts the focused
|
|
430
|
+
* item's row, which the short last row of a wrap leaves short (Up from it skipped items); DrawnUi.Rust afef66f.
|
|
431
|
+
*/
|
|
432
|
+
function RowLength(group) {
|
|
433
|
+
const l = asLayout(group);
|
|
434
|
+
if (l && l.Split > 1)
|
|
435
|
+
return l.Split;
|
|
436
|
+
const first = ItemAt(group, 0) ?? FirstRealized(group);
|
|
437
|
+
if (!first)
|
|
438
|
+
return 1;
|
|
439
|
+
const top = first.GetAccessibilityPixelRect();
|
|
440
|
+
if (top.Height <= 0)
|
|
441
|
+
return 1;
|
|
442
|
+
const mid = (top.Top + top.Bottom) / 2;
|
|
443
|
+
let count = 0;
|
|
444
|
+
for (const child of ViewsOf(group)) {
|
|
445
|
+
const r = child.GetAccessibilityPixelRect();
|
|
446
|
+
if (r.Height > 0 && Math.abs((r.Top + r.Bottom) / 2 - mid) < top.Height / 2)
|
|
447
|
+
count++;
|
|
448
|
+
}
|
|
449
|
+
return Math.max(1, count);
|
|
450
|
+
}
|
|
451
|
+
function ItemCount(group) {
|
|
452
|
+
const l = asLayout(group);
|
|
453
|
+
return l?.IsTemplated ? l.ItemsSource?.length ?? 0 : ViewsOf(group).length;
|
|
454
|
+
}
|
|
455
|
+
function IndexOf(group, item) {
|
|
456
|
+
const l = asLayout(group);
|
|
457
|
+
return l?.IsTemplated ? item.ContextIndex : ViewsOf(group).indexOf(item);
|
|
458
|
+
}
|
|
459
|
+
/** The view of an item: the realized cell of a templated layout (none when its cell is not in use), else the child. */
|
|
460
|
+
function ItemAt(group, index) {
|
|
461
|
+
const l = asLayout(group);
|
|
462
|
+
if (l?.IsTemplated) {
|
|
463
|
+
const v = l.ChildrenFactory.GetViewForIndex(index);
|
|
464
|
+
return v && v.ContextIndex === index ? v : undefined;
|
|
465
|
+
}
|
|
466
|
+
return index >= 0 && index < ViewsOf(group).length ? ViewsOf(group)[index] : undefined;
|
|
467
|
+
}
|
|
468
|
+
/** The realized item with the lowest index (item 0 of a scrolled recycled group may not be realized). */
|
|
469
|
+
function FirstRealized(group) {
|
|
470
|
+
let best;
|
|
471
|
+
for (const v of ViewsOf(group))
|
|
472
|
+
if (!best || IndexOf(group, v) < IndexOf(group, best))
|
|
473
|
+
best = v;
|
|
474
|
+
return best;
|
|
475
|
+
}
|
|
476
|
+
/** Arranged on the canvas: realized and laid out (a templated layout keeps only the cells it draws in use). */
|
|
477
|
+
function IsDrawn(control) {
|
|
478
|
+
const r = control.GetAccessibilityPixelRect();
|
|
479
|
+
return !!control.Superview && r.Width > 0 && r.Height > 0;
|
|
480
|
+
}
|
|
481
|
+
/** Items per viewport of the scroll around the group. */
|
|
482
|
+
function PageSize(scroll, item, vertical) {
|
|
483
|
+
const v = scroll.DrawingRect, size = item.MeasuredSize.Pixels;
|
|
484
|
+
const viewport = vertical ? v.Height : v.Width, s = vertical ? size.Height : size.Width;
|
|
485
|
+
return s > 0 ? Math.max(1, Math.floor(viewport / s)) : 1;
|
|
486
|
+
}
|
|
487
|
+
/** The item itself when it is an interactive node, else its first interactive node in tree order. */
|
|
488
|
+
function FindTarget(item) {
|
|
489
|
+
if (item.IsAccessibilityElement && item.AccessibilityCanInteract)
|
|
490
|
+
return item;
|
|
491
|
+
for (const child of ViewsOf(item)) {
|
|
492
|
+
const found = FindTarget(child);
|
|
493
|
+
if (found)
|
|
494
|
+
return found;
|
|
495
|
+
}
|
|
496
|
+
return undefined;
|
|
497
|
+
}
|
|
141
498
|
/** True when any ancestor is hidden (IsVisible false or Opacity 0): the subtree is not drawn, so it gets no nodes. */
|
|
142
499
|
function AccessibilityManagerHiddenByAncestor(control) {
|
|
143
500
|
let p = control.Parent;
|
package/dist/core/Animators.d.ts
CHANGED
|
@@ -18,17 +18,43 @@ export declare class AnimatorBase {
|
|
|
18
18
|
StartFrameTimeNanos: number;
|
|
19
19
|
OnStart?: () => void;
|
|
20
20
|
OnStop?: () => void;
|
|
21
|
+
/** Paused: the canvas does not tick it and it wakes no frames; Resume goes on from where it was (C# Pause / Resume). */
|
|
22
|
+
IsPaused: boolean;
|
|
23
|
+
/** Paused by the canvas because its control is hidden; resumed when it shows again. */
|
|
24
|
+
PausedWhileHidden: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Frame time (ns) before which nothing it shows changes: the canvas does not tick it and it keeps no frames coming
|
|
27
|
+
* until then, a timer wakes the frame loop at that time (a GIF or a sprite between two frames; DrawnUi.Rust frame
|
|
28
|
+
* animators sleep between changes). 0 = tick every frame.
|
|
29
|
+
*/
|
|
30
|
+
SleepUntilNanos: number;
|
|
21
31
|
private delayHandle;
|
|
32
|
+
private delayEnd;
|
|
33
|
+
private delayLeft;
|
|
34
|
+
private pausedAt;
|
|
22
35
|
constructor(parent: SkiaControl);
|
|
23
36
|
protected Register(): boolean;
|
|
24
37
|
protected Unregister(): void;
|
|
25
38
|
Start(delayMs?: number): void;
|
|
39
|
+
/** Freezes the run (and what is left of a start delay); Resume goes on from there. */
|
|
40
|
+
Pause(): void;
|
|
41
|
+
/** Goes on from where Pause froze it: the paused time is not counted. */
|
|
42
|
+
Resume(): void;
|
|
26
43
|
Stop(): void;
|
|
27
44
|
/** Returns true when there is nothing more to do (not running / finished). */
|
|
28
45
|
TickFrame(frameTimeNanos: number): boolean;
|
|
29
46
|
Dispose(): void;
|
|
30
47
|
private SetRunning;
|
|
31
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* DrawnUi ActionOnTickAnimator: runs `action(frameTimeNanos)` on every frame until stopped, a game loop for
|
|
51
|
+
* DrawnGame or any per-frame work.
|
|
52
|
+
*/
|
|
53
|
+
export declare class ActionOnTickAnimator extends AnimatorBase {
|
|
54
|
+
private readonly action;
|
|
55
|
+
constructor(parent: SkiaControl, action: (frameTimeNanos: number) => void);
|
|
56
|
+
TickFrame(frameTimeNanos: number): boolean;
|
|
57
|
+
}
|
|
32
58
|
/** DrawnUi SkiaValueAnimator: drives mValue from mMinValue to mMaxValue over Speed ms with Easing. */
|
|
33
59
|
export declare class SkiaValueAnimator extends AnimatorBase {
|
|
34
60
|
mValue: number;
|
|
@@ -46,10 +72,20 @@ export declare class SkiaValueAnimator extends AnimatorBase {
|
|
|
46
72
|
TickFrame(frameTimeNanos: number): boolean;
|
|
47
73
|
/** Passed over mValue; subclasses derive their own reported value here. */
|
|
48
74
|
protected TransformReportedValue(_deltaT: number): number;
|
|
49
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Updates mValue from elapsed time; true when the run is over. The eased value is not clamped to the range: a spring
|
|
77
|
+
* easing goes past its ends, and a run whose mMinValue is above mMaxValue (a ping-pong's way back) goes down.
|
|
78
|
+
*/
|
|
50
79
|
protected UpdateValue(_deltaT: number, deltaFromStart: number): boolean;
|
|
51
80
|
protected FinishedRunning(): boolean;
|
|
52
81
|
}
|
|
82
|
+
/** DrawnUi PingPongAnimator: every repeat runs the other way (Repeat -1 = forever by default). */
|
|
83
|
+
export declare class PingPongAnimator extends SkiaValueAnimator {
|
|
84
|
+
/** One way done, the next starts (C# CycleFInished). */
|
|
85
|
+
CycleFinished?: () => void;
|
|
86
|
+
constructor(parent: SkiaControl);
|
|
87
|
+
protected FinishedRunning(): boolean;
|
|
88
|
+
}
|
|
53
89
|
/** DrawnUi RenderingAnimator: a value animator that paints itself above its Parent (IOverlayEffect). */
|
|
54
90
|
export declare class RenderingAnimator extends SkiaValueAnimator implements IOverlayEffect {
|
|
55
91
|
constructor(parent: SkiaControl);
|