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.
Files changed (87) hide show
  1. package/README.md +17 -4
  2. package/SKIPPED.md +33 -27
  3. package/dist/controls/AnimatedFramesRenderer.d.ts +7 -0
  4. package/dist/controls/AnimatedFramesRenderer.js +16 -1
  5. package/dist/controls/DrawnGame.d.ts +40 -0
  6. package/dist/controls/DrawnGame.js +64 -0
  7. package/dist/controls/GridStructure.d.ts +16 -2
  8. package/dist/controls/GridStructure.js +40 -4
  9. package/dist/controls/SkiaBackdrop.d.ts +8 -2
  10. package/dist/controls/SkiaBackdrop.js +48 -8
  11. package/dist/controls/SkiaButton.d.ts +19 -2
  12. package/dist/controls/SkiaButton.js +41 -11
  13. package/dist/controls/SkiaCarousel.d.ts +9 -1
  14. package/dist/controls/SkiaCarousel.js +16 -3
  15. package/dist/controls/SkiaCheckbox.d.ts +0 -1
  16. package/dist/controls/SkiaCheckbox.js +0 -6
  17. package/dist/controls/SkiaDrawer.d.ts +2 -0
  18. package/dist/controls/SkiaDrawer.js +2 -0
  19. package/dist/controls/SkiaEditor.d.ts +12 -1
  20. package/dist/controls/SkiaEditor.js +46 -19
  21. package/dist/controls/SkiaGif.d.ts +3 -0
  22. package/dist/controls/SkiaGif.js +12 -2
  23. package/dist/controls/SkiaImage.js +15 -10
  24. package/dist/controls/SkiaLabel.d.ts +54 -3
  25. package/dist/controls/SkiaLabel.js +235 -52
  26. package/dist/controls/SkiaLabelFps.d.ts +16 -0
  27. package/dist/controls/SkiaLabelFps.js +41 -0
  28. package/dist/controls/SkiaLayout.d.ts +64 -7
  29. package/dist/controls/SkiaLayout.js +316 -19
  30. package/dist/controls/SkiaLottie.js +4 -2
  31. package/dist/controls/SkiaProgress.d.ts +2 -1
  32. package/dist/controls/SkiaProgress.js +6 -3
  33. package/dist/controls/SkiaRadioButton.js +4 -3
  34. package/dist/controls/SkiaScroll.d.ts +35 -5
  35. package/dist/controls/SkiaScroll.js +233 -23
  36. package/dist/controls/SkiaScrollBar.d.ts +27 -0
  37. package/dist/controls/SkiaScrollBar.js +51 -2
  38. package/dist/controls/SkiaShaderCarousel.d.ts +5 -0
  39. package/dist/controls/SkiaShaderCarousel.js +5 -0
  40. package/dist/controls/SkiaShape.d.ts +13 -2
  41. package/dist/controls/SkiaShape.js +50 -10
  42. package/dist/controls/SkiaSlider.d.ts +20 -2
  43. package/dist/controls/SkiaSlider.js +59 -5
  44. package/dist/controls/SkiaSprite.d.ts +1 -0
  45. package/dist/controls/SkiaSprite.js +4 -0
  46. package/dist/controls/SkiaSvg.js +1 -1
  47. package/dist/controls/SkiaSwitch.d.ts +0 -1
  48. package/dist/controls/SkiaSwitch.js +0 -6
  49. package/dist/controls/SkiaToggle.d.ts +25 -5
  50. package/dist/controls/SkiaToggle.js +58 -5
  51. package/dist/controls/SnappingLayout.d.ts +12 -1
  52. package/dist/controls/SnappingLayout.js +19 -1
  53. package/dist/core/Accessibility.d.ts +80 -0
  54. package/dist/core/Accessibility.js +365 -8
  55. package/dist/core/Animators.d.ts +37 -1
  56. package/dist/core/Animators.js +102 -9
  57. package/dist/core/Canvas.d.ts +108 -7
  58. package/dist/core/Canvas.js +363 -35
  59. package/dist/core/Easing.d.ts +12 -1
  60. package/dist/core/Easing.js +25 -1
  61. package/dist/core/Gestures.d.ts +48 -2
  62. package/dist/core/Gestures.js +95 -3
  63. package/dist/core/Hover.d.ts +47 -0
  64. package/dist/core/Hover.js +134 -0
  65. package/dist/core/KeyboardManager.d.ts +13 -0
  66. package/dist/core/KeyboardManager.js +18 -0
  67. package/dist/core/ScrollAnimators.js +9 -0
  68. package/dist/core/SkiaControl.d.ts +216 -7
  69. package/dist/core/SkiaControl.js +516 -59
  70. package/dist/core/SkiaEffect.d.ts +17 -0
  71. package/dist/core/SkiaEffect.js +43 -8
  72. package/dist/core/SkiaImageManager.js +1 -1
  73. package/dist/core/Styles.d.ts +51 -0
  74. package/dist/core/Styles.js +92 -0
  75. package/dist/core/Super.d.ts +17 -1
  76. package/dist/core/Super.js +35 -4
  77. package/dist/core/ViewsAdapter.d.ts +7 -2
  78. package/dist/core/ViewsAdapter.js +10 -3
  79. package/dist/index.d.ts +4 -0
  80. package/dist/index.js +4 -0
  81. package/dist/react/SkiaShell.js +33 -14
  82. package/dist/react/index.d.ts +15 -4
  83. package/dist/react/index.js +102 -15
  84. package/dist/react/reconciler.js +34 -12
  85. package/dist/vite/index.d.ts +9 -0
  86. package/dist/vite/index.js +36 -3
  87. package/package.json +33 -3
@@ -88,7 +88,7 @@ export class SkiaGridStructure {
88
88
  spans = new Map();
89
89
  explicitWidth;
90
90
  explicitHeight;
91
- constructor(grid, widthConstraint, heightConstraint, scale) {
91
+ constructor(grid, widthConstraint, heightConstraint, scale, children = grid.Views) {
92
92
  this.grid = grid;
93
93
  this.widthConstraint = widthConstraint;
94
94
  this.heightConstraint = heightConstraint;
@@ -99,7 +99,7 @@ export class SkiaGridStructure {
99
99
  this.explicitHeight = grid.HeightRequest;
100
100
  this.Rows = this.InitializeTracks(grid.RowDefinitions, grid.DefaultRowDefinition);
101
101
  this.Columns = this.InitializeTracks(grid.ColumnDefinitions, grid.DefaultColumnDefinition);
102
- this.children = grid.Views.filter((v) => v.IsVisible);
102
+ this.children = children.filter((v) => v.IsVisible);
103
103
  this.InitializeCells();
104
104
  this.MeasureCells();
105
105
  }
@@ -294,7 +294,20 @@ export class SkiaGridStructure {
294
294
  ResolveStarRows(heightConstraint) {
295
295
  this.ResolveStars(this.Rows, heightConstraint - this.GridHeight(), (c) => c.IsRowSpanStar, (v) => v.MeasuredSize.Units.Height);
296
296
  }
297
+ /**
298
+ * C# SizesAutoRow: the child decides the height of its single Auto row, so it is measured with the height that row
299
+ * could still grow to, not with the row's current size (drawnui-cross 6p).
300
+ */
301
+ SizesAutoRow(cell) { return cell.RowSpan === 1 && this.Rows[cell.Row].IsAuto; }
302
+ static NeedFillY(child) { return child.VerticalOptions === "Fill" && child.HeightRequest < 0; }
303
+ /**
304
+ * C# HeightOnAutoRow: the height the single Auto row could still grow to, unbounded for a vertical Fill child (which
305
+ * would take the whole constraint). The row's size so far came from a first pass at the grid's whole width, so a
306
+ * label wrapping in a star column was measured against its one-line height.
307
+ */
308
+ HeightOnAutoRow(cell, child) { return SkiaGridStructure.NeedFillY(child) ? Infinity : this.AvailableHeight(cell); }
297
309
  MeasureKnownCells() {
310
+ let autoRowGrew = false;
298
311
  for (const cell of this.cells) {
299
312
  if (!cell.NeedsKnownMeasurePass)
300
313
  continue;
@@ -307,12 +320,30 @@ export class SkiaGridStructure {
307
320
  continue;
308
321
  const child = this.children[cell.ViewIndex];
309
322
  const rect = this.GetCellBoundsFor(child);
310
- this.MeasurePx(child, rect.Width, rect.Height);
323
+ if (child.IsVisible && this.SizesAutoRow(cell)) {
324
+ // the row grows to the child at its real width, within the room the grid has left
325
+ let grown = this.MeasurePx(child, rect.Width, this.HeightOnAutoRow(cell, child)).Height;
326
+ const available = this.AvailableHeight(cell);
327
+ if (isFinite(available) && grown > available)
328
+ grown = available;
329
+ if (grown > this.Rows[cell.Row].Size) {
330
+ this.Rows[cell.Row].Update(grown);
331
+ autoRowGrew = true;
332
+ }
333
+ }
334
+ else
335
+ this.MeasurePx(child, rect.Width, rect.Height);
311
336
  if (cell.IsColumnSpanStar && cell.ColumnSpan > 1)
312
337
  this.TrackSpan({ Key: `c${cell.Column}:${cell.ColumnSpan}`, Start: cell.Column, Length: cell.ColumnSpan, IsColumn: true, Requested: rect.Width });
313
338
  if (cell.IsRowSpanStar && cell.RowSpan > 1)
314
339
  this.TrackSpan({ Key: `r${cell.Row}:${cell.RowSpan}`, Start: cell.Row, Length: cell.RowSpan, IsColumn: false, Requested: rect.Height });
315
340
  }
341
+ if (autoRowGrew) {
342
+ // star rows were resolved against the smaller Auto rows: they get what is left now, nothing when Auto took it all
343
+ SkiaGridStructure.ZeroOutStars(this.Rows);
344
+ if (this.heightConstraint - this.GridHeight() > 0)
345
+ this.ResolveStarRows(this.heightConstraint);
346
+ }
316
347
  }
317
348
  AvailableWidth(cell) {
318
349
  let w = 0, absolute = true;
@@ -390,10 +421,15 @@ export class SkiaGridStructure {
390
421
  RemeasureChildrenAtFinalCells() {
391
422
  for (const cell of this.cells) {
392
423
  const child = this.children[cell.ViewIndex];
424
+ if (!child.IsVisible)
425
+ continue;
393
426
  const rect = this.GetCellBoundsFor(child);
394
427
  if (rect.Width <= 0 || rect.Height <= 0)
395
428
  continue;
396
- const m = this.MeasurePx(child, rect.Width, rect.Height);
429
+ // on an Auto row a child that is not Fill vertically keeps the height the row can grow to, as in
430
+ // MeasureKnownCells: the same request returns at once, and wrapped text is not cut to the row
431
+ const height = this.SizesAutoRow(cell) && !SkiaGridStructure.NeedFillY(child) ? Math.max(rect.Height, this.AvailableHeight(cell)) : rect.Height;
432
+ const m = this.MeasurePx(child, rect.Width, height);
397
433
  if (cell.ColumnSpan === 1 && this.Columns[cell.Column].IsAuto && m.Width > this.Columns[cell.Column].Size)
398
434
  this.Columns[cell.Column].Update(m.Width);
399
435
  if (cell.RowSpan === 1 && this.Rows[cell.Row].IsAuto && m.Height > this.Rows[cell.Row].Size)
@@ -4,10 +4,14 @@ import { SkiaLayout } from "./SkiaLayout";
4
4
  /**
5
5
  * Mirrors DrawnUi SkiaBackdrop: draws what is already painted beneath its box through `Blur` (points, default 5)
6
6
  * and `Brightness` (gamma, 1 = unchanged), tinted by `BackgroundColor`; children draw first and are blurred too.
7
- * The snapshot comes from the surface being drawn (`UseContext`) or the on-screen surface. Not cached (re-blurs
8
- * on every frame it is drawn in; inside a cached parent it is re-recorded with that parent).
7
+ * The snapshot comes from the surface being drawn (`UseContext`, the area it covers there through every ancestor
8
+ * transform) or the on-screen surface. Not cached (re-blurs on every frame it is drawn in); picture-cached ancestors
9
+ * draw live once it is inside them (HostsBackdrop); inside an Image-cached ancestor it copies that cache's surface
10
+ * (right when what shows through the glass is inside that cache, wrong when it should show content outside it).
9
11
  */
10
12
  export declare class SkiaBackdrop extends SkiaLayout {
13
+ /** Samples what is drawn below: a composite above redraws it whole when something inside changes (drawnui-cross 6m). */
14
+ protected get SamplesContent(): boolean;
11
15
  Blur: number;
12
16
  Brightness: number;
13
17
  /** Snapshot the surface currently drawn into (true) or the on-screen surface (false). */
@@ -21,5 +25,7 @@ export declare class SkiaBackdrop extends SkiaLayout {
21
25
  protected PaintBackground(_ctx: DrawingContext): void;
22
26
  protected PaintsBackgroundWithoutColor(): boolean;
23
27
  protected Paint(ctx: DrawingContext): void;
28
+ /** C# StopPictureCachesAbove: the picture-cached ancestors draw live from the next frame, which this asks for. */
29
+ protected StopPictureCachesAbove(): void;
24
30
  protected OnDisposing(): void;
25
31
  }
@@ -4,10 +4,14 @@ import { SkiaLayout } from "./SkiaLayout";
4
4
  /**
5
5
  * Mirrors DrawnUi SkiaBackdrop: draws what is already painted beneath its box through `Blur` (points, default 5)
6
6
  * and `Brightness` (gamma, 1 = unchanged), tinted by `BackgroundColor`; children draw first and are blurred too.
7
- * The snapshot comes from the surface being drawn (`UseContext`) or the on-screen surface. Not cached (re-blurs
8
- * on every frame it is drawn in; inside a cached parent it is re-recorded with that parent).
7
+ * The snapshot comes from the surface being drawn (`UseContext`, the area it covers there through every ancestor
8
+ * transform) or the on-screen surface. Not cached (re-blurs on every frame it is drawn in); picture-cached ancestors
9
+ * draw live once it is inside them (HostsBackdrop); inside an Image-cached ancestor it copies that cache's surface
10
+ * (right when what shows through the glass is inside that cache, wrong when it should show content outside it).
9
11
  */
10
12
  export class SkiaBackdrop extends SkiaLayout {
13
+ /** Samples what is drawn below: a composite above redraws it whole when something inside changes (drawnui-cross 6m). */
14
+ get SamplesContent() { return true; }
11
15
  Blur = 5;
12
16
  Brightness = 1;
13
17
  /** Snapshot the surface currently drawn into (true) or the on-screen surface (false). */
@@ -38,16 +42,36 @@ export class SkiaBackdrop extends SkiaLayout {
38
42
  }
39
43
  if (!this.HasEffects)
40
44
  return;
45
+ // C# StopPictureCachesAbove: a screen copy recorded into an ancestor's picture would be replayed frozen; this frame
46
+ // draws no copy, the picture-cached ancestors draw live from the next frame on (HostsBackdrop)
47
+ if (ctx.Context.Recording) {
48
+ this.StopPictureCachesAbove();
49
+ return;
50
+ }
41
51
  // C# UseContext: snapshot the surface the pixels end up on — the on-screen surface or the nearest Image cache
42
- // surface (Origin = its top-left in canvas pixels). Content beneath is already there because caches are recorded
43
- // inline while the parent paints, so this also works while an Operations picture is being recorded.
52
+ // surface. Copied where the backdrop really is on that surface: its rect through the canvas matrix (ancestor
53
+ // scale, move, rotation, 3D, the cache origin), rounded out, clamped to the surface, and drawn back 1:1 with the
54
+ // matrix undone, clipped to the backdrop's own (maybe turned) rect: the blur stays the same on screen while an
55
+ // ancestor scales (C# 6fd3bae3). The global surface (UseContext false) is copied at the layout rect.
44
56
  const surface = this.UseContext ? ctx.Context.Surface : this.Superview?.Surface;
45
57
  if (!surface)
46
58
  return;
47
59
  surface.flush();
48
- const origin = this.UseContext ? ctx.Context.Origin : undefined;
49
- const ox = origin?.X ?? 0, oy = origin?.Y ?? 0;
50
- const l = Math.floor(d.Left - ox), t = Math.floor(d.Top - oy), r = Math.ceil(d.Right - ox), b = Math.ceil(d.Bottom - oy);
60
+ let l = Math.floor(d.Left), t = Math.floor(d.Top), r = Math.ceil(d.Right), b = Math.ceil(d.Bottom);
61
+ let device = null;
62
+ if (this.UseContext) {
63
+ const m = canvas.getTotalMatrix();
64
+ const p = CK.Matrix.mapPoints(m, [d.Left, d.Top, d.Right, d.Top, d.Right, d.Bottom, d.Left, d.Bottom]);
65
+ l = Math.max(0, Math.floor(Math.min(p[0], p[2], p[4], p[6])));
66
+ t = Math.max(0, Math.floor(Math.min(p[1], p[3], p[5], p[7])));
67
+ r = Math.min(surface.width(), Math.ceil(Math.max(p[0], p[2], p[4], p[6])));
68
+ b = Math.min(surface.height(), Math.ceil(Math.max(p[1], p[3], p[5], p[7])));
69
+ if (r <= l || b <= t)
70
+ return;
71
+ device = CK.Matrix.invert(m);
72
+ if (!device)
73
+ return;
74
+ }
51
75
  // whole-surface snapshot, sub-rect on draw: a bounded snapshot of a GPU surface is not origin-safe in CanvasKit
52
76
  const image = surface.makeImageSnapshot();
53
77
  if (!image)
@@ -61,7 +85,13 @@ export class SkiaBackdrop extends SkiaLayout {
61
85
  paint.setColorFilter(gamma);
62
86
  const saved = canvas.save();
63
87
  canvas.clipRect(CK.LTRBRect(d.Left, d.Top, d.Right, d.Bottom), CK.ClipOp.Intersect, true);
64
- canvas.drawImageRectOptions(image, CK.LTRBRect(l, t, r, b), CK.LTRBRect(d.Left, d.Top, d.Right, d.Bottom), CK.FilterMode.Linear, CK.MipmapMode.None, paint);
88
+ if (device) {
89
+ canvas.concat(device);
90
+ canvas.drawImageRectOptions(image, CK.LTRBRect(l, t, r, b), CK.LTRBRect(l, t, r, b), CK.FilterMode.Nearest, CK.MipmapMode.None, paint);
91
+ }
92
+ else {
93
+ canvas.drawImageRectOptions(image, CK.LTRBRect(l, t, r, b), CK.LTRBRect(d.Left, d.Top, d.Right, d.Bottom), CK.FilterMode.Linear, CK.MipmapMode.None, paint);
94
+ }
65
95
  canvas.restoreToCount(saved);
66
96
  blur?.delete();
67
97
  gamma?.delete();
@@ -76,5 +106,15 @@ export class SkiaBackdrop extends SkiaLayout {
76
106
  p = p.Parent;
77
107
  } });
78
108
  }
109
+ /** C# StopPictureCachesAbove: the picture-cached ancestors draw live from the next frame, which this asks for. */
110
+ StopPictureCachesAbove() {
111
+ for (let p = this.Parent; p; p = p.Parent) {
112
+ if (p.UsingCacheType === "Operations") {
113
+ p.HostsBackdrop = true;
114
+ p.InvalidateCache();
115
+ }
116
+ }
117
+ queueMicrotask(() => this.Update());
118
+ }
79
119
  OnDisposing() { this.snapshot?.delete(); this.snapshot = undefined; }
80
120
  }
@@ -10,6 +10,8 @@ import { type PrebuiltControlStyle, type ResolvedControlStyle } from "../core/Co
10
10
  * PanThreshold. Press feedback = ApplyEffect (ripple), no darkening.
11
11
  */
12
12
  export declare class SkiaButton extends SkiaLayout {
13
+ /** Takes mouse hover unless ReceivesHover is set false (C# ReceivesHoverByDefault). */
14
+ protected ReceivesHoverByDefault(): boolean;
13
15
  static PanThreshold: number;
14
16
  Text: string;
15
17
  /** Look of the default content (C# SkiaButton.ControlStyle): background, corners, font, minimum size when left unset. */
@@ -24,7 +26,17 @@ export declare class SkiaButton extends SkiaLayout {
24
26
  StrokeColor: Color;
25
27
  StrokeWidth: number;
26
28
  IsPressed: boolean;
27
- IsDisabled: boolean;
29
+ private respondsToGestures;
30
+ /**
31
+ * False switches the button off for the user: taps, presses and keyboard activation do nothing (no Tapped, Down, Up),
32
+ * a tap on it does not reach controls under it, and screen readers report it disabled. Code can still change it.
33
+ * Same property as on toggles, sliders, scrolls and drawers (drawnui-cross 6w). Default true.
34
+ */
35
+ get RespondsToGestures(): boolean;
36
+ set RespondsToGestures(v: boolean);
37
+ /** @deprecated The opposite of RespondsToGestures, which every control uses: set `RespondsToGestures = false`. */
38
+ get IsDisabled(): boolean;
39
+ set IsDisabled(v: boolean);
28
40
  LockPanning: boolean;
29
41
  /** Touch feedback played on Down (DrawnUi SkiaButton.ApplyEffect). */
30
42
  ApplyEffect: SkiaTouchAnimation;
@@ -41,10 +53,15 @@ export declare class SkiaButton extends SkiaLayout {
41
53
  static DefaultAccessibilityRole?: string;
42
54
  protected DefaultAccessibilityLabel(): string | undefined;
43
55
  protected DefaultAccessibilityCanInteract(): boolean;
56
+ /** With RespondsToGestures = false the button ignores taps, keyboard navigation skips it and it is reported disabled. */
57
+ protected AcceptsInput(): boolean;
44
58
  /** The button's own BackgroundColor/CornerRadius/Stroke are the frame's; the button itself paints nothing. */
45
59
  protected PaintBackground(): void;
46
60
  get UsingControlStyle(): ResolvedControlStyle;
47
- /** C# Create*StyleContent defaults: accent, corner radius, font size/weight, minimum content size. */
61
+ /**
62
+ * C# Create*StyleContent defaults: accent, corner radius, font size/weight, minimum content size.
63
+ * Every C# style calls SetDefaultMinimumContentSize(100, minH), so the width floor is 100 everywhere.
64
+ */
48
65
  private static Look;
49
66
  protected MeasureAbsolute(w: number, h: number, scale: number): ScaledSize;
50
67
  /** The frame sits under the padding box: arrange children in the full rect, the label centered inside padding. */
@@ -10,6 +10,8 @@ import { ResolveControlStyle } from "../core/ControlStyle";
10
10
  * PanThreshold. Press feedback = ApplyEffect (ripple), no darkening.
11
11
  */
12
12
  export class SkiaButton extends SkiaLayout {
13
+ /** Takes mouse hover unless ReceivesHover is set false (C# ReceivesHoverByDefault). */
14
+ ReceivesHoverByDefault() { return true; }
13
15
  static PanThreshold = 5;
14
16
  Text = "";
15
17
  /** Look of the default content (C# SkiaButton.ControlStyle): background, corners, font, minimum size when left unset. */
@@ -24,7 +26,20 @@ export class SkiaButton extends SkiaLayout {
24
26
  StrokeColor = Colors.Transparent;
25
27
  StrokeWidth = 0;
26
28
  IsPressed = false;
27
- IsDisabled = false;
29
+ respondsToGestures = true;
30
+ /**
31
+ * False switches the button off for the user: taps, presses and keyboard activation do nothing (no Tapped, Down, Up),
32
+ * a tap on it does not reach controls under it, and screen readers report it disabled. Code can still change it.
33
+ * Same property as on toggles, sliders, scrolls and drawers (drawnui-cross 6w). Default true.
34
+ */
35
+ get RespondsToGestures() { return this.respondsToGestures; }
36
+ set RespondsToGestures(v) { if (this.respondsToGestures !== v) {
37
+ this.respondsToGestures = v;
38
+ this.NotifyAccessibility();
39
+ } }
40
+ /** @deprecated The opposite of RespondsToGestures, which every control uses: set `RespondsToGestures = false`. */
41
+ get IsDisabled() { return !this.respondsToGestures; }
42
+ set IsDisabled(v) { this.RespondsToGestures = !v; }
28
43
  LockPanning = false;
29
44
  /** Touch feedback played on Down (DrawnUi SkiaButton.ApplyEffect). */
30
45
  ApplyEffect = "Ripple"; // C# default
@@ -49,22 +64,32 @@ export class SkiaButton extends SkiaLayout {
49
64
  this.label.VerticalOptions = "Center";
50
65
  this.AddSubView(this.frame);
51
66
  this.AddSubView(this.label);
67
+ // C# ButtonLabel is styled when the button builds its content, and ApplyProperties then overwrites FontFamily with
68
+ // the button's own (empty by default) value, which the style no longer touches. Same order here: style the caption
69
+ // now, MeasureAbsolute below pushes SkiaButton.FontFamily over it every measure.
70
+ this.frame.ApplyInitialStyles(true);
71
+ this.label.ApplyInitialStyles(true);
52
72
  }
53
73
  /** Set to `Aria.RoleButton` to expose every button (React extension). */
54
74
  static DefaultAccessibilityRole;
55
75
  DefaultAccessibilityLabel() { return this.Text || undefined; }
56
- DefaultAccessibilityCanInteract() { return !this.IsDisabled; }
76
+ DefaultAccessibilityCanInteract() { return this.respondsToGestures; }
77
+ /** With RespondsToGestures = false the button ignores taps, keyboard navigation skips it and it is reported disabled. */
78
+ AcceptsInput() { return this.respondsToGestures; }
57
79
  /** The button's own BackgroundColor/CornerRadius/Stroke are the frame's; the button itself paints nothing. */
58
80
  PaintBackground() { }
59
81
  get UsingControlStyle() { return ResolveControlStyle(this.ControlStyle); }
60
- /** C# Create*StyleContent defaults: accent, corner radius, font size/weight, minimum content size. */
82
+ /**
83
+ * C# Create*StyleContent defaults: accent, corner radius, font size/weight, minimum content size.
84
+ * Every C# style calls SetDefaultMinimumContentSize(100, minH), so the width floor is 100 everywhere.
85
+ */
61
86
  static Look(s) {
62
87
  switch (s) {
63
- case "Cupertino": return { bg: "#007AFF", radius: 8, font: 17, weight: 600, minH: 36, shadow: new SkiaShadow({ X: 0, Y: 1, Blur: 2, Opacity: 0.2, Color: Colors.Black }) };
64
- case "Material": return { bg: "#2196F3", radius: 4, font: 14, weight: 0, minH: 40, shadow: new SkiaShadow({ X: 0, Y: 2, Blur: 4, Opacity: 0.3, Color: Colors.Black }) };
65
- case "Material3": return { bg: "#6750A4", radius: 20, font: 14, weight: 0, minH: 40 };
66
- case "Windows": return { bg: "#0078D7", radius: 4, font: 15, weight: 500, minH: 32, shadow: new SkiaShadow({ X: 0, Y: 1, Blur: 1, Opacity: 0.2, Color: Colors.Black }) };
67
- default: return { bg: "#DC143C", radius: 8, font: 15, weight: 0, minH: 41 };
88
+ case "Cupertino": return { bg: "#007AFF", radius: 8, font: 17, weight: 600, minW: 100, minH: 36, shadow: new SkiaShadow({ X: 0, Y: 1, Blur: 2, Opacity: 0.2, Color: Colors.Black }) };
89
+ case "Material": return { bg: "#2196F3", radius: 4, font: 14, weight: 0, minW: 100, minH: 40, shadow: new SkiaShadow({ X: 0, Y: 2, Blur: 4, Opacity: 0.3, Color: Colors.Black }) };
90
+ case "Material3": return { bg: "#6750A4", radius: 20, font: 14, weight: 0, minW: 100, minH: 40 };
91
+ case "Windows": return { bg: "#0078D7", radius: 4, font: 15, weight: 500, minW: 100, minH: 32, shadow: new SkiaShadow({ X: 0, Y: 1, Blur: 1, Opacity: 0.2, Color: Colors.Black }) };
92
+ default: return { bg: "#DC143C", radius: 8, font: 15, weight: 0, minW: 100, minH: 41 };
68
93
  }
69
94
  }
70
95
  MeasureAbsolute(w, h, scale) {
@@ -75,7 +100,12 @@ export class SkiaButton extends SkiaLayout {
75
100
  this.frame.StrokeWidth = this.StrokeWidth;
76
101
  if (this.frame.Shadows[0] !== look.shadow)
77
102
  this.frame.Shadows = look.shadow ? [look.shadow] : [];
78
- if (this.MinimumHeightRequest < 0 && this.UsingControlStyle !== "Unset")
103
+ // C# SetDefaultMinimumContentSize(100, minH) from the style builder: a style default only where the app left the
104
+ // size unset, skipped on a Fill axis. C# adds Margins because it compares the minimum against a margin-inclusive
105
+ // size; here Measure applies it to the margin-excluded size and adds the margin after, so the plain value is used.
106
+ if (this.WidthRequest < 0 && this.MinimumWidthRequest < 0 && this.HorizontalOptions !== "Fill")
107
+ this.MinimumWidthRequest = look.minW;
108
+ if (this.HeightRequest < 0 && this.MinimumHeightRequest < 0 && this.VerticalOptions !== "Fill")
79
109
  this.MinimumHeightRequest = look.minH;
80
110
  this.label.Text = this.Text;
81
111
  this.label.TextColor = this.TextColor;
@@ -113,8 +143,8 @@ export class SkiaButton extends SkiaLayout {
113
143
  this.Repaint();
114
144
  }
115
145
  ProcessGestures(args, apply) {
116
- if (this.IsDisabled)
117
- return null;
146
+ if (!this.respondsToGestures)
147
+ return this.BlockedGestureResult(args); // no press, no Down / Up / Tapped; a tap on it stops here
118
148
  const point = args.Event.Location;
119
149
  if (args.Type === "Down") {
120
150
  this.IsPressed = true;
@@ -15,6 +15,8 @@ export interface SnapPoint {
15
15
  * `DynamicSize` sizes an auto-sized carousel from the selected slide, `SwipeSpeed` / `LinearSpeedMs` tune the snap.
16
16
  */
17
17
  export declare class SkiaCarousel extends SnappingLayout {
18
+ /** Takes mouse hover unless ReceivesHover is set false (C# ReceivesHoverByDefault). */
19
+ protected ReceivesHoverByDefault(): boolean;
18
20
  IsVertical: boolean;
19
21
  /** Side padding in points so the previous/next slides peek in. */
20
22
  SidesOffset: number;
@@ -99,7 +101,7 @@ export declare class SkiaCarousel extends SnappingLayout {
99
101
  private GetVirtualSnapPoints;
100
102
  protected GetVirtualAnchor(current: SKPoint): SnapPoint;
101
103
  protected FindNearestAnchorInternal(current: SKPoint, velocity: SKPoint): SKPoint;
102
- /** C# SkiaCarousel.ScrollToNearestAnchor: velocity below 100 counts as none; looped snaps around virtual anchors. */
104
+ /** C# SkiaCarousel.ScrollToNearestAnchor: velocity below SnapVelocityThreshold counts as none; looped snaps around virtual anchors. */
103
105
  ScrollToNearestAnchor(location: SKPoint, velocity: SKPoint): void;
104
106
  /** C# SkiaCarousel.SelectNextAnchor: base choice, plus a wrap to the virtual anchor at the strip borders when looped. */
105
107
  SelectNextAnchor(origin: SKPoint, velocity: SKPoint): SKPoint;
@@ -127,6 +129,12 @@ export declare class SkiaCarousel extends SnappingLayout {
127
129
  /** C# RenderViewsList: position every slide, draw the visible ones (+ neighbours when PreloadNeighboors), release the rest. */
128
130
  protected Paint(ctx: DrawingContext): void;
129
131
  private SlideRect;
132
+ /**
133
+ * Whether the slide at `index` takes part in hit-testing for the frame being rendered (C# IsSlideHitTestable).
134
+ * Every slide drawn on screen does by default; a carousel whose slides share one rect (SkiaShaderCarousel) narrows
135
+ * this to the slide the user actually sees.
136
+ */
137
+ protected IsSlideHitTestable(_index: number): boolean;
130
138
  protected GetGestureListeners(): readonly SkiaControl[];
131
139
  ProcessGestures(args: SkiaGesturesParameters, apply: GestureEventProcessingInfo): SkiaControl | null;
132
140
  protected DefaultAccessibilityLabel(): string | undefined;
@@ -11,6 +11,8 @@ import { SnappingLayout } from "./SnappingLayout";
11
11
  * `DynamicSize` sizes an auto-sized carousel from the selected slide, `SwipeSpeed` / `LinearSpeedMs` tune the snap.
12
12
  */
13
13
  export class SkiaCarousel extends SnappingLayout {
14
+ /** Takes mouse hover unless ReceivesHover is set false (C# ReceivesHoverByDefault). */
15
+ ReceivesHoverByDefault() { return true; }
14
16
  IsVertical = false;
15
17
  /** Side padding in points so the previous/next slides peek in. */
16
18
  SidesOffset = 0;
@@ -230,9 +232,10 @@ export class SkiaCarousel extends SnappingLayout {
230
232
  }
231
233
  return super.FindNearestAnchorInternal(current, velocity);
232
234
  }
233
- /** C# SkiaCarousel.ScrollToNearestAnchor: velocity below 100 counts as none; looped snaps around virtual anchors. */
235
+ /** C# SkiaCarousel.ScrollToNearestAnchor: velocity below SnapVelocityThreshold counts as none; looped snaps around virtual anchors. */
234
236
  ScrollToNearestAnchor(location, velocity) {
235
- velocity = new SKPoint(Math.abs(velocity.X) < 100 ? 0 : velocity.X, Math.abs(velocity.Y) < 100 ? 0 : velocity.Y);
237
+ const t = this.SnapVelocityThreshold;
238
+ velocity = new SKPoint(Math.abs(velocity.X) < t ? 0 : velocity.X, Math.abs(velocity.Y) < t ? 0 : velocity.Y);
236
239
  if (this.ApplyLoopedLogic) {
237
240
  const origin = this.FindNearestAnchorInternal(location, velocity);
238
241
  const target = this.SelectNextAnchor(origin, velocity);
@@ -504,7 +507,7 @@ export class SkiaCarousel extends SnappingLayout {
504
507
  view.Measure(inner.Width, inner.Height, scale); // recycled cells carry new content
505
508
  view.Arrange(this.SlideRect(inner, this.SlideOffset(cell.offset), scale), view.WidthRequest, view.HeightRequest, scale);
506
509
  view.Render(ctx);
507
- if (cell.visible)
510
+ if (cell.visible && this.IsSlideHitTestable(cell.index))
508
511
  visible.push(view);
509
512
  }
510
513
  this.visibleViews = visible;
@@ -514,6 +517,12 @@ export class SkiaCarousel extends SnappingLayout {
514
517
  ? SKRect.Create(inner.Left, inner.Top + offset.Y * scale, inner.Width, inner.Height)
515
518
  : SKRect.Create(inner.Left + offset.X * scale, inner.Top, inner.Width, inner.Height);
516
519
  }
520
+ /**
521
+ * Whether the slide at `index` takes part in hit-testing for the frame being rendered (C# IsSlideHitTestable).
522
+ * Every slide drawn on screen does by default; a carousel whose slides share one rect (SkiaShaderCarousel) narrows
523
+ * this to the slide the user actually sees.
524
+ */
525
+ IsSlideHitTestable(_index) { return true; }
517
526
  GetGestureListeners() { return this.visibleViews; }
518
527
  // ---- gestures (port of C# SkiaCarousel.ProcessGestures) ----
519
528
  ProcessGestures(args, apply) {
@@ -555,6 +564,10 @@ export class SkiaCarousel extends SnappingLayout {
555
564
  if (!this.IsUserPanning) {
556
565
  const movex = Math.abs(e.Distance.Total.X), movey = Math.abs(e.Distance.Total.Y);
557
566
  const along = this.IsVertical ? movey : movex, across = this.IsVertical ? movex : movey;
567
+ // a first move too short to tell the direction decides nothing yet (a 1 CSS px move at devicePixelRatio 2):
568
+ // marking it wrong kept the whole drag dead until the next press (DrawnUi.Rust c6b22e3; C# decides on every move)
569
+ if (along < scale * 2 && across < scale * 2)
570
+ return consumedDefault;
558
571
  if (along < scale * 2 || across > along) {
559
572
  this.wrongDirection = true;
560
573
  return consumedDefault;
@@ -15,7 +15,6 @@ export declare class SkiaCheckbox extends SkiaToggle {
15
15
  get ColorCheckOn(): Color;
16
16
  set ColorCheckOn(v: Color);
17
17
  protected StyleDefault(name: "ColorThumbOn" | "ColorFrameOn" | "ColorThumbOff" | "ColorFrameOff"): Color | undefined;
18
- private SetContentSize;
19
18
  protected CreateDefaultContent(): void;
20
19
  ApplyProperties(): void;
21
20
  }
@@ -27,12 +27,6 @@ export class SkiaCheckbox extends SkiaToggle {
27
27
  };
28
28
  return table[this.UsingControlStyle]?.[name];
29
29
  }
30
- SetContentSize(w, h) {
31
- if (this.WidthRequest < 0)
32
- this.WidthRequest = w;
33
- if (this.HeightRequest < 0)
34
- this.HeightRequest = h;
35
- }
36
30
  CreateDefaultContent() {
37
31
  const off = new SkiaShape();
38
32
  off.Tag = "FrameOff";
@@ -9,6 +9,8 @@ import { SnappingLayout } from "./SnappingLayout";
9
9
  * so it must be aligned to its edge by the parent (VerticalOptions="End" for FromBottom, etc.).
10
10
  */
11
11
  export declare class SkiaDrawer extends SnappingLayout {
12
+ /** Takes mouse hover unless ReceivesHover is set false (C# ReceivesHoverByDefault). */
13
+ protected ReceivesHoverByDefault(): boolean;
12
14
  Direction: DrawerDirection;
13
15
  /** Points that stay visible when closed. */
14
16
  HeaderSize: number;
@@ -7,6 +7,8 @@ import { SnappingLayout } from "./SnappingLayout";
7
7
  * so it must be aligned to its edge by the parent (VerticalOptions="End" for FromBottom, etc.).
8
8
  */
9
9
  export class SkiaDrawer extends SnappingLayout {
10
+ /** Takes mouse hover unless ReceivesHover is set false (C# ReceivesHoverByDefault). */
11
+ ReceivesHoverByDefault() { return true; }
10
12
  Direction = "FromBottom";
11
13
  /** Points that stay visible when closed. */
12
14
  HeaderSize = 0;
@@ -58,7 +58,8 @@ export declare class SkiaEditor extends SkiaShape {
58
58
  private readonly placeholder;
59
59
  private scrollX;
60
60
  private scrollY;
61
- private blink;
61
+ /** The caret blinks on a timer, asleep between blinks: no frame runs while it waits (DrawnUi.Rust editor_rules). */
62
+ private blinkTimer;
62
63
  private caretVisible;
63
64
  private subscribed;
64
65
  private stubSelectionStop;
@@ -102,6 +103,9 @@ export declare class SkiaEditor extends SkiaShape {
102
103
  set CursorPosition(v: number);
103
104
  get SelectionLength(): number;
104
105
  set SelectionLength(v: number);
106
+ /** Shows the caret for a full half second, then blinks every 500 ms. */
107
+ private StartBlink;
108
+ private StopBlink;
105
109
  private OnCursorMoved;
106
110
  get IsFocused(): boolean;
107
111
  set IsFocused(v: boolean);
@@ -128,7 +132,14 @@ export declare class SkiaEditor extends SkiaShape {
128
132
  private LabelPoint;
129
133
  protected Paint(ctx: DrawingContext): void;
130
134
  ProcessGestures(args: SkiaGesturesParameters, apply: GestureEventProcessingInfo): SkiaControl | null;
135
+ /** A text field is a Tab stop by default, like a native text box, with no Tapped handler needed. */
136
+ protected DefaultAccessibilityCanInteract(): boolean;
137
+ /** Keyboard navigation reached the field (Tab) or left it: take the caret and the input sink like a native text box, or release them. */
138
+ OnAccessibilityFocused(focused: boolean): void;
139
+ /** Enter / Space on the field while it has no caret start editing; while editing they are text. */
131
140
  OnAccessibilityActivated(): void;
141
+ /** While editing, arrows, Home / End and PageUp / PageDown belong to the text: they never move a surrounding group. */
142
+ OnAccessibilityKey(_key: string): boolean;
132
143
  protected DefaultAccessibilityLabel(): string | undefined;
133
144
  Submit(): void;
134
145
  private ExecuteSubmit;