drawnui-react 0.1.0-preview.15 → 0.1.0-preview.17
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 +1 -1
- package/SKIPPED.md +9 -8
- package/dist/controls/AnimatedFramesRenderer.d.ts +7 -0
- package/dist/controls/AnimatedFramesRenderer.js +16 -1
- package/dist/controls/DrawnGame.d.ts +4 -1
- package/dist/controls/DrawnGame.js +7 -3
- package/dist/controls/GridStructure.d.ts +13 -1
- package/dist/controls/GridStructure.js +38 -2
- package/dist/controls/SkiaBackdrop.d.ts +2 -0
- package/dist/controls/SkiaBackdrop.js +2 -0
- package/dist/controls/SkiaButton.d.ts +2 -0
- package/dist/controls/SkiaButton.js +2 -0
- package/dist/controls/SkiaCarousel.d.ts +3 -1
- package/dist/controls/SkiaCarousel.js +5 -2
- package/dist/controls/SkiaDrawer.d.ts +2 -0
- package/dist/controls/SkiaDrawer.js +2 -0
- package/dist/controls/SkiaEditor.d.ts +5 -1
- package/dist/controls/SkiaEditor.js +29 -18
- package/dist/controls/SkiaGif.d.ts +3 -0
- package/dist/controls/SkiaGif.js +11 -1
- 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/SkiaLayout.d.ts +20 -0
- package/dist/controls/SkiaLayout.js +78 -6
- package/dist/controls/SkiaLottie.js +3 -1
- package/dist/controls/SkiaProgress.d.ts +2 -1
- package/dist/controls/SkiaProgress.js +6 -3
- package/dist/controls/SkiaScroll.d.ts +16 -0
- package/dist/controls/SkiaScroll.js +85 -8
- package/dist/controls/SkiaShape.d.ts +13 -2
- package/dist/controls/SkiaShape.js +50 -10
- package/dist/controls/SkiaSlider.d.ts +7 -1
- package/dist/controls/SkiaSlider.js +12 -4
- package/dist/controls/SkiaSprite.d.ts +1 -0
- package/dist/controls/SkiaSprite.js +4 -0
- package/dist/controls/SkiaToggle.d.ts +2 -0
- package/dist/controls/SkiaToggle.js +3 -1
- package/dist/controls/SnappingLayout.d.ts +7 -0
- package/dist/controls/SnappingLayout.js +11 -0
- package/dist/core/Accessibility.d.ts +21 -0
- package/dist/core/Accessibility.js +66 -9
- package/dist/core/Animators.d.ts +28 -1
- package/dist/core/Animators.js +76 -8
- package/dist/core/Canvas.d.ts +26 -4
- package/dist/core/Canvas.js +136 -23
- package/dist/core/Easing.d.ts +12 -1
- package/dist/core/Easing.js +25 -1
- package/dist/core/Gestures.d.ts +24 -1
- package/dist/core/Gestures.js +75 -2
- 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 +97 -1
- package/dist/core/SkiaControl.js +271 -38
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/react/SkiaShell.js +33 -14
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +30 -3
- package/dist/react/reconciler.js +29 -10
- package/package.json +18 -1
|
@@ -50,6 +50,8 @@ export declare abstract class SnappingLayout extends SkiaLayout {
|
|
|
50
50
|
protected StopSnapAnimators(): void;
|
|
51
51
|
/** C# SnappingLayout.Render: the transition state follows the position every frame. */
|
|
52
52
|
Render(ctx: DrawingContext): void;
|
|
53
|
+
/** Hover waits while the content slides (C# InTransition) or follows a pan. */
|
|
54
|
+
MovesContent(): boolean;
|
|
53
55
|
ClampOffset(x: number, y: number, rubber: boolean): SKPoint;
|
|
54
56
|
/** C# GetContentOffsetBounds: the extent covered by the snap points. */
|
|
55
57
|
protected BoundsFromSnapPoints(): SKRect;
|
|
@@ -59,6 +61,11 @@ export declare abstract class SnappingLayout extends SkiaLayout {
|
|
|
59
61
|
protected FindNearestAnchorInternal(current: SKPoint, _velocity: SKPoint): SKPoint;
|
|
60
62
|
/** The closest anchor lying in the direction of the velocity (dot product), else the origin. */
|
|
61
63
|
SelectNextAnchor(origin: SKPoint, velocity: SKPoint): SKPoint;
|
|
64
|
+
/**
|
|
65
|
+
* Release speed, points per second along an axis, under which the snap ignores the velocity and goes to the nearest
|
|
66
|
+
* anchor by distance (C# SnapVelocityThreshold, drawnui-cross 6o): a finger held still before lifting is not a flick.
|
|
67
|
+
*/
|
|
68
|
+
SnapVelocityThreshold: number;
|
|
62
69
|
ScrollToNearestAnchor(location: SKPoint, velocity: SKPoint): void;
|
|
63
70
|
get CanAnimate(): boolean;
|
|
64
71
|
protected GetAutoVelocity(displacement: SKPoint): SKPoint;
|
|
@@ -80,9 +80,12 @@ export class SnappingLayout extends SkiaLayout {
|
|
|
80
80
|
}
|
|
81
81
|
/** C# SnappingLayout.Render: the transition state follows the position every frame. */
|
|
82
82
|
Render(ctx) {
|
|
83
|
+
this.Superview?.Hover?.RegisterMover(this);
|
|
83
84
|
super.Render(ctx);
|
|
84
85
|
this.InTransition = !this.CheckTransitionEnded();
|
|
85
86
|
}
|
|
87
|
+
/** Hover waits while the content slides (C# InTransition) or follows a pan. */
|
|
88
|
+
MovesContent() { return this.InTransition || this.IsUserPanning; }
|
|
86
89
|
// ---- clamping ----
|
|
87
90
|
ClampOffset(x, y, rubber) {
|
|
88
91
|
const b = this.ContentOffsetBounds;
|
|
@@ -135,9 +138,17 @@ export class SnappingLayout extends SkiaLayout {
|
|
|
135
138
|
}
|
|
136
139
|
return origin;
|
|
137
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Release speed, points per second along an axis, under which the snap ignores the velocity and goes to the nearest
|
|
143
|
+
* anchor by distance (C# SnapVelocityThreshold, drawnui-cross 6o): a finger held still before lifting is not a flick.
|
|
144
|
+
*/
|
|
145
|
+
SnapVelocityThreshold = 100;
|
|
138
146
|
ScrollToNearestAnchor(location, velocity) {
|
|
139
147
|
if (this.SnapPoints.length === 0)
|
|
140
148
|
return;
|
|
149
|
+
if (Math.abs(velocity.X) < this.SnapVelocityThreshold || Math.abs(velocity.Y) < this.SnapVelocityThreshold) {
|
|
150
|
+
velocity = new SKPoint(Math.abs(velocity.X) < this.SnapVelocityThreshold ? 0 : velocity.X, Math.abs(velocity.Y) < this.SnapVelocityThreshold ? 0 : velocity.Y);
|
|
151
|
+
}
|
|
141
152
|
const origin = this.FindNearestAnchorInternal(location, velocity);
|
|
142
153
|
const target = this.SelectNextAnchor(origin, velocity);
|
|
143
154
|
if (SnappingLayout.Dist(location, target) >= 0.5)
|
|
@@ -52,6 +52,12 @@ export declare const Aria: {
|
|
|
52
52
|
* its current item, and the arrow keys move between its items.
|
|
53
53
|
*/
|
|
54
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;
|
|
55
61
|
};
|
|
56
62
|
/** One laid-out text line exposed for native selection (AccessibilityTextSelectable): CSS px relative to the node. */
|
|
57
63
|
export interface AccessibilityTextLine {
|
|
@@ -64,6 +70,17 @@ export interface AccessibilityTextLine {
|
|
|
64
70
|
FontWeight: number;
|
|
65
71
|
FontSize: number;
|
|
66
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
|
+
}
|
|
67
84
|
/** One entry of the accessibility snapshot (DrawnUi AccessibilityNode). Rect is CSS pixels relative to the canvas. */
|
|
68
85
|
export interface AccessibilityNode {
|
|
69
86
|
Id: number;
|
|
@@ -72,9 +89,13 @@ export interface AccessibilityNode {
|
|
|
72
89
|
Role: string;
|
|
73
90
|
Rect: SKRect;
|
|
74
91
|
CanInteract: boolean;
|
|
92
|
+
/** A role a user operates (Aria.IsInteractiveRole) that cannot take input: aria-disabled, no tab stop. */
|
|
93
|
+
Disabled: boolean;
|
|
75
94
|
IsPressed?: boolean;
|
|
76
95
|
Live?: string;
|
|
77
96
|
Source: SkiaControl;
|
|
97
|
+
/** A range control's value (aria-valuenow / min / max / valuetext); its name stays the app's label. */
|
|
98
|
+
Value?: AccessibilityValue;
|
|
78
99
|
/** Real text lines rendered into the overlay so the browser can select / copy them (opt-in per control). */
|
|
79
100
|
TextLines?: AccessibilityTextLine[];
|
|
80
101
|
}
|
|
@@ -26,6 +26,16 @@ export const Aria = {
|
|
|
26
26
|
return role === "list" || role === "listbox" || role === "grid" || role === "toolbar" || role === "radiogroup"
|
|
27
27
|
|| role === "tablist" || role === "menu" || role === "menubar";
|
|
28
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
|
+
},
|
|
29
39
|
};
|
|
30
40
|
const asLayout = (c) => ("ChildrenFactory" in c && "Split" in c ? c : undefined);
|
|
31
41
|
const asScroll = (c) => ("ScrollToIndex" in c && "ViewportOffsetY" in c ? c : undefined);
|
|
@@ -70,6 +80,7 @@ export class SkiaAccessibilityManager {
|
|
|
70
80
|
if (!node || !node.AccessibilityCanInteract)
|
|
71
81
|
return false;
|
|
72
82
|
node.OnAccessibilityActivated();
|
|
83
|
+
node.Superview?.AccessibilityManager.ForceRebuildOnNextFrame(); // a screen reader reads the new state right back
|
|
73
84
|
return true;
|
|
74
85
|
}
|
|
75
86
|
/**
|
|
@@ -80,8 +91,10 @@ export class SkiaAccessibilityManager {
|
|
|
80
91
|
static Key(node, key) {
|
|
81
92
|
if (!node)
|
|
82
93
|
return false;
|
|
83
|
-
if (node.CanReceiveGesture("Panning") && node.OnAccessibilityKey(key))
|
|
94
|
+
if (node.CanReceiveGesture("Panning") && node.OnAccessibilityKey(key)) {
|
|
95
|
+
node.Superview?.AccessibilityManager.ForceRebuildOnNextFrame(); // the new value is read right back, not a second later
|
|
84
96
|
return true;
|
|
97
|
+
}
|
|
85
98
|
return node.Superview?.AccessibilityManager.MoveInGroup(node, key) ?? false;
|
|
86
99
|
}
|
|
87
100
|
// ---- arrow-key groups (C# SkiaAccessibilityManager, "Arrow-key groups") ----
|
|
@@ -150,7 +163,7 @@ export class SkiaAccessibilityManager {
|
|
|
150
163
|
const current = this.groupRequest?.Group === group ? this.groupRequest.Index : IndexOf(group, item);
|
|
151
164
|
if (current < 0 || count < 1)
|
|
152
165
|
return false;
|
|
153
|
-
const row = axis === "Both" ? RowLength(group
|
|
166
|
+
const row = axis === "Both" ? RowLength(group) : 1;
|
|
154
167
|
const vertical = axis !== "Horizontal";
|
|
155
168
|
const page = () => PageSize(scroll, item, vertical) * row;
|
|
156
169
|
let target, step;
|
|
@@ -245,7 +258,7 @@ export class SkiaAccessibilityManager {
|
|
|
245
258
|
for (const cb of this.liveUpdated)
|
|
246
259
|
cb(node);
|
|
247
260
|
}
|
|
248
|
-
ForceRebuildOnNextFrame() { this.lastRebuild =
|
|
261
|
+
ForceRebuildOnNextFrame() { this.lastRebuild = -Infinity; this.dirty = true; }
|
|
249
262
|
Unregister(node) {
|
|
250
263
|
if (this.nodes.delete(node)) {
|
|
251
264
|
node.OnAccessibilityUnregistered();
|
|
@@ -282,7 +295,7 @@ export class SkiaAccessibilityManager {
|
|
|
282
295
|
Rebuild(scale, canvasWidthPx, canvasHeightPx) {
|
|
283
296
|
this.dirty = false;
|
|
284
297
|
this.lastRebuild = performance.now();
|
|
285
|
-
|
|
298
|
+
let list = [];
|
|
286
299
|
for (const n of this.nodes) {
|
|
287
300
|
if (!n.Superview) {
|
|
288
301
|
this.nodes.delete(n);
|
|
@@ -301,12 +314,14 @@ export class SkiaAccessibilityManager {
|
|
|
301
314
|
if (px.Right < -canvasWidthPx || px.Bottom < -canvasHeightPx || px.Left > 2 * canvasWidthPx || px.Top > 2 * canvasHeightPx)
|
|
302
315
|
continue;
|
|
303
316
|
list.push({
|
|
304
|
-
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,
|
|
305
318
|
Rect: new SKRect(px.Left / scale, px.Top / scale, px.Right / scale, px.Bottom / scale),
|
|
306
319
|
CanInteract: n.AccessibilityCanInteract, IsPressed: n.AccessibilityIsPressed, Live: n.AccessibilityLive, Source: n,
|
|
320
|
+
Disabled: !n.AccessibilityCanInteract && Aria.IsInteractiveRole(n.AccessibilityRole),
|
|
307
321
|
TextLines: n.AccessibilityTextSelectable ? n.GetAccessibilityTextLines(scale) : undefined,
|
|
308
322
|
});
|
|
309
323
|
}
|
|
324
|
+
list = SayNamesOnce(list);
|
|
310
325
|
list.sort((a, b) => a.Rect.Top - b.Rect.Top || a.Rect.Left - b.Rect.Left);
|
|
311
326
|
// reading order: nodes whose tops are within half the smaller height form one row, read left to right, so a row of
|
|
312
327
|
// vertically centered controls of different heights keeps its visual order (C# RectComparer + LeftComparer)
|
|
@@ -334,8 +349,8 @@ export class SkiaAccessibilityManager {
|
|
|
334
349
|
return false;
|
|
335
350
|
for (let i = 0; i < a.length; i++) {
|
|
336
351
|
const x = a[i], y = b[i];
|
|
337
|
-
if (x.Source !== y.Source || x.Label !== y.Label || x.Hint !== y.Hint || x.Role !== y.Role || x.CanInteract !== y.CanInteract
|
|
338
|
-
|| 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)
|
|
339
354
|
|| Math.abs(x.Rect.Left - y.Rect.Left) > 0.5 || Math.abs(x.Rect.Top - y.Rect.Top) > 0.5
|
|
340
355
|
|| Math.abs(x.Rect.Right - y.Rect.Right) > 0.5 || Math.abs(x.Rect.Bottom - y.Rect.Bottom) > 0.5)
|
|
341
356
|
return false;
|
|
@@ -353,6 +368,33 @@ export class SkiaAccessibilityManager {
|
|
|
353
368
|
return true;
|
|
354
369
|
}
|
|
355
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
|
+
}
|
|
356
398
|
// ---- group helpers (duck-typed layouts and scrolls) ----
|
|
357
399
|
/** Children of a control (a layout's Views, a scroll's Content). */
|
|
358
400
|
function ViewsOf(c) {
|
|
@@ -383,11 +425,18 @@ function AxisOf(group) {
|
|
|
383
425
|
return "Both";
|
|
384
426
|
}
|
|
385
427
|
/** Items per row of a 2D group: Split when set, else the items sharing the row of `item`. */
|
|
386
|
-
|
|
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) {
|
|
387
433
|
const l = asLayout(group);
|
|
388
434
|
if (l && l.Split > 1)
|
|
389
435
|
return l.Split;
|
|
390
|
-
const
|
|
436
|
+
const first = ItemAt(group, 0) ?? FirstRealized(group);
|
|
437
|
+
if (!first)
|
|
438
|
+
return 1;
|
|
439
|
+
const top = first.GetAccessibilityPixelRect();
|
|
391
440
|
if (top.Height <= 0)
|
|
392
441
|
return 1;
|
|
393
442
|
const mid = (top.Top + top.Bottom) / 2;
|
|
@@ -416,6 +465,14 @@ function ItemAt(group, index) {
|
|
|
416
465
|
}
|
|
417
466
|
return index >= 0 && index < ViewsOf(group).length ? ViewsOf(group)[index] : undefined;
|
|
418
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
|
+
}
|
|
419
476
|
/** Arranged on the canvas: realized and laid out (a templated layout keeps only the cells it draws in use). */
|
|
420
477
|
function IsDrawn(control) {
|
|
421
478
|
const r = control.GetAccessibilityPixelRect();
|
package/dist/core/Animators.d.ts
CHANGED
|
@@ -18,11 +18,28 @@ 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;
|
|
@@ -55,10 +72,20 @@ export declare class SkiaValueAnimator extends AnimatorBase {
|
|
|
55
72
|
TickFrame(frameTimeNanos: number): boolean;
|
|
56
73
|
/** Passed over mValue; subclasses derive their own reported value here. */
|
|
57
74
|
protected TransformReportedValue(_deltaT: number): number;
|
|
58
|
-
/**
|
|
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
|
+
*/
|
|
59
79
|
protected UpdateValue(_deltaT: number, deltaFromStart: number): boolean;
|
|
60
80
|
protected FinishedRunning(): boolean;
|
|
61
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
|
+
}
|
|
62
89
|
/** DrawnUi RenderingAnimator: a value animator that paints itself above its Parent (IOverlayEffect). */
|
|
63
90
|
export declare class RenderingAnimator extends SkiaValueAnimator implements IOverlayEffect {
|
|
64
91
|
constructor(parent: SkiaControl);
|
package/dist/core/Animators.js
CHANGED
|
@@ -16,7 +16,20 @@ export class AnimatorBase {
|
|
|
16
16
|
StartFrameTimeNanos = 0;
|
|
17
17
|
OnStart;
|
|
18
18
|
OnStop;
|
|
19
|
+
/** Paused: the canvas does not tick it and it wakes no frames; Resume goes on from where it was (C# Pause / Resume). */
|
|
20
|
+
IsPaused = false;
|
|
21
|
+
/** Paused by the canvas because its control is hidden; resumed when it shows again. */
|
|
22
|
+
PausedWhileHidden = false;
|
|
23
|
+
/**
|
|
24
|
+
* Frame time (ns) before which nothing it shows changes: the canvas does not tick it and it keeps no frames coming
|
|
25
|
+
* until then, a timer wakes the frame loop at that time (a GIF or a sprite between two frames; DrawnUi.Rust frame
|
|
26
|
+
* animators sleep between changes). 0 = tick every frame.
|
|
27
|
+
*/
|
|
28
|
+
SleepUntilNanos = 0;
|
|
19
29
|
delayHandle = 0;
|
|
30
|
+
delayEnd = 0;
|
|
31
|
+
delayLeft = 0;
|
|
32
|
+
pausedAt = 0;
|
|
20
33
|
constructor(parent) { this.Parent = parent; }
|
|
21
34
|
Register() {
|
|
22
35
|
const parent = this.Parent;
|
|
@@ -60,6 +73,8 @@ export class AnimatorBase {
|
|
|
60
73
|
this.delayHandle = 0;
|
|
61
74
|
}
|
|
62
75
|
if (delayMs > 0) {
|
|
76
|
+
// the delay waits without frames
|
|
77
|
+
this.delayEnd = performance.now() + delayMs;
|
|
63
78
|
this.delayHandle = window.setTimeout(() => { this.delayHandle = 0; this.Start(); }, delayMs);
|
|
64
79
|
return;
|
|
65
80
|
}
|
|
@@ -72,7 +87,43 @@ export class AnimatorBase {
|
|
|
72
87
|
}
|
|
73
88
|
}
|
|
74
89
|
}
|
|
90
|
+
/** Freezes the run (and what is left of a start delay); Resume goes on from there. */
|
|
91
|
+
Pause() {
|
|
92
|
+
if (this.IsPaused)
|
|
93
|
+
return;
|
|
94
|
+
this.IsPaused = true;
|
|
95
|
+
this.pausedAt = performance.now();
|
|
96
|
+
if (this.delayHandle) {
|
|
97
|
+
clearTimeout(this.delayHandle);
|
|
98
|
+
this.delayHandle = 0;
|
|
99
|
+
this.delayLeft = Math.max(0, this.delayEnd - this.pausedAt);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** Goes on from where Pause froze it: the paused time is not counted. */
|
|
103
|
+
Resume() {
|
|
104
|
+
if (!this.IsPaused)
|
|
105
|
+
return;
|
|
106
|
+
this.IsPaused = false;
|
|
107
|
+
const paused = (performance.now() - this.pausedAt) * 1_000_000;
|
|
108
|
+
if (this.StartFrameTimeNanos !== 0) {
|
|
109
|
+
this.StartFrameTimeNanos += paused;
|
|
110
|
+
this.LastFrameTimeNanos += paused;
|
|
111
|
+
}
|
|
112
|
+
if (this.SleepUntilNanos !== 0)
|
|
113
|
+
this.SleepUntilNanos += paused;
|
|
114
|
+
if (this.delayLeft > 0) {
|
|
115
|
+
const left = this.delayLeft;
|
|
116
|
+
this.delayLeft = 0;
|
|
117
|
+
this.Start(left);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this.Parent?.Repaint(); // frames again
|
|
121
|
+
}
|
|
75
122
|
Stop() {
|
|
123
|
+
this.IsPaused = false;
|
|
124
|
+
this.PausedWhileHidden = false;
|
|
125
|
+
this.SleepUntilNanos = 0;
|
|
126
|
+
this.delayLeft = 0;
|
|
76
127
|
if (this.delayHandle) {
|
|
77
128
|
clearTimeout(this.delayHandle);
|
|
78
129
|
this.delayHandle = 0;
|
|
@@ -155,23 +206,21 @@ export class SkiaValueAnimator extends AnimatorBase {
|
|
|
155
206
|
}
|
|
156
207
|
/** Passed over mValue; subclasses derive their own reported value here. */
|
|
157
208
|
TransformReportedValue(_deltaT) { return this.mValue; }
|
|
158
|
-
/**
|
|
209
|
+
/**
|
|
210
|
+
* Updates mValue from elapsed time; true when the run is over. The eased value is not clamped to the range: a spring
|
|
211
|
+
* easing goes past its ends, and a run whose mMinValue is above mMaxValue (a ping-pong's way back) goes down.
|
|
212
|
+
*/
|
|
159
213
|
UpdateValue(_deltaT, deltaFromStart) {
|
|
160
214
|
const elapsedMs = deltaFromStart / 1_000_000;
|
|
161
215
|
const progress = this.Speed > 0 ? elapsedMs / this.Speed : 1;
|
|
162
216
|
const deltaValue = this.mMaxValue - this.mMinValue;
|
|
163
217
|
const eased = this.Easing.Ease(Math.min(progress, 1));
|
|
164
218
|
this.Progress = eased;
|
|
165
|
-
|
|
166
|
-
if (value < this.mMinValue) {
|
|
167
|
-
this.mValue = this.mMinValue;
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
if (value >= this.mMaxValue || progress >= 1) {
|
|
219
|
+
if (progress >= 1) {
|
|
171
220
|
this.mValue = this.mMaxValue;
|
|
172
221
|
return true;
|
|
173
222
|
}
|
|
174
|
-
this.mValue =
|
|
223
|
+
this.mValue = deltaValue * eased + this.mMinValue;
|
|
175
224
|
return false;
|
|
176
225
|
}
|
|
177
226
|
FinishedRunning() {
|
|
@@ -188,6 +237,25 @@ export class SkiaValueAnimator extends AnimatorBase {
|
|
|
188
237
|
return true;
|
|
189
238
|
}
|
|
190
239
|
}
|
|
240
|
+
/** DrawnUi PingPongAnimator: every repeat runs the other way (Repeat -1 = forever by default). */
|
|
241
|
+
export class PingPongAnimator extends SkiaValueAnimator {
|
|
242
|
+
/** One way done, the next starts (C# CycleFInished). */
|
|
243
|
+
CycleFinished;
|
|
244
|
+
constructor(parent) { super(parent); this.Repeat = -1; }
|
|
245
|
+
FinishedRunning() {
|
|
246
|
+
if (this.Repeat !== 0) {
|
|
247
|
+
this.CycleFinished?.();
|
|
248
|
+
if (this.Repeat > 0)
|
|
249
|
+
this.Repeat--;
|
|
250
|
+
[this.mMinValue, this.mMaxValue] = [this.mMaxValue, this.mMinValue];
|
|
251
|
+
this.mValue = this.mMinValue;
|
|
252
|
+
this.LastFrameTimeNanos = 0;
|
|
253
|
+
this.StartFrameTimeNanos = 0;
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
return super.FinishedRunning();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
191
259
|
/** DrawnUi RenderingAnimator: a value animator that paints itself above its Parent (IOverlayEffect). */
|
|
192
260
|
export class RenderingAnimator extends SkiaValueAnimator {
|
|
193
261
|
constructor(parent) { super(parent); }
|
package/dist/core/Canvas.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { Surface } from "canvaskit-wasm";
|
|
|
2
2
|
import type { DrawingContext, SkiaControl } from "./SkiaControl";
|
|
3
3
|
import type { AnimatorBase } from "./Animators";
|
|
4
4
|
import { SkiaAccessibilityManager } from "./Accessibility";
|
|
5
|
+
import { HoverManager } from "./Hover";
|
|
5
6
|
import { type Color, type RenderingModeType } from "./Types";
|
|
6
7
|
import { ContextMenuEventArgs, type GesturesMode, SkiaGesturesParameters, TouchActionEventArgs } from "./Gestures";
|
|
7
8
|
/**
|
|
@@ -14,6 +15,11 @@ export declare class Canvas {
|
|
|
14
15
|
readonly Element: HTMLCanvasElement;
|
|
15
16
|
/** Points a pointer may travel between Down and Up and still count as a tap (AppoMobi TouchEffect default). */
|
|
16
17
|
static TappedCancelMoveThresholdPoints: number;
|
|
18
|
+
/**
|
|
19
|
+
* A press held this long without moving past TappedCancelMoveThresholdPoints is a LongPressing, at the press point
|
|
20
|
+
* (AppoMobi TouchEffect, DrawnUi.Rust LONG_PRESS_MS); its release sends no Tapped. A timer, no frames while waiting.
|
|
21
|
+
*/
|
|
22
|
+
static LongPressTimeMs: number;
|
|
17
23
|
BackgroundColor: Color;
|
|
18
24
|
/** Accelerated = WebGL surface, Default = software. Read once at first frame. */
|
|
19
25
|
RenderingMode: RenderingModeType;
|
|
@@ -37,6 +43,8 @@ export declare class Canvas {
|
|
|
37
43
|
WasDrawn?: (sender: Canvas) => void;
|
|
38
44
|
/** Registry + rate-limited snapshot of accessible controls, rendered by the DOM overlay (DrawnUi AccessibilityManager). */
|
|
39
45
|
readonly AccessibilityManager: SkiaAccessibilityManager;
|
|
46
|
+
/** Mouse hover of the controls (drawnui-cross 6m): ReceivesHover / IsHovered / HoverChanged. */
|
|
47
|
+
readonly Hover: HoverManager;
|
|
40
48
|
private content?;
|
|
41
49
|
get Content(): SkiaControl | undefined;
|
|
42
50
|
set Content(value: SkiaControl | undefined);
|
|
@@ -93,6 +101,10 @@ export declare class Canvas {
|
|
|
93
101
|
UnregisterAnimator(uid: number): void;
|
|
94
102
|
/** Ticks every registered animator once; returns how many ran. */
|
|
95
103
|
protected ExecuteAnimators(frameTimeNanos: number): number;
|
|
104
|
+
private wakeTimer;
|
|
105
|
+
private wakeAtNanos;
|
|
106
|
+
/** One timer for the earliest sleeping animator: the frame loop wakes then, no frames run in between. */
|
|
107
|
+
private WakeAt;
|
|
96
108
|
/** Frames drawn so far (SkiaBackdrop uses it to refresh once after a cached record). */
|
|
97
109
|
FrameIndex: number;
|
|
98
110
|
/**
|
|
@@ -126,6 +138,8 @@ export declare class Canvas {
|
|
|
126
138
|
private readonly previousTouchArgs;
|
|
127
139
|
private readonly pendingGestures;
|
|
128
140
|
private readonly onPointer;
|
|
141
|
+
/** The mouse left the canvas: hover ends at once, also while content moves. */
|
|
142
|
+
private readonly onPointerLeave;
|
|
129
143
|
private readonly preventTouch;
|
|
130
144
|
/** Gestures="Enabled": touch / pen pointers whose Down a control used (see OnTouchAction). */
|
|
131
145
|
private readonly claimedTouches;
|
|
@@ -138,10 +152,11 @@ export declare class Canvas {
|
|
|
138
152
|
private cursorPointer;
|
|
139
153
|
/**
|
|
140
154
|
* DrawnUi.Blazor shows `cursor: pointer` over interactive controls through its overlay elements; here the overlay
|
|
141
|
-
* is pointer-events:none, so the
|
|
142
|
-
*
|
|
143
|
-
* (itself tappable, or a tappable span of a label)
|
|
144
|
-
*
|
|
155
|
+
* is pointer-events:none, so the canvas decides from the hover hit path (Hover.Path: the controls under the mouse by
|
|
156
|
+
* the routing rules of ProcessGestures, so nothing below a popup that blocks gestures below): a control in the
|
|
157
|
+
* accessibility tree (it has a role) that answers WantsPointerCursor (itself tappable, or a tappable span of a label)
|
|
158
|
+
* shows the hand. A popup's close-on-background-tap wrapper has no role, so its backdrop shows none. The canvas
|
|
159
|
+
* element's cursor is switched only when the answer changes. Mouse moves only, none in the frame loop.
|
|
145
160
|
*/
|
|
146
161
|
private UpdateCursor;
|
|
147
162
|
/**
|
|
@@ -174,9 +189,16 @@ export declare class Canvas {
|
|
|
174
189
|
private readonly UpdateTouchAction;
|
|
175
190
|
private pageObserver?;
|
|
176
191
|
private AttachInput;
|
|
192
|
+
/** The page's own overscroll-behavior before Lock set it to none (html, body), put back on detach. */
|
|
193
|
+
private pageOverscroll?;
|
|
177
194
|
private DetachInput;
|
|
178
195
|
/** Port of DrawnUi.Blazor Canvas.OnTouchAction: per-pointer state machine producing Down / Panning / Tapped / Up. */
|
|
179
196
|
OnTouchAction(args: TouchActionEventArgs): void;
|
|
197
|
+
private readonly longPressTimers;
|
|
198
|
+
private readonly longPressed;
|
|
199
|
+
/** The long press of a new press: due LongPressTimeMs later unless the pointer moves away or lets go. */
|
|
200
|
+
private ArmLongPress;
|
|
201
|
+
private DisarmLongPress;
|
|
180
202
|
private OnGestureEvent;
|
|
181
203
|
private ProcessPendingGestures;
|
|
182
204
|
/**
|