drawnui-react 0.1.0-preview.2 → 0.1.0-preview.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -4
- package/SKIPPED.md +33 -27
- package/dist/controls/AnimatedFramesRenderer.d.ts +7 -0
- package/dist/controls/AnimatedFramesRenderer.js +16 -1
- package/dist/controls/DrawnGame.d.ts +40 -0
- package/dist/controls/DrawnGame.js +64 -0
- package/dist/controls/GridStructure.d.ts +16 -2
- package/dist/controls/GridStructure.js +40 -4
- package/dist/controls/SkiaBackdrop.d.ts +8 -2
- package/dist/controls/SkiaBackdrop.js +48 -8
- package/dist/controls/SkiaButton.d.ts +19 -2
- package/dist/controls/SkiaButton.js +41 -11
- package/dist/controls/SkiaCarousel.d.ts +9 -1
- package/dist/controls/SkiaCarousel.js +16 -3
- package/dist/controls/SkiaCheckbox.d.ts +0 -1
- package/dist/controls/SkiaCheckbox.js +0 -6
- package/dist/controls/SkiaDrawer.d.ts +2 -0
- package/dist/controls/SkiaDrawer.js +2 -0
- package/dist/controls/SkiaEditor.d.ts +12 -1
- package/dist/controls/SkiaEditor.js +46 -19
- package/dist/controls/SkiaGif.d.ts +3 -0
- package/dist/controls/SkiaGif.js +12 -2
- package/dist/controls/SkiaImage.js +15 -10
- package/dist/controls/SkiaLabel.d.ts +54 -3
- package/dist/controls/SkiaLabel.js +235 -52
- package/dist/controls/SkiaLabelFps.d.ts +16 -0
- package/dist/controls/SkiaLabelFps.js +41 -0
- package/dist/controls/SkiaLayout.d.ts +64 -7
- package/dist/controls/SkiaLayout.js +316 -19
- package/dist/controls/SkiaLottie.js +4 -2
- package/dist/controls/SkiaProgress.d.ts +2 -1
- package/dist/controls/SkiaProgress.js +6 -3
- package/dist/controls/SkiaRadioButton.js +4 -3
- package/dist/controls/SkiaScroll.d.ts +35 -5
- package/dist/controls/SkiaScroll.js +233 -23
- package/dist/controls/SkiaScrollBar.d.ts +27 -0
- package/dist/controls/SkiaScrollBar.js +51 -2
- package/dist/controls/SkiaShaderCarousel.d.ts +5 -0
- package/dist/controls/SkiaShaderCarousel.js +5 -0
- package/dist/controls/SkiaShape.d.ts +13 -2
- package/dist/controls/SkiaShape.js +50 -10
- package/dist/controls/SkiaSlider.d.ts +20 -2
- package/dist/controls/SkiaSlider.js +59 -5
- package/dist/controls/SkiaSprite.d.ts +1 -0
- package/dist/controls/SkiaSprite.js +4 -0
- package/dist/controls/SkiaSvg.js +1 -1
- package/dist/controls/SkiaSwitch.d.ts +0 -1
- package/dist/controls/SkiaSwitch.js +0 -6
- package/dist/controls/SkiaToggle.d.ts +25 -5
- package/dist/controls/SkiaToggle.js +58 -5
- package/dist/controls/SnappingLayout.d.ts +12 -1
- package/dist/controls/SnappingLayout.js +19 -1
- package/dist/core/Accessibility.d.ts +80 -0
- package/dist/core/Accessibility.js +365 -8
- package/dist/core/Animators.d.ts +37 -1
- package/dist/core/Animators.js +102 -9
- package/dist/core/Canvas.d.ts +108 -7
- package/dist/core/Canvas.js +363 -35
- package/dist/core/Easing.d.ts +12 -1
- package/dist/core/Easing.js +25 -1
- package/dist/core/Gestures.d.ts +48 -2
- package/dist/core/Gestures.js +95 -3
- package/dist/core/Hover.d.ts +47 -0
- package/dist/core/Hover.js +134 -0
- package/dist/core/KeyboardManager.d.ts +13 -0
- package/dist/core/KeyboardManager.js +18 -0
- package/dist/core/ScrollAnimators.js +9 -0
- package/dist/core/SkiaControl.d.ts +216 -7
- package/dist/core/SkiaControl.js +516 -59
- package/dist/core/SkiaEffect.d.ts +17 -0
- package/dist/core/SkiaEffect.js +43 -8
- package/dist/core/SkiaImageManager.js +1 -1
- package/dist/core/Styles.d.ts +51 -0
- package/dist/core/Styles.js +92 -0
- package/dist/core/Super.d.ts +17 -1
- package/dist/core/Super.js +35 -4
- package/dist/core/ViewsAdapter.d.ts +7 -2
- package/dist/core/ViewsAdapter.js +10 -3
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/react/SkiaShell.js +33 -14
- package/dist/react/index.d.ts +15 -4
- package/dist/react/index.js +102 -15
- package/dist/react/reconciler.js +34 -12
- package/dist/vite/index.d.ts +9 -0
- package/dist/vite/index.js +36 -3
- package/package.json +33 -3
|
@@ -6,9 +6,11 @@ import { SkiaGridStructure } from "./GridStructure";
|
|
|
6
6
|
* Mirrors DrawnUi SkiaLayout (Absolute / Column / Row / Wrap / Grid).
|
|
7
7
|
* Column/Row give children an infinite main axis (MAUI stack semantics: Fill on the main axis = auto-sized).
|
|
8
8
|
*
|
|
9
|
-
* Templated mode (ItemsSource + ItemTemplate)
|
|
10
|
-
* the indexes inside the visible viewport (+ VirtualisationInflated), everything else is arithmetic —
|
|
11
|
-
* MeasureFirst measures one cell and assumes uniform size
|
|
9
|
+
* Templated mode (ItemsSource + ItemTemplate): a single-column Column is the virtualized list, cells are created through
|
|
10
|
+
* the ViewsAdapter for the indexes inside the visible viewport (+ VirtualisationInflated), everything else is arithmetic —
|
|
11
|
+
* MeasureAll (default) measures every item once, MeasureFirst measures one cell and assumes uniform size. A templated
|
|
12
|
+
* Row / Wrap / Grid / split Column follows RecyclingTemplate: Enabled measures every item on pooled views and binds views
|
|
13
|
+
* only to the slots drawn, Disabled keeps one view per item.
|
|
12
14
|
*/
|
|
13
15
|
export declare class SkiaLayout extends SkiaControl {
|
|
14
16
|
/** Layout type. SkiaShape redeclares it as ShapeType (any shape value lays out as Absolute), like the C# hidden Type. */
|
|
@@ -24,7 +26,10 @@ export declare class SkiaLayout extends SkiaControl {
|
|
|
24
26
|
RowSpacing: number;
|
|
25
27
|
/** Structure computed by the last measure; cells are arranged from it. */
|
|
26
28
|
GridStructure?: SkiaGridStructure;
|
|
27
|
-
|
|
29
|
+
private recyclingTemplate;
|
|
30
|
+
/** Enabled: views come from a pool and are bound to the items being drawn; Disabled: one view per item, never rebound. */
|
|
31
|
+
get RecyclingTemplate(): RecyclingTemplate;
|
|
32
|
+
set RecyclingTemplate(value: RecyclingTemplate);
|
|
28
33
|
/** Column count for Column / Wrap content and a templated Grid (0 = free flow); C# Split. */
|
|
29
34
|
Split: number;
|
|
30
35
|
/** Split > 0: every slot keeps the same width (true) or the cell keeps its measured width (C# SplitAlign). */
|
|
@@ -33,6 +38,7 @@ export declare class SkiaLayout extends SkiaControl {
|
|
|
33
38
|
DynamicColumns: boolean;
|
|
34
39
|
/** Templated Grid: fill column-major (top to bottom, then next column) instead of row-major (C# Invert). */
|
|
35
40
|
Invert: boolean;
|
|
41
|
+
/** Default MeasureAll (DrawnUi 1.10.6.20): every item keeps its own size; MeasureFirst is explicit uniform rows. */
|
|
36
42
|
MeasureItemsStrategy: MeasuringStrategy;
|
|
37
43
|
/** Realized views per item (Disabled) or a recycled pool for the visible range (Enabled). */
|
|
38
44
|
readonly ChildrenFactory: ViewsAdapter;
|
|
@@ -41,7 +47,7 @@ export declare class SkiaLayout extends SkiaControl {
|
|
|
41
47
|
private itemsSource?;
|
|
42
48
|
private itemTemplate?;
|
|
43
49
|
private structureDirty;
|
|
44
|
-
/** Per-item heights in pixels (MeasureAll) or a single uniform height (MeasureFirst). */
|
|
50
|
+
/** Per-item heights in pixels (MeasureAll, the default) or a single uniform height (MeasureFirst). */
|
|
45
51
|
private itemHeights;
|
|
46
52
|
private uniformHeight;
|
|
47
53
|
private measuredWidthPx;
|
|
@@ -70,13 +76,30 @@ export declare class SkiaLayout extends SkiaControl {
|
|
|
70
76
|
* anything else rebuilds. Returns false when a full rebuild is needed.
|
|
71
77
|
*/
|
|
72
78
|
private ApplyIncrementalChange;
|
|
79
|
+
/**
|
|
80
|
+
* Same items in another order — a drag-to-reorder, or any code writing a permuted array. DrawnUi handles the
|
|
81
|
+
* matching `ObservableCollection.Move` by keeping the structure and only rebinding the contexts, because its
|
|
82
|
+
* arrange re-flows each row from its bound view (`HandleStructurePreservingMove` / `ApplyMoveChange`, 2026-09-08).
|
|
83
|
+
* The measured heights here are index-keyed and authoritative, so they travel with their items instead: nothing is
|
|
84
|
+
* remeasured, the extent cannot change, and the scroll keeps its offset. Rebuilding instead would reset
|
|
85
|
+
* `MeasureVisible` to "exact for the first screen, estimated after" and throw a scrolled list back towards the top.
|
|
86
|
+
* Returns false when the two arrays are not a permutation of each other, so a real content change still rebuilds.
|
|
87
|
+
*/
|
|
88
|
+
private ApplyReorderChange;
|
|
73
89
|
/** Factory creating one cell (DrawnUi DataTemplate). Cells receive the item as BindingContext. */
|
|
74
90
|
get ItemTemplate(): (() => SkiaControl) | undefined;
|
|
75
91
|
set ItemTemplate(value: (() => SkiaControl) | undefined);
|
|
76
92
|
get IsTemplated(): boolean;
|
|
77
|
-
/** The virtualized list case: a templated single-column Column.
|
|
93
|
+
/** The virtualized list case: a templated single-column Column. */
|
|
78
94
|
private get IsTemplatedList();
|
|
79
|
-
/**
|
|
95
|
+
/** A templated Row / Wrap / Grid / split Column with RecyclingTemplate Enabled: slots measured on pooled views, views bound to the slots drawn. */
|
|
96
|
+
private get IsRecycledLayout();
|
|
97
|
+
/** Slots of a recycled layout, one per item. */
|
|
98
|
+
private slots?;
|
|
99
|
+
/**
|
|
100
|
+
* What the layout pass measures and arranges: static children; the slots of a recycled templated layout; or, with
|
|
101
|
+
* RecyclingTemplate Disabled, one realized view per item.
|
|
102
|
+
*/
|
|
80
103
|
private LayoutViews;
|
|
81
104
|
/** Drops realized cells and rebuilds the structure (DrawnUi ApplyItemsSource). */
|
|
82
105
|
ApplyItemsSource(): void;
|
|
@@ -89,7 +112,22 @@ export declare class SkiaLayout extends SkiaControl {
|
|
|
89
112
|
get Views(): readonly SkiaControl[];
|
|
90
113
|
/** Settable children list like DrawnUi Children (ignored while templated). */
|
|
91
114
|
get Children(): readonly SkiaControl[];
|
|
115
|
+
/**
|
|
116
|
+
* C# Children reset (ChildrenCollectionSync, 66a1eb02): the app's children the new list no longer holds are removed,
|
|
117
|
+
* the ones it gained are added, then all of them take the list's order inside the slots app children occupy, so the
|
|
118
|
+
* parts a control made itself keep their place.
|
|
119
|
+
*/
|
|
92
120
|
set Children(value: readonly SkiaControl[]);
|
|
121
|
+
/**
|
|
122
|
+
* C# Insert / Move of a Children collection (the reconciler's appendChild / insertBefore): `child` goes before
|
|
123
|
+
* `before` among the app's children, or last without it. A new child is attached at the end of the views and an
|
|
124
|
+
* append stops there; otherwise the app's children take their new order inside the slots they occupy.
|
|
125
|
+
*/
|
|
126
|
+
InsertChild(child: SkiaControl, before?: SkiaControl): void;
|
|
127
|
+
/** C# OrderViews: the given children, in their order, inside the slots they occupy in the views; nothing else moves. */
|
|
128
|
+
private OrderChildren;
|
|
129
|
+
/** C# ClearChildren: removes every subview, the control's own parts included. */
|
|
130
|
+
ClearChildren(): void;
|
|
93
131
|
private orderedViews?;
|
|
94
132
|
/** Static children sorted by ZIndex (stable), computed once per change (C# GetOrderedSubviews). */
|
|
95
133
|
protected GetOrderedSubviews(): readonly SkiaControl[];
|
|
@@ -127,12 +165,31 @@ export declare class SkiaLayout extends SkiaControl {
|
|
|
127
165
|
private MvScheduleBackground;
|
|
128
166
|
/** Binds a cell to items[index] (through the adapter, so MeasureFirst's cell 0 stays realized) and measures it. */
|
|
129
167
|
private MeasureItem;
|
|
168
|
+
/**
|
|
169
|
+
* The item's offset and height are real, not estimated (C# LastMeasuredIndexLocal >= index): MeasureVisible knows
|
|
170
|
+
* them once the measured prefix passed the item; the other strategies always do.
|
|
171
|
+
*/
|
|
172
|
+
IsItemMeasured(index: number): boolean;
|
|
130
173
|
/** Pixel offset of item index from the top of the layout content (inside padding). */
|
|
131
174
|
GetItemOffsetPixels(index: number): number;
|
|
132
175
|
private ItemHeightPixels;
|
|
133
176
|
protected OnLayoutChanged(): void;
|
|
177
|
+
/** Static children and realized views are arranged; the slots of a recycled layout record where their item goes. */
|
|
178
|
+
private ArrangeChildren;
|
|
134
179
|
protected Paint(ctx: DrawingContext): void;
|
|
135
180
|
protected GetCompositeChildren(): readonly SkiaControl[];
|
|
181
|
+
private readonly drawnSlots;
|
|
182
|
+
/**
|
|
183
|
+
* Recycled templated layout (C# DrawStack): a view from the pool is bound to each slot that can be seen and drawn
|
|
184
|
+
* there; the views of the other slots go back to the pool.
|
|
185
|
+
*/
|
|
186
|
+
private PaintSlots;
|
|
187
|
+
/**
|
|
188
|
+
* Where slots are drawn (C# GetOnScreenVisibleArea): what can be seen, inflated by VirtualisationInflated. Under a
|
|
189
|
+
* cache (this layout's or an ancestor's, below the nearest scroll) every slot, because the cache is blitted later at
|
|
190
|
+
* other offsets.
|
|
191
|
+
*/
|
|
192
|
+
private SlotsArea;
|
|
136
193
|
/** Realizes, binds, arranges and draws only the cells intersecting the visible viewport (+ inflation). */
|
|
137
194
|
private PaintTemplated;
|
|
138
195
|
/**
|
|
@@ -2,13 +2,81 @@ import { SkiaControl } from "../core/SkiaControl";
|
|
|
2
2
|
import { ViewsAdapter } from "../core/ViewsAdapter";
|
|
3
3
|
import { SKRect, ScaledSize, Thickness } from "../core/Types";
|
|
4
4
|
import { SkiaGridStructure } from "./GridStructure";
|
|
5
|
+
/**
|
|
6
|
+
* The place of one item in a recycled templated Row / Wrap / Grid / split Column. C# measures the structure on one
|
|
7
|
+
* template instance bound to each item in turn: Measure here binds a pooled view to the item, keeps its size, the
|
|
8
|
+
* constraints and what the template decided for that item, then gives the view back unless the item is on screen.
|
|
9
|
+
* The same item measured again with the same constraints keeps its size without a rebind (as the list keeps its item
|
|
10
|
+
* heights: a new ItemsSource array is how content changes). Arrange only records the slot; the drawing pass binds a
|
|
11
|
+
* view to each slot it draws.
|
|
12
|
+
*/
|
|
13
|
+
class TemplatedSlot {
|
|
14
|
+
Index;
|
|
15
|
+
factory;
|
|
16
|
+
IsVisible = true;
|
|
17
|
+
Row = 0;
|
|
18
|
+
Column = 0;
|
|
19
|
+
RowSpan = 1;
|
|
20
|
+
ColumnSpan = 1;
|
|
21
|
+
HorizontalOptions = "Start";
|
|
22
|
+
VerticalOptions = "Start";
|
|
23
|
+
Margin = Thickness.Zero;
|
|
24
|
+
WidthRequest = -1;
|
|
25
|
+
HeightRequest = -1;
|
|
26
|
+
MinimumWidthRequest = -1;
|
|
27
|
+
MinimumHeightRequest = -1;
|
|
28
|
+
MeasuredSize = ScaledSize.Default;
|
|
29
|
+
WidthConstraint = Infinity;
|
|
30
|
+
HeightConstraint = Infinity;
|
|
31
|
+
/** The rect the layout arranged this slot in (pixels). */
|
|
32
|
+
Destination = SKRect.Empty;
|
|
33
|
+
/** The view last arranged in this slot and the Destination it got, so an unchanged view is not arranged twice. */
|
|
34
|
+
ArrangedView;
|
|
35
|
+
ArrangedFor;
|
|
36
|
+
measuredItem;
|
|
37
|
+
measuredScale = 0;
|
|
38
|
+
constructor(Index, factory) {
|
|
39
|
+
this.Index = Index;
|
|
40
|
+
this.factory = factory;
|
|
41
|
+
}
|
|
42
|
+
Measure(widthConstraint, heightConstraint, scale) {
|
|
43
|
+
const shown = this.factory.GetViewForIndex(this.Index), item = this.factory.Items[this.Index];
|
|
44
|
+
if (!shown && item === this.measuredItem && scale === this.measuredScale
|
|
45
|
+
&& Object.is(widthConstraint, this.WidthConstraint) && Object.is(heightConstraint, this.HeightConstraint))
|
|
46
|
+
return this.MeasuredSize;
|
|
47
|
+
const view = shown ?? this.factory.GetOrCreateViewForIndex(this.Index, true);
|
|
48
|
+
if (!view)
|
|
49
|
+
return (this.MeasuredSize = ScaledSize.Default);
|
|
50
|
+
this.MeasuredSize = view.Measure(widthConstraint, heightConstraint, scale);
|
|
51
|
+
this.measuredItem = item;
|
|
52
|
+
this.measuredScale = scale;
|
|
53
|
+
this.WidthConstraint = widthConstraint;
|
|
54
|
+
this.HeightConstraint = heightConstraint;
|
|
55
|
+
this.IsVisible = view.IsVisible;
|
|
56
|
+
this.RowSpan = view.RowSpan;
|
|
57
|
+
this.ColumnSpan = view.ColumnSpan;
|
|
58
|
+
this.HorizontalOptions = view.HorizontalOptions;
|
|
59
|
+
this.VerticalOptions = view.VerticalOptions;
|
|
60
|
+
this.Margin = view.Margin;
|
|
61
|
+
this.WidthRequest = view.WidthRequest;
|
|
62
|
+
this.HeightRequest = view.HeightRequest;
|
|
63
|
+
this.MinimumWidthRequest = view.MinimumWidthRequest;
|
|
64
|
+
this.MinimumHeightRequest = view.MinimumHeightRequest;
|
|
65
|
+
if (!shown)
|
|
66
|
+
this.factory.ReleaseViewAt(this.Index);
|
|
67
|
+
return this.MeasuredSize;
|
|
68
|
+
}
|
|
69
|
+
Arrange(destination, _widthRequest, _heightRequest, _scale) { this.Destination = destination; }
|
|
70
|
+
}
|
|
5
71
|
/**
|
|
6
72
|
* Mirrors DrawnUi SkiaLayout (Absolute / Column / Row / Wrap / Grid).
|
|
7
73
|
* Column/Row give children an infinite main axis (MAUI stack semantics: Fill on the main axis = auto-sized).
|
|
8
74
|
*
|
|
9
|
-
* Templated mode (ItemsSource + ItemTemplate)
|
|
10
|
-
* the indexes inside the visible viewport (+ VirtualisationInflated), everything else is arithmetic —
|
|
11
|
-
* MeasureFirst measures one cell and assumes uniform size
|
|
75
|
+
* Templated mode (ItemsSource + ItemTemplate): a single-column Column is the virtualized list, cells are created through
|
|
76
|
+
* the ViewsAdapter for the indexes inside the visible viewport (+ VirtualisationInflated), everything else is arithmetic —
|
|
77
|
+
* MeasureAll (default) measures every item once, MeasureFirst measures one cell and assumes uniform size. A templated
|
|
78
|
+
* Row / Wrap / Grid / split Column follows RecyclingTemplate: Enabled measures every item on pooled views and binds views
|
|
79
|
+
* only to the slots drawn, Disabled keeps one view per item.
|
|
12
80
|
*/
|
|
13
81
|
export class SkiaLayout extends SkiaControl {
|
|
14
82
|
/** Layout type. SkiaShape redeclares it as ShapeType (any shape value lays out as Absolute), like the C# hidden Type. */
|
|
@@ -26,7 +94,16 @@ export class SkiaLayout extends SkiaControl {
|
|
|
26
94
|
/** Structure computed by the last measure; cells are arranged from it. */
|
|
27
95
|
GridStructure;
|
|
28
96
|
// ---- templated children (same names as DrawnUi) ----
|
|
29
|
-
|
|
97
|
+
recyclingTemplate = "Enabled";
|
|
98
|
+
/** Enabled: views come from a pool and are bound to the items being drawn; Disabled: one view per item, never rebound. */
|
|
99
|
+
get RecyclingTemplate() { return this.recyclingTemplate; }
|
|
100
|
+
set RecyclingTemplate(value) {
|
|
101
|
+
if (this.recyclingTemplate === value)
|
|
102
|
+
return;
|
|
103
|
+
this.recyclingTemplate = value;
|
|
104
|
+
if (this.itemTemplate)
|
|
105
|
+
this.ApplyItemsSource(); // the pool follows the mode
|
|
106
|
+
}
|
|
30
107
|
/** Column count for Column / Wrap content and a templated Grid (0 = free flow); C# Split. */
|
|
31
108
|
Split = 0;
|
|
32
109
|
/** Split > 0: every slot keeps the same width (true) or the cell keeps its measured width (C# SplitAlign). */
|
|
@@ -35,7 +112,8 @@ export class SkiaLayout extends SkiaControl {
|
|
|
35
112
|
DynamicColumns = false;
|
|
36
113
|
/** Templated Grid: fill column-major (top to bottom, then next column) instead of row-major (C# Invert). */
|
|
37
114
|
Invert = false;
|
|
38
|
-
|
|
115
|
+
/** Default MeasureAll (DrawnUi 1.10.6.20): every item keeps its own size; MeasureFirst is explicit uniform rows. */
|
|
116
|
+
MeasureItemsStrategy = "MeasureAll";
|
|
39
117
|
/** Realized views per item (Disabled) or a recycled pool for the visible range (Enabled). */
|
|
40
118
|
ChildrenFactory = new ViewsAdapter(this);
|
|
41
119
|
FirstVisibleIndex = -1;
|
|
@@ -43,7 +121,7 @@ export class SkiaLayout extends SkiaControl {
|
|
|
43
121
|
itemsSource;
|
|
44
122
|
itemTemplate;
|
|
45
123
|
structureDirty = true;
|
|
46
|
-
/** Per-item heights in pixels (MeasureAll) or a single uniform height (MeasureFirst). */
|
|
124
|
+
/** Per-item heights in pixels (MeasureAll, the default) or a single uniform height (MeasureFirst). */
|
|
47
125
|
itemHeights = [];
|
|
48
126
|
uniformHeight = 0;
|
|
49
127
|
measuredWidthPx = 0;
|
|
@@ -87,7 +165,11 @@ export class SkiaLayout extends SkiaControl {
|
|
|
87
165
|
*/
|
|
88
166
|
ApplyIncrementalChange(old, items) {
|
|
89
167
|
const n = items.length, o = old.length;
|
|
90
|
-
if (
|
|
168
|
+
if (o === 0)
|
|
169
|
+
return false;
|
|
170
|
+
if (n === o)
|
|
171
|
+
return this.ApplyReorderChange(old, items);
|
|
172
|
+
if (n < o)
|
|
91
173
|
return false;
|
|
92
174
|
const k = n - o;
|
|
93
175
|
const scale = this.RenderingScale, gap = this.Spacing * scale, w = this.measuredWidthPx;
|
|
@@ -143,6 +225,65 @@ export class SkiaLayout extends SkiaControl {
|
|
|
143
225
|
}
|
|
144
226
|
return false;
|
|
145
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Same items in another order — a drag-to-reorder, or any code writing a permuted array. DrawnUi handles the
|
|
230
|
+
* matching `ObservableCollection.Move` by keeping the structure and only rebinding the contexts, because its
|
|
231
|
+
* arrange re-flows each row from its bound view (`HandleStructurePreservingMove` / `ApplyMoveChange`, 2026-09-08).
|
|
232
|
+
* The measured heights here are index-keyed and authoritative, so they travel with their items instead: nothing is
|
|
233
|
+
* remeasured, the extent cannot change, and the scroll keeps its offset. Rebuilding instead would reset
|
|
234
|
+
* `MeasureVisible` to "exact for the first screen, estimated after" and throw a scrolled list back towards the top.
|
|
235
|
+
* Returns false when the two arrays are not a permutation of each other, so a real content change still rebuilds.
|
|
236
|
+
*/
|
|
237
|
+
ApplyReorderChange(old, items) {
|
|
238
|
+
const n = items.length;
|
|
239
|
+
if (this.MeasureItemsStrategy === "MeasureAll" && this.itemHeights.length !== n)
|
|
240
|
+
return false;
|
|
241
|
+
if (this.MeasureItemsStrategy === "MeasureVisible" && this.mvHeights.length !== n)
|
|
242
|
+
return false;
|
|
243
|
+
const moved = [];
|
|
244
|
+
for (let i = 0; i < n; i++)
|
|
245
|
+
if (old[i] !== items[i])
|
|
246
|
+
moved.push(i);
|
|
247
|
+
if (moved.length === 0) {
|
|
248
|
+
this.ChildrenFactory.UpdateItems(items);
|
|
249
|
+
return true;
|
|
250
|
+
} // same order, new array reference
|
|
251
|
+
// every slot that changed must hold an item that was in one of those slots before, otherwise this is not a reorder
|
|
252
|
+
const free = new Map();
|
|
253
|
+
for (const i of moved) {
|
|
254
|
+
const q = free.get(old[i]);
|
|
255
|
+
if (q)
|
|
256
|
+
q.push(i);
|
|
257
|
+
else
|
|
258
|
+
free.set(old[i], [i]);
|
|
259
|
+
}
|
|
260
|
+
const from = new Int32Array(n);
|
|
261
|
+
for (let i = 0; i < n; i++)
|
|
262
|
+
from[i] = i;
|
|
263
|
+
for (const i of moved) {
|
|
264
|
+
const q = free.get(items[i]);
|
|
265
|
+
if (!q || q.length === 0)
|
|
266
|
+
return false;
|
|
267
|
+
from[i] = q.shift();
|
|
268
|
+
}
|
|
269
|
+
if (this.MeasureItemsStrategy === "MeasureAll") {
|
|
270
|
+
const was = this.itemHeights;
|
|
271
|
+
this.itemHeights = Array.from({ length: n }, (_, i) => was[from[i]]);
|
|
272
|
+
}
|
|
273
|
+
else if (this.MeasureItemsStrategy === "MeasureVisible") {
|
|
274
|
+
const was = this.mvHeights, heights = new Float64Array(n);
|
|
275
|
+
for (let i = 0; i < n; i++)
|
|
276
|
+
heights[i] = was[from[i]];
|
|
277
|
+
this.mvHeights = heights;
|
|
278
|
+
// an item that was never measured can land inside the exact prefix: rebuild it over the leading measured run
|
|
279
|
+
this.mvPrefix = new Float64Array(n + 1);
|
|
280
|
+
this.mvMeasured = 0;
|
|
281
|
+
this.MvExtendPrefix(this.Spacing * this.RenderingScale);
|
|
282
|
+
}
|
|
283
|
+
// MeasureFirst has one height for every row, so a reorder is a pure rebind there (same as the C# note).
|
|
284
|
+
this.ChildrenFactory.UpdateItems(items);
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
146
287
|
/** Factory creating one cell (DrawnUi DataTemplate). Cells receive the item as BindingContext. */
|
|
147
288
|
get ItemTemplate() { return this.itemTemplate; }
|
|
148
289
|
set ItemTemplate(value) {
|
|
@@ -152,13 +293,26 @@ export class SkiaLayout extends SkiaControl {
|
|
|
152
293
|
this.ApplyItemsSource();
|
|
153
294
|
}
|
|
154
295
|
get IsTemplated() { return !!this.itemTemplate && !!this.itemsSource; }
|
|
155
|
-
/** The virtualized list case: a templated single-column Column.
|
|
296
|
+
/** The virtualized list case: a templated single-column Column. */
|
|
156
297
|
get IsTemplatedList() { return this.IsTemplated && this.Type === "Column" && this.Split <= 1; }
|
|
157
|
-
/**
|
|
298
|
+
/** A templated Row / Wrap / Grid / split Column with RecyclingTemplate Enabled: slots measured on pooled views, views bound to the slots drawn. */
|
|
299
|
+
get IsRecycledLayout() { return this.IsTemplated && !this.IsTemplatedList && this.recyclingTemplate === "Enabled"; }
|
|
300
|
+
/** Slots of a recycled layout, one per item. */
|
|
301
|
+
slots;
|
|
302
|
+
/**
|
|
303
|
+
* What the layout pass measures and arranges: static children; the slots of a recycled templated layout; or, with
|
|
304
|
+
* RecyclingTemplate Disabled, one realized view per item.
|
|
305
|
+
*/
|
|
158
306
|
LayoutViews() {
|
|
159
307
|
if (!this.IsTemplated)
|
|
160
308
|
return this.views;
|
|
161
|
-
const n = this.itemsSource.length
|
|
309
|
+
const n = this.itemsSource.length;
|
|
310
|
+
if (this.IsRecycledLayout) {
|
|
311
|
+
if (this.slots?.length !== n)
|
|
312
|
+
this.slots = Array.from({ length: n }, (_, i) => new TemplatedSlot(i, this.ChildrenFactory));
|
|
313
|
+
return this.slots;
|
|
314
|
+
}
|
|
315
|
+
const out = [];
|
|
162
316
|
for (let i = 0; i < n; i++) {
|
|
163
317
|
const v = this.ChildrenFactory.GetOrCreateViewForIndex(i);
|
|
164
318
|
if (v)
|
|
@@ -168,7 +322,8 @@ export class SkiaLayout extends SkiaControl {
|
|
|
168
322
|
}
|
|
169
323
|
/** Drops realized cells and rebuilds the structure (DrawnUi ApplyItemsSource). */
|
|
170
324
|
ApplyItemsSource() {
|
|
171
|
-
this.ChildrenFactory.Initialize(this.itemTemplate, this.itemsSource ?? [], this.
|
|
325
|
+
this.ChildrenFactory.Initialize(this.itemTemplate, this.itemsSource ?? [], this.recyclingTemplate);
|
|
326
|
+
this.slots = undefined;
|
|
172
327
|
this.structureDirty = true;
|
|
173
328
|
this.InvalidateMeasure();
|
|
174
329
|
}
|
|
@@ -204,11 +359,67 @@ export class SkiaLayout extends SkiaControl {
|
|
|
204
359
|
get Views() { return this.IsTemplated ? this.ChildrenFactory.GetViewsInUse() : this.views; }
|
|
205
360
|
/** Settable children list like DrawnUi Children (ignored while templated). */
|
|
206
361
|
get Children() { return this.views; }
|
|
362
|
+
/**
|
|
363
|
+
* C# Children reset (ChildrenCollectionSync, 66a1eb02): the app's children the new list no longer holds are removed,
|
|
364
|
+
* the ones it gained are added, then all of them take the list's order inside the slots app children occupy, so the
|
|
365
|
+
* parts a control made itself keep their place.
|
|
366
|
+
*/
|
|
207
367
|
set Children(value) {
|
|
368
|
+
const keep = new Set(value);
|
|
369
|
+
for (const v of [...this.views])
|
|
370
|
+
if (v.IsChildrenItem && !keep.has(v))
|
|
371
|
+
this.RemoveSubView(v);
|
|
372
|
+
const inViews = new Set(this.views);
|
|
373
|
+
for (const v of value) {
|
|
374
|
+
v.IsChildrenItem = true;
|
|
375
|
+
if (!inViews.has(v))
|
|
376
|
+
this.AddSubView(v);
|
|
377
|
+
}
|
|
378
|
+
this.OrderChildren(value);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* C# Insert / Move of a Children collection (the reconciler's appendChild / insertBefore): `child` goes before
|
|
382
|
+
* `before` among the app's children, or last without it. A new child is attached at the end of the views and an
|
|
383
|
+
* append stops there; otherwise the app's children take their new order inside the slots they occupy.
|
|
384
|
+
*/
|
|
385
|
+
InsertChild(child, before) {
|
|
386
|
+
child.IsChildrenItem = true;
|
|
387
|
+
const attached = child.Parent === this && this.views.includes(child);
|
|
388
|
+
if (!attached)
|
|
389
|
+
this.AddSubView(child);
|
|
390
|
+
if (!attached && !before)
|
|
391
|
+
return;
|
|
392
|
+
const order = this.views.filter((v) => v.IsChildrenItem && v !== child);
|
|
393
|
+
const at = before ? order.indexOf(before) : -1;
|
|
394
|
+
order.splice(at < 0 ? order.length : at, 0, child);
|
|
395
|
+
this.OrderChildren(order);
|
|
396
|
+
}
|
|
397
|
+
/** C# OrderViews: the given children, in their order, inside the slots they occupy in the views; nothing else moves. */
|
|
398
|
+
OrderChildren(ordered) {
|
|
399
|
+
const inViews = new Set(this.views);
|
|
400
|
+
const present = ordered.filter((v) => inViews.delete(v));
|
|
401
|
+
if (present.length < 2)
|
|
402
|
+
return;
|
|
403
|
+
const isChild = new Set(present);
|
|
404
|
+
let next = 0, changed = false;
|
|
405
|
+
for (let i = 0; i < this.views.length && next < present.length; i++) {
|
|
406
|
+
if (!isChild.has(this.views[i]))
|
|
407
|
+
continue;
|
|
408
|
+
if (this.views[i] !== present[next]) {
|
|
409
|
+
this.views[i] = present[next];
|
|
410
|
+
changed = true;
|
|
411
|
+
}
|
|
412
|
+
next++;
|
|
413
|
+
}
|
|
414
|
+
if (changed) {
|
|
415
|
+
this.orderedViews = undefined;
|
|
416
|
+
this.InvalidateMeasure();
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
/** C# ClearChildren: removes every subview, the control's own parts included. */
|
|
420
|
+
ClearChildren() {
|
|
208
421
|
for (const v of [...this.views])
|
|
209
422
|
this.RemoveSubView(v);
|
|
210
|
-
for (const v of value)
|
|
211
|
-
this.AddSubView(v);
|
|
212
423
|
}
|
|
213
424
|
orderedViews;
|
|
214
425
|
/** Static children sorted by ZIndex (stable), computed once per change (C# GetOrderedSubviews). */
|
|
@@ -235,10 +446,18 @@ export class SkiaLayout extends SkiaControl {
|
|
|
235
446
|
}
|
|
236
447
|
AddSubView(control) { this.InsertSubView(this.views.length, control); }
|
|
237
448
|
InsertSubView(index, control) {
|
|
449
|
+
// already a child: a move (React reorders keyed children by inserting an existing one elsewhere), never a 2nd copy
|
|
450
|
+
const at = control.Parent === this ? this.views.indexOf(control) : -1;
|
|
451
|
+
if (at >= 0) {
|
|
452
|
+
this.views.splice(at, 1);
|
|
453
|
+
if (at < index)
|
|
454
|
+
index--;
|
|
455
|
+
}
|
|
238
456
|
control.Parent = this;
|
|
239
|
-
this.views.splice(index, 0, control);
|
|
457
|
+
this.views.splice(Math.min(Math.max(0, index), this.views.length), 0, control);
|
|
240
458
|
this.orderedViews = undefined;
|
|
241
459
|
this.InvalidateMeasure();
|
|
460
|
+
this.Superview?.Hover?.RequestCheck(); // a popup / page under a still mouse
|
|
242
461
|
}
|
|
243
462
|
RemoveSubView(control) {
|
|
244
463
|
const i = this.views.indexOf(control);
|
|
@@ -248,6 +467,7 @@ export class SkiaLayout extends SkiaControl {
|
|
|
248
467
|
this.orderedViews = undefined;
|
|
249
468
|
control.Parent = undefined;
|
|
250
469
|
this.InvalidateMeasure();
|
|
470
|
+
this.Superview?.Hover?.RequestCheck();
|
|
251
471
|
}
|
|
252
472
|
// ---- measure ----
|
|
253
473
|
MeasureAbsolute(widthConstraint, heightConstraint, scale) {
|
|
@@ -371,7 +591,7 @@ export class SkiaLayout extends SkiaControl {
|
|
|
371
591
|
}
|
|
372
592
|
});
|
|
373
593
|
}
|
|
374
|
-
const g = new SkiaGridStructure(this, wPts, hPts, scale);
|
|
594
|
+
const g = new SkiaGridStructure(this, wPts, hPts, scale, this.LayoutViews());
|
|
375
595
|
g.DecompressStars(wPts, hPts);
|
|
376
596
|
const needAutoWidth = this.WidthRequest < 0 && this.HorizontalOptions !== "Fill";
|
|
377
597
|
const needAutoHeight = this.HeightRequest < 0 && this.VerticalOptions !== "Fill";
|
|
@@ -511,6 +731,11 @@ export class SkiaLayout extends SkiaControl {
|
|
|
511
731
|
this.ChildrenFactory.ReleaseViewAt(index);
|
|
512
732
|
return h;
|
|
513
733
|
}
|
|
734
|
+
/**
|
|
735
|
+
* The item's offset and height are real, not estimated (C# LastMeasuredIndexLocal >= index): MeasureVisible knows
|
|
736
|
+
* them once the measured prefix passed the item; the other strategies always do.
|
|
737
|
+
*/
|
|
738
|
+
IsItemMeasured(index) { return this.MeasureItemsStrategy !== "MeasureVisible" || index < this.mvMeasured; }
|
|
514
739
|
/** Pixel offset of item index from the top of the layout content (inside padding). */
|
|
515
740
|
GetItemOffsetPixels(index) {
|
|
516
741
|
const scale = this.RenderingScale;
|
|
@@ -536,6 +761,22 @@ export class SkiaLayout extends SkiaControl {
|
|
|
536
761
|
OnLayoutChanged() {
|
|
537
762
|
if (this.IsTemplatedList)
|
|
538
763
|
return; // cells are arranged per frame for the visible range only
|
|
764
|
+
this.ArrangeChildren();
|
|
765
|
+
// views on screen follow their slots also when a cache above skips the drawing pass (gestures, accessibility)
|
|
766
|
+
if (this.IsRecycledLayout) {
|
|
767
|
+
const scale = this.RenderingScale;
|
|
768
|
+
for (const v of this.ChildrenFactory.GetViewsInUse()) {
|
|
769
|
+
const s = this.slots?.[v.ContextIndex];
|
|
770
|
+
if (s) {
|
|
771
|
+
v.Arrange(s.Destination, v.WidthRequest, v.HeightRequest, scale);
|
|
772
|
+
s.ArrangedView = v;
|
|
773
|
+
s.ArrangedFor = s.Destination;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
/** Static children and realized views are arranged; the slots of a recycled layout record where their item goes. */
|
|
779
|
+
ArrangeChildren() {
|
|
539
780
|
const scale = this.RenderingScale;
|
|
540
781
|
const p = this.Padding;
|
|
541
782
|
const r = this.DrawingRect;
|
|
@@ -582,12 +823,68 @@ export class SkiaLayout extends SkiaControl {
|
|
|
582
823
|
this.PaintTemplated(ctx);
|
|
583
824
|
return;
|
|
584
825
|
}
|
|
826
|
+
if (this.IsRecycledLayout) {
|
|
827
|
+
this.PaintSlots(ctx);
|
|
828
|
+
return;
|
|
829
|
+
}
|
|
585
830
|
const composing = this.IsRenderingWithComposition;
|
|
586
|
-
for (const v of this.GetOrderedSubviews())
|
|
587
|
-
if (
|
|
588
|
-
|
|
831
|
+
for (const v of this.GetOrderedSubviews()) {
|
|
832
|
+
if (composing && !this.DirtyChildrenInternal.has(v))
|
|
833
|
+
continue;
|
|
834
|
+
v.Render(ctx); // a child entirely outside the clip skips itself
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
GetCompositeChildren() { return this.IsTemplatedList || this.IsRecycledLayout ? [] : this.GetOrderedSubviews(); }
|
|
838
|
+
drawnSlots = new Set();
|
|
839
|
+
/**
|
|
840
|
+
* Recycled templated layout (C# DrawStack): a view from the pool is bound to each slot that can be seen and drawn
|
|
841
|
+
* there; the views of the other slots go back to the pool.
|
|
842
|
+
*/
|
|
843
|
+
PaintSlots(ctx) {
|
|
844
|
+
const slots = this.slots ?? [], f = this.ChildrenFactory, scale = this.RenderingScale, area = this.SlotsArea();
|
|
845
|
+
const drawn = this.drawnSlots;
|
|
846
|
+
drawn.clear();
|
|
847
|
+
for (const s of slots) {
|
|
848
|
+
const d = s.Destination;
|
|
849
|
+
if (s.IsVisible && d.Right > area.Left && d.Left < area.Right && d.Bottom > area.Top && d.Top < area.Bottom)
|
|
850
|
+
drawn.add(s.Index);
|
|
851
|
+
}
|
|
852
|
+
f.ReleaseExcept(drawn);
|
|
853
|
+
this.FirstVisibleIndex = this.LastVisibleIndex = -1;
|
|
854
|
+
for (const i of drawn) {
|
|
855
|
+
const s = slots[i], view = f.GetOrCreateViewForIndex(i, true);
|
|
856
|
+
if (!view)
|
|
857
|
+
continue;
|
|
858
|
+
// a view last measured for another item is measured for this one; the whole size is compared because a Wrap has
|
|
859
|
+
// no main-axis size key (C# 3d7bd78f)
|
|
860
|
+
const has = view.MeasuredSize.Pixels, want = s.MeasuredSize.Pixels;
|
|
861
|
+
if (Math.abs(has.Width - want.Width) > 1 || Math.abs(has.Height - want.Height) > 1)
|
|
862
|
+
view.NeedMeasure = true;
|
|
863
|
+
const size = view.MeasuredSize;
|
|
864
|
+
view.Measure(s.WidthConstraint, s.HeightConstraint, scale);
|
|
865
|
+
if (s.ArrangedView !== view || s.ArrangedFor !== s.Destination || view.MeasuredSize !== size) {
|
|
866
|
+
view.Arrange(s.Destination, view.WidthRequest, view.HeightRequest, scale);
|
|
867
|
+
s.ArrangedView = view;
|
|
868
|
+
s.ArrangedFor = s.Destination;
|
|
869
|
+
}
|
|
870
|
+
view.Render(ctx);
|
|
871
|
+
if (this.FirstVisibleIndex < 0)
|
|
872
|
+
this.FirstVisibleIndex = i;
|
|
873
|
+
this.LastVisibleIndex = i;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Where slots are drawn (C# GetOnScreenVisibleArea): what can be seen, inflated by VirtualisationInflated. Under a
|
|
878
|
+
* cache (this layout's or an ancestor's, below the nearest scroll) every slot, because the cache is blitted later at
|
|
879
|
+
* other offsets.
|
|
880
|
+
*/
|
|
881
|
+
SlotsArea() {
|
|
882
|
+
for (let c = this; c && !("ViewportOffsetY" in c); c = c.Parent)
|
|
883
|
+
if (c.UsingCacheType !== "None")
|
|
884
|
+
return this.DrawingRect;
|
|
885
|
+
const v = this.GetVisibleViewport(), inflate = this.VirtualisationInflated * this.RenderingScale;
|
|
886
|
+
return new SKRect(v.Left - inflate, v.Top - inflate, v.Right + inflate, v.Bottom + inflate);
|
|
589
887
|
}
|
|
590
|
-
GetCompositeChildren() { return this.IsTemplatedList ? [] : this.GetOrderedSubviews(); }
|
|
591
888
|
/** Realizes, binds, arranges and draws only the cells intersecting the visible viewport (+ inflation). */
|
|
592
889
|
PaintTemplated(ctx) {
|
|
593
890
|
const items = this.itemsSource;
|
|
@@ -32,7 +32,9 @@ export class SkiaLottie extends AnimatedFramesRenderer {
|
|
|
32
32
|
Animation;
|
|
33
33
|
constructor() {
|
|
34
34
|
super();
|
|
35
|
-
|
|
35
|
+
// C# ImageDoubleBuffered pays off where a thread bakes the bitmap; on the web it is one offscreen pass per frame
|
|
36
|
+
// while playing, so the web engines default to Operations (drawnui-cross rule 3)
|
|
37
|
+
this.UseCache = "Operations";
|
|
36
38
|
}
|
|
37
39
|
get Source() { return this.source; }
|
|
38
40
|
set Source(v) { if (this.source !== v) {
|
|
@@ -174,7 +176,7 @@ export class SkiaLottie extends AnimatedFramesRenderer {
|
|
|
174
176
|
return Promise.resolve(cached);
|
|
175
177
|
let p = SkiaLottie.inFlight.get(source);
|
|
176
178
|
if (!p) {
|
|
177
|
-
p = fetch(source).then(async (r) => { if (!r.ok)
|
|
179
|
+
p = fetch(Super.ResolveAssetUrl(source)).then(async (r) => { if (!r.ok)
|
|
178
180
|
throw new Error(`${r.status} ${source}`); const json = await r.text(); SkiaLottie.CachedAnimations.set(source, json); return json; })
|
|
179
181
|
.finally(() => SkiaLottie.inFlight.delete(source));
|
|
180
182
|
SkiaLottie.inFlight.set(source, p);
|
|
@@ -36,5 +36,6 @@ export declare class SkiaProgress extends SkiaControl {
|
|
|
36
36
|
get Ratio(): number;
|
|
37
37
|
protected MeasureAbsolute(w: number, _h: number, scale: number): ScaledSize;
|
|
38
38
|
protected Paint(ctx: DrawingContext): void;
|
|
39
|
-
|
|
39
|
+
/** Value between Min and Max, read as "65%"; the name is the app's AccessibilityLabel only (drawnui-cross 6c). */
|
|
40
|
+
get AccessibilityValue(): import("../core/Accessibility").AccessibilityValue;
|
|
40
41
|
}
|
|
@@ -33,9 +33,9 @@ export class SkiaProgress extends SkiaControl {
|
|
|
33
33
|
set Max(v) { this.max = v; this.Update(); }
|
|
34
34
|
/** Unset = per-style palette. */
|
|
35
35
|
get TrackColor() { return this.trackColor ?? SkiaProgress.Palette(this.UsingControlStyle).track; }
|
|
36
|
-
set TrackColor(v) { this.trackColor = v; this.
|
|
36
|
+
set TrackColor(v) { this.trackColor = v; this.UpdateDraw(); }
|
|
37
37
|
get ProgressColor() { return this.progressColor ?? SkiaProgress.Palette(this.UsingControlStyle).progress; }
|
|
38
|
-
set ProgressColor(v) { this.progressColor = v; this.
|
|
38
|
+
set ProgressColor(v) { this.progressColor = v; this.UpdateDraw(); }
|
|
39
39
|
/** C# ResolvedTrackColor / ResolvedProgressColor / track heights per style. */
|
|
40
40
|
static Palette(s) {
|
|
41
41
|
switch (s) {
|
|
@@ -76,5 +76,8 @@ export class SkiaProgress extends SkiaControl {
|
|
|
76
76
|
}
|
|
77
77
|
paint.delete();
|
|
78
78
|
}
|
|
79
|
-
|
|
79
|
+
/** Value between Min and Max, read as "65%"; the name is the app's AccessibilityLabel only (drawnui-cross 6c). */
|
|
80
|
+
get AccessibilityValue() {
|
|
81
|
+
return { Now: this.value, Min: this.min, Max: this.max, Step: 0, Text: `${Math.round(this.Ratio * 100)}%` };
|
|
82
|
+
}
|
|
80
83
|
}
|
|
@@ -37,8 +37,7 @@ export class SkiaRadioButton extends SkiaToggle {
|
|
|
37
37
|
return table[this.UsingControlStyle]?.[name];
|
|
38
38
|
}
|
|
39
39
|
CreateDefaultContent() {
|
|
40
|
-
|
|
41
|
-
this.MinimumHeightRequest = 24;
|
|
40
|
+
this.SetMinimumContentHeight(24);
|
|
42
41
|
const s = this.usingStyle;
|
|
43
42
|
const size = s === "Unset" ? 18 : 20;
|
|
44
43
|
const box = new SkiaLayout();
|
|
@@ -145,8 +144,10 @@ export class SkiaRadioButton extends SkiaToggle {
|
|
|
145
144
|
super.OnToggledChanged();
|
|
146
145
|
}
|
|
147
146
|
ProcessGestures(args, apply) {
|
|
147
|
+
if (!this.RespondsToGestures)
|
|
148
|
+
return this.BlockedGestureResult(args); // a tap does not select it, nor reach controls under it
|
|
148
149
|
if (args.Type === "Tapped") {
|
|
149
|
-
if (!this.IsToggled
|
|
150
|
+
if (!this.IsToggled) {
|
|
150
151
|
this.IsToggled = true;
|
|
151
152
|
return this;
|
|
152
153
|
}
|