Skip to content

Commit

Permalink
feat(scp): Add ability to reverse pointer wheel on the SCP for manage…
Browse files Browse the repository at this point in the history
…d scrolling on skia and wasm
  • Loading branch information
dr1rrb committed Jun 13, 2023
1 parent 5d302d3 commit 4be3764
Show file tree
Hide file tree
Showing 6 changed files with 265 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,14 @@ public void MouseWheelRight()
global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Windows.UI.Xaml.Controls.ScrollContentPresenter", "void ScrollContentPresenter.MouseWheelRight()");
}
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || false || __NETSTD_REFERENCE__ || false
#if __ANDROID__ || __IOS__ || NET461 || false || false || __NETSTD_REFERENCE__ || false
[global::Uno.NotImplemented("__ANDROID__", "__IOS__", "NET461", "__WASM__", "__NETSTD_REFERENCE__")]
public void SetHorizontalOffset( double offset)
{
global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Windows.UI.Xaml.Controls.ScrollContentPresenter", "void ScrollContentPresenter.SetHorizontalOffset(double offset)");
}
#endif
#if __ANDROID__ || __IOS__ || NET461 || __WASM__ || false || __NETSTD_REFERENCE__ || false
#if __ANDROID__ || __IOS__ || NET461 || false || false || __NETSTD_REFERENCE__ || false
[global::Uno.NotImplemented("__ANDROID__", "__IOS__", "NET461", "__WASM__", "__NETSTD_REFERENCE__")]
public void SetVerticalOffset( double offset)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,61 +19,6 @@ namespace Windows.UI.Xaml.Controls
{
public partial class ScrollContentPresenter : ContentPresenter, ICustomClippingElement
{
// Default physical amount to scroll with Up/Down/Left/Right key
//const double ScrollViewerLineDelta = 16.0;

// This value comes from WHEEL_DELTA defined in WinUser.h. It represents the universal default mouse wheel delta.
const int ScrollViewerDefaultMouseWheelDelta = 120;

// These macros compute how many integral pixels need to be scrolled based on the viewport size and mouse wheel delta.
// - First the maximum between 48 and 15% of the viewport size is picked.
// - Then that number is multiplied by (mouse wheel delta/120), 120 being the universal default value.
// - Finally if the resulting number is larger than the viewport size, then that viewport size is picked instead.
private static double GetVerticalScrollWheelDelta(Size size, double delta)
=> Math.Min(Math.Floor(size.Height), Math.Round(delta * Math.Max(48.0, Math.Round(size.Height * 0.15, 0)) / ScrollViewerDefaultMouseWheelDelta, 0));
private static double GetHorizontalScrollWheelDelta(Size size, double delta)
=> Math.Min(Math.Floor(size.Width), Math.Round(delta * Math.Max(48.0, Math.Round(size.Width * 0.15, 0)) / ScrollViewerDefaultMouseWheelDelta, 0));

// Minimum value of MinZoomFactor, ZoomFactor and MaxZoomFactor
// ZoomFactor can be manipulated to a slightly smaller value, but
// will jump back to 0.1 when the manipulation completes.
//const double ScrollViewerMinimumZoomFactor = 0.1f;

// Tolerated rounding delta in pixels between requested scroll offset and
// effective value. Used to handle non-DM-driven scrolls.
//const double ScrollViewerScrollRoundingTolerance = 0.05f;

// Tolerated rounding delta in pixels between requested scroll offset and
// effective value for cases where IScrollInfo is implemented by a
// IManipulationDataProvider provider. Used to handle non-DM-driven scrolls.
//const double ScrollViewerScrollRoundingToleranceForProvider = 1.0f;

// Delta required between the current scroll offsets and target scroll offsets
// in order to warrant a call to BringIntoViewport instead of
// SetOffsetsWithExtents, SetHorizontalOffset, SetVerticalOffset.
//const double ScrollViewerScrollRoundingToleranceForBringIntoViewport = 0.001f;

// Tolerated rounding delta in between requested zoom factor and
// effective value. Used to handle non-DM-driven zooms.
//const double ScrollViewerZoomExtentRoundingTolerance = 0.001f;

// Tolerated rounding delta in between old and new zoom factor
// in DM delta handling.
//const double ScrollViewerZoomRoundingTolerance = 0.000001f;

// Delta required between the current zoom factor and target zoom factor
// in order to warrant a call to BringIntoViewport instead of ZoomToFactor.
//const double ScrollViewerZoomRoundingToleranceForBringIntoViewport = 0.00001f;

// When a snap point is within this tolerance of the scrollviewer's extent
// minus its viewport we nudge the snap point back into place.
//const double ScrollViewerSnapPointLocationTolerance = 0.0001f;

// If a ScrollViewer is going to reflow around docked CoreInputView occlussions
// by shrinking its viewport, we want to at least guarantee that it will keep
// an appropriate size.
//const double ScrollViewerMinHeightToReflowAroundOcclusions = 32.0f;

private /*readonly - partial*/ IScrollStrategy _strategy;

private bool _canHorizontallyScroll;
Expand Down Expand Up @@ -123,7 +68,7 @@ partial void InitializePartial()
_strategy.Initialize(this);

// Mouse wheel support
PointerWheelChanged += ScrollContentPresenter_PointerWheelChanged;
PointerWheelChanged += PointerWheelScroll;

// Touch scroll support
ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY; // Updated in PrepareTouchScroll!
Expand All @@ -132,12 +77,6 @@ partial void InitializePartial()
ManipulationCompleted += CompleteTouchScroll;
}

public void SetVerticalOffset(double offset)
=> Set(verticalOffset: offset);

public void SetHorizontalOffset(double offset)
=> Set(horizontalOffset: offset);

/// <inheritdoc />
protected override void OnContentChanged(object oldValue, object newValue)
{
Expand All @@ -162,7 +101,7 @@ internal bool Set(
double? horizontalOffset = null,
double? verticalOffset = null,
float? zoomFactor = null,
bool disableAnimation = true,
bool disableAnimation = false,
bool isIntermediate = false)
{
var success = true;
Expand Down Expand Up @@ -215,44 +154,6 @@ private void Apply(bool disableAnimation, bool isIntermediate)
InvalidateViewport();
}

// Ensure the offset we're scrolling to is valid.
private double ValidateInputOffset(double offset, int minOffset, double maxOffset)
{
if (offset.IsNaN())
{
throw new InvalidOperationException($"Invalid scroll offset value");
}

return Math.Max(minOffset, Math.Min(offset, maxOffset));
}

private void ScrollContentPresenter_PointerWheelChanged(object sender, Input.PointerRoutedEventArgs e)
{
var properties = e.GetCurrentPoint(null).Properties;

if (Content is UIElement)
{
var canScrollHorizontally = CanHorizontallyScroll;
var canScrollVertically = CanVerticallyScroll;

if (e.KeyModifiers == VirtualKeyModifiers.Control)
{
// TODO: Handle zoom https://github.com/unoplatform/uno/issues/4309
}
else if (!canScrollVertically || properties.IsHorizontalMouseWheel || e.KeyModifiers == VirtualKeyModifiers.Shift)
{
if (canScrollHorizontally)
{
SetHorizontalOffset(HorizontalOffset + GetHorizontalScrollWheelDelta(DesiredSize, -properties.MouseWheelDelta * ScrollViewerDefaultMouseWheelDelta));
}
}
else
{
SetVerticalOffset(VerticalOffset + GetVerticalScrollWheelDelta(DesiredSize, properties.MouseWheelDelta * ScrollViewerDefaultMouseWheelDelta));
}
}
}

private void PrepareTouchScroll(object sender, ManipulationStartingRoutedEventArgs e)
{
if (e.Container != this)
Expand Down Expand Up @@ -289,6 +190,7 @@ private void UpdateTouchScroll(object sender, ManipulationDeltaRoutedEventArgs e
Set(
horizontalOffset: HorizontalOffset - e.Delta.Translation.X,
verticalOffset: VerticalOffset - e.Delta.Translation.Y,
disableAnimation: true,
isIntermediate: true);
}

Expand All @@ -299,7 +201,7 @@ private void CompleteTouchScroll(object sender, ManipulationCompletedRoutedEvent
return;
}

Set(isIntermediate: false);
Set(disableAnimation: true, isIntermediate: false);
}

bool ICustomClippingElement.AllowClippingToLayoutSlot => true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System;
using Windows.Foundation;
using Uno.UI;
using Windows.System;
#if XAMARIN_ANDROID
using View = Android.Views.View;
using Font = Android.Graphics.Typeface;
Expand Down Expand Up @@ -182,7 +183,71 @@ protected override Size ArrangeOverride(Size finalSize)

internal override bool IsViewHit()
=> true;
#elif __IOS__ // Note: No __ANDROID__, the ICustomScrollInfo support is made directly in the NativeScrollContentPresenter

private void PointerWheelScroll(object sender, Input.PointerRoutedEventArgs e)
{
var properties = e.GetCurrentPoint(null).Properties;

if (Content is UIElement)
{
var canScrollHorizontally = CanHorizontallyScroll;
var canScrollVertically = CanVerticallyScroll;
var delta = IsPointerWheelReversed
? -properties.MouseWheelDelta
: properties.MouseWheelDelta;

if (e.KeyModifiers == VirtualKeyModifiers.Control)
{
// TODO: Handle zoom https://github.com/unoplatform/uno/issues/4309
}
else if (!canScrollVertically || properties.IsHorizontalMouseWheel || e.KeyModifiers == VirtualKeyModifiers.Shift)
{
if (canScrollHorizontally)
{
#if __WASM__ // On wasm the scroll might be async (especially with disableAnimation: false), so we need to use the pending value to support high speed multiple wheel events
var horizontalOffset = _pendingScrollTo?.horizontal ?? HorizontalOffset;
#else
var horizontalOffset = HorizontalOffset;
#endif

Set(
horizontalOffset: horizontalOffset + GetHorizontalScrollWheelDelta(DesiredSize, delta),
disableAnimation: false);
}
}
else
{
#if __WASM__ // On wasm the scroll might be async (especially with disableAnimation: false), so we need to use the pending value to support high speed multiple wheel events
var verticalOffset = _pendingScrollTo?.vertical ?? VerticalOffset;
#else
var verticalOffset = VerticalOffset;
#endif

Set(
verticalOffset: verticalOffset + GetVerticalScrollWheelDelta(DesiredSize, -delta),
disableAnimation: false);
}
}
}

public void SetVerticalOffset(double offset)
=> Set(verticalOffset: offset, disableAnimation: true);

public void SetHorizontalOffset(double offset)
=> Set(horizontalOffset: offset, disableAnimation: true);

// Ensure the offset we're scrolling to is valid.
private double ValidateInputOffset(double offset, int minOffset, double maxOffset)
{
if (offset.IsNaN())
{
throw new InvalidOperationException($"Invalid scroll offset value");
}

return Math.Max(minOffset, Math.Min(offset, maxOffset));
}

#elif __IOS__ // Note: No __ANDROID__, the ICustomScrollInfo support is made directly in the NativeScrollContentPresenter
protected override Size MeasureOverride(Size size)
{
var result = base.MeasureOverride(size);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,62 @@ namespace Windows.UI.Xaml.Controls;

public partial class ScrollContentPresenter
{
// Default physical amount to scroll with Up/Down/Left/Right key
//const double ScrollViewerLineDelta = 16.0;

// This value comes from WHEEL_DELTA defined in WinUser.h. It represents the universal default mouse wheel delta.
internal const int ScrollViewerDefaultMouseWheelDelta = 120;

// These macros compute how many integral pixels need to be scrolled based on the viewport size and mouse wheel delta.
// - First the maximum between 48 and 15% of the viewport size is picked.
// - Then that number is multiplied by (mouse wheel delta/120), 120 being the universal default value.
// - Finally if the resulting number is larger than the viewport size, then that viewport size is picked instead.
private static double GetVerticalScrollWheelDelta(Size size, double delta)
=> Math.Min(Math.Floor(size.Height), Math.Round(delta * Math.Max(48.0, Math.Round(size.Height * 0.15, 0)) / ScrollViewerDefaultMouseWheelDelta, 0));
private static double GetHorizontalScrollWheelDelta(Size size, double delta)
=> Math.Min(Math.Floor(size.Width), Math.Round(delta * Math.Max(48.0, Math.Round(size.Width * 0.15, 0)) / ScrollViewerDefaultMouseWheelDelta, 0));

// Minimum value of MinZoomFactor, ZoomFactor and MaxZoomFactor
// ZoomFactor can be manipulated to a slightly smaller value, but
// will jump back to 0.1 when the manipulation completes.
//const double ScrollViewerMinimumZoomFactor = 0.1f;

// Tolerated rounding delta in pixels between requested scroll offset and
// effective value. Used to handle non-DM-driven scrolls.
//const double ScrollViewerScrollRoundingTolerance = 0.05f;

// Tolerated rounding delta in pixels between requested scroll offset and
// effective value for cases where IScrollInfo is implemented by a
// IManipulationDataProvider provider. Used to handle non-DM-driven scrolls.
//const double ScrollViewerScrollRoundingToleranceForProvider = 1.0f;

// Delta required between the current scroll offsets and target scroll offsets
// in order to warrant a call to BringIntoViewport instead of
// SetOffsetsWithExtents, SetHorizontalOffset, SetVerticalOffset.
//const double ScrollViewerScrollRoundingToleranceForBringIntoViewport = 0.001f;

// Tolerated rounding delta in between requested zoom factor and
// effective value. Used to handle non-DM-driven zooms.
//const double ScrollViewerZoomExtentRoundingTolerance = 0.001f;

// Tolerated rounding delta in between old and new zoom factor
// in DM delta handling.
//const double ScrollViewerZoomRoundingTolerance = 0.000001f;

// Delta required between the current zoom factor and target zoom factor
// in order to warrant a call to BringIntoViewport instead of ZoomToFactor.
//const double ScrollViewerZoomRoundingToleranceForBringIntoViewport = 0.00001f;

// When a snap point is within this tolerance of the scrollviewer's extent
// minus its viewport we nudge the snap point back into place.
//const double ScrollViewerSnapPointLocationTolerance = 0.0001f;

// If a ScrollViewer is going to reflow around docked CoreInputView occlussions
// by shrinking its viewport, we want to at least guarantee that it will keep
// an appropriate size.
//const double ScrollViewerMinHeightToReflowAroundOcclusions = 32.0f;


// BringIntoView functionality is ported from WinUI ScrollPresenter
// https://github.com/microsoft/microsoft-ui-xaml/blob/main/dev/ScrollPresenter/ScrollPresenter.cpp
// with partial modifications to match the ScrollViewer control behavior.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;

namespace Uno.UI.Xaml.Controls
{
public class ScrollContentPresenter
{
/// <summary>
/// Backing property for the IsPointerWheelReversed attached property.
/// </summary>
public static readonly DependencyProperty IsPointerWheelReversedProperty = DependencyProperty.RegisterAttached(
"IsPointerWheelReversed",
typeof(bool),
typeof(Windows.UI.Xaml.Controls.ScrollContentPresenter),
new FrameworkPropertyMetadata((snd, e) => ((Windows.UI.Xaml.Controls.ScrollContentPresenter)snd).IsPointerWheelReversed = (bool)e.NewValue));

/// <summary>
/// Gets a boolean which indicates if the pointer wheel should be reversed or not for the <paramref name="scrollViewer"/>.
/// </summary>
/// <param name="scrollViewer"></param>
/// <returns></returns>
public static bool GetIsPointerWheelReversed(Windows.UI.Xaml.Controls.ScrollContentPresenter scrollViewer)
=> (bool)scrollViewer.GetValue(IsPointerWheelReversedProperty);

/// <summary>
/// Sets if the pointer wheel should be reversed or not for the <paramref name="scrollViewer"/>.
/// </summary>
/// <param name="scrollViewer">The target ScrollViewer to configure</param>
/// <param name="isReversed">A boolean which indicates if the wheel should be reversed of not.</param>
public static void SetIsPointerWheelReversed(Windows.UI.Xaml.Controls.ScrollContentPresenter scrollViewer, bool isReversed)
=> scrollViewer.SetValue(IsPointerWheelReversedProperty, isReversed);

}
}

namespace Windows.UI.Xaml.Controls
{
partial class ScrollContentPresenter
{
private bool _isPointerWheelReversed;

/// <summary>
/// Cached value of <see cref="Uno.UI.Xaml.Controls.ScrollContentPresenter.IsPointerWheelReversedProperty"/>,
/// in order to not access the DP on each scroll (perf considerations)
/// </summary>
internal bool IsPointerWheelReversed
{
get => _isPointerWheelReversed;
set
{
if (_isPointerWheelReversed != value)
{
_isPointerWheelReversed = value;
OnIsPointerWheelReversedChanged(value);
}
}
}

partial void OnIsPointerWheelReversedChanged(bool isReversed);
}
}
Loading

0 comments on commit 4be3764

Please sign in to comment.