drawnui-react 0.1.0-preview.1

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 (111) hide show
  1. package/LICENSE +21 -0
  2. package/PARITY.md +287 -0
  3. package/README.md +92 -0
  4. package/SKIPPED.md +107 -0
  5. package/dist/controls/AnimatedFramesRenderer.d.ts +58 -0
  6. package/dist/controls/AnimatedFramesRenderer.js +133 -0
  7. package/dist/controls/GridStructure.d.ts +79 -0
  8. package/dist/controls/GridStructure.js +403 -0
  9. package/dist/controls/RefreshIndicator.d.ts +35 -0
  10. package/dist/controls/RefreshIndicator.js +82 -0
  11. package/dist/controls/SkiaBackdrop.d.ts +25 -0
  12. package/dist/controls/SkiaBackdrop.js +80 -0
  13. package/dist/controls/SkiaButton.d.ts +58 -0
  14. package/dist/controls/SkiaButton.js +148 -0
  15. package/dist/controls/SkiaCarousel.d.ts +133 -0
  16. package/dist/controls/SkiaCarousel.js +603 -0
  17. package/dist/controls/SkiaCheckbox.d.ts +21 -0
  18. package/dist/controls/SkiaCheckbox.js +118 -0
  19. package/dist/controls/SkiaDecoratedGrid.d.ts +15 -0
  20. package/dist/controls/SkiaDecoratedGrid.js +46 -0
  21. package/dist/controls/SkiaDrawer.d.ts +46 -0
  22. package/dist/controls/SkiaDrawer.js +212 -0
  23. package/dist/controls/SkiaDynamicDrawnCell.d.ts +11 -0
  24. package/dist/controls/SkiaDynamicDrawnCell.js +17 -0
  25. package/dist/controls/SkiaEditor.d.ts +163 -0
  26. package/dist/controls/SkiaEditor.js +803 -0
  27. package/dist/controls/SkiaGif.d.ts +54 -0
  28. package/dist/controls/SkiaGif.js +206 -0
  29. package/dist/controls/SkiaHotspot.d.ts +13 -0
  30. package/dist/controls/SkiaHotspot.js +36 -0
  31. package/dist/controls/SkiaImage.d.ts +75 -0
  32. package/dist/controls/SkiaImage.js +222 -0
  33. package/dist/controls/SkiaImageTiles.d.ts +27 -0
  34. package/dist/controls/SkiaImageTiles.js +76 -0
  35. package/dist/controls/SkiaLabel.d.ts +160 -0
  36. package/dist/controls/SkiaLabel.js +580 -0
  37. package/dist/controls/SkiaLayout.d.ts +163 -0
  38. package/dist/controls/SkiaLayout.js +723 -0
  39. package/dist/controls/SkiaLottie.d.ts +75 -0
  40. package/dist/controls/SkiaLottie.js +290 -0
  41. package/dist/controls/SkiaProgress.d.ts +40 -0
  42. package/dist/controls/SkiaProgress.js +80 -0
  43. package/dist/controls/SkiaRadioButton.d.ts +32 -0
  44. package/dist/controls/SkiaRadioButton.js +157 -0
  45. package/dist/controls/SkiaRichLabel.d.ts +77 -0
  46. package/dist/controls/SkiaRichLabel.js +274 -0
  47. package/dist/controls/SkiaScroll.d.ts +231 -0
  48. package/dist/controls/SkiaScroll.js +958 -0
  49. package/dist/controls/SkiaScrollBar.d.ts +56 -0
  50. package/dist/controls/SkiaScrollBar.js +158 -0
  51. package/dist/controls/SkiaShaderCarousel.d.ts +67 -0
  52. package/dist/controls/SkiaShaderCarousel.js +246 -0
  53. package/dist/controls/SkiaShape.d.ts +63 -0
  54. package/dist/controls/SkiaShape.js +317 -0
  55. package/dist/controls/SkiaSlider.d.ts +74 -0
  56. package/dist/controls/SkiaSlider.js +243 -0
  57. package/dist/controls/SkiaSprite.d.ts +99 -0
  58. package/dist/controls/SkiaSprite.js +324 -0
  59. package/dist/controls/SkiaSpriteSet.d.ts +27 -0
  60. package/dist/controls/SkiaSpriteSet.js +75 -0
  61. package/dist/controls/SkiaSvg.d.ts +43 -0
  62. package/dist/controls/SkiaSvg.js +181 -0
  63. package/dist/controls/SkiaSwitch.d.ts +26 -0
  64. package/dist/controls/SkiaSwitch.js +147 -0
  65. package/dist/controls/SkiaToggle.d.ts +50 -0
  66. package/dist/controls/SkiaToggle.js +71 -0
  67. package/dist/controls/SnappingLayout.d.ts +78 -0
  68. package/dist/controls/SnappingLayout.js +217 -0
  69. package/dist/controls/TextSpan.d.ts +70 -0
  70. package/dist/controls/TextSpan.js +76 -0
  71. package/dist/core/Accessibility.d.ts +104 -0
  72. package/dist/core/Accessibility.js +150 -0
  73. package/dist/core/Animators.d.ts +82 -0
  74. package/dist/core/Animators.js +247 -0
  75. package/dist/core/Canvas.d.ts +89 -0
  76. package/dist/core/Canvas.js +328 -0
  77. package/dist/core/ControlStyle.d.ts +6 -0
  78. package/dist/core/ControlStyle.js +11 -0
  79. package/dist/core/Easing.d.ts +13 -0
  80. package/dist/core/Easing.js +15 -0
  81. package/dist/core/Gestures.d.ts +74 -0
  82. package/dist/core/Gestures.js +104 -0
  83. package/dist/core/ImageEffects.d.ts +29 -0
  84. package/dist/core/ImageEffects.js +55 -0
  85. package/dist/core/KeyboardManager.d.ts +25 -0
  86. package/dist/core/KeyboardManager.js +54 -0
  87. package/dist/core/ScrollAnimators.d.ts +90 -0
  88. package/dist/core/ScrollAnimators.js +244 -0
  89. package/dist/core/SkiaControl.d.ts +401 -0
  90. package/dist/core/SkiaControl.js +1239 -0
  91. package/dist/core/SkiaEffect.d.ts +216 -0
  92. package/dist/core/SkiaEffect.js +571 -0
  93. package/dist/core/SkiaImageManager.d.ts +43 -0
  94. package/dist/core/SkiaImageManager.js +122 -0
  95. package/dist/core/Super.d.ts +61 -0
  96. package/dist/core/Super.js +178 -0
  97. package/dist/core/TextInputProxy.d.ts +31 -0
  98. package/dist/core/TextInputProxy.js +144 -0
  99. package/dist/core/Types.d.ts +177 -0
  100. package/dist/core/Types.js +122 -0
  101. package/dist/core/ViewsAdapter.d.ts +41 -0
  102. package/dist/core/ViewsAdapter.js +113 -0
  103. package/dist/index.d.ts +48 -0
  104. package/dist/index.js +48 -0
  105. package/dist/react/SkiaShell.d.ts +144 -0
  106. package/dist/react/SkiaShell.js +490 -0
  107. package/dist/react/index.d.ts +110 -0
  108. package/dist/react/index.js +158 -0
  109. package/dist/react/reconciler.d.ts +14 -0
  110. package/dist/react/reconciler.js +160 -0
  111. package/package.json +60 -0
@@ -0,0 +1,58 @@
1
+ import { type DrawingContext, SkiaControl } from "../core/SkiaControl";
2
+ import { SkiaValueAnimator } from "../core/Animators";
3
+ /**
4
+ * Mirrors DrawnUi AnimatedFramesRenderer: base for controls that play frames (Lottie, GIF, sprites) with a
5
+ * range animator driven by the canvas frame loop: `AutoPlay`, `Repeat` (-1 = forever), `SpeedRatio`,
6
+ * `DefaultFrame` (-1 = last), `Start`/`Stop`/`Seek`, `Started`/`Finished`.
7
+ */
8
+ export declare class AnimatedFramesRenderer extends SkiaControl {
9
+ Finished?: (sender: AnimatedFramesRenderer) => void;
10
+ Started?: (sender: AnimatedFramesRenderer) => void;
11
+ /** Range animator over the frames, created on the first layout (C# RangeAnimator). */
12
+ Animator?: SkiaValueAnimator;
13
+ /** Start() was asked before the control could play; plays as soon as it can. */
14
+ PlayWhenAvailable: boolean;
15
+ protected WasStarted: boolean;
16
+ private delayedPlay;
17
+ private wasLayout;
18
+ private defaultFrame;
19
+ private speedRatio;
20
+ private repeat;
21
+ private autoPlay;
22
+ /** Frame shown when not playing; -1 = the last frame. */
23
+ get DefaultFrame(): number;
24
+ set DefaultFrame(v: number);
25
+ /** Playback speed multiplier. */
26
+ get SpeedRatio(): number;
27
+ set SpeedRatio(v: number);
28
+ /** > 0 how many more times to repeat, < 0 loops forever. */
29
+ get Repeat(): number;
30
+ set Repeat(v: number);
31
+ get AutoPlay(): boolean;
32
+ set AutoPlay(v: boolean);
33
+ get IsPlaying(): boolean;
34
+ protected get CanPlay(): boolean;
35
+ protected Paint(ctx: DrawingContext): void;
36
+ /** Draws the current frame into ctx.Destination. */
37
+ protected RenderFrame(_ctx: DrawingContext): void;
38
+ protected OnLayoutChanged(): void;
39
+ PlayIfNeeded(): void;
40
+ InitializeAnimator(): void;
41
+ /** Configure the animator range/speed once the frames source is known. */
42
+ protected OnAnimatorInitializing(): void;
43
+ /** The animator produced a new frame value. */
44
+ protected OnAnimatorUpdated(_value: number): void;
45
+ protected CheckCanStartAnimator(): boolean;
46
+ protected OnAnimatorStarting(): void;
47
+ protected OnAnimatorSeeking(_frame: number): void;
48
+ Seek(frame: number): void;
49
+ Start(delayMs?: number): void;
50
+ Stop(): void;
51
+ /** Frame at a 0..1 ratio of the animator range. */
52
+ GetFrameAt(ratio: number): number;
53
+ protected OnFinished(): void;
54
+ protected OnStarted(): void;
55
+ protected ApplySpeed(): void;
56
+ protected ApplyDefaultFrame(): void;
57
+ protected OnDisposing(): void;
58
+ }
@@ -0,0 +1,133 @@
1
+ import { SkiaControl } from "../core/SkiaControl";
2
+ import { SkiaValueAnimator } from "../core/Animators";
3
+ /**
4
+ * Mirrors DrawnUi AnimatedFramesRenderer: base for controls that play frames (Lottie, GIF, sprites) with a
5
+ * range animator driven by the canvas frame loop: `AutoPlay`, `Repeat` (-1 = forever), `SpeedRatio`,
6
+ * `DefaultFrame` (-1 = last), `Start`/`Stop`/`Seek`, `Started`/`Finished`.
7
+ */
8
+ export class AnimatedFramesRenderer extends SkiaControl {
9
+ Finished;
10
+ Started;
11
+ /** Range animator over the frames, created on the first layout (C# RangeAnimator). */
12
+ Animator;
13
+ /** Start() was asked before the control could play; plays as soon as it can. */
14
+ PlayWhenAvailable = false;
15
+ WasStarted = false;
16
+ delayedPlay = false;
17
+ wasLayout = false;
18
+ defaultFrame = 0;
19
+ speedRatio = 1;
20
+ repeat = 0;
21
+ autoPlay = true;
22
+ /** Frame shown when not playing; -1 = the last frame. */
23
+ get DefaultFrame() { return this.defaultFrame; }
24
+ set DefaultFrame(v) { if (this.defaultFrame !== v) {
25
+ this.defaultFrame = v;
26
+ this.ApplyDefaultFrame();
27
+ } }
28
+ /** Playback speed multiplier. */
29
+ get SpeedRatio() { return this.speedRatio; }
30
+ set SpeedRatio(v) { if (this.speedRatio !== v) {
31
+ this.speedRatio = v;
32
+ this.ApplySpeed();
33
+ } }
34
+ /** > 0 how many more times to repeat, < 0 loops forever. */
35
+ get Repeat() { return this.repeat; }
36
+ set Repeat(v) { if (this.repeat !== v) {
37
+ this.repeat = v;
38
+ if (this.Animator)
39
+ this.Animator.Repeat = v;
40
+ } }
41
+ get AutoPlay() { return this.autoPlay; }
42
+ set AutoPlay(v) { if (this.autoPlay !== v) {
43
+ this.autoPlay = v;
44
+ if (!v)
45
+ this.Stop();
46
+ } }
47
+ get IsPlaying() { return !!this.Animator?.IsRunning; }
48
+ get CanPlay() { return this.wasLayout && this.CheckCanStartAnimator(); }
49
+ Paint(ctx) { this.RenderFrame(ctx); }
50
+ /** Draws the current frame into ctx.Destination. */
51
+ RenderFrame(_ctx) { }
52
+ OnLayoutChanged() {
53
+ super.OnLayoutChanged();
54
+ // Arrange runs every frame here (C# only on a real layout change): initialize once, or a playing
55
+ // animator would be restarted at its first frame on every draw
56
+ if (!this.wasLayout) {
57
+ this.wasLayout = true;
58
+ this.InitializeAnimator();
59
+ }
60
+ this.PlayIfNeeded();
61
+ }
62
+ PlayIfNeeded() {
63
+ if (this.PlayWhenAvailable && this.Animator) {
64
+ this.PlayWhenAvailable = false;
65
+ if (!this.Animator.IsRunning)
66
+ this.Start();
67
+ }
68
+ }
69
+ InitializeAnimator() {
70
+ if (!this.Animator) {
71
+ const a = new SkiaValueAnimator(this);
72
+ a.OnUpdated = (v) => this.OnAnimatorUpdated(v);
73
+ a.OnStart = () => { this.WasStarted = true; this.OnStarted(); };
74
+ a.OnStop = () => { if (this.WasStarted)
75
+ this.OnFinished(); this.WasStarted = false; };
76
+ this.Animator = a;
77
+ }
78
+ this.Animator.Repeat = this.repeat;
79
+ this.OnAnimatorInitializing();
80
+ if (this.delayedPlay || (this.autoPlay && this.CheckCanStartAnimator())) {
81
+ this.delayedPlay = false;
82
+ this.Start();
83
+ }
84
+ }
85
+ /** Configure the animator range/speed once the frames source is known. */
86
+ OnAnimatorInitializing() { }
87
+ /** The animator produced a new frame value. */
88
+ OnAnimatorUpdated(_value) { }
89
+ CheckCanStartAnimator() { return true; }
90
+ OnAnimatorStarting() { }
91
+ OnAnimatorSeeking(_frame) { }
92
+ Seek(frame) { this.OnAnimatorSeeking(frame); }
93
+ Start(delayMs = 0) {
94
+ if (!this.Animator) {
95
+ this.delayedPlay = true;
96
+ return;
97
+ }
98
+ if (this.Animator.IsRunning)
99
+ this.Animator.Stop();
100
+ if (this.CanPlay) {
101
+ this.Animator.Repeat = this.repeat; // the animator consumes its counter while looping
102
+ this.OnAnimatorStarting();
103
+ this.Animator.Start(delayMs);
104
+ }
105
+ if (!this.Animator.IsRunning)
106
+ this.PlayWhenAvailable = true;
107
+ this.InvalidateCache();
108
+ this.RepaintComposition();
109
+ }
110
+ Stop() {
111
+ this.PlayWhenAvailable = false;
112
+ this.delayedPlay = false;
113
+ if (!this.Animator)
114
+ return;
115
+ this.Animator.Stop();
116
+ this.InvalidateCache();
117
+ this.RepaintComposition();
118
+ }
119
+ /** Frame at a 0..1 ratio of the animator range. */
120
+ GetFrameAt(ratio) { return this.Animator ? (this.Animator.mMaxValue - this.Animator.mMinValue) * ratio : 0; }
121
+ OnFinished() { this.Finished?.(this); }
122
+ OnStarted() { this.Started?.(this); }
123
+ ApplySpeed() { }
124
+ ApplyDefaultFrame() { if (!this.IsPlaying && this.Animator)
125
+ this.Seek(this.defaultFrame); }
126
+ OnDisposing() {
127
+ if (this.Animator) {
128
+ this.Stop();
129
+ this.Animator.Dispose();
130
+ this.Animator = undefined;
131
+ }
132
+ }
133
+ }
@@ -0,0 +1,79 @@
1
+ import type { SkiaControl } from "../core/SkiaControl";
2
+ import type { GridLength } from "../core/Types";
3
+ import type { SkiaLayout } from "./SkiaLayout";
4
+ type GridUnit = "Absolute" | "Auto" | "Star";
5
+ /** One row/column track (MAUI DefinitionInfo): its GridLength and the resolved size in points. */
6
+ export declare class DefinitionInfo {
7
+ Unit: GridUnit;
8
+ Value: number;
9
+ Size: number;
10
+ constructor(length: GridLength);
11
+ get IsAuto(): boolean;
12
+ get IsStar(): boolean;
13
+ get IsAbsolute(): boolean;
14
+ /** Auto tracks grow to the largest measured child. */
15
+ Update(size: number): void;
16
+ }
17
+ /** Parses "*, 2*, Auto, 100" (or an array of the same values) into tracks. */
18
+ export declare function ParseDefinitions(defs: string | GridLength[] | undefined): DefinitionInfo[];
19
+ /**
20
+ * Port of DrawnUi SkiaGridStructure (itself adapted from the MAUI Grid manager). Works in POINTS: constraints come in
21
+ * already reduced by the grid's padding, cells are returned relative to the padded box. Children are measured in
22
+ * pixels through SkiaControl.Measure and their Units sizes drive the tracks.
23
+ */
24
+ export declare class SkiaGridStructure {
25
+ private readonly grid;
26
+ private readonly widthConstraint;
27
+ private readonly heightConstraint;
28
+ private readonly scale;
29
+ readonly Rows: DefinitionInfo[];
30
+ readonly Columns: DefinitionInfo[];
31
+ readonly ColumnSpacing: number;
32
+ readonly RowSpacing: number;
33
+ private readonly children;
34
+ private readonly cells;
35
+ private readonly spans;
36
+ private readonly explicitWidth;
37
+ private readonly explicitHeight;
38
+ constructor(grid: SkiaLayout, widthConstraint: number, heightConstraint: number, scale: number);
39
+ private InitializeTracks;
40
+ private static CreateDefinitionIfMissing;
41
+ private static ToLengthType;
42
+ private InitializeCells;
43
+ private static Sum;
44
+ GridWidth(): number;
45
+ GridHeight(): number;
46
+ LeftEdgeOfColumn(column: number): number;
47
+ TopEdgeOfRow(row: number): number;
48
+ /** Cell rectangle in points relative to the padded box (+ offsets). */
49
+ GetCellBoundsFor(view: SkiaControl, xOffset?: number, yOffset?: number): {
50
+ Left: number;
51
+ Top: number;
52
+ Width: number;
53
+ Height: number;
54
+ };
55
+ private MeasureCells;
56
+ /** Points -> pixels for Measure; negative/NaN = unconstrained. */
57
+ private MeasurePx;
58
+ private MeasureChild;
59
+ private TrackSpan;
60
+ private ResolveSpans;
61
+ private static ResolveSpan;
62
+ private ApplyMinimumDimensionsFromFillChildren;
63
+ private ResolveStars;
64
+ private ResolveStarColumns;
65
+ private ResolveStarRows;
66
+ private MeasureKnownCells;
67
+ private AvailableWidth;
68
+ private AvailableHeight;
69
+ /** Re-resolves stars against the final size when the grid has an explicit size request (C# DecompressStars). */
70
+ DecompressStars(targetWidth: number, targetHeight: number): void;
71
+ private CompressStarMeasurements;
72
+ /** Stars shrink to what their cells actually need (so an auto-sized grid does not take the whole constraint). */
73
+ private CompressStars;
74
+ private static ExpandStarsInSpan;
75
+ private static ZeroOutStars;
76
+ /** Tracks are final only now: measure every child at its final cell so its internal layout matches the arranged box. */
77
+ RemeasureChildrenAtFinalCells(): void;
78
+ }
79
+ export {};
@@ -0,0 +1,403 @@
1
+ /** One row/column track (MAUI DefinitionInfo): its GridLength and the resolved size in points. */
2
+ export class DefinitionInfo {
3
+ Unit;
4
+ Value;
5
+ Size;
6
+ constructor(length) {
7
+ if (typeof length === "number") {
8
+ this.Unit = "Absolute";
9
+ this.Value = length;
10
+ this.Size = length;
11
+ }
12
+ else if (length === "Auto") {
13
+ this.Unit = "Auto";
14
+ this.Value = 1;
15
+ this.Size = 0;
16
+ }
17
+ else {
18
+ this.Unit = "Star";
19
+ this.Value = length === "*" ? 1 : parseFloat(length) || 1;
20
+ this.Size = 0;
21
+ }
22
+ }
23
+ get IsAuto() { return this.Unit === "Auto"; }
24
+ get IsStar() { return this.Unit === "Star"; }
25
+ get IsAbsolute() { return this.Unit === "Absolute"; }
26
+ /** Auto tracks grow to the largest measured child. */
27
+ Update(size) { if (size > this.Size)
28
+ this.Size = size; }
29
+ }
30
+ /** Parses "*, 2*, Auto, 100" (or an array of the same values) into tracks. */
31
+ export function ParseDefinitions(defs) {
32
+ if (!defs)
33
+ return [];
34
+ const list = typeof defs === "string"
35
+ ? defs.split(",").map((s) => s.trim()).filter(Boolean).map((s) => (/^\d+(\.\d+)?$/.test(s) ? parseFloat(s) : s))
36
+ : defs;
37
+ return list.map((l) => new DefinitionInfo(l));
38
+ }
39
+ var LengthType;
40
+ (function (LengthType) {
41
+ LengthType[LengthType["None"] = 0] = "None";
42
+ LengthType[LengthType["Absolute"] = 1] = "Absolute";
43
+ LengthType[LengthType["Star"] = 2] = "Star";
44
+ LengthType[LengthType["Auto"] = 4] = "Auto";
45
+ })(LengthType || (LengthType = {}));
46
+ /** A child placed in the grid with the combined track types it spans (MAUI Cell). */
47
+ class Cell {
48
+ ViewIndex;
49
+ Row;
50
+ Column;
51
+ RowSpan;
52
+ ColumnSpan;
53
+ ColumnGridLengthType;
54
+ RowGridLengthType;
55
+ constructor(ViewIndex, Row, Column, RowSpan, ColumnSpan, ColumnGridLengthType, RowGridLengthType) {
56
+ this.ViewIndex = ViewIndex;
57
+ this.Row = Row;
58
+ this.Column = Column;
59
+ this.RowSpan = RowSpan;
60
+ this.ColumnSpan = ColumnSpan;
61
+ this.ColumnGridLengthType = ColumnGridLengthType;
62
+ this.RowGridLengthType = RowGridLengthType;
63
+ }
64
+ get IsColumnSpanAuto() { return (this.ColumnGridLengthType & LengthType.Auto) === LengthType.Auto; }
65
+ get IsRowSpanAuto() { return (this.RowGridLengthType & LengthType.Auto) === LengthType.Auto; }
66
+ get IsColumnSpanStar() { return (this.ColumnGridLengthType & LengthType.Star) === LengthType.Star; }
67
+ get IsRowSpanStar() { return (this.RowGridLengthType & LengthType.Star) === LengthType.Star; }
68
+ get IsAbsolute() { return this.ColumnGridLengthType === LengthType.Absolute && this.RowGridLengthType === LengthType.Absolute; }
69
+ /** Any Absolute/Star part means a second measure once Auto tracks are known. */
70
+ get NeedsKnownMeasurePass() { return ((this.ColumnGridLengthType | this.RowGridLengthType) ^ LengthType.Auto) > 0; }
71
+ }
72
+ /**
73
+ * Port of DrawnUi SkiaGridStructure (itself adapted from the MAUI Grid manager). Works in POINTS: constraints come in
74
+ * already reduced by the grid's padding, cells are returned relative to the padded box. Children are measured in
75
+ * pixels through SkiaControl.Measure and their Units sizes drive the tracks.
76
+ */
77
+ export class SkiaGridStructure {
78
+ grid;
79
+ widthConstraint;
80
+ heightConstraint;
81
+ scale;
82
+ Rows;
83
+ Columns;
84
+ ColumnSpacing;
85
+ RowSpacing;
86
+ children;
87
+ cells = [];
88
+ spans = new Map();
89
+ explicitWidth;
90
+ explicitHeight;
91
+ constructor(grid, widthConstraint, heightConstraint, scale) {
92
+ this.grid = grid;
93
+ this.widthConstraint = widthConstraint;
94
+ this.heightConstraint = heightConstraint;
95
+ this.scale = scale;
96
+ this.ColumnSpacing = grid.ColumnSpacing;
97
+ this.RowSpacing = grid.RowSpacing;
98
+ this.explicitWidth = grid.WidthRequest;
99
+ this.explicitHeight = grid.HeightRequest;
100
+ this.Rows = this.InitializeTracks(grid.RowDefinitions, grid.DefaultRowDefinition);
101
+ this.Columns = this.InitializeTracks(grid.ColumnDefinitions, grid.DefaultColumnDefinition);
102
+ this.children = grid.Views.filter((v) => v.IsVisible);
103
+ this.InitializeCells();
104
+ this.MeasureCells();
105
+ }
106
+ InitializeTracks(defs, fallback) {
107
+ const tracks = ParseDefinitions(defs);
108
+ return tracks.length > 0 ? tracks : [new DefinitionInfo(fallback)];
109
+ }
110
+ static CreateDefinitionIfMissing(array, requiredIndex, fallback) {
111
+ while (array.length <= requiredIndex)
112
+ array.push(new DefinitionInfo(fallback));
113
+ }
114
+ static ToLengthType(d) {
115
+ return d.IsAbsolute ? LengthType.Absolute : d.IsStar ? LengthType.Star : LengthType.Auto;
116
+ }
117
+ InitializeCells() {
118
+ let maxRow = 0, maxColumn = 0;
119
+ for (const child of this.children) {
120
+ maxRow = Math.max(maxRow, child.Row + child.RowSpan - 1);
121
+ maxColumn = Math.max(maxColumn, child.Column + child.ColumnSpan - 1);
122
+ }
123
+ SkiaGridStructure.CreateDefinitionIfMissing(this.Rows, maxRow, this.grid.DefaultRowDefinition);
124
+ SkiaGridStructure.CreateDefinitionIfMissing(this.Columns, maxColumn, this.grid.DefaultColumnDefinition);
125
+ for (let n = 0; n < this.children.length; n++) {
126
+ const v = this.children[n];
127
+ let ct = LengthType.None, rt = LengthType.None;
128
+ for (let c = v.Column; c < v.Column + v.ColumnSpan; c++)
129
+ ct |= SkiaGridStructure.ToLengthType(this.Columns[c]);
130
+ for (let r = v.Row; r < v.Row + v.RowSpan; r++)
131
+ rt |= SkiaGridStructure.ToLengthType(this.Rows[r]);
132
+ this.cells.push(new Cell(n, v.Row, v.Column, v.RowSpan, v.ColumnSpan, ct, rt));
133
+ }
134
+ }
135
+ static Sum(defs, spacing) {
136
+ let sum = 0;
137
+ for (let n = 0; n < defs.length; n++) {
138
+ sum += defs[n].Size;
139
+ if (n > 0)
140
+ sum += spacing;
141
+ }
142
+ return sum;
143
+ }
144
+ GridWidth() { return SkiaGridStructure.Sum(this.Columns, this.ColumnSpacing); }
145
+ GridHeight() { return SkiaGridStructure.Sum(this.Rows, this.RowSpacing); }
146
+ LeftEdgeOfColumn(column) { let x = 0; for (let n = 0; n < column; n++)
147
+ x += this.Columns[n].Size + this.ColumnSpacing; return x; }
148
+ TopEdgeOfRow(row) { let y = 0; for (let n = 0; n < row; n++)
149
+ y += this.Rows[n].Size + this.RowSpacing; return y; }
150
+ /** Cell rectangle in points relative to the padded box (+ offsets). */
151
+ GetCellBoundsFor(view, xOffset = 0, yOffset = 0) {
152
+ const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
153
+ const firstColumn = clamp(view.Column, 0, this.Columns.length - 1);
154
+ const columnSpan = clamp(view.ColumnSpan, 1, this.Columns.length - firstColumn);
155
+ const firstRow = clamp(view.Row, 0, this.Rows.length - 1);
156
+ const rowSpan = clamp(view.RowSpan, 1, this.Rows.length - firstRow);
157
+ let width = 0, height = 0;
158
+ for (let n = firstColumn; n < firstColumn + columnSpan; n++)
159
+ width += this.Columns[n].Size;
160
+ for (let n = firstRow; n < firstRow + rowSpan; n++)
161
+ height += this.Rows[n].Size;
162
+ width += (columnSpan - 1) * this.ColumnSpacing;
163
+ height += (rowSpan - 1) * this.RowSpacing;
164
+ return { Left: this.LeftEdgeOfColumn(firstColumn) + xOffset, Top: this.TopEdgeOfRow(firstRow) + yOffset, Width: width, Height: height };
165
+ }
166
+ // ---- measure passes (same order as C#) ----
167
+ MeasureCells() {
168
+ for (const cell of this.cells)
169
+ this.MeasureChild(cell);
170
+ this.ResolveStarColumns(this.widthConstraint);
171
+ this.ResolveStarRows(this.heightConstraint);
172
+ this.MeasureKnownCells();
173
+ this.ResolveSpans();
174
+ this.ApplyMinimumDimensionsFromFillChildren();
175
+ this.CompressStarMeasurements();
176
+ }
177
+ /** Points -> pixels for Measure; negative/NaN = unconstrained. */
178
+ MeasurePx(child, wPts, hPts) {
179
+ const w = wPts < 0 ? Infinity : wPts * this.scale;
180
+ const h = hPts < 0 ? Infinity : Math.round(hPts * this.scale);
181
+ return child.Measure(w, h, this.scale).Units;
182
+ }
183
+ MeasureChild(cell) {
184
+ if (cell.IsAbsolute)
185
+ return;
186
+ const child = this.children[cell.ViewIndex];
187
+ let availableWidth = this.AvailableWidth(cell);
188
+ let availableHeight = this.AvailableHeight(cell);
189
+ const finiteW = availableWidth, finiteH = availableHeight;
190
+ // a Fill child in an Auto track is measured unconstrained (content-sized track, MAUI desired-size semantics)
191
+ if (cell.IsColumnSpanAuto && child.HorizontalOptions === "Fill")
192
+ availableWidth = Infinity;
193
+ if (cell.IsRowSpanAuto && child.VerticalOptions === "Fill")
194
+ availableHeight = Infinity;
195
+ const m = this.MeasurePx(child, availableWidth, availableHeight);
196
+ let mw = m.Width, mh = m.Height;
197
+ // ...but clamped to the finite grid so a wrapping label / scroll cannot inflate the track
198
+ if (availableWidth === Infinity && isFinite(finiteW) && finiteW >= 0 && mw > finiteW)
199
+ mw = finiteW;
200
+ if (availableHeight === Infinity && isFinite(finiteH) && finiteH >= 0 && mh > finiteH)
201
+ mh = finiteH;
202
+ if (cell.IsColumnSpanAuto) {
203
+ if (cell.ColumnSpan === 1)
204
+ this.Columns[cell.Column].Update(mw);
205
+ else
206
+ this.TrackSpan({ Key: `c${cell.Column}:${cell.ColumnSpan}`, Start: cell.Column, Length: cell.ColumnSpan, IsColumn: true, Requested: mw });
207
+ }
208
+ if (cell.IsRowSpanAuto) {
209
+ if (cell.RowSpan === 1)
210
+ this.Rows[cell.Row].Update(mh);
211
+ else
212
+ this.TrackSpan({ Key: `r${cell.Row}:${cell.RowSpan}`, Start: cell.Row, Length: cell.RowSpan, IsColumn: false, Requested: mh });
213
+ }
214
+ }
215
+ TrackSpan(span) {
216
+ const other = this.spans.get(span.Key);
217
+ if (!other || span.Requested > other.Requested)
218
+ this.spans.set(span.Key, span);
219
+ }
220
+ ResolveSpans() {
221
+ for (const span of this.spans.values()) {
222
+ if (span.IsColumn)
223
+ SkiaGridStructure.ResolveSpan(this.Columns, span.Start, span.Length, this.ColumnSpacing, span.Requested);
224
+ else
225
+ SkiaGridStructure.ResolveSpan(this.Rows, span.Start, span.Length, this.RowSpacing, span.Requested);
226
+ }
227
+ }
228
+ static ResolveSpan(defs, start, length, spacing, requested) {
229
+ let current = 0;
230
+ const end = start + length;
231
+ for (let n = start; n < end; n++) {
232
+ current += defs[n].Size;
233
+ if (n > start)
234
+ current += spacing;
235
+ }
236
+ if (requested <= current)
237
+ return;
238
+ const required = requested - current;
239
+ let autoCount = 0;
240
+ for (let n = start; n < end; n++) {
241
+ if (defs[n].IsAuto)
242
+ autoCount++;
243
+ else if (defs[n].IsStar)
244
+ return;
245
+ }
246
+ if (autoCount === 0)
247
+ return;
248
+ const distribution = required / autoCount;
249
+ for (let n = start; n < end; n++)
250
+ if (defs[n].IsAuto)
251
+ defs[n].Size += distribution;
252
+ }
253
+ ApplyMinimumDimensionsFromFillChildren() {
254
+ for (const cell of this.cells) {
255
+ const child = this.children[cell.ViewIndex];
256
+ if (child.HorizontalOptions === "Fill" && child.MinimumWidthRequest >= 0) {
257
+ const min = (child.MinimumWidthRequest + child.Margin.HorizontalThickness) / cell.ColumnSpan;
258
+ for (let c = cell.Column; c < cell.Column + cell.ColumnSpan; c++)
259
+ if (this.Columns[c].Size < min)
260
+ this.Columns[c].Size = min;
261
+ }
262
+ if (child.VerticalOptions === "Fill" && child.MinimumHeightRequest >= 0) {
263
+ const min = (child.MinimumHeightRequest + child.Margin.VerticalThickness) / cell.RowSpan;
264
+ for (let r = cell.Row; r < cell.Row + cell.RowSpan; r++)
265
+ if (this.Rows[r].Size < min)
266
+ this.Rows[r].Size = min;
267
+ }
268
+ }
269
+ }
270
+ ResolveStars(defs, availableSpace, cellCheck, dimension) {
271
+ let starCount = 0;
272
+ for (const d of defs)
273
+ if (d.IsStar)
274
+ starCount += d.Value;
275
+ if (starCount === 0)
276
+ return;
277
+ let starSize = 0;
278
+ if (!isFinite(availableSpace)) {
279
+ // unbounded axis: stars take the largest star cell's desired size
280
+ for (const cell of this.cells)
281
+ if (cellCheck(cell))
282
+ starSize = Math.max(starSize, dimension(this.children[cell.ViewIndex]));
283
+ }
284
+ else {
285
+ starSize = availableSpace / starCount;
286
+ }
287
+ for (const d of defs)
288
+ if (d.IsStar)
289
+ d.Size = starSize * d.Value;
290
+ }
291
+ ResolveStarColumns(widthConstraint) {
292
+ this.ResolveStars(this.Columns, widthConstraint - this.GridWidth(), (c) => c.IsColumnSpanStar, (v) => v.MeasuredSize.Units.Width);
293
+ }
294
+ ResolveStarRows(heightConstraint) {
295
+ this.ResolveStars(this.Rows, heightConstraint - this.GridHeight(), (c) => c.IsRowSpanStar, (v) => v.MeasuredSize.Units.Height);
296
+ }
297
+ MeasureKnownCells() {
298
+ for (const cell of this.cells) {
299
+ if (!cell.NeedsKnownMeasurePass)
300
+ continue;
301
+ let width = 0, height = 0;
302
+ for (let n = cell.Row; n < cell.Row + cell.RowSpan; n++)
303
+ height += this.Rows[n].Size;
304
+ for (let n = cell.Column; n < cell.Column + cell.ColumnSpan; n++)
305
+ width += this.Columns[n].Size;
306
+ if (width === 0 || height === 0)
307
+ continue;
308
+ const child = this.children[cell.ViewIndex];
309
+ const rect = this.GetCellBoundsFor(child);
310
+ this.MeasurePx(child, rect.Width, rect.Height);
311
+ if (cell.IsColumnSpanStar && cell.ColumnSpan > 1)
312
+ this.TrackSpan({ Key: `c${cell.Column}:${cell.ColumnSpan}`, Start: cell.Column, Length: cell.ColumnSpan, IsColumn: true, Requested: rect.Width });
313
+ if (cell.IsRowSpanStar && cell.RowSpan > 1)
314
+ this.TrackSpan({ Key: `r${cell.Row}:${cell.RowSpan}`, Start: cell.Row, Length: cell.RowSpan, IsColumn: false, Requested: rect.Height });
315
+ }
316
+ }
317
+ AvailableWidth(cell) {
318
+ let w = 0, absolute = true;
319
+ for (let c = cell.Column; c < cell.Column + cell.ColumnSpan; c++) {
320
+ w += this.Columns[c].Size;
321
+ if (!this.Columns[c].IsAbsolute)
322
+ absolute = false;
323
+ }
324
+ w += (cell.ColumnSpan - 1) * this.ColumnSpacing;
325
+ if (absolute)
326
+ return w;
327
+ return this.widthConstraint - this.GridWidth() + w;
328
+ }
329
+ AvailableHeight(cell) {
330
+ let h = 0, absolute = true;
331
+ for (let r = cell.Row; r < cell.Row + cell.RowSpan; r++) {
332
+ h += this.Rows[r].Size;
333
+ if (!this.Rows[r].IsAbsolute)
334
+ absolute = false;
335
+ }
336
+ h += (cell.RowSpan - 1) * this.RowSpacing;
337
+ if (absolute)
338
+ return h;
339
+ return this.heightConstraint - this.GridHeight() + h;
340
+ }
341
+ /** Re-resolves stars against the final size when the grid has an explicit size request (C# DecompressStars). */
342
+ DecompressStars(targetWidth, targetHeight) {
343
+ if (this.explicitHeight >= 0) {
344
+ SkiaGridStructure.ZeroOutStars(this.Rows);
345
+ this.ResolveStarRows(targetHeight);
346
+ }
347
+ if (this.explicitWidth >= 0) {
348
+ SkiaGridStructure.ZeroOutStars(this.Columns);
349
+ this.ResolveStarColumns(targetWidth);
350
+ }
351
+ }
352
+ CompressStarMeasurements() {
353
+ this.CompressStars(this.Rows, (c) => c.IsRowSpanStar, (c) => c.Row, (c) => c.RowSpan, this.heightConstraint, (v) => v.MeasuredSize.Units.Height);
354
+ this.CompressStars(this.Columns, (c) => c.IsColumnSpanStar, (c) => c.Column, (c) => c.ColumnSpan, this.widthConstraint, (v) => v.MeasuredSize.Units.Width);
355
+ }
356
+ /** Stars shrink to what their cells actually need (so an auto-sized grid does not take the whole constraint). */
357
+ CompressStars(defs, isStar, start, span, constraint, dimension) {
358
+ const copy = defs.map((d) => { const c = new DefinitionInfo(d.Unit === "Absolute" ? d.Value : d.Unit === "Auto" ? "Auto" : `${d.Value}*`); c.Size = d.IsStar ? 0 : d.Size; return c; });
359
+ for (const cell of this.cells) {
360
+ if (!isStar(cell))
361
+ continue;
362
+ const needed = Math.min(constraint, dimension(this.children[cell.ViewIndex]));
363
+ SkiaGridStructure.ExpandStarsInSpan(needed, defs, copy, start(cell), start(cell) + span(cell));
364
+ }
365
+ for (let n = 0; n < copy.length; n++)
366
+ if (copy[n].IsStar)
367
+ defs[n].Size = copy[n].Size;
368
+ }
369
+ static ExpandStarsInSpan(spaceNeeded, original, updated, start, end) {
370
+ for (let n = start; n < end; n++)
371
+ if (original[n].IsAbsolute || original[n].IsAuto)
372
+ spaceNeeded -= original[n].Size;
373
+ let spaceAvailable = 0, starCount = 0;
374
+ for (let n = start; n < end; n++)
375
+ if (updated[n].IsStar) {
376
+ starCount++;
377
+ spaceAvailable += updated[n].Size;
378
+ }
379
+ if (starCount > 0 && spaceAvailable < spaceNeeded) {
380
+ const toAdd = (spaceNeeded - spaceAvailable) / starCount;
381
+ for (let n = start; n < end; n++)
382
+ if (updated[n].IsStar)
383
+ updated[n].Size += toAdd;
384
+ }
385
+ }
386
+ static ZeroOutStars(defs) { for (const d of defs)
387
+ if (d.IsStar)
388
+ d.Size = 0; }
389
+ /** Tracks are final only now: measure every child at its final cell so its internal layout matches the arranged box. */
390
+ RemeasureChildrenAtFinalCells() {
391
+ for (const cell of this.cells) {
392
+ const child = this.children[cell.ViewIndex];
393
+ const rect = this.GetCellBoundsFor(child);
394
+ if (rect.Width <= 0 || rect.Height <= 0)
395
+ continue;
396
+ const m = this.MeasurePx(child, rect.Width, rect.Height);
397
+ if (cell.ColumnSpan === 1 && this.Columns[cell.Column].IsAuto && m.Width > this.Columns[cell.Column].Size)
398
+ this.Columns[cell.Column].Update(m.Width);
399
+ if (cell.RowSpan === 1 && this.Rows[cell.Row].IsAuto && m.Height > this.Rows[cell.Row].Size)
400
+ this.Rows[cell.Row].Update(m.Height);
401
+ }
402
+ }
403
+ }