drawnui-react 0.1.0-preview.15 → 0.1.0-preview.16
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/SKIPPED.md +5 -4
- 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 +2 -0
- package/dist/controls/SkiaCarousel.js +2 -0
- package/dist/controls/SkiaDrawer.d.ts +2 -0
- package/dist/controls/SkiaDrawer.js +2 -0
- package/dist/controls/SkiaEditor.js +10 -1
- package/dist/controls/SkiaLabel.d.ts +2 -0
- package/dist/controls/SkiaLabel.js +13 -2
- package/dist/controls/SkiaLayout.d.ts +15 -0
- package/dist/controls/SkiaLayout.js +75 -6
- package/dist/controls/SkiaProgress.js +2 -2
- package/dist/controls/SkiaScroll.d.ts +4 -0
- package/dist/controls/SkiaScroll.js +9 -0
- package/dist/controls/SkiaSlider.d.ts +2 -0
- package/dist/controls/SkiaSlider.js +5 -3
- package/dist/controls/SkiaToggle.d.ts +2 -0
- package/dist/controls/SkiaToggle.js +3 -1
- package/dist/controls/SnappingLayout.d.ts +2 -0
- package/dist/controls/SnappingLayout.js +3 -0
- package/dist/core/Accessibility.d.ts +8 -0
- package/dist/core/Accessibility.js +56 -5
- package/dist/core/Canvas.d.ts +5 -0
- package/dist/core/Canvas.js +15 -1
- package/dist/core/Hover.d.ts +37 -0
- package/dist/core/Hover.js +118 -0
- package/dist/core/KeyboardManager.d.ts +13 -0
- package/dist/core/KeyboardManager.js +18 -0
- package/dist/core/SkiaControl.d.ts +85 -1
- package/dist/core/SkiaControl.js +217 -16
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +2 -2
- package/dist/react/reconciler.js +29 -10
- package/package.json +4 -1
|
@@ -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);
|
|
@@ -150,7 +160,7 @@ export class SkiaAccessibilityManager {
|
|
|
150
160
|
const current = this.groupRequest?.Group === group ? this.groupRequest.Index : IndexOf(group, item);
|
|
151
161
|
if (current < 0 || count < 1)
|
|
152
162
|
return false;
|
|
153
|
-
const row = axis === "Both" ? RowLength(group
|
|
163
|
+
const row = axis === "Both" ? RowLength(group) : 1;
|
|
154
164
|
const vertical = axis !== "Horizontal";
|
|
155
165
|
const page = () => PageSize(scroll, item, vertical) * row;
|
|
156
166
|
let target, step;
|
|
@@ -282,7 +292,7 @@ export class SkiaAccessibilityManager {
|
|
|
282
292
|
Rebuild(scale, canvasWidthPx, canvasHeightPx) {
|
|
283
293
|
this.dirty = false;
|
|
284
294
|
this.lastRebuild = performance.now();
|
|
285
|
-
|
|
295
|
+
let list = [];
|
|
286
296
|
for (const n of this.nodes) {
|
|
287
297
|
if (!n.Superview) {
|
|
288
298
|
this.nodes.delete(n);
|
|
@@ -304,9 +314,11 @@ export class SkiaAccessibilityManager {
|
|
|
304
314
|
Id: n.AccessibilityId, Label: n.AccessibilityLabel, Hint: n.AccessibilityHint, Role: n.AccessibilityRole,
|
|
305
315
|
Rect: new SKRect(px.Left / scale, px.Top / scale, px.Right / scale, px.Bottom / scale),
|
|
306
316
|
CanInteract: n.AccessibilityCanInteract, IsPressed: n.AccessibilityIsPressed, Live: n.AccessibilityLive, Source: n,
|
|
317
|
+
Disabled: !n.AccessibilityCanInteract && Aria.IsInteractiveRole(n.AccessibilityRole),
|
|
307
318
|
TextLines: n.AccessibilityTextSelectable ? n.GetAccessibilityTextLines(scale) : undefined,
|
|
308
319
|
});
|
|
309
320
|
}
|
|
321
|
+
list = SayNamesOnce(list);
|
|
310
322
|
list.sort((a, b) => a.Rect.Top - b.Rect.Top || a.Rect.Left - b.Rect.Left);
|
|
311
323
|
// reading order: nodes whose tops are within half the smaller height form one row, read left to right, so a row of
|
|
312
324
|
// vertically centered controls of different heights keeps its visual order (C# RectComparer + LeftComparer)
|
|
@@ -334,7 +346,7 @@ export class SkiaAccessibilityManager {
|
|
|
334
346
|
return false;
|
|
335
347
|
for (let i = 0; i < a.length; i++) {
|
|
336
348
|
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
|
|
349
|
+
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
|
|
338
350
|
|| x.IsPressed !== y.IsPressed || x.Live !== y.Live
|
|
339
351
|
|| Math.abs(x.Rect.Left - y.Rect.Left) > 0.5 || Math.abs(x.Rect.Top - y.Rect.Top) > 0.5
|
|
340
352
|
|| Math.abs(x.Rect.Right - y.Rect.Right) > 0.5 || Math.abs(x.Rect.Bottom - y.Rect.Bottom) > 0.5)
|
|
@@ -353,6 +365,30 @@ export class SkiaAccessibilityManager {
|
|
|
353
365
|
return true;
|
|
354
366
|
}
|
|
355
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* Each name is said once in the flat overlay (DrawnUi.Rust said_by_child): a text or a heading whose label repeats the
|
|
370
|
+
* label of the node it is in (its nearest ancestor node) is said by one of them. Under an interactive node (a card
|
|
371
|
+
* button) the child is left out, the button keeps its name; under any other node (a group card and its title) the
|
|
372
|
+
* node loses its name and the heading says it. Selectable text (TextLines) always stays.
|
|
373
|
+
*/
|
|
374
|
+
function SayNamesOnce(list) {
|
|
375
|
+
const bySource = new Map(list.map((x) => [x.Source, x]));
|
|
376
|
+
const said = new Set();
|
|
377
|
+
for (const x of list) {
|
|
378
|
+
if (!x.Label || x.TextLines || (x.Role !== Aria.RoleText && x.Role !== Aria.RoleHeading))
|
|
379
|
+
continue;
|
|
380
|
+
let owner;
|
|
381
|
+
for (let p = x.Source.Parent; p && !owner; p = p.Parent)
|
|
382
|
+
owner = bySource.get(p);
|
|
383
|
+
if (!owner || owner.Label !== x.Label)
|
|
384
|
+
continue;
|
|
385
|
+
if (owner.CanInteract)
|
|
386
|
+
said.add(x);
|
|
387
|
+
else
|
|
388
|
+
owner.Label = undefined;
|
|
389
|
+
}
|
|
390
|
+
return said.size ? list.filter((x) => !said.has(x)) : list;
|
|
391
|
+
}
|
|
356
392
|
// ---- group helpers (duck-typed layouts and scrolls) ----
|
|
357
393
|
/** Children of a control (a layout's Views, a scroll's Content). */
|
|
358
394
|
function ViewsOf(c) {
|
|
@@ -383,11 +419,18 @@ function AxisOf(group) {
|
|
|
383
419
|
return "Both";
|
|
384
420
|
}
|
|
385
421
|
/** Items per row of a 2D group: Split when set, else the items sharing the row of `item`. */
|
|
386
|
-
|
|
422
|
+
/**
|
|
423
|
+
* 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
|
|
424
|
+
* item's row, which the short last row of a wrap leaves short (Up from it skipped items); DrawnUi.Rust afef66f.
|
|
425
|
+
*/
|
|
426
|
+
function RowLength(group) {
|
|
387
427
|
const l = asLayout(group);
|
|
388
428
|
if (l && l.Split > 1)
|
|
389
429
|
return l.Split;
|
|
390
|
-
const
|
|
430
|
+
const first = ItemAt(group, 0) ?? FirstRealized(group);
|
|
431
|
+
if (!first)
|
|
432
|
+
return 1;
|
|
433
|
+
const top = first.GetAccessibilityPixelRect();
|
|
391
434
|
if (top.Height <= 0)
|
|
392
435
|
return 1;
|
|
393
436
|
const mid = (top.Top + top.Bottom) / 2;
|
|
@@ -416,6 +459,14 @@ function ItemAt(group, index) {
|
|
|
416
459
|
}
|
|
417
460
|
return index >= 0 && index < ViewsOf(group).length ? ViewsOf(group)[index] : undefined;
|
|
418
461
|
}
|
|
462
|
+
/** The realized item with the lowest index (item 0 of a scrolled recycled group may not be realized). */
|
|
463
|
+
function FirstRealized(group) {
|
|
464
|
+
let best;
|
|
465
|
+
for (const v of ViewsOf(group))
|
|
466
|
+
if (!best || IndexOf(group, v) < IndexOf(group, best))
|
|
467
|
+
best = v;
|
|
468
|
+
return best;
|
|
469
|
+
}
|
|
419
470
|
/** Arranged on the canvas: realized and laid out (a templated layout keeps only the cells it draws in use). */
|
|
420
471
|
function IsDrawn(control) {
|
|
421
472
|
const r = control.GetAccessibilityPixelRect();
|
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
|
/**
|
|
@@ -37,6 +38,8 @@ export declare class Canvas {
|
|
|
37
38
|
WasDrawn?: (sender: Canvas) => void;
|
|
38
39
|
/** Registry + rate-limited snapshot of accessible controls, rendered by the DOM overlay (DrawnUi AccessibilityManager). */
|
|
39
40
|
readonly AccessibilityManager: SkiaAccessibilityManager;
|
|
41
|
+
/** Mouse hover of the controls (drawnui-cross 6m): ReceivesHover / IsHovered / HoverChanged. */
|
|
42
|
+
readonly Hover: HoverManager;
|
|
40
43
|
private content?;
|
|
41
44
|
get Content(): SkiaControl | undefined;
|
|
42
45
|
set Content(value: SkiaControl | undefined);
|
|
@@ -126,6 +129,8 @@ export declare class Canvas {
|
|
|
126
129
|
private readonly previousTouchArgs;
|
|
127
130
|
private readonly pendingGestures;
|
|
128
131
|
private readonly onPointer;
|
|
132
|
+
/** The mouse left the canvas: hover ends at once, also while content moves. */
|
|
133
|
+
private readonly onPointerLeave;
|
|
129
134
|
private readonly preventTouch;
|
|
130
135
|
/** Gestures="Enabled": touch / pen pointers whose Down a control used (see OnTouchAction). */
|
|
131
136
|
private readonly claimedTouches;
|
package/dist/core/Canvas.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Super } from "./Super";
|
|
2
2
|
import { SkiaAccessibilityManager } from "./Accessibility";
|
|
3
|
+
import { HoverManager } from "./Hover";
|
|
3
4
|
import { Colors, SKRect } from "./Types";
|
|
4
5
|
import { ContextMenuEventArgs, GestureEventProcessingInfo, PointerData, SKPoint, SkiaGesturesParameters, TouchActionEventArgs, } from "./Gestures";
|
|
5
6
|
/**
|
|
@@ -35,6 +36,8 @@ export class Canvas {
|
|
|
35
36
|
WasDrawn;
|
|
36
37
|
/** Registry + rate-limited snapshot of accessible controls, rendered by the DOM overlay (DrawnUi AccessibilityManager). */
|
|
37
38
|
AccessibilityManager = new SkiaAccessibilityManager();
|
|
39
|
+
/** Mouse hover of the controls (drawnui-cross 6m): ReceivesHover / IsHovered / HoverChanged. */
|
|
40
|
+
Hover = new HoverManager();
|
|
38
41
|
content;
|
|
39
42
|
get Content() { return this.content; }
|
|
40
43
|
set Content(value) {
|
|
@@ -45,6 +48,7 @@ export class Canvas {
|
|
|
45
48
|
value.Parent = undefined;
|
|
46
49
|
value._superview = this;
|
|
47
50
|
}
|
|
51
|
+
this.Hover.RequestCheck();
|
|
48
52
|
this.Update();
|
|
49
53
|
}
|
|
50
54
|
surface;
|
|
@@ -201,6 +205,7 @@ export class Canvas {
|
|
|
201
205
|
root.Render({ Context: { Canvas: canvas, Surface: this.surface }, Destination: new SKRect(0, 0, w, h), Scale: scale });
|
|
202
206
|
}
|
|
203
207
|
this.AccessibilityManager.OnFrameEnd(this.RenderingScale, this.Element.width, this.Element.height, () => this.Update());
|
|
208
|
+
this.Hover.AfterFrame(this.content, this.activeTouchIds.size > 0);
|
|
204
209
|
const now = performance.now();
|
|
205
210
|
this.FrameTime = now - started;
|
|
206
211
|
this.FrameIndex++;
|
|
@@ -344,10 +349,14 @@ export class Canvas {
|
|
|
344
349
|
if (!type)
|
|
345
350
|
return;
|
|
346
351
|
if (type === "Moved" && !this.activeTouchIds.has(e.pointerId)) {
|
|
352
|
+
if (e.pointerType === "touch")
|
|
353
|
+
return; // touch never hovers
|
|
347
354
|
if (e.pointerType === "mouse")
|
|
348
355
|
this.UpdateCursor(e.offsetX, e.offsetY);
|
|
356
|
+
if (this.activeTouchIds.size === 0)
|
|
357
|
+
this.Hover.Move(this.content, e.offsetX * this.RenderingScale, e.offsetY * this.RenderingScale);
|
|
349
358
|
return;
|
|
350
|
-
}
|
|
359
|
+
}
|
|
351
360
|
if ((type === "Released" || type === "Cancelled") && !this.activeTouchIds.has(e.pointerId))
|
|
352
361
|
return; // Up of a pointer that never pressed here
|
|
353
362
|
// Capture so Up outside the element still arrives; throws for synthetic events (tests) — harmless.
|
|
@@ -377,6 +386,9 @@ export class Canvas {
|
|
|
377
386
|
this.UpdateTouchAction();
|
|
378
387
|
}
|
|
379
388
|
};
|
|
389
|
+
/** The mouse left the canvas: hover ends at once, also while content moves. */
|
|
390
|
+
onPointerLeave = (e) => { if (e.pointerType !== "touch")
|
|
391
|
+
this.Hover.Leave(); };
|
|
380
392
|
preventTouch = (e) => e.preventDefault();
|
|
381
393
|
/** Gestures="Enabled": touch / pen pointers whose Down a control used (see OnTouchAction). */
|
|
382
394
|
claimedTouches = new Set();
|
|
@@ -519,6 +531,7 @@ export class Canvas {
|
|
|
519
531
|
el.style.userSelect = "none";
|
|
520
532
|
for (const t of ["pointerdown", "pointermove", "pointerup", "pointercancel"])
|
|
521
533
|
el.addEventListener(t, this.onPointer);
|
|
534
|
+
el.addEventListener("pointerleave", this.onPointerLeave);
|
|
522
535
|
el.addEventListener("contextmenu", this.onContextMenu);
|
|
523
536
|
el.addEventListener("wheel", this.onWheel, { passive: false });
|
|
524
537
|
if (this.gestures === "Lock")
|
|
@@ -535,6 +548,7 @@ export class Canvas {
|
|
|
535
548
|
el.style.userSelect = "";
|
|
536
549
|
for (const t of ["pointerdown", "pointermove", "pointerup", "pointercancel"])
|
|
537
550
|
el.removeEventListener(t, this.onPointer);
|
|
551
|
+
el.removeEventListener("pointerleave", this.onPointerLeave);
|
|
538
552
|
el.removeEventListener("contextmenu", this.onContextMenu);
|
|
539
553
|
el.removeEventListener("wheel", this.onWheel);
|
|
540
554
|
el.removeEventListener("touchmove", this.preventTouch);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { SkiaControl } from "./SkiaControl";
|
|
2
|
+
/**
|
|
3
|
+
* DrawnUI hover (drawnui-cross 6m, C# DrawnView ReportHover / CommitHover): every control under the mouse that takes
|
|
4
|
+
* hover (`ReceivesHover`) is hovered, a card and the button inside it alike, as CSS :hover. Decided from the hit path
|
|
5
|
+
* of one pointer position, by the routing rules of ProcessGestures (visible, not InputTransparent, inside, children
|
|
6
|
+
* not locked, nothing below a control that blocks gestures below).
|
|
7
|
+
* While content moves under the pointer (a scroll glide, fling or pan, a carousel or drawer transition) hover is not
|
|
8
|
+
* tracked and what was hovered stays; one check at the last pointer position follows when it stops, and after
|
|
9
|
+
* controls are added or removed, a cell is rebound, a hovered control is hidden or a scroll jumps. Leaving the canvas
|
|
10
|
+
* clears hover at once. Touch never hovers. No allocation on frames where nothing changes.
|
|
11
|
+
*/
|
|
12
|
+
export declare class HoverManager {
|
|
13
|
+
/** The hovered controls, root first. */
|
|
14
|
+
readonly Hovered: SkiaControl[];
|
|
15
|
+
private readonly next;
|
|
16
|
+
private readonly path;
|
|
17
|
+
private readonly movers;
|
|
18
|
+
private hasPointer;
|
|
19
|
+
private x;
|
|
20
|
+
private y;
|
|
21
|
+
private paused;
|
|
22
|
+
private check;
|
|
23
|
+
/** The mouse (or a pen in range) moved to (x, y), canvas pixels, nothing pressed. */
|
|
24
|
+
Move(root: SkiaControl | undefined, x: number, y: number): void;
|
|
25
|
+
/** The pointer left the canvas: nothing is hovered, also while content moves. */
|
|
26
|
+
Leave(): void;
|
|
27
|
+
/** The controls under a still pointer may have changed: checked once at the end of the frame. */
|
|
28
|
+
RequestCheck(): void;
|
|
29
|
+
/** A control that moves its content (MovesContent) while drawn: hover waits while any of them moves. */
|
|
30
|
+
RegisterMover(control: SkiaControl): void;
|
|
31
|
+
/** End of a frame: pause while content moves, else run a pending check (at most one a frame, none while pressed). */
|
|
32
|
+
AfterFrame(root: SkiaControl | undefined, pressed: boolean): void;
|
|
33
|
+
private static Shown;
|
|
34
|
+
private Update;
|
|
35
|
+
/** `next` becomes the hovered set: the ones that left get HoverChanged(false), then the new ones (true). */
|
|
36
|
+
private Apply;
|
|
37
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { SKPoint } from "./Gestures";
|
|
2
|
+
/**
|
|
3
|
+
* DrawnUI hover (drawnui-cross 6m, C# DrawnView ReportHover / CommitHover): every control under the mouse that takes
|
|
4
|
+
* hover (`ReceivesHover`) is hovered, a card and the button inside it alike, as CSS :hover. Decided from the hit path
|
|
5
|
+
* of one pointer position, by the routing rules of ProcessGestures (visible, not InputTransparent, inside, children
|
|
6
|
+
* not locked, nothing below a control that blocks gestures below).
|
|
7
|
+
* While content moves under the pointer (a scroll glide, fling or pan, a carousel or drawer transition) hover is not
|
|
8
|
+
* tracked and what was hovered stays; one check at the last pointer position follows when it stops, and after
|
|
9
|
+
* controls are added or removed, a cell is rebound, a hovered control is hidden or a scroll jumps. Leaving the canvas
|
|
10
|
+
* clears hover at once. Touch never hovers. No allocation on frames where nothing changes.
|
|
11
|
+
*/
|
|
12
|
+
export class HoverManager {
|
|
13
|
+
/** The hovered controls, root first. */
|
|
14
|
+
Hovered = [];
|
|
15
|
+
next = [];
|
|
16
|
+
path = [];
|
|
17
|
+
movers = [];
|
|
18
|
+
hasPointer = false;
|
|
19
|
+
x = 0;
|
|
20
|
+
y = 0;
|
|
21
|
+
paused = false;
|
|
22
|
+
check = false;
|
|
23
|
+
/** The mouse (or a pen in range) moved to (x, y), canvas pixels, nothing pressed. */
|
|
24
|
+
Move(root, x, y) {
|
|
25
|
+
this.hasPointer = true;
|
|
26
|
+
this.x = x;
|
|
27
|
+
this.y = y;
|
|
28
|
+
if (this.paused)
|
|
29
|
+
return; // content moves under the pointer: what was hovered stays, one check when it stops
|
|
30
|
+
this.check = false;
|
|
31
|
+
this.Update(root);
|
|
32
|
+
}
|
|
33
|
+
/** The pointer left the canvas: nothing is hovered, also while content moves. */
|
|
34
|
+
Leave() {
|
|
35
|
+
this.hasPointer = false;
|
|
36
|
+
this.check = false;
|
|
37
|
+
this.next.length = 0;
|
|
38
|
+
this.Apply();
|
|
39
|
+
}
|
|
40
|
+
/** The controls under a still pointer may have changed: checked once at the end of the frame. */
|
|
41
|
+
RequestCheck() { if (this.hasPointer)
|
|
42
|
+
this.check = true; }
|
|
43
|
+
/** A control that moves its content (MovesContent) while drawn: hover waits while any of them moves. */
|
|
44
|
+
RegisterMover(control) { if (this.movers.indexOf(control) < 0)
|
|
45
|
+
this.movers.push(control); }
|
|
46
|
+
/** End of a frame: pause while content moves, else run a pending check (at most one a frame, none while pressed). */
|
|
47
|
+
AfterFrame(root, pressed) {
|
|
48
|
+
let moving = false;
|
|
49
|
+
for (let i = this.movers.length - 1; i >= 0; i--) {
|
|
50
|
+
const m = this.movers[i];
|
|
51
|
+
if (m.IsDisposed || !m.Superview) {
|
|
52
|
+
this.movers.splice(i, 1);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (!moving && m.IsVisible && m.MovesContent())
|
|
56
|
+
moving = true;
|
|
57
|
+
}
|
|
58
|
+
if (moving) {
|
|
59
|
+
this.paused = true;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (this.paused) {
|
|
63
|
+
this.paused = false;
|
|
64
|
+
this.check = true;
|
|
65
|
+
}
|
|
66
|
+
// a hovered control (or an ancestor) hidden or removed under a still pointer stops being hovered
|
|
67
|
+
for (let i = 0; i < this.Hovered.length && !this.check; i++)
|
|
68
|
+
if (!HoverManager.Shown(this.Hovered[i]))
|
|
69
|
+
this.check = true;
|
|
70
|
+
if (!this.check)
|
|
71
|
+
return;
|
|
72
|
+
this.check = false;
|
|
73
|
+
if (!this.hasPointer || pressed)
|
|
74
|
+
return;
|
|
75
|
+
this.Update(root);
|
|
76
|
+
}
|
|
77
|
+
static Shown(control) {
|
|
78
|
+
if (control.IsDisposed || !control.Superview)
|
|
79
|
+
return false;
|
|
80
|
+
for (let c = control; c; c = c.Parent)
|
|
81
|
+
if (!c.IsVisible)
|
|
82
|
+
return false;
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
Update(root) {
|
|
86
|
+
const path = this.path, next = this.next;
|
|
87
|
+
path.length = 0;
|
|
88
|
+
next.length = 0;
|
|
89
|
+
if (root && root.IsVisible && !root.InputTransparent) {
|
|
90
|
+
const point = root.TransformPointToLocalSpace(new SKPoint(this.x, this.y));
|
|
91
|
+
if (root.HitIsInside(point.X, point.Y))
|
|
92
|
+
root.CollectHovered(point, path);
|
|
93
|
+
}
|
|
94
|
+
for (const c of path)
|
|
95
|
+
if (c.ReceivesHover)
|
|
96
|
+
next.push(c);
|
|
97
|
+
this.Apply();
|
|
98
|
+
}
|
|
99
|
+
/** `next` becomes the hovered set: the ones that left get HoverChanged(false), then the new ones (true). */
|
|
100
|
+
Apply() {
|
|
101
|
+
const now = this.Hovered, next = this.next;
|
|
102
|
+
if (now.length === next.length) {
|
|
103
|
+
let same = true;
|
|
104
|
+
for (let i = 0; i < now.length && same; i++)
|
|
105
|
+
same = now[i] === next[i];
|
|
106
|
+
if (same)
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const was = now.splice(0);
|
|
110
|
+
now.push(...next);
|
|
111
|
+
for (const c of was)
|
|
112
|
+
if (now.indexOf(c) < 0)
|
|
113
|
+
c.ApplyHover(false);
|
|
114
|
+
for (const c of now)
|
|
115
|
+
if (was.indexOf(c) < 0)
|
|
116
|
+
c.ApplyHover(true);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -20,6 +20,19 @@ export declare class KeyboardManager {
|
|
|
20
20
|
static AttachToKeyboard(): void;
|
|
21
21
|
static Subscribe(down: (key: InputKey, e: KeyboardEvent) => void, char: (ch: string, e: KeyboardEvent) => void, up?: (key: InputKey, e: KeyboardEvent) => void): void;
|
|
22
22
|
static Unsubscribe(down: (key: InputKey, e: KeyboardEvent) => void, char: (ch: string, e: KeyboardEvent) => void, up?: (key: InputKey, e: KeyboardEvent) => void): void;
|
|
23
|
+
/**
|
|
24
|
+
* Hooks observe keys, they never take them from the element that owns them (C# 845b26e9, GitHub #231). True when a
|
|
25
|
+
* focused DOM element is the key's target: a page input, textarea, select, button, link or contenteditable, an
|
|
26
|
+
* accessibility overlay node, the drawn editor's hidden textarea. False when the key goes to the page itself (body,
|
|
27
|
+
* html) or the canvas. A handler that acts on keys (edits, moves, calls preventDefault) skips owned keys, except
|
|
28
|
+
* those of its own element.
|
|
29
|
+
*/
|
|
30
|
+
static IsOwnedByElement(e: KeyboardEvent): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* The key belongs to the input method (an IME composition, a soft keyboard): isComposing, keyCode 229, key "Process"
|
|
33
|
+
* or "Unidentified". Its result arrives as input events (TextInputProxy), never act on the key itself.
|
|
34
|
+
*/
|
|
35
|
+
static IsInputMethodKey(e: KeyboardEvent): boolean;
|
|
23
36
|
/** JS blurExternalTextInput: a page text input outside the canvas would keep receiving the keys. */
|
|
24
37
|
static BlurExternalTextInput(): void;
|
|
25
38
|
}
|
|
@@ -45,6 +45,24 @@ export class KeyboardManager {
|
|
|
45
45
|
if (up)
|
|
46
46
|
KeyboardManager.keyUp.delete(up);
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Hooks observe keys, they never take them from the element that owns them (C# 845b26e9, GitHub #231). True when a
|
|
50
|
+
* focused DOM element is the key's target: a page input, textarea, select, button, link or contenteditable, an
|
|
51
|
+
* accessibility overlay node, the drawn editor's hidden textarea. False when the key goes to the page itself (body,
|
|
52
|
+
* html) or the canvas. A handler that acts on keys (edits, moves, calls preventDefault) skips owned keys, except
|
|
53
|
+
* those of its own element.
|
|
54
|
+
*/
|
|
55
|
+
static IsOwnedByElement(e) {
|
|
56
|
+
const tag = e.target?.tagName;
|
|
57
|
+
return !!tag && tag !== "BODY" && tag !== "HTML" && tag !== "CANVAS";
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The key belongs to the input method (an IME composition, a soft keyboard): isComposing, keyCode 229, key "Process"
|
|
61
|
+
* or "Unidentified". Its result arrives as input events (TextInputProxy), never act on the key itself.
|
|
62
|
+
*/
|
|
63
|
+
static IsInputMethodKey(e) {
|
|
64
|
+
return e.isComposing || e.keyCode === 229 || e.key === "Process" || e.key === "Unidentified";
|
|
65
|
+
}
|
|
48
66
|
/** JS blurExternalTextInput: a page text input outside the canvas would keep receiving the keys. */
|
|
49
67
|
static BlurExternalTextInput() {
|
|
50
68
|
const a = typeof document !== "undefined" ? document.activeElement : null;
|
|
@@ -138,18 +138,43 @@ export declare class SkiaControl {
|
|
|
138
138
|
private compositeSurface?;
|
|
139
139
|
/** Own content or structure changed: the next composite record is a full one. */
|
|
140
140
|
private compositeFull;
|
|
141
|
+
/** Where the kept composite surface starts relative to DrawingRect (the effects margin it was laid out with). */
|
|
142
|
+
private compositeInsetX;
|
|
143
|
+
private compositeInsetY;
|
|
141
144
|
/** Canvas-pixel bounds this control covered when its parent last recorded it (composite erase region). */
|
|
142
145
|
private lastCompositeBounds?;
|
|
143
|
-
/**
|
|
146
|
+
/**
|
|
147
|
+
* What the last composite record did (diagnostics): "full" or "partial", the number of children re-recorded and which
|
|
148
|
+
* ones; Areas: the pixels erased and drawn again for changes deeper than a child (C# Areas), Changed: the controls
|
|
149
|
+
* those areas belong to (C# Changed).
|
|
150
|
+
*/
|
|
144
151
|
LastCompositeRecord: {
|
|
145
152
|
Mode: "full" | "partial";
|
|
146
153
|
Children: number;
|
|
147
154
|
Dirty: readonly SkiaControl[];
|
|
155
|
+
Areas: readonly SKRect[];
|
|
156
|
+
Changed: readonly SkiaControl[];
|
|
148
157
|
};
|
|
158
|
+
/** C# MaxCompositionAreas: more areas than this in one composite record draw it whole instead. */
|
|
159
|
+
static MaxCompositionAreas: number;
|
|
160
|
+
/** C# MaxCompositionShare: areas covering more than this share of a composite draw it whole instead. */
|
|
161
|
+
static MaxCompositionShare: number;
|
|
162
|
+
/**
|
|
163
|
+
* Changes deeper than a direct child since the last composite record (drawnui-cross 6m): the control, the direct child
|
|
164
|
+
* it is in, where it was drawn before the change (its own drawn bounds, as drawn) and its rect then.
|
|
165
|
+
*/
|
|
166
|
+
private compositeDeep?;
|
|
167
|
+
/** Set while an UpdateDraw climbs: the ancestors re-measure but an ImageComposite among them keeps its partial record. */
|
|
168
|
+
private static drawOrigin;
|
|
169
|
+
/** Nonzero while a composite redraws only some areas: layouts skip children entirely outside the clip. */
|
|
170
|
+
static CompositionCulling: number;
|
|
149
171
|
/** The children a composite record can re-record individually; layouts return their views. */
|
|
150
172
|
protected GetCompositeChildren(): readonly SkiaControl[];
|
|
151
173
|
/** C# TrackChildAsDirty (DirtyChildrenTracker): the child changed without a remeasure of this control. */
|
|
152
174
|
TrackChildAsDirty(child: SkiaControl): void;
|
|
175
|
+
private static readonly rejectRect;
|
|
176
|
+
/** During a partial composite record: true when this control draws nothing inside the canvas clip (skipped whole). */
|
|
177
|
+
OutsideCompositionClip(canvas: SkCanvas): boolean;
|
|
153
178
|
/** Drawn bounds in canvas pixels including transforms and effects margins (C# GetTransformedDirtyBounds). */
|
|
154
179
|
GetTransformedDirtyBounds(): SKRect;
|
|
155
180
|
/** Current cache, if any. */
|
|
@@ -160,6 +185,36 @@ export declare class SkiaControl {
|
|
|
160
185
|
/** Consume every gesture that lands on this control so nothing below (z-order) receives it. */
|
|
161
186
|
BlockGesturesBelow: boolean;
|
|
162
187
|
LockChildrenGestures: LockTouch;
|
|
188
|
+
private receivesHover?;
|
|
189
|
+
/**
|
|
190
|
+
* Whether this control takes mouse hover: the canvas sets IsHovered while the mouse is over it. Opt-in: false by
|
|
191
|
+
* default, true by default only in the controls that always had hover (SkiaButton, SkiaSlider, the toggles,
|
|
192
|
+
* SkiaRadioButton, SkiaCarousel, SkiaDrawer), never because a control has a tap handler. A HoverChanged handler turns
|
|
193
|
+
* it on (C# fluent OnHovered) unless it is set.
|
|
194
|
+
*/
|
|
195
|
+
get ReceivesHover(): boolean;
|
|
196
|
+
set ReceivesHover(value: boolean);
|
|
197
|
+
/** The value of ReceivesHover until it is set. */
|
|
198
|
+
protected ReceivesHoverByDefault(): boolean;
|
|
199
|
+
private isHovered;
|
|
200
|
+
/**
|
|
201
|
+
* True while the mouse is over this control and it takes hover. Every such control under the pointer is hovered at
|
|
202
|
+
* once (a card and the button inside it, as CSS :hover); it stays as it is while content moves under the pointer and
|
|
203
|
+
* is checked again when that stops. Touch never hovers.
|
|
204
|
+
*/
|
|
205
|
+
get IsHovered(): boolean;
|
|
206
|
+
/** IsHovered changed: true when the mouse came over this control, false when it left (C# HoverChanged / OnHovered). */
|
|
207
|
+
HoverChanged?: (sender: SkiaControl, hovered: boolean) => void;
|
|
208
|
+
/** Set by the canvas's HoverManager. */
|
|
209
|
+
ApplyHover(on: boolean): void;
|
|
210
|
+
/** A scroll, carousel or drawer moving its content right now: hover waits until it stops (C# IsScrolling / InTransition). */
|
|
211
|
+
MovesContent(): boolean;
|
|
212
|
+
/**
|
|
213
|
+
* Hover hit path (HoverManager): this control is under the point (in its own space), its children under it follow,
|
|
214
|
+
* top-most first, by the routing rules of ProcessGestures. True when this subtree blocks gestures below it, so the
|
|
215
|
+
* controls under it get no hover (a popup over a list).
|
|
216
|
+
*/
|
|
217
|
+
CollectHovered(point: SKPoint, into: SkiaControl[]): boolean;
|
|
163
218
|
TouchEffectColor: Color;
|
|
164
219
|
/** Plays a ripple (from the tap point) when Tapped fires. */
|
|
165
220
|
AnimationTapped: SkiaTouchAnimation;
|
|
@@ -211,6 +266,11 @@ export declare class SkiaControl {
|
|
|
211
266
|
* built in code-behind is styled on its first measure and keeps whatever the code already set.
|
|
212
267
|
*/
|
|
213
268
|
ApplyInitialStyles(atConstruction?: boolean): void;
|
|
269
|
+
/**
|
|
270
|
+
* C# IsChildrenItem: added by the app (JSX children or the `Children` list), not a part the control made itself. A new
|
|
271
|
+
* `Children` list drops only these, so a control's own parts keep their place.
|
|
272
|
+
*/
|
|
273
|
+
IsChildrenItem: boolean;
|
|
214
274
|
AddSubView(_control: SkiaControl): void;
|
|
215
275
|
InsertSubView(_index: number, control: SkiaControl): void;
|
|
216
276
|
RemoveSubView(_control: SkiaControl): void;
|
|
@@ -416,6 +476,30 @@ export declare class SkiaControl {
|
|
|
416
476
|
* cache holds its composited output, so those are staled before redrawing (DrawnUi RedrawCanvas + parent invalidation).
|
|
417
477
|
*/
|
|
418
478
|
RepaintComposition(): void;
|
|
479
|
+
/**
|
|
480
|
+
* Update() for a change of how this control looks, not of its size (a color set through props, C# NeedDraw): the same
|
|
481
|
+
* re-measure and cache invalidation, but an ImageComposite above redraws only this control's area instead of
|
|
482
|
+
* recording whole (drawnui-cross 6m). The record still goes whole when the control's rect changed after all.
|
|
483
|
+
*/
|
|
484
|
+
UpdateDraw(): void;
|
|
485
|
+
/**
|
|
486
|
+
* A change of this control that keeps its layout, to every ImageComposite above: a direct child is drawn again whole
|
|
487
|
+
* (C# TrackChildAsDirty), a deeper control by its area, read now as it was drawn (its last matrix and effects margin).
|
|
488
|
+
*/
|
|
489
|
+
private ReportToComposites;
|
|
490
|
+
/** The matrix this control will be drawn with (Render), from its current properties; undefined when none. */
|
|
491
|
+
private CurrentMatrix;
|
|
492
|
+
/** Where this control will be drawn: its rect grown by its effects margin, through its current matrix. */
|
|
493
|
+
private DrawnBoundsNow;
|
|
494
|
+
private static MapRect;
|
|
495
|
+
/** Its output depends on all of its content (a visual effect; a backdrop samples below): a change inside redraws it whole. */
|
|
496
|
+
protected get SamplesContent(): boolean;
|
|
497
|
+
/**
|
|
498
|
+
* Where a deep change shows inside the composite (drawnui-cross 6m rule b): the control as drawn before and as drawn
|
|
499
|
+
* now, then through the transform of every ancestor up to the composite. undefined: draw the child whole (an effect
|
|
500
|
+
* or a backdrop on the way, the control gone from the child); null: the layout changed, record whole.
|
|
501
|
+
*/
|
|
502
|
+
private static DeepArea;
|
|
419
503
|
/**
|
|
420
504
|
* DrawnUi IsParentIndependent: this control's size never changes the layout around it, so its own invalidation
|
|
421
505
|
* stops here (ancestors composite again, nothing above re-measures). The parent re-measures it on draw when
|