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
|
@@ -12,7 +12,7 @@ export interface ScaledPoint {
|
|
|
12
12
|
* Mirrors DrawnUi SkiaScroll (plain content, no header/footer/refresh/virtualization yet).
|
|
13
13
|
* Offsets are in POINTS and <= 0 while inside bounds (content moves up/left as you scroll).
|
|
14
14
|
* Physics ported 1:1: deceleration fling (rate = 1 - FrictionScrolled/100) cut at the content edge,
|
|
15
|
-
* rubber-band overscroll while dragging, spring bounce back on release, wheel = WheelLineSize per notch.
|
|
15
|
+
* rubber-band overscroll while dragging, spring bounce back on release, wheel = WheelLineSize per notch (an event moves by its share of a notch).
|
|
16
16
|
*/
|
|
17
17
|
export declare class SkiaScroll extends SkiaControl {
|
|
18
18
|
static WheelLineSize: number;
|
|
@@ -31,7 +31,11 @@ export declare class SkiaScroll extends SkiaControl {
|
|
|
31
31
|
AutoScrollingSpeedMs: number;
|
|
32
32
|
ScrollingSpeedMs: number;
|
|
33
33
|
IgnoreWrongDirection: boolean;
|
|
34
|
-
|
|
34
|
+
private respondsToGestures;
|
|
35
|
+
/** False: neither a drag, the wheel nor the keyboard scrolls it (code still can), and it is reported disabled; its content keeps its own taps (drawnui-cross 6w). */
|
|
36
|
+
get RespondsToGestures(): boolean;
|
|
37
|
+
set RespondsToGestures(v: boolean);
|
|
38
|
+
protected AcceptsInput(): boolean;
|
|
35
39
|
private header?;
|
|
36
40
|
private footer?;
|
|
37
41
|
/** A view before the content along the scroll axis; `Tag="Header"` on a JSX child sets it. */
|
|
@@ -156,6 +160,10 @@ export declare class SkiaScroll extends SkiaControl {
|
|
|
156
160
|
set ViewportOffsetX(value: number);
|
|
157
161
|
get ViewportOffsetY(): number;
|
|
158
162
|
set ViewportOffsetY(value: number);
|
|
163
|
+
/** An offset set without a glide or a pan (a jump, a touchpad step): the controls under a still mouse changed. */
|
|
164
|
+
private OffsetJumped;
|
|
165
|
+
/** Hover waits while the content glides, flings, bounces or follows a pan (C# IsScrolling). */
|
|
166
|
+
MovesContent(): boolean;
|
|
159
167
|
private readonly animatorFlingX;
|
|
160
168
|
private readonly animatorFlingY;
|
|
161
169
|
private readonly bounceX;
|
|
@@ -214,18 +222,40 @@ export declare class SkiaScroll extends SkiaControl {
|
|
|
214
222
|
* templated layout (the recycled list is the scroll's only child; a header goes above the scroll or in Header).
|
|
215
223
|
*/
|
|
216
224
|
ScrollToIndex(index: number, animate: boolean, option?: RelativePositionType): void;
|
|
225
|
+
/**
|
|
226
|
+
* An open ScrollToIndex (C# OrderedScrollToIndex, DrawnUi.Rust scroll_to_index): a row not measured yet is gone to by
|
|
227
|
+
* the sizes as they are estimated, and the order is aimed again every frame until the row has its real place and the
|
|
228
|
+
* viewport stands on it; an animated move goes on to a moved target in the time it has left, later corrections jump.
|
|
229
|
+
* A press, the wheel or another scroll drops it; while it is open LoadMore waits.
|
|
230
|
+
*/
|
|
231
|
+
private indexOrder?;
|
|
232
|
+
private pursuing;
|
|
233
|
+
/** One step of the open order (C# ExecuteScrollToIndexOrder, Rust `pursue`), at the start of every paint. */
|
|
234
|
+
private PursueIndexOrder;
|
|
235
|
+
/** The offset (points) that puts item `index` where `option` says, by the sizes the list knows now; Exact = its place is measured. */
|
|
236
|
+
private AimIndex;
|
|
217
237
|
private StopAnimators;
|
|
218
238
|
/** DrawnUi DecelerationRatio: FrictionScrolled / 100, floored at 0.1 friction. */
|
|
219
239
|
private get DecelerationRatio();
|
|
220
240
|
private ResetPan;
|
|
241
|
+
private draggedScrollBar?;
|
|
242
|
+
private scrollBarOwnsGesture;
|
|
243
|
+
/**
|
|
244
|
+
* Thumb drag and track press on a ScrollBar / ScrollBarHorizontal with IsDraggable set (C# ProcessScrollBarGestures).
|
|
245
|
+
* A gesture that starts on the bar belongs to it until the next down: the content neither pans nor gets the tap.
|
|
246
|
+
* True when the gesture was handled here.
|
|
247
|
+
*/
|
|
248
|
+
protected ProcessScrollBarGestures(args: SkiaGesturesParameters, apply: GestureEventProcessingInfo): boolean;
|
|
249
|
+
private ApplyScrollBarDrag;
|
|
221
250
|
ProcessGestures(args: SkiaGesturesParameters, apply: GestureEventProcessingInfo): SkiaControl | null;
|
|
222
251
|
private Bounce;
|
|
223
252
|
/** DrawnUi StartToFlingFrom + PrepareToFlingAfterInitialized: cut the curve so it stops exactly at the edge. */
|
|
224
253
|
private StartToFlingFrom;
|
|
225
254
|
/**
|
|
226
|
-
* One wheel notch = WheelLineSize points, hard-clamped, animated over AutoScrollingSpeedMs.
|
|
227
|
-
*
|
|
228
|
-
*
|
|
255
|
+
* One wheel notch = WheelLineSize points, hard-clamped, animated over AutoScrollingSpeedMs. An event moves by its share
|
|
256
|
+
* of a notch (Canvas.WheelDeltaPerNotch), so a touchpad's many small events scroll as far as the fingers moved (C#
|
|
257
|
+
* aaa871c3); an event under half a notch moves the content at once (no glide). Events arriving while the previous one is still animating add onto its target, so a fast wheel spin
|
|
258
|
+
* travels N steps instead of restarting from the barely-moved current offset.
|
|
229
259
|
*/
|
|
230
260
|
private ApplyWheelScroll;
|
|
231
261
|
}
|
|
@@ -9,7 +9,7 @@ import { RubberBandUtils, ScrollFlingAnimator, Spring, SpringWithVelocityAnimato
|
|
|
9
9
|
* Mirrors DrawnUi SkiaScroll (plain content, no header/footer/refresh/virtualization yet).
|
|
10
10
|
* Offsets are in POINTS and <= 0 while inside bounds (content moves up/left as you scroll).
|
|
11
11
|
* Physics ported 1:1: deceleration fling (rate = 1 - FrictionScrolled/100) cut at the content edge,
|
|
12
|
-
* rubber-band overscroll while dragging, spring bounce back on release, wheel = WheelLineSize per notch.
|
|
12
|
+
* rubber-band overscroll while dragging, spring bounce back on release, wheel = WheelLineSize per notch (an event moves by its share of a notch).
|
|
13
13
|
*/
|
|
14
14
|
export class SkiaScroll extends SkiaControl {
|
|
15
15
|
static WheelLineSize = 150;
|
|
@@ -28,7 +28,14 @@ export class SkiaScroll extends SkiaControl {
|
|
|
28
28
|
AutoScrollingSpeedMs = 600;
|
|
29
29
|
ScrollingSpeedMs = 400;
|
|
30
30
|
IgnoreWrongDirection = false;
|
|
31
|
-
|
|
31
|
+
respondsToGestures = true;
|
|
32
|
+
/** False: neither a drag, the wheel nor the keyboard scrolls it (code still can), and it is reported disabled; its content keeps its own taps (drawnui-cross 6w). */
|
|
33
|
+
get RespondsToGestures() { return this.respondsToGestures; }
|
|
34
|
+
set RespondsToGestures(v) { if (this.respondsToGestures !== v) {
|
|
35
|
+
this.respondsToGestures = v;
|
|
36
|
+
this.NotifyAccessibility();
|
|
37
|
+
} }
|
|
38
|
+
AcceptsInput() { return this.respondsToGestures; }
|
|
32
39
|
// ---- header / footer (C# SkiaScroll.Header / Footer) ----
|
|
33
40
|
header;
|
|
34
41
|
footer;
|
|
@@ -121,7 +128,7 @@ export class SkiaScroll extends SkiaControl {
|
|
|
121
128
|
}
|
|
122
129
|
/** C# UpdateScrollBarIndicator: pushes progress / thumb ratio / overscroll / scrolling state when any changed. */
|
|
123
130
|
UpdateScrollBarIndicator() {
|
|
124
|
-
const isScrolling = this.IsScrolling || this.IsUserPanning;
|
|
131
|
+
const isScrolling = this.IsScrolling || this.IsUserPanning || !!this.draggedScrollBar;
|
|
125
132
|
const scale = this.RenderingScale;
|
|
126
133
|
const viewportH = this.DrawingRect.Height / scale, viewportW = this.DrawingRect.Width / scale;
|
|
127
134
|
const extentH = this.ContentSize.Units.Height + this.HeaderExtentPts + this.FooterExtentPts, extentW = this.ContentSize.Units.Width + this.HeaderExtentPts + this.FooterExtentPts;
|
|
@@ -333,6 +340,8 @@ export class SkiaScroll extends SkiaControl {
|
|
|
333
340
|
loadMoreTopArmed = false;
|
|
334
341
|
/** C# CheckNeedToLoadMore: fires once per content extent near an edge; re-arms when the content grows or the user moves away (>offset+100pt, >2 s). */
|
|
335
342
|
CheckLoadMore() {
|
|
343
|
+
if (this.indexOrder)
|
|
344
|
+
return; // an open ScrollToIndex has the viewport on its way: the edges are looked at after it
|
|
336
345
|
const vertical = this.Orientation !== "Horizontal";
|
|
337
346
|
const offset = vertical ? this.offsetY : this.offsetX;
|
|
338
347
|
const min = vertical ? this.ContentOffsetBounds.Top : this.ContentOffsetBounds.Left; // most negative offset
|
|
@@ -399,12 +408,19 @@ export class SkiaScroll extends SkiaControl {
|
|
|
399
408
|
set ViewportOffsetX(value) { if (this.offsetX !== value) {
|
|
400
409
|
this.offsetX = value;
|
|
401
410
|
this.OnScrolled();
|
|
411
|
+
this.OffsetJumped();
|
|
402
412
|
} }
|
|
403
413
|
get ViewportOffsetY() { return this.offsetY; }
|
|
404
414
|
set ViewportOffsetY(value) { if (this.offsetY !== value) {
|
|
405
415
|
this.offsetY = value;
|
|
406
416
|
this.OnScrolled();
|
|
417
|
+
this.OffsetJumped();
|
|
407
418
|
} }
|
|
419
|
+
/** An offset set without a glide or a pan (a jump, a touchpad step): the controls under a still mouse changed. */
|
|
420
|
+
OffsetJumped() { if (!this.MovesContent())
|
|
421
|
+
this.Superview?.Hover?.RequestCheck(); }
|
|
422
|
+
/** Hover waits while the content glides, flings, bounces or follows a pan (C# IsScrolling). */
|
|
423
|
+
MovesContent() { return this.IsScrolling || this.IsUserPanning || !!this.draggedScrollBar; }
|
|
408
424
|
animatorFlingX = new ScrollFlingAnimator(this);
|
|
409
425
|
animatorFlingY = new ScrollFlingAnimator(this);
|
|
410
426
|
bounceX = new SpringWithVelocityAnimator(this);
|
|
@@ -506,6 +522,7 @@ export class SkiaScroll extends SkiaControl {
|
|
|
506
522
|
}
|
|
507
523
|
InsertSubView(_index, control) { this.AddSubView(control); }
|
|
508
524
|
RemoveSubView(control) {
|
|
525
|
+
this.Superview?.Hover?.RequestCheck();
|
|
509
526
|
if (this.content === control)
|
|
510
527
|
this.Content = undefined;
|
|
511
528
|
else if (this.header === control)
|
|
@@ -560,6 +577,15 @@ export class SkiaScroll extends SkiaControl {
|
|
|
560
577
|
const width = Math.max(0, this.ContentSize.Units.Width + (this.Orientation === "Horizontal" ? extra : 0) - viewportW);
|
|
561
578
|
const height = Math.max(0, this.ContentSize.Units.Height + (this.Orientation !== "Horizontal" ? extra : 0) - viewportH);
|
|
562
579
|
this.ContentOffsetBounds = new SKRect(-width, -height, 0, 0);
|
|
580
|
+
// C# InitializeViewport: pull an offset the new bounds no longer contain back inside them, here where
|
|
581
|
+
// the bounds change. Shrinking content (fewer items) otherwise leaves the viewport parked past its end
|
|
582
|
+
// with nothing on screen. Skipped under a live pan, a fling / bounce or pull-to-refresh, where being
|
|
583
|
+
// past the edge is intentional (rubber band, refresh indicator).
|
|
584
|
+
if (!this.IsUserPanning && !this.IsScrolling && !this.isRefreshing) {
|
|
585
|
+
const c = this.ClampOffset(this.offsetX, this.offsetY, this.ContentOffsetBounds, true);
|
|
586
|
+
this.ViewportOffsetX = c.X;
|
|
587
|
+
this.ViewportOffsetY = c.Y;
|
|
588
|
+
}
|
|
563
589
|
this.ArrangeContent();
|
|
564
590
|
this.CheckLoadMore();
|
|
565
591
|
}
|
|
@@ -617,6 +643,8 @@ export class SkiaScroll extends SkiaControl {
|
|
|
617
643
|
}
|
|
618
644
|
}
|
|
619
645
|
Paint(ctx) {
|
|
646
|
+
this.Superview?.Hover?.RegisterMover(this);
|
|
647
|
+
this.PursueIndexOrder();
|
|
620
648
|
const c = this.content;
|
|
621
649
|
if (!c)
|
|
622
650
|
return;
|
|
@@ -637,9 +665,18 @@ export class SkiaScroll extends SkiaControl {
|
|
|
637
665
|
if (this.RefreshEnabled && this.refreshIndicator && this.refreshIndicator.IsVisible && (this.OverScrolled || this.isRefreshing))
|
|
638
666
|
this.refreshIndicator.Render(ctx);
|
|
639
667
|
this.UpdateScrollBarIndicator();
|
|
640
|
-
for (const bar of [this.scrollBar, this.scrollBarHorizontal])
|
|
641
|
-
if (bar
|
|
642
|
-
|
|
668
|
+
for (const bar of [this.scrollBar, this.scrollBarHorizontal]) {
|
|
669
|
+
if (!bar || !bar.IsVisible || bar.Opacity <= 0)
|
|
670
|
+
continue;
|
|
671
|
+
// the bar is parent-independent: its thumb resizing (squash on a bounce) invalidates the bar alone, so it is
|
|
672
|
+
// re-measured here on draw instead of dragging the scroll and every ancestor through a measure each frame
|
|
673
|
+
if (bar.NeedMeasure) {
|
|
674
|
+
const r = this.DrawingRect;
|
|
675
|
+
bar.Measure(r.Width, r.Height, this.RenderingScale);
|
|
676
|
+
bar.Arrange(r, bar.WidthRequest, bar.HeightRequest, this.RenderingScale);
|
|
677
|
+
}
|
|
678
|
+
bar.Render(ctx);
|
|
679
|
+
}
|
|
643
680
|
canvas.restoreToCount(saved);
|
|
644
681
|
}
|
|
645
682
|
OnScrolled() {
|
|
@@ -710,6 +747,8 @@ export class SkiaScroll extends SkiaControl {
|
|
|
710
747
|
this.ViewportOffsetY = target;
|
|
711
748
|
}
|
|
712
749
|
ScrollTo(x, y, maxSpeedSecs, clamp = true) {
|
|
750
|
+
if (!this.pursuing)
|
|
751
|
+
this.indexOrder = undefined; // another scroll drops an open ScrollToIndex
|
|
713
752
|
this.StopAnimators(); // also forgets any pending edge bounce: a programmatic scroll never bounces
|
|
714
753
|
let tx = x, ty = y;
|
|
715
754
|
if (clamp) {
|
|
@@ -742,25 +781,109 @@ export class SkiaScroll extends SkiaControl {
|
|
|
742
781
|
else
|
|
743
782
|
this.ScrollTo(this.offsetX, this.ContentOffsetBounds.Top, maxTimeSecs);
|
|
744
783
|
}
|
|
745
|
-
StopScrolling() { this.StopAnimators(); this.IsUserPanning = false; }
|
|
784
|
+
StopScrolling() { this.indexOrder = undefined; this.StopAnimators(); this.IsUserPanning = false; }
|
|
746
785
|
/**
|
|
747
786
|
* Scrolls so that item `index` is at the viewport start (or end). Like DrawnUi, Content must BE the
|
|
748
787
|
* templated layout (the recycled list is the scroll's only child; a header goes above the scroll or in Header).
|
|
749
788
|
*/
|
|
750
789
|
ScrollToIndex(index, animate, option = "Start") {
|
|
790
|
+
this.indexOrder = { Index: index, Option: option, Secs: animate ? this.ScrollingSpeedMs / 1000 : 0, Stalled: 0 };
|
|
791
|
+
this.PursueIndexOrder();
|
|
792
|
+
this.Repaint();
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* An open ScrollToIndex (C# OrderedScrollToIndex, DrawnUi.Rust scroll_to_index): a row not measured yet is gone to by
|
|
796
|
+
* the sizes as they are estimated, and the order is aimed again every frame until the row has its real place and the
|
|
797
|
+
* viewport stands on it; an animated move goes on to a moved target in the time it has left, later corrections jump.
|
|
798
|
+
* A press, the wheel or another scroll drops it; while it is open LoadMore waits.
|
|
799
|
+
*/
|
|
800
|
+
indexOrder;
|
|
801
|
+
pursuing = false;
|
|
802
|
+
/** One step of the open order (C# ExecuteScrollToIndexOrder, Rust `pursue`), at the start of every paint. */
|
|
803
|
+
PursueIndexOrder() {
|
|
804
|
+
const order = this.indexOrder;
|
|
805
|
+
if (!order)
|
|
806
|
+
return;
|
|
807
|
+
const horizontal = this.Orientation === "Horizontal";
|
|
808
|
+
const before = horizontal ? this.offsetX : this.offsetY;
|
|
809
|
+
const running = horizontal ? this.animatorFlingX : this.animatorFlingY;
|
|
810
|
+
const aim = this.AimIndex(order.Index, order.Option);
|
|
811
|
+
let done = false;
|
|
812
|
+
if (aim) {
|
|
813
|
+
const c = this.ClampOffset(horizontal ? aim.Offset : this.offsetX, horizontal ? this.offsetY : aim.Offset, this.ContentOffsetBounds, true);
|
|
814
|
+
const target = horizontal ? c.X : c.Y;
|
|
815
|
+
const runningTo = horizontal ? this.scrollToTargetX : this.scrollToTargetY;
|
|
816
|
+
const move = (to, secs) => {
|
|
817
|
+
this.pursuing = true;
|
|
818
|
+
try {
|
|
819
|
+
this.ScrollTo(horizontal ? to : this.offsetX, horizontal ? this.offsetY : to, secs, true);
|
|
820
|
+
}
|
|
821
|
+
finally {
|
|
822
|
+
this.pursuing = false;
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
if (running.IsRunning && runningTo !== null) {
|
|
826
|
+
if (Math.abs(runningTo - target) > 0.5) {
|
|
827
|
+
// on the way: the move goes on to the new target in the time it has left
|
|
828
|
+
const now = this.Superview?.FrameTimeNanos || performance.now() * 1_000_000;
|
|
829
|
+
move(target, running.StartFrameTimeNanos ? Math.max(0.016, running.Speed - (now - running.StartFrameTimeNanos) / 1e9) : running.Speed);
|
|
830
|
+
running.StartFrameTimeNanos = running.LastFrameTimeNanos = now;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
else if (Math.abs(before - target) <= 0.5) {
|
|
834
|
+
if (horizontal)
|
|
835
|
+
this.ViewportOffsetX = target;
|
|
836
|
+
else
|
|
837
|
+
this.ViewportOffsetY = target;
|
|
838
|
+
done = aim.Exact;
|
|
839
|
+
}
|
|
840
|
+
else {
|
|
841
|
+
move(target, order.Secs);
|
|
842
|
+
order.Secs = 0;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
// no list yet, or a row that never gets its real place: not forever
|
|
846
|
+
order.Stalled = (horizontal ? this.offsetX : this.offsetY) === before && !running.IsRunning ? order.Stalled + 1 : 0;
|
|
847
|
+
if (done || order.Stalled >= 10) {
|
|
848
|
+
this.indexOrder = undefined;
|
|
849
|
+
this.CheckLoadMore();
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
this.Repaint(); // the next frame aims again
|
|
853
|
+
}
|
|
854
|
+
/** The offset (points) that puts item `index` where `option` says, by the sizes the list knows now; Exact = its place is measured. */
|
|
855
|
+
AimIndex(index, option) {
|
|
751
856
|
const layout = this.content instanceof SkiaLayout && this.content.IsTemplated ? this.content : undefined;
|
|
752
857
|
const items = layout?.ItemsSource;
|
|
753
858
|
if (!layout || !items || items.length === 0 || !this.content)
|
|
754
|
-
return;
|
|
859
|
+
return undefined;
|
|
755
860
|
const i = Math.max(0, Math.min(items.length - 1, index));
|
|
756
861
|
const scale = this.RenderingScale;
|
|
757
|
-
const
|
|
758
|
-
|
|
862
|
+
const horizontal = this.Orientation === "Horizontal";
|
|
863
|
+
const viewport = (horizontal ? this.DrawingRect.Width : this.DrawingRect.Height) / scale;
|
|
864
|
+
// Item start and size in points, relative to the content start along the scroll axis.
|
|
865
|
+
let itemStart, itemSize;
|
|
866
|
+
if (!horizontal && layout.Type === "Column" && layout.Split <= 1) {
|
|
867
|
+
// The virtualized list: cells past the viewport are not realized, the structure knows their offsets.
|
|
868
|
+
itemStart = (layout.DrawingRect.Top - this.content.DrawingRect.Top + layout.GetItemOffsetPixels(i)) / scale;
|
|
869
|
+
itemSize = (layout.GetItemOffsetPixels(i + 1) - layout.GetItemOffsetPixels(i)) / scale;
|
|
870
|
+
}
|
|
871
|
+
else {
|
|
872
|
+
// A templated Row / Wrap / Grid realizes every item (C# non-list layouts are not virtualized): read the cell's
|
|
873
|
+
// arranged rect, like C# reads the structure cell's Destination.
|
|
874
|
+
const cell = layout.ChildrenFactory.GetViewForIndex(i);
|
|
875
|
+
if (!cell)
|
|
876
|
+
return undefined;
|
|
877
|
+
const r = cell.DrawingRect, c = this.content.DrawingRect;
|
|
878
|
+
itemStart = (horizontal ? r.Left - c.Left : r.Top - c.Top) / scale;
|
|
879
|
+
itemSize = (horizontal ? r.Width : r.Height) / scale;
|
|
880
|
+
}
|
|
881
|
+
let target = itemStart;
|
|
759
882
|
if (option === "End")
|
|
760
|
-
target -=
|
|
883
|
+
target -= viewport - itemSize;
|
|
761
884
|
else if (option === "Center")
|
|
762
|
-
target -=
|
|
763
|
-
|
|
885
|
+
target -= (viewport - itemSize) / 2;
|
|
886
|
+
return { Offset: -target, Exact: layout.IsItemMeasured(i) };
|
|
764
887
|
}
|
|
765
888
|
StopAnimators() {
|
|
766
889
|
this.flingEdgeX = null;
|
|
@@ -783,11 +906,70 @@ export class SkiaScroll extends SkiaControl {
|
|
|
783
906
|
this.panningLastDelta = SKPoint.Empty;
|
|
784
907
|
this.panningCurrentOffsetPts = new SKPoint(this.offsetX, this.offsetY);
|
|
785
908
|
}
|
|
909
|
+
draggedScrollBar;
|
|
910
|
+
scrollBarOwnsGesture = false;
|
|
911
|
+
/**
|
|
912
|
+
* Thumb drag and track press on a ScrollBar / ScrollBarHorizontal with IsDraggable set (C# ProcessScrollBarGestures).
|
|
913
|
+
* A gesture that starts on the bar belongs to it until the next down: the content neither pans nor gets the tap.
|
|
914
|
+
* True when the gesture was handled here.
|
|
915
|
+
*/
|
|
916
|
+
ProcessScrollBarGestures(args, apply) {
|
|
917
|
+
if (args.Type === "Down") {
|
|
918
|
+
this.draggedScrollBar = undefined;
|
|
919
|
+
this.scrollBarOwnsGesture = false;
|
|
920
|
+
const draggable = (b) => b && b.IsDraggable && b.BeginDrag && b.GetDragProgress ? b : undefined;
|
|
921
|
+
const v = draggable(this.scrollBar), h = draggable(this.scrollBarHorizontal);
|
|
922
|
+
if (!v && !h)
|
|
923
|
+
return false;
|
|
924
|
+
const x = apply.MappedLocation.X + apply.ChildOffset.X, y = apply.MappedLocation.Y + apply.ChildOffset.Y;
|
|
925
|
+
if (v && v.BeginDrag(x, y))
|
|
926
|
+
this.draggedScrollBar = v;
|
|
927
|
+
else if (h && h.BeginDrag(x, y))
|
|
928
|
+
this.draggedScrollBar = h;
|
|
929
|
+
if (!this.draggedScrollBar)
|
|
930
|
+
return false;
|
|
931
|
+
this.scrollBarOwnsGesture = true;
|
|
932
|
+
this.StopScrolling();
|
|
933
|
+
this.ApplyScrollBarDrag(x, y);
|
|
934
|
+
args.Event.Handled = true; // used: a touch on the bar is claimed, the page must not take the drag
|
|
935
|
+
return true;
|
|
936
|
+
}
|
|
937
|
+
if (!this.scrollBarOwnsGesture)
|
|
938
|
+
return false;
|
|
939
|
+
if (!this.draggedScrollBar)
|
|
940
|
+
return args.Type === "Tapped" || args.Type === "LongPressing"; // released already: only the tap that follows belongs to the bar
|
|
941
|
+
if (args.Type === "Panning") {
|
|
942
|
+
this.ApplyScrollBarDrag(apply.MappedLocation.X + apply.ChildOffset.X, apply.MappedLocation.Y + apply.ChildOffset.Y);
|
|
943
|
+
args.Event.Handled = true;
|
|
944
|
+
}
|
|
945
|
+
else if (args.Type === "Up") {
|
|
946
|
+
this.draggedScrollBar = undefined;
|
|
947
|
+
this.scrollBarLast = undefined; // push the idle state so an auto-hiding bar fades again
|
|
948
|
+
this.scrollBarHLast = undefined;
|
|
949
|
+
this.Repaint();
|
|
950
|
+
}
|
|
951
|
+
return true;
|
|
952
|
+
}
|
|
953
|
+
ApplyScrollBarDrag(x, y) {
|
|
954
|
+
const bar = this.draggedScrollBar;
|
|
955
|
+
if (!bar)
|
|
956
|
+
return;
|
|
957
|
+
const progress = bar.GetDragProgress(x, y);
|
|
958
|
+
const b = this.ContentOffsetBounds;
|
|
959
|
+
if (bar === this.scrollBarHorizontal)
|
|
960
|
+
this.ViewportOffsetX = this.ClampOffset(-progress * b.Width, this.offsetY, b, true).X;
|
|
961
|
+
else
|
|
962
|
+
this.ViewportOffsetY = this.ClampOffset(this.offsetX, -progress * b.Height, b, true).Y;
|
|
963
|
+
this.Repaint();
|
|
964
|
+
}
|
|
786
965
|
ProcessGestures(args, apply) {
|
|
787
966
|
const consumedDefault = this.BlockGesturesBelow ? this : null;
|
|
788
967
|
const scale = this.RenderingScale;
|
|
789
968
|
const e = args.Event;
|
|
969
|
+
if (this.ProcessScrollBarGestures(args, apply))
|
|
970
|
+
return this;
|
|
790
971
|
if (args.Type === "Down") {
|
|
972
|
+
this.indexOrder = undefined; // a press drops an open ScrollToIndex
|
|
791
973
|
this.hadDown = true;
|
|
792
974
|
this.snapped = false;
|
|
793
975
|
if (this.RespondsToGestures) {
|
|
@@ -801,15 +983,21 @@ export class SkiaScroll extends SkiaControl {
|
|
|
801
983
|
const child = super.ProcessGestures(args, apply);
|
|
802
984
|
if (child && child !== this)
|
|
803
985
|
return child;
|
|
804
|
-
if (!this.RespondsToGestures)
|
|
986
|
+
if (!this.RespondsToGestures || this.Orientation === "Neither")
|
|
987
|
+
return child ?? consumedDefault; // Neither: not used, a parent scroll may take it
|
|
988
|
+
// a vertical scroll leaves horizontal events (the sideways part of a diagonal swipe) to whatever is around it
|
|
989
|
+
if (e.Wheel.IsHorizontal && this.Orientation === "Vertical")
|
|
805
990
|
return child ?? consumedDefault;
|
|
806
|
-
|
|
991
|
+
if (!this.ApplyWheelScroll(e.Wheel.Delta))
|
|
992
|
+
return consumedDefault; // at its edge: not used, the page may take it
|
|
993
|
+
e.Handled = true;
|
|
994
|
+
return this;
|
|
807
995
|
}
|
|
808
996
|
this.velocityY = e.Distance.Velocity.Y / scale;
|
|
809
997
|
this.velocityX = e.Distance.Velocity.X / scale;
|
|
810
998
|
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
999
|
(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) {
|
|
1000
|
+
if (args.Type === "Panning" && this.RespondsToGestures && this.hadDown && this.Orientation !== "Neither") {
|
|
813
1001
|
if (!this.IsUserPanning) {
|
|
814
1002
|
// A child may own the pan (slider, nested horizontal scroll); ask once before taking over.
|
|
815
1003
|
const childConsumed = super.ProcessGestures(args, apply);
|
|
@@ -860,6 +1048,12 @@ export class SkiaScroll extends SkiaControl {
|
|
|
860
1048
|
const swipeThreshold = SkiaScroll.ThesholdSwipeOnUp * scale;
|
|
861
1049
|
let vx = finalVelocity.X * this.ChangeVelocityScrolled;
|
|
862
1050
|
let vy = finalVelocity.Y * this.ChangeVelocityScrolled;
|
|
1051
|
+
// a cancelled pointer (the browser took the pan to scroll the page, or a parent intercepted it) settles in place:
|
|
1052
|
+
// flinging from the few moves that arrived before the cancel would scroll the list while the page scrolls
|
|
1053
|
+
if (e.Type === "Cancelled") {
|
|
1054
|
+
vx = 0;
|
|
1055
|
+
vy = 0;
|
|
1056
|
+
}
|
|
863
1057
|
let fling = false;
|
|
864
1058
|
if (this.OverScrolled) {
|
|
865
1059
|
const rest = this.ClampOffset(this.offsetX, this.offsetY, this.ContentOffsetBounds, true);
|
|
@@ -934,15 +1128,20 @@ export class SkiaScroll extends SkiaControl {
|
|
|
934
1128
|
return true;
|
|
935
1129
|
}
|
|
936
1130
|
/**
|
|
937
|
-
* One wheel notch = WheelLineSize points, hard-clamped, animated over AutoScrollingSpeedMs.
|
|
938
|
-
*
|
|
939
|
-
*
|
|
1131
|
+
* One wheel notch = WheelLineSize points, hard-clamped, animated over AutoScrollingSpeedMs. An event moves by its share
|
|
1132
|
+
* of a notch (Canvas.WheelDeltaPerNotch), so a touchpad's many small events scroll as far as the fingers moved (C#
|
|
1133
|
+
* aaa871c3); an event under half a notch moves the content at once (no glide). Events arriving while the previous one is still animating add onto its target, so a fast wheel spin
|
|
1134
|
+
* travels N steps instead of restarting from the barely-moved current offset.
|
|
940
1135
|
*/
|
|
941
1136
|
ApplyWheelScroll(delta) {
|
|
942
|
-
const
|
|
1137
|
+
const perNotch = this.Superview?.WheelDeltaPerNotch ?? 0;
|
|
1138
|
+
const lines = perNotch > 0 ? delta / perNotch : Math.sign(delta);
|
|
1139
|
+
const step = -SkiaScroll.WheelLineSize * lines; // wheel down = content up = more negative offset
|
|
943
1140
|
const horizontal = this.Orientation === "Horizontal";
|
|
944
|
-
|
|
945
|
-
|
|
1141
|
+
// the target the running wheel / ScrollTo move lands on: Parameters.Destination is where its deceleration curve would
|
|
1142
|
+
// end if it ran on, beyond that target, so continuing from it overshot more with every event (a touchpad swipe flew away)
|
|
1143
|
+
const running = horizontal ? this.animatorFlingX : this.animatorFlingY, target = horizontal ? this.scrollToTargetX : this.scrollToTargetY;
|
|
1144
|
+
const base = running.IsRunning && target !== null ? target : horizontal ? this.offsetX : this.offsetY;
|
|
946
1145
|
const b = this.ContentOffsetBounds;
|
|
947
1146
|
const min = horizontal ? b.Left : b.Top, max = horizontal ? b.Right : b.Bottom;
|
|
948
1147
|
if ((step < 0 && base <= min) || (step > 0 && base >= max))
|
|
@@ -952,7 +1151,18 @@ export class SkiaScroll extends SkiaControl {
|
|
|
952
1151
|
x = base + step;
|
|
953
1152
|
else
|
|
954
1153
|
y = base + step;
|
|
955
|
-
|
|
1154
|
+
// a notch glides over AutoScrollingSpeedMs; smaller events (a touchpad, a free-spinning or high-resolution wheel) come
|
|
1155
|
+
// as a stream and move the content at once, as the browser scrolls a page: easing each one over 600 ms kept the
|
|
1156
|
+
// content behind the fingers, a visible delay before it started to move
|
|
1157
|
+
const glide = perNotch <= 0 || Math.abs(delta) >= perNotch / 2;
|
|
1158
|
+
this.ScrollTo(x, y, glide ? this.AutoScrollingSpeedMs / 1000 : 0, true);
|
|
1159
|
+
// a restarted glide starts at the event, not at its first frame: a fast trackpad swipe sends a notch-sized event
|
|
1160
|
+
// before every frame, and a glide drawn at progress 0 on each of them left the content standing still until the
|
|
1161
|
+
// events stopped, then it jumped (DrawnUi.Rust dfe36fe). Never before the last frame, at most one frame back.
|
|
1162
|
+
if (glide && running.IsRunning) {
|
|
1163
|
+
const event = performance.now() * 1_000_000;
|
|
1164
|
+
running.StartFrameTimeNanos = running.LastFrameTimeNanos = Math.max(this.Superview?.FrameTimeNanos ?? 0, event - 16_666_667);
|
|
1165
|
+
}
|
|
956
1166
|
return true;
|
|
957
1167
|
}
|
|
958
1168
|
}
|
|
@@ -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;
|