drawnui-react 0.1.0-preview.1 → 0.1.0-preview.11
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 +98 -29
- package/SKIPPED.md +23 -16
- package/dist/controls/DrawnGame.d.ts +37 -0
- package/dist/controls/DrawnGame.js +60 -0
- package/dist/controls/SkiaButton.d.ts +4 -1
- package/dist/controls/SkiaButton.js +20 -7
- package/dist/controls/SkiaCarousel.d.ts +6 -0
- package/dist/controls/SkiaCarousel.js +7 -1
- package/dist/controls/SkiaCheckbox.d.ts +0 -1
- package/dist/controls/SkiaCheckbox.js +0 -6
- package/dist/controls/SkiaEditor.js +8 -0
- package/dist/controls/SkiaLabel.d.ts +2 -0
- package/dist/controls/SkiaLabel.js +9 -0
- package/dist/controls/SkiaLayout.d.ts +10 -0
- package/dist/controls/SkiaLayout.js +64 -1
- package/dist/controls/SkiaRadioButton.js +1 -2
- package/dist/controls/SkiaScroll.d.ts +9 -0
- package/dist/controls/SkiaScroll.js +120 -13
- 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/SkiaSwitch.d.ts +0 -1
- package/dist/controls/SkiaSwitch.js +0 -6
- package/dist/controls/SkiaToggle.d.ts +18 -4
- package/dist/controls/SkiaToggle.js +45 -3
- package/dist/core/Animators.d.ts +9 -0
- package/dist/core/Animators.js +26 -1
- package/dist/core/Canvas.d.ts +82 -4
- package/dist/core/Canvas.js +261 -10
- package/dist/core/Gestures.d.ts +47 -1
- package/dist/core/Gestures.js +43 -1
- package/dist/core/SkiaControl.d.ts +38 -2
- package/dist/core/SkiaControl.js +76 -3
- package/dist/core/Styles.d.ts +51 -0
- package/dist/core/Styles.js +92 -0
- package/dist/core/Super.d.ts +10 -1
- package/dist/core/Super.js +20 -3
- package/dist/core/Types.d.ts +7 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/react/SkiaShell.js +5 -2
- package/dist/react/index.d.ts +22 -4
- package/dist/react/index.js +25 -4
- package/dist/react/reconciler.js +2 -0
- package/dist/vite/index.d.ts +74 -0
- package/dist/vite/index.js +204 -0
- package/package.json +35 -4
- package/PARITY.md +0 -287
|
@@ -155,6 +155,8 @@ export declare class SkiaLabel extends SkiaControl {
|
|
|
155
155
|
protected FillGradientPaintsBackground(): boolean;
|
|
156
156
|
protected Paint(ctx: DrawingContext): void;
|
|
157
157
|
ProcessGestures(args: SkiaGesturesParameters, apply: GestureEventProcessingInfo): SkiaControl | null;
|
|
158
|
+
/** Pointer cursor over a tappable span (the label itself may not be tappable). */
|
|
159
|
+
WantsPointerCursor(x: number, y: number): boolean;
|
|
158
160
|
/** Return null to not consume the tap. */
|
|
159
161
|
protected OnSpanTapped(span: TextSpan, args: SkiaGesturesParameters, apply: GestureEventProcessingInfo): SkiaControl | null;
|
|
160
162
|
}
|
|
@@ -572,6 +572,15 @@ export class SkiaLabel extends SkiaControl {
|
|
|
572
572
|
}
|
|
573
573
|
return super.ProcessGestures(args, apply);
|
|
574
574
|
}
|
|
575
|
+
/** Pointer cursor over a tappable span (the label itself may not be tappable). */
|
|
576
|
+
WantsPointerCursor(x, y) {
|
|
577
|
+
if (super.WantsPointerCursor(x, y))
|
|
578
|
+
return true;
|
|
579
|
+
for (const span of this.Spans)
|
|
580
|
+
if (span.HasTapHandler && span.HitIsInside(x, y))
|
|
581
|
+
return true;
|
|
582
|
+
return false;
|
|
583
|
+
}
|
|
575
584
|
/** Return null to not consume the tap. */
|
|
576
585
|
OnSpanTapped(span, args, apply) {
|
|
577
586
|
span.FireTap(new ControlTappedEventArgs(this, args, apply));
|
|
@@ -70,6 +70,16 @@ export declare class SkiaLayout extends SkiaControl {
|
|
|
70
70
|
* anything else rebuilds. Returns false when a full rebuild is needed.
|
|
71
71
|
*/
|
|
72
72
|
private ApplyIncrementalChange;
|
|
73
|
+
/**
|
|
74
|
+
* Same items in another order — a drag-to-reorder, or any code writing a permuted array. DrawnUi handles the
|
|
75
|
+
* matching `ObservableCollection.Move` by keeping the structure and only rebinding the contexts, because its
|
|
76
|
+
* arrange re-flows each row from its bound view (`HandleStructurePreservingMove` / `ApplyMoveChange`, 2026-09-08).
|
|
77
|
+
* The measured heights here are index-keyed and authoritative, so they travel with their items instead: nothing is
|
|
78
|
+
* remeasured, the extent cannot change, and the scroll keeps its offset. Rebuilding instead would reset
|
|
79
|
+
* `MeasureVisible` to "exact for the first screen, estimated after" and throw a scrolled list back towards the top.
|
|
80
|
+
* Returns false when the two arrays are not a permutation of each other, so a real content change still rebuilds.
|
|
81
|
+
*/
|
|
82
|
+
private ApplyReorderChange;
|
|
73
83
|
/** Factory creating one cell (DrawnUi DataTemplate). Cells receive the item as BindingContext. */
|
|
74
84
|
get ItemTemplate(): (() => SkiaControl) | undefined;
|
|
75
85
|
set ItemTemplate(value: (() => SkiaControl) | undefined);
|
|
@@ -87,7 +87,11 @@ export class SkiaLayout extends SkiaControl {
|
|
|
87
87
|
*/
|
|
88
88
|
ApplyIncrementalChange(old, items) {
|
|
89
89
|
const n = items.length, o = old.length;
|
|
90
|
-
if (
|
|
90
|
+
if (o === 0)
|
|
91
|
+
return false;
|
|
92
|
+
if (n === o)
|
|
93
|
+
return this.ApplyReorderChange(old, items);
|
|
94
|
+
if (n < o)
|
|
91
95
|
return false;
|
|
92
96
|
const k = n - o;
|
|
93
97
|
const scale = this.RenderingScale, gap = this.Spacing * scale, w = this.measuredWidthPx;
|
|
@@ -143,6 +147,65 @@ export class SkiaLayout extends SkiaControl {
|
|
|
143
147
|
}
|
|
144
148
|
return false;
|
|
145
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Same items in another order — a drag-to-reorder, or any code writing a permuted array. DrawnUi handles the
|
|
152
|
+
* matching `ObservableCollection.Move` by keeping the structure and only rebinding the contexts, because its
|
|
153
|
+
* arrange re-flows each row from its bound view (`HandleStructurePreservingMove` / `ApplyMoveChange`, 2026-09-08).
|
|
154
|
+
* The measured heights here are index-keyed and authoritative, so they travel with their items instead: nothing is
|
|
155
|
+
* remeasured, the extent cannot change, and the scroll keeps its offset. Rebuilding instead would reset
|
|
156
|
+
* `MeasureVisible` to "exact for the first screen, estimated after" and throw a scrolled list back towards the top.
|
|
157
|
+
* Returns false when the two arrays are not a permutation of each other, so a real content change still rebuilds.
|
|
158
|
+
*/
|
|
159
|
+
ApplyReorderChange(old, items) {
|
|
160
|
+
const n = items.length;
|
|
161
|
+
if (this.MeasureItemsStrategy === "MeasureAll" && this.itemHeights.length !== n)
|
|
162
|
+
return false;
|
|
163
|
+
if (this.MeasureItemsStrategy === "MeasureVisible" && this.mvHeights.length !== n)
|
|
164
|
+
return false;
|
|
165
|
+
const moved = [];
|
|
166
|
+
for (let i = 0; i < n; i++)
|
|
167
|
+
if (old[i] !== items[i])
|
|
168
|
+
moved.push(i);
|
|
169
|
+
if (moved.length === 0) {
|
|
170
|
+
this.ChildrenFactory.UpdateItems(items);
|
|
171
|
+
return true;
|
|
172
|
+
} // same order, new array reference
|
|
173
|
+
// every slot that changed must hold an item that was in one of those slots before, otherwise this is not a reorder
|
|
174
|
+
const free = new Map();
|
|
175
|
+
for (const i of moved) {
|
|
176
|
+
const q = free.get(old[i]);
|
|
177
|
+
if (q)
|
|
178
|
+
q.push(i);
|
|
179
|
+
else
|
|
180
|
+
free.set(old[i], [i]);
|
|
181
|
+
}
|
|
182
|
+
const from = new Int32Array(n);
|
|
183
|
+
for (let i = 0; i < n; i++)
|
|
184
|
+
from[i] = i;
|
|
185
|
+
for (const i of moved) {
|
|
186
|
+
const q = free.get(items[i]);
|
|
187
|
+
if (!q || q.length === 0)
|
|
188
|
+
return false;
|
|
189
|
+
from[i] = q.shift();
|
|
190
|
+
}
|
|
191
|
+
if (this.MeasureItemsStrategy === "MeasureAll") {
|
|
192
|
+
const was = this.itemHeights;
|
|
193
|
+
this.itemHeights = Array.from({ length: n }, (_, i) => was[from[i]]);
|
|
194
|
+
}
|
|
195
|
+
else if (this.MeasureItemsStrategy === "MeasureVisible") {
|
|
196
|
+
const was = this.mvHeights, heights = new Float64Array(n);
|
|
197
|
+
for (let i = 0; i < n; i++)
|
|
198
|
+
heights[i] = was[from[i]];
|
|
199
|
+
this.mvHeights = heights;
|
|
200
|
+
// an item that was never measured can land inside the exact prefix: rebuild it over the leading measured run
|
|
201
|
+
this.mvPrefix = new Float64Array(n + 1);
|
|
202
|
+
this.mvMeasured = 0;
|
|
203
|
+
this.MvExtendPrefix(this.Spacing * this.RenderingScale);
|
|
204
|
+
}
|
|
205
|
+
// MeasureFirst has one height for every row, so a reorder is a pure rebind there (same as the C# note).
|
|
206
|
+
this.ChildrenFactory.UpdateItems(items);
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
146
209
|
/** Factory creating one cell (DrawnUi DataTemplate). Cells receive the item as BindingContext. */
|
|
147
210
|
get ItemTemplate() { return this.itemTemplate; }
|
|
148
211
|
set ItemTemplate(value) {
|
|
@@ -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();
|
|
@@ -218,6 +218,15 @@ export declare class SkiaScroll extends SkiaControl {
|
|
|
218
218
|
/** DrawnUi DecelerationRatio: FrictionScrolled / 100, floored at 0.1 friction. */
|
|
219
219
|
private get DecelerationRatio();
|
|
220
220
|
private ResetPan;
|
|
221
|
+
private draggedScrollBar?;
|
|
222
|
+
private scrollBarOwnsGesture;
|
|
223
|
+
/**
|
|
224
|
+
* Thumb drag and track press on a ScrollBar / ScrollBarHorizontal with IsDraggable set (C# ProcessScrollBarGestures).
|
|
225
|
+
* A gesture that starts on the bar belongs to it until the next down: the content neither pans nor gets the tap.
|
|
226
|
+
* True when the gesture was handled here.
|
|
227
|
+
*/
|
|
228
|
+
protected ProcessScrollBarGestures(args: SkiaGesturesParameters, apply: GestureEventProcessingInfo): boolean;
|
|
229
|
+
private ApplyScrollBarDrag;
|
|
221
230
|
ProcessGestures(args: SkiaGesturesParameters, apply: GestureEventProcessingInfo): SkiaControl | null;
|
|
222
231
|
private Bounce;
|
|
223
232
|
/** DrawnUi StartToFlingFrom + PrepareToFlingAfterInitialized: cut the curve so it stops exactly at the edge. */
|
|
@@ -121,7 +121,7 @@ export class SkiaScroll extends SkiaControl {
|
|
|
121
121
|
}
|
|
122
122
|
/** C# UpdateScrollBarIndicator: pushes progress / thumb ratio / overscroll / scrolling state when any changed. */
|
|
123
123
|
UpdateScrollBarIndicator() {
|
|
124
|
-
const isScrolling = this.IsScrolling || this.IsUserPanning;
|
|
124
|
+
const isScrolling = this.IsScrolling || this.IsUserPanning || !!this.draggedScrollBar;
|
|
125
125
|
const scale = this.RenderingScale;
|
|
126
126
|
const viewportH = this.DrawingRect.Height / scale, viewportW = this.DrawingRect.Width / scale;
|
|
127
127
|
const extentH = this.ContentSize.Units.Height + this.HeaderExtentPts + this.FooterExtentPts, extentW = this.ContentSize.Units.Width + this.HeaderExtentPts + this.FooterExtentPts;
|
|
@@ -560,6 +560,15 @@ export class SkiaScroll extends SkiaControl {
|
|
|
560
560
|
const width = Math.max(0, this.ContentSize.Units.Width + (this.Orientation === "Horizontal" ? extra : 0) - viewportW);
|
|
561
561
|
const height = Math.max(0, this.ContentSize.Units.Height + (this.Orientation !== "Horizontal" ? extra : 0) - viewportH);
|
|
562
562
|
this.ContentOffsetBounds = new SKRect(-width, -height, 0, 0);
|
|
563
|
+
// C# InitializeViewport: pull an offset the new bounds no longer contain back inside them, here where
|
|
564
|
+
// the bounds change. Shrinking content (fewer items) otherwise leaves the viewport parked past its end
|
|
565
|
+
// with nothing on screen. Skipped under a live pan, a fling / bounce or pull-to-refresh, where being
|
|
566
|
+
// past the edge is intentional (rubber band, refresh indicator).
|
|
567
|
+
if (!this.IsUserPanning && !this.IsScrolling && !this.isRefreshing) {
|
|
568
|
+
const c = this.ClampOffset(this.offsetX, this.offsetY, this.ContentOffsetBounds, true);
|
|
569
|
+
this.ViewportOffsetX = c.X;
|
|
570
|
+
this.ViewportOffsetY = c.Y;
|
|
571
|
+
}
|
|
563
572
|
this.ArrangeContent();
|
|
564
573
|
this.CheckLoadMore();
|
|
565
574
|
}
|
|
@@ -637,9 +646,18 @@ export class SkiaScroll extends SkiaControl {
|
|
|
637
646
|
if (this.RefreshEnabled && this.refreshIndicator && this.refreshIndicator.IsVisible && (this.OverScrolled || this.isRefreshing))
|
|
638
647
|
this.refreshIndicator.Render(ctx);
|
|
639
648
|
this.UpdateScrollBarIndicator();
|
|
640
|
-
for (const bar of [this.scrollBar, this.scrollBarHorizontal])
|
|
641
|
-
if (bar
|
|
642
|
-
|
|
649
|
+
for (const bar of [this.scrollBar, this.scrollBarHorizontal]) {
|
|
650
|
+
if (!bar || !bar.IsVisible || bar.Opacity <= 0)
|
|
651
|
+
continue;
|
|
652
|
+
// the bar is parent-independent: its thumb resizing (squash on a bounce) invalidates the bar alone, so it is
|
|
653
|
+
// re-measured here on draw instead of dragging the scroll and every ancestor through a measure each frame
|
|
654
|
+
if (bar.NeedMeasure) {
|
|
655
|
+
const r = this.DrawingRect;
|
|
656
|
+
bar.Measure(r.Width, r.Height, this.RenderingScale);
|
|
657
|
+
bar.Arrange(r, bar.WidthRequest, bar.HeightRequest, this.RenderingScale);
|
|
658
|
+
}
|
|
659
|
+
bar.Render(ctx);
|
|
660
|
+
}
|
|
643
661
|
canvas.restoreToCount(saved);
|
|
644
662
|
}
|
|
645
663
|
OnScrolled() {
|
|
@@ -754,13 +772,35 @@ export class SkiaScroll extends SkiaControl {
|
|
|
754
772
|
return;
|
|
755
773
|
const i = Math.max(0, Math.min(items.length - 1, index));
|
|
756
774
|
const scale = this.RenderingScale;
|
|
757
|
-
const
|
|
758
|
-
|
|
775
|
+
const horizontal = this.Orientation === "Horizontal";
|
|
776
|
+
const viewport = (horizontal ? this.DrawingRect.Width : this.DrawingRect.Height) / scale;
|
|
777
|
+
// Item start and size in points, relative to the content start along the scroll axis.
|
|
778
|
+
let itemStart, itemSize;
|
|
779
|
+
if (!horizontal && layout.Type === "Column" && layout.Split <= 1) {
|
|
780
|
+
// The virtualized list: cells past the viewport are not realized, the structure knows their offsets.
|
|
781
|
+
itemStart = (layout.DrawingRect.Top - this.content.DrawingRect.Top + layout.GetItemOffsetPixels(i)) / scale;
|
|
782
|
+
itemSize = (layout.GetItemOffsetPixels(i + 1) - layout.GetItemOffsetPixels(i)) / scale;
|
|
783
|
+
}
|
|
784
|
+
else {
|
|
785
|
+
// A templated Row / Wrap / Grid realizes every item (C# non-list layouts are not virtualized): read the cell's
|
|
786
|
+
// arranged rect, like C# reads the structure cell's Destination.
|
|
787
|
+
const cell = layout.ChildrenFactory.GetViewForIndex(i);
|
|
788
|
+
if (!cell)
|
|
789
|
+
return;
|
|
790
|
+
const r = cell.DrawingRect, c = this.content.DrawingRect;
|
|
791
|
+
itemStart = (horizontal ? r.Left - c.Left : r.Top - c.Top) / scale;
|
|
792
|
+
itemSize = (horizontal ? r.Width : r.Height) / scale;
|
|
793
|
+
}
|
|
794
|
+
let target = itemStart;
|
|
759
795
|
if (option === "End")
|
|
760
|
-
target -=
|
|
796
|
+
target -= viewport - itemSize;
|
|
761
797
|
else if (option === "Center")
|
|
762
|
-
target -=
|
|
763
|
-
|
|
798
|
+
target -= (viewport - itemSize) / 2;
|
|
799
|
+
const time = animate ? this.ScrollingSpeedMs / 1000 : 0;
|
|
800
|
+
if (horizontal)
|
|
801
|
+
this.ScrollTo(-target, this.offsetY, time, true);
|
|
802
|
+
else
|
|
803
|
+
this.ScrollTo(this.offsetX, -target, time, true);
|
|
764
804
|
}
|
|
765
805
|
StopAnimators() {
|
|
766
806
|
this.flingEdgeX = null;
|
|
@@ -783,10 +823,68 @@ export class SkiaScroll extends SkiaControl {
|
|
|
783
823
|
this.panningLastDelta = SKPoint.Empty;
|
|
784
824
|
this.panningCurrentOffsetPts = new SKPoint(this.offsetX, this.offsetY);
|
|
785
825
|
}
|
|
826
|
+
draggedScrollBar;
|
|
827
|
+
scrollBarOwnsGesture = false;
|
|
828
|
+
/**
|
|
829
|
+
* Thumb drag and track press on a ScrollBar / ScrollBarHorizontal with IsDraggable set (C# ProcessScrollBarGestures).
|
|
830
|
+
* A gesture that starts on the bar belongs to it until the next down: the content neither pans nor gets the tap.
|
|
831
|
+
* True when the gesture was handled here.
|
|
832
|
+
*/
|
|
833
|
+
ProcessScrollBarGestures(args, apply) {
|
|
834
|
+
if (args.Type === "Down") {
|
|
835
|
+
this.draggedScrollBar = undefined;
|
|
836
|
+
this.scrollBarOwnsGesture = false;
|
|
837
|
+
const draggable = (b) => b && b.IsDraggable && b.BeginDrag && b.GetDragProgress ? b : undefined;
|
|
838
|
+
const v = draggable(this.scrollBar), h = draggable(this.scrollBarHorizontal);
|
|
839
|
+
if (!v && !h)
|
|
840
|
+
return false;
|
|
841
|
+
const x = apply.MappedLocation.X + apply.ChildOffset.X, y = apply.MappedLocation.Y + apply.ChildOffset.Y;
|
|
842
|
+
if (v && v.BeginDrag(x, y))
|
|
843
|
+
this.draggedScrollBar = v;
|
|
844
|
+
else if (h && h.BeginDrag(x, y))
|
|
845
|
+
this.draggedScrollBar = h;
|
|
846
|
+
if (!this.draggedScrollBar)
|
|
847
|
+
return false;
|
|
848
|
+
this.scrollBarOwnsGesture = true;
|
|
849
|
+
this.StopScrolling();
|
|
850
|
+
this.ApplyScrollBarDrag(x, y);
|
|
851
|
+
args.Event.Handled = true; // used: a touch on the bar is claimed, the page must not take the drag
|
|
852
|
+
return true;
|
|
853
|
+
}
|
|
854
|
+
if (!this.scrollBarOwnsGesture)
|
|
855
|
+
return false;
|
|
856
|
+
if (!this.draggedScrollBar)
|
|
857
|
+
return args.Type === "Tapped" || args.Type === "LongPressing"; // released already: only the tap that follows belongs to the bar
|
|
858
|
+
if (args.Type === "Panning") {
|
|
859
|
+
this.ApplyScrollBarDrag(apply.MappedLocation.X + apply.ChildOffset.X, apply.MappedLocation.Y + apply.ChildOffset.Y);
|
|
860
|
+
args.Event.Handled = true;
|
|
861
|
+
}
|
|
862
|
+
else if (args.Type === "Up") {
|
|
863
|
+
this.draggedScrollBar = undefined;
|
|
864
|
+
this.scrollBarLast = undefined; // push the idle state so an auto-hiding bar fades again
|
|
865
|
+
this.scrollBarHLast = undefined;
|
|
866
|
+
this.Repaint();
|
|
867
|
+
}
|
|
868
|
+
return true;
|
|
869
|
+
}
|
|
870
|
+
ApplyScrollBarDrag(x, y) {
|
|
871
|
+
const bar = this.draggedScrollBar;
|
|
872
|
+
if (!bar)
|
|
873
|
+
return;
|
|
874
|
+
const progress = bar.GetDragProgress(x, y);
|
|
875
|
+
const b = this.ContentOffsetBounds;
|
|
876
|
+
if (bar === this.scrollBarHorizontal)
|
|
877
|
+
this.ViewportOffsetX = this.ClampOffset(-progress * b.Width, this.offsetY, b, true).X;
|
|
878
|
+
else
|
|
879
|
+
this.ViewportOffsetY = this.ClampOffset(this.offsetX, -progress * b.Height, b, true).Y;
|
|
880
|
+
this.Repaint();
|
|
881
|
+
}
|
|
786
882
|
ProcessGestures(args, apply) {
|
|
787
883
|
const consumedDefault = this.BlockGesturesBelow ? this : null;
|
|
788
884
|
const scale = this.RenderingScale;
|
|
789
885
|
const e = args.Event;
|
|
886
|
+
if (this.ProcessScrollBarGestures(args, apply))
|
|
887
|
+
return this;
|
|
790
888
|
if (args.Type === "Down") {
|
|
791
889
|
this.hadDown = true;
|
|
792
890
|
this.snapped = false;
|
|
@@ -801,15 +899,18 @@ export class SkiaScroll extends SkiaControl {
|
|
|
801
899
|
const child = super.ProcessGestures(args, apply);
|
|
802
900
|
if (child && child !== this)
|
|
803
901
|
return child;
|
|
804
|
-
if (!this.RespondsToGestures)
|
|
805
|
-
return child ?? consumedDefault;
|
|
806
|
-
|
|
902
|
+
if (!this.RespondsToGestures || this.Orientation === "Neither")
|
|
903
|
+
return child ?? consumedDefault; // Neither: not used, a parent scroll may take it
|
|
904
|
+
if (!this.ApplyWheelScroll(e.Wheel.Delta))
|
|
905
|
+
return consumedDefault; // at its edge: not used, the page may take it
|
|
906
|
+
e.Handled = true;
|
|
907
|
+
return this;
|
|
807
908
|
}
|
|
808
909
|
this.velocityY = e.Distance.Velocity.Y / scale;
|
|
809
910
|
this.velocityX = e.Distance.Velocity.X / scale;
|
|
810
911
|
const wrongDirection = (this.Orientation === "Vertical" && Math.abs(e.Distance.Total.X) > Math.abs(e.Distance.Total.Y) && Math.abs(e.Distance.Total.X) > SkiaScroll.ScrollVelocityThreshold * scale) ||
|
|
811
912
|
(this.Orientation === "Horizontal" && Math.abs(e.Distance.Total.Y) > Math.abs(e.Distance.Total.X) && Math.abs(e.Distance.Total.Y) > SkiaScroll.ScrollVelocityThreshold * scale);
|
|
812
|
-
if (args.Type === "Panning" && this.RespondsToGestures && this.hadDown) {
|
|
913
|
+
if (args.Type === "Panning" && this.RespondsToGestures && this.hadDown && this.Orientation !== "Neither") {
|
|
813
914
|
if (!this.IsUserPanning) {
|
|
814
915
|
// A child may own the pan (slider, nested horizontal scroll); ask once before taking over.
|
|
815
916
|
const childConsumed = super.ProcessGestures(args, apply);
|
|
@@ -860,6 +961,12 @@ export class SkiaScroll extends SkiaControl {
|
|
|
860
961
|
const swipeThreshold = SkiaScroll.ThesholdSwipeOnUp * scale;
|
|
861
962
|
let vx = finalVelocity.X * this.ChangeVelocityScrolled;
|
|
862
963
|
let vy = finalVelocity.Y * this.ChangeVelocityScrolled;
|
|
964
|
+
// a cancelled pointer (the browser took the pan to scroll the page, or a parent intercepted it) settles in place:
|
|
965
|
+
// flinging from the few moves that arrived before the cancel would scroll the list while the page scrolls
|
|
966
|
+
if (e.Type === "Cancelled") {
|
|
967
|
+
vx = 0;
|
|
968
|
+
vy = 0;
|
|
969
|
+
}
|
|
863
970
|
let fling = false;
|
|
864
971
|
if (this.OverScrolled) {
|
|
865
972
|
const rest = this.ClampOffset(this.offsetX, this.offsetY, this.ContentOffsetBounds, true);
|
|
@@ -11,6 +11,12 @@ export type ScrollBarDock = "End" | "Start";
|
|
|
11
11
|
*/
|
|
12
12
|
export interface IScrollBar {
|
|
13
13
|
SetScrollProgress(orientation: ScrollOrientation, progress: number, thumbSizeRatio: number, overscrollPts: number, isScrolling: boolean): void;
|
|
14
|
+
/** Optional desktop behavior: the bar takes the gesture that starts on it (thumb drag, track press). */
|
|
15
|
+
IsDraggable?: boolean;
|
|
16
|
+
/** Canvas pixels; true when the point is on the bar and a drag starts. */
|
|
17
|
+
BeginDrag?(x: number, y: number): boolean;
|
|
18
|
+
/** Scroll progress 0..1 for the pointer during a drag started by BeginDrag. */
|
|
19
|
+
GetDragProgress?(x: number, y: number): number;
|
|
14
20
|
}
|
|
15
21
|
/**
|
|
16
22
|
* Mirrors DrawnUi SkiaScrollBar: thin rounded thumb over an optional track, docked at the right edge for vertical
|
|
@@ -41,6 +47,19 @@ export declare class SkiaScrollBar extends SkiaLayout implements IScrollBar {
|
|
|
41
47
|
/** Fade out after scrolling stops. */
|
|
42
48
|
AutoHide: boolean;
|
|
43
49
|
HideDelaySecs: number;
|
|
50
|
+
/** Duration in seconds of the fade-out once HideDelaySecs has passed. */
|
|
51
|
+
HideDurationSecs: number;
|
|
52
|
+
/**
|
|
53
|
+
* Lets the user drag the thumb and press the track to jump there (desktop scroll bar behavior). The owning
|
|
54
|
+
* SkiaScroll then takes the whole gesture, from down to up, when it starts on the bar. Default false: the bar only
|
|
55
|
+
* indicates and every gesture passes through to the content.
|
|
56
|
+
*/
|
|
57
|
+
IsDraggable: boolean;
|
|
58
|
+
/** Extra grab area in points on both sides of the bar, across the scrolling axis, so a thin bar is easy to hit with a mouse. */
|
|
59
|
+
GrabPadding: number;
|
|
60
|
+
private thumbOffsetPts;
|
|
61
|
+
private dragGrabPx;
|
|
62
|
+
private hasTravel;
|
|
44
63
|
protected readonly thumb: SkiaShape;
|
|
45
64
|
protected readonly track: SkiaShape;
|
|
46
65
|
private hideTimer;
|
|
@@ -52,5 +71,13 @@ export declare class SkiaScrollBar extends SkiaLayout implements IScrollBar {
|
|
|
52
71
|
protected ApplyOrientation(): void;
|
|
53
72
|
SetScrollProgress(orientation: ScrollOrientation, progress: number, thumbSizeRatio: number, overscrollPts: number, isScrolling: boolean): void;
|
|
54
73
|
private CancelHide;
|
|
74
|
+
/**
|
|
75
|
+
* Called by the owning scroll on touch down, coordinates in canvas pixels. True when the point is on the bar (track
|
|
76
|
+
* plus GrabPadding) and a drag starts: on the thumb it keeps the grab point, on the track the thumb jumps to center
|
|
77
|
+
* under the pointer.
|
|
78
|
+
*/
|
|
79
|
+
BeginDrag(x: number, y: number): boolean;
|
|
80
|
+
/** Scroll progress 0..1 for the pointer position during a drag started by BeginDrag, coordinates in canvas pixels. */
|
|
81
|
+
GetDragProgress(x: number, y: number): number;
|
|
55
82
|
protected OnDisposing(): void;
|
|
56
83
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Colors, Thickness } from "../core/Types";
|
|
1
|
+
import { Colors, SKRect, Thickness } from "../core/Types";
|
|
2
2
|
import { SkiaLayout } from "./SkiaLayout";
|
|
3
3
|
import { SkiaShape } from "./SkiaShape";
|
|
4
4
|
/**
|
|
@@ -49,6 +49,19 @@ export class SkiaScrollBar extends SkiaLayout {
|
|
|
49
49
|
/** Fade out after scrolling stops. */
|
|
50
50
|
AutoHide = true;
|
|
51
51
|
HideDelaySecs = 1;
|
|
52
|
+
/** Duration in seconds of the fade-out once HideDelaySecs has passed. */
|
|
53
|
+
HideDurationSecs = 0.25;
|
|
54
|
+
/**
|
|
55
|
+
* Lets the user drag the thumb and press the track to jump there (desktop scroll bar behavior). The owning
|
|
56
|
+
* SkiaScroll then takes the whole gesture, from down to up, when it starts on the bar. Default false: the bar only
|
|
57
|
+
* indicates and every gesture passes through to the content.
|
|
58
|
+
*/
|
|
59
|
+
IsDraggable = false;
|
|
60
|
+
/** Extra grab area in points on both sides of the bar, across the scrolling axis, so a thin bar is easy to hit with a mouse. */
|
|
61
|
+
GrabPadding = 8;
|
|
62
|
+
thumbOffsetPts = 0;
|
|
63
|
+
dragGrabPx = 0;
|
|
64
|
+
hasTravel = false;
|
|
52
65
|
thumb = new SkiaShape();
|
|
53
66
|
track = new SkiaShape();
|
|
54
67
|
hideTimer = 0;
|
|
@@ -62,6 +75,7 @@ export class SkiaScrollBar extends SkiaLayout {
|
|
|
62
75
|
this.HorizontalOptions = "Fill";
|
|
63
76
|
this.VerticalOptions = "Fill";
|
|
64
77
|
this.InputTransparent = true; // display only, gestures pass through
|
|
78
|
+
this.IsParentIndependent = true; // the bar's look (thumb squashed on overscroll) never changes the layout around it
|
|
65
79
|
this.Opacity = 0; // hidden until the first scroll
|
|
66
80
|
this.track.Type = "Rectangle";
|
|
67
81
|
this.track.BackgroundColor = this.trackColor;
|
|
@@ -127,6 +141,8 @@ export class SkiaScrollBar extends SkiaLayout {
|
|
|
127
141
|
thumbLen = Math.max(this.MinThumbSize / 2, thumbLen - Math.abs(overscrollPts)); // squash on bounce
|
|
128
142
|
const travel = track - thumbLen;
|
|
129
143
|
const offset = Math.max(0, Math.min(1, progress)) * travel;
|
|
144
|
+
this.thumbOffsetPts = offset;
|
|
145
|
+
this.hasTravel = travel > 0;
|
|
130
146
|
if (Math.abs(thumbLen - this.lastThumbLen) > 0.5) {
|
|
131
147
|
this.lastThumbLen = thumbLen;
|
|
132
148
|
if (orientation === "Horizontal")
|
|
@@ -142,7 +158,7 @@ export class SkiaScrollBar extends SkiaLayout {
|
|
|
142
158
|
this.CancelHide();
|
|
143
159
|
this.Opacity = 1;
|
|
144
160
|
if (this.AutoHide && !isScrolling) {
|
|
145
|
-
this.hideTimer = window.setTimeout(() => { this.hideTimer = 0; this.hideAbort = new AbortController(); void this.FadeToAsync(0,
|
|
161
|
+
this.hideTimer = window.setTimeout(() => { this.hideTimer = 0; this.hideAbort = new AbortController(); void this.FadeToAsync(0, Math.max(0, this.HideDurationSecs * 1000), undefined, this.hideAbort.signal); }, this.HideDelaySecs * 1000);
|
|
146
162
|
}
|
|
147
163
|
this.RepaintComposition();
|
|
148
164
|
}
|
|
@@ -154,5 +170,38 @@ export class SkiaScrollBar extends SkiaLayout {
|
|
|
154
170
|
this.hideAbort?.abort();
|
|
155
171
|
this.hideAbort = undefined;
|
|
156
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Called by the owning scroll on touch down, coordinates in canvas pixels. True when the point is on the bar (track
|
|
175
|
+
* plus GrabPadding) and a drag starts: on the thumb it keeps the grab point, on the track the thumb jumps to center
|
|
176
|
+
* under the pointer.
|
|
177
|
+
*/
|
|
178
|
+
BeginDrag(x, y) {
|
|
179
|
+
if (!this.IsDraggable || !this.hasTravel)
|
|
180
|
+
return false;
|
|
181
|
+
const scale = this.RenderingScale || 1;
|
|
182
|
+
const track = this.track.DrawingRect;
|
|
183
|
+
const pad = this.GrabPadding * scale;
|
|
184
|
+
const vertical = this.orientation !== "Horizontal";
|
|
185
|
+
const hit = vertical ? new SKRect(track.Left - pad, track.Top, track.Right + pad, track.Bottom) : new SKRect(track.Left, track.Top - pad, track.Right, track.Bottom + pad);
|
|
186
|
+
if (x < hit.Left || x > hit.Right || y < hit.Top || y > hit.Bottom)
|
|
187
|
+
return false;
|
|
188
|
+
// an auto-hidden bar that gets grabbed shows again right away: never drag something invisible
|
|
189
|
+
this.CancelHide();
|
|
190
|
+
this.Opacity = 1;
|
|
191
|
+
const pos = vertical ? y - track.Top : x - track.Left;
|
|
192
|
+
const thumbStart = this.thumbOffsetPts * scale, thumbLen = this.lastThumbLen * scale;
|
|
193
|
+
this.dragGrabPx = pos >= thumbStart && pos <= thumbStart + thumbLen ? pos - thumbStart : thumbLen / 2;
|
|
194
|
+
this.RepaintComposition();
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
/** Scroll progress 0..1 for the pointer position during a drag started by BeginDrag, coordinates in canvas pixels. */
|
|
198
|
+
GetDragProgress(x, y) {
|
|
199
|
+
const scale = this.RenderingScale || 1;
|
|
200
|
+
const track = this.track.DrawingRect;
|
|
201
|
+
const vertical = this.orientation !== "Horizontal";
|
|
202
|
+
const pos = (vertical ? y - track.Top : x - track.Left) - this.dragGrabPx;
|
|
203
|
+
const travel = (vertical ? track.Height : track.Width) - this.lastThumbLen * scale;
|
|
204
|
+
return travel > 0 ? Math.max(0, Math.min(1, pos / travel)) : 0;
|
|
205
|
+
}
|
|
157
206
|
OnDisposing() { this.CancelHide(); super.OnDisposing(); }
|
|
158
207
|
}
|
|
@@ -56,6 +56,11 @@ export declare class SkiaShaderCarousel extends SkiaCarousel {
|
|
|
56
56
|
protected OnScrollProgressChanged(): void;
|
|
57
57
|
/** Slides never move: the transition effect renders the change (C# AnimateVisibleChild no-op). */
|
|
58
58
|
protected SlideOffset(_offset: SKPoint): SKPoint;
|
|
59
|
+
/**
|
|
60
|
+
* Slides all sit on the same rect, so only the selected one may take gestures: otherwise the topmost slide in the
|
|
61
|
+
* gesture list (the last on-screen one, a neighbour exactly one width away counts as on screen) caught every tap.
|
|
62
|
+
*/
|
|
63
|
+
protected IsSlideHitTestable(index: number): boolean;
|
|
59
64
|
ProcessGestures(args: SkiaGesturesParameters, apply: GestureEventProcessingInfo): SkiaControl | null;
|
|
60
65
|
/**
|
|
61
66
|
* Gesture targeting (C#): one gesture moves at most one slide from the slide it started on; a flick moves one slide
|
|
@@ -145,6 +145,11 @@ export class SkiaShaderCarousel extends SkiaCarousel {
|
|
|
145
145
|
}
|
|
146
146
|
/** Slides never move: the transition effect renders the change (C# AnimateVisibleChild no-op). */
|
|
147
147
|
SlideOffset(_offset) { return SKPoint.Empty; }
|
|
148
|
+
/**
|
|
149
|
+
* Slides all sit on the same rect, so only the selected one may take gestures: otherwise the topmost slide in the
|
|
150
|
+
* gesture list (the last on-screen one, a neighbour exactly one width away counts as on screen) caught every tap.
|
|
151
|
+
*/
|
|
152
|
+
IsSlideHitTestable(index) { return index === this.SelectedIndex; }
|
|
148
153
|
ProcessGestures(args, apply) {
|
|
149
154
|
if (args.Type === "Down") {
|
|
150
155
|
const interrupted = this.InTransition;
|
|
@@ -14,7 +14,6 @@ export declare class SkiaSwitch extends SkiaToggle {
|
|
|
14
14
|
private static readonly DefaultTrack;
|
|
15
15
|
private static readonly MaterialOutline;
|
|
16
16
|
protected StyleDefault(name: "ColorThumbOn" | "ColorFrameOn" | "ColorThumbOff" | "ColorFrameOff"): Color | undefined;
|
|
17
|
-
private SetContentSize;
|
|
18
17
|
protected CreateDefaultContent(): void;
|
|
19
18
|
/** C# GetThumbPosForOn: track width minus thumb width and margins, in points. */
|
|
20
19
|
private ThumbPosForOn;
|
|
@@ -25,12 +25,6 @@ export class SkiaSwitch extends SkiaToggle {
|
|
|
25
25
|
};
|
|
26
26
|
return table[s]?.[name];
|
|
27
27
|
}
|
|
28
|
-
SetContentSize(w, h) {
|
|
29
|
-
if (this.WidthRequest < 0)
|
|
30
|
-
this.WidthRequest = w;
|
|
31
|
-
if (this.HeightRequest < 0)
|
|
32
|
-
this.HeightRequest = h;
|
|
33
|
-
}
|
|
34
28
|
CreateDefaultContent() {
|
|
35
29
|
const frame = new SkiaShape();
|
|
36
30
|
frame.Tag = "Frame";
|
|
@@ -5,11 +5,14 @@ import { type Color, type ScaledSize } from "../core/Types";
|
|
|
5
5
|
import { SkiaLayout } from "./SkiaLayout";
|
|
6
6
|
/**
|
|
7
7
|
* Mirrors DrawnUi SkiaToggle: base of SkiaSwitch / SkiaCheckbox / SkiaRadioButton. Tapped flips IsToggled,
|
|
8
|
-
* `Toggled` reports user changes, `ControlStyle` selects the look the default content is built with (
|
|
9
|
-
* measure, like C# CreateDefaultContent
|
|
8
|
+
* `Toggled` reports user changes, `ControlStyle` selects the look the default content is built with (at first
|
|
9
|
+
* measure, like C# CreateDefaultContent; a later change rebuilds it, C# RebuildDefaultContent). Colors left unset
|
|
10
|
+
* take the style defaults (C# SetStyleDefault).
|
|
10
11
|
*/
|
|
11
12
|
export declare abstract class SkiaToggle extends SkiaLayout {
|
|
12
|
-
|
|
13
|
+
private controlStyle;
|
|
14
|
+
get ControlStyle(): PrebuiltControlStyle;
|
|
15
|
+
set ControlStyle(v: PrebuiltControlStyle);
|
|
13
16
|
/** Fires on every IsToggled change; C# fires only on user/external changes, not on DefaultValue application. */
|
|
14
17
|
Toggled?: (sender: SkiaToggle, value: boolean) => void;
|
|
15
18
|
DefaultValue: boolean;
|
|
@@ -37,10 +40,21 @@ export declare abstract class SkiaToggle extends SkiaLayout {
|
|
|
37
40
|
/** Per-style color defaults (C# SetStyleDefault); undefined = the SkiaToggle default. */
|
|
38
41
|
protected StyleDefault(_name: "ColorThumbOn" | "ColorFrameOn" | "ColorThumbOff" | "ColorFrameOff"): Color | undefined;
|
|
39
42
|
protected OnToggledChanged(): void;
|
|
40
|
-
/** Builds the look
|
|
43
|
+
/** Builds the look (C# CreateDefaultContent) — subclasses add their views here; called again after a rebuild. */
|
|
41
44
|
protected abstract CreateDefaultContent(): void;
|
|
42
45
|
/** Pushes IsToggled + colors into the views (C# ApplyProperties). */
|
|
43
46
|
abstract ApplyProperties(): void;
|
|
47
|
+
/** Size requests the style pinned (C# SetDefaultContentSize / SetDefaultMinimumContentSize); a rebuild releases only these. */
|
|
48
|
+
private stylePinned;
|
|
49
|
+
/** C# SetDefaultContentSize: the style's size, only where the app left the request unset. */
|
|
50
|
+
protected SetContentSize(w: number, h: number): void;
|
|
51
|
+
/** C# SetDefaultMinimumContentSize (height only): the style's minimum, only where the app left it unset. */
|
|
52
|
+
protected SetMinimumContentHeight(h: number): void;
|
|
53
|
+
/**
|
|
54
|
+
* C# RebuildDefaultContent: drops the content built for the previous style and the sizes it pinned (the app's own
|
|
55
|
+
* requests stay), so the next Measure builds the current style. No-op before the first build.
|
|
56
|
+
*/
|
|
57
|
+
RebuildDefaultContent(): void;
|
|
44
58
|
/** Content (and the size requests the style sets) must exist before Measure reads WidthRequest/HeightRequest. */
|
|
45
59
|
Measure(widthConstraint: number, heightConstraint: number, scale: number): ScaledSize;
|
|
46
60
|
protected DefaultAccessibilityCanInteract(): boolean;
|
|
@@ -3,11 +3,19 @@ import { Colors } from "../core/Types";
|
|
|
3
3
|
import { SkiaLayout } from "./SkiaLayout";
|
|
4
4
|
/**
|
|
5
5
|
* Mirrors DrawnUi SkiaToggle: base of SkiaSwitch / SkiaCheckbox / SkiaRadioButton. Tapped flips IsToggled,
|
|
6
|
-
* `Toggled` reports user changes, `ControlStyle` selects the look the default content is built with (
|
|
7
|
-
* measure, like C# CreateDefaultContent
|
|
6
|
+
* `Toggled` reports user changes, `ControlStyle` selects the look the default content is built with (at first
|
|
7
|
+
* measure, like C# CreateDefaultContent; a later change rebuilds it, C# RebuildDefaultContent). Colors left unset
|
|
8
|
+
* take the style defaults (C# SetStyleDefault).
|
|
8
9
|
*/
|
|
9
10
|
export class SkiaToggle extends SkiaLayout {
|
|
10
|
-
|
|
11
|
+
controlStyle = "Unset";
|
|
12
|
+
get ControlStyle() { return this.controlStyle; }
|
|
13
|
+
set ControlStyle(v) {
|
|
14
|
+
if (this.controlStyle === v)
|
|
15
|
+
return;
|
|
16
|
+
this.controlStyle = v;
|
|
17
|
+
this.RebuildDefaultContent();
|
|
18
|
+
}
|
|
11
19
|
/** Fires on every IsToggled change; C# fires only on user/external changes, not on DefaultValue application. */
|
|
12
20
|
Toggled;
|
|
13
21
|
DefaultValue = false;
|
|
@@ -48,6 +56,40 @@ export class SkiaToggle extends SkiaLayout {
|
|
|
48
56
|
this.Toggled?.(this, this.isToggled);
|
|
49
57
|
this.NotifyAccessibility();
|
|
50
58
|
}
|
|
59
|
+
/** Size requests the style pinned (C# SetDefaultContentSize / SetDefaultMinimumContentSize); a rebuild releases only these. */
|
|
60
|
+
stylePinned = [];
|
|
61
|
+
/** C# SetDefaultContentSize: the style's size, only where the app left the request unset. */
|
|
62
|
+
SetContentSize(w, h) {
|
|
63
|
+
if (this.WidthRequest < 0) {
|
|
64
|
+
this.WidthRequest = w;
|
|
65
|
+
this.stylePinned.push("WidthRequest");
|
|
66
|
+
}
|
|
67
|
+
if (this.HeightRequest < 0) {
|
|
68
|
+
this.HeightRequest = h;
|
|
69
|
+
this.stylePinned.push("HeightRequest");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** C# SetDefaultMinimumContentSize (height only): the style's minimum, only where the app left it unset. */
|
|
73
|
+
SetMinimumContentHeight(h) {
|
|
74
|
+
if (this.MinimumHeightRequest < 0) {
|
|
75
|
+
this.MinimumHeightRequest = h;
|
|
76
|
+
this.stylePinned.push("MinimumHeightRequest");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* C# RebuildDefaultContent: drops the content built for the previous style and the sizes it pinned (the app's own
|
|
81
|
+
* requests stay), so the next Measure builds the current style. No-op before the first build.
|
|
82
|
+
*/
|
|
83
|
+
RebuildDefaultContent() {
|
|
84
|
+
if (!this.contentCreated)
|
|
85
|
+
return;
|
|
86
|
+
this.Children = [];
|
|
87
|
+
for (const p of this.stylePinned)
|
|
88
|
+
this[p] = -1;
|
|
89
|
+
this.stylePinned = [];
|
|
90
|
+
this.contentCreated = false;
|
|
91
|
+
this.Update();
|
|
92
|
+
}
|
|
51
93
|
/** Content (and the size requests the style sets) must exist before Measure reads WidthRequest/HeightRequest. */
|
|
52
94
|
Measure(widthConstraint, heightConstraint, scale) {
|
|
53
95
|
if (!this.contentCreated) {
|
package/dist/core/Animators.d.ts
CHANGED
|
@@ -29,6 +29,15 @@ export declare class AnimatorBase {
|
|
|
29
29
|
Dispose(): void;
|
|
30
30
|
private SetRunning;
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* DrawnUi ActionOnTickAnimator: runs `action(frameTimeNanos)` on every frame until stopped, a game loop for
|
|
34
|
+
* DrawnGame or any per-frame work.
|
|
35
|
+
*/
|
|
36
|
+
export declare class ActionOnTickAnimator extends AnimatorBase {
|
|
37
|
+
private readonly action;
|
|
38
|
+
constructor(parent: SkiaControl, action: (frameTimeNanos: number) => void);
|
|
39
|
+
TickFrame(frameTimeNanos: number): boolean;
|
|
40
|
+
}
|
|
32
41
|
/** DrawnUi SkiaValueAnimator: drives mValue from mMinValue to mMaxValue over Speed ms with Easing. */
|
|
33
42
|
export declare class SkiaValueAnimator extends AnimatorBase {
|
|
34
43
|
mValue: number;
|