Skip to content

Commit

Permalink
chore: add mouse wheel zooming
Browse files Browse the repository at this point in the history
  • Loading branch information
Xiaoy312 committed Oct 23, 2024
1 parent 19aaee6 commit 3fbb235
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -100,23 +100,6 @@ private double HorizontalZoomCenter
set => SetValue(HorizontalZoomCenterProperty, value);
}

#endregion
#region DependencyProperty: [Private] IsHorizontalScrollBarVisible

/// <summary>Identifies the IsHorizontalScrollBarVisible dependency property.</summary>
private static DependencyProperty IsHorizontalScrollBarVisibleProperty { get; } = DependencyProperty.Register(
nameof(IsHorizontalScrollBarVisible),
typeof(bool),
typeof(ZoomContentControl),
new PropertyMetadata(true));

/// <summary>Gets or sets a value indicating whether the horizontal scrollbar is visible.</summary>
private bool IsHorizontalScrollBarVisible
{
get => (bool)GetValue(IsHorizontalScrollBarVisibleProperty);
set => SetValue(IsHorizontalScrollBarVisibleProperty, value);
}

#endregion

#region DependencyProperty: [Private] VerticalScrollValue
Expand Down Expand Up @@ -184,23 +167,6 @@ private double VerticalZoomCenter
set => SetValue(VerticalZoomCenterProperty, value);
}

#endregion
#region DependencyProperty: [Private] IsVerticalScrollBarVisible

/// <summary>Identifies the IsVerticalScrollBarVisible dependency property.</summary>
private static DependencyProperty IsVerticalScrollBarVisibleProperty { get; } = DependencyProperty.Register(
nameof(IsVerticalScrollBarVisible),
typeof(bool),
typeof(ZoomContentControl),
new PropertyMetadata(true));

/// <summary>Gets or sets a value indicating whether the vertical scrollbar is visible.</summary>
private bool IsVerticalScrollBarVisible
{
get => (bool)GetValue(IsVerticalScrollBarVisibleProperty);
set => SetValue(IsVerticalScrollBarVisibleProperty, value);
}

#endregion

#region DependencyProperty: [Private] ZoomLevel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,20 @@ private void UpdateScrollVisibility()
{
if (Viewport is { } vp)
{
IsHorizontalScrollBarVisible = vp.ActualWidth < ScrollExtentWidth;
IsVerticalScrollBarVisible = vp.ActualHeight < ScrollExtentHeight;
ToggleScrollBarVisibility(_scrollH, vp.ActualWidth < ScrollExtentWidth);
ToggleScrollBarVisibility(_scrollV, vp.ActualWidth < ScrollExtentWidth);
}

void ToggleScrollBarVisibility(ScrollBar? sb, bool value)
{
if (sb is null) return;

// Showing/hiding the ScrollBar(s)could cause the ContentPresenter to move as it re-centers.
// This adds unnecessary complexity for the zooming logics as we need to preserve the focal point
// under the cursor position or the pinch center point after zooming.
// To avoid all that, we just make them permanently there for layout calculation.
sb.IsEnabled = value;
sb.Opacity = value ? 1 : 0;
}
}

Expand Down Expand Up @@ -271,7 +283,7 @@ private void OnPointerMoved(object sender, PointerRoutedEventArgs e)
var delta = context.Position - position;
delta.X *= -1;

ScrollValue = context.ScrollOffset + delta;
SetScrollValue(context.ScrollOffset + delta);
}
}

Expand All @@ -289,23 +301,41 @@ private void OnPointerWheelChanged(object sender, PointerRoutedEventArgs e)
#endif
) return;


// MouseWheel + Ctrl: Zoom
if (e.KeyModifiers.HasFlag(Windows.System.VirtualKeyModifiers.Control))
{
if (!IsZoomAllowed) return;

return; // todo
var oldPosition = (p.Position - vp.ActualSize.ToPoint().DivideBy(2)) - ScrollValue.MultiplyBy(1, -1);
var basePosition = oldPosition.DivideBy(ZoomLevel);

var newZoom = ZoomLevel * (1 + p.Properties.MouseWheelDelta * ScaleWheelRatio);
//var newZoom = ZoomLevel + Math.Sign(p.Properties.MouseWheelDelta);
newZoom = Math.Clamp(newZoom, MinZoomLevel, MaxZoomLevel);

var newPosition = basePosition.MultiplyBy(newZoom);
var delta = (newPosition - oldPosition).MultiplyBy(-1, 1);
var offset = ScrollValue + delta;

// note: updating ZoomLevel can have side effects on ScrollValue:
// ZoomLevel --UpdateScrollBars-> ScrollBar.Maximum --clamp-> ScrollBar.Value --bound-> H/VScrollValue
// before we set the ZoomLevel, make sure to snapshot ScrollValue or finish the calculation using ScrollValue
ZoomLevel = newZoom;
SetScrollValue(offset, shouldClamp: false);

e.Handled = true;
}
// MouseWheel + Shift: Scroll Horizontally
// MouseWheel: Scroll Vertically
else
{
var delta = p.Properties.MouseWheelDelta * PanWheelRatio;
ScrollValue += e.KeyModifiers.HasFlag(Windows.System.VirtualKeyModifiers.Shift)
? new (delta, 0)
: new (0, -delta);
var magnitude = p.Properties.MouseWheelDelta * PanWheelRatio;
var delta = e.KeyModifiers.HasFlag(Windows.System.VirtualKeyModifiers.Shift)
? new Point(magnitude, 0)
: new Point(0, -magnitude);
var offset = ScrollValue + delta;

SetScrollValue(offset);
e.Handled = true;
}
}
Expand Down Expand Up @@ -337,6 +367,20 @@ public void FitToCanvas()
}
}

private void SetScrollValue(Point value, bool shouldClamp = true)
{
// we allow unconstrained panning for MouseWheelZoom(desktop) and PinchToZoom(mobile)
// where the focal point should remain stationary after zooming.
if (shouldClamp)
{
value.X = Math.Clamp(value.X, HorizontalMinScroll, HorizontalMaxScroll);
value.Y = Math.Clamp(value.Y, VerticalMinScroll, VerticalMaxScroll);
}

HorizontalScrollValue = value.X;
VerticalScrollValue = value.Y;
}

// Helper

private bool IsAllowedToWork => (IsLoaded && IsActive && _contentPresenter is not null);
Expand All @@ -345,15 +389,7 @@ public void FitToCanvas()

private FrameworkElement? Viewport => _contentGrid;

private Point ScrollValue
{
get => new Point(HorizontalScrollValue, VerticalScrollValue);
set
{
HorizontalScrollValue = Math.Clamp(value.X, HorizontalMinScroll, HorizontalMaxScroll);
VerticalScrollValue = Math.Clamp(value.Y, VerticalMinScroll, VerticalMaxScroll);
}
}
private Point ScrollValue => new Point(HorizontalScrollValue, VerticalScrollValue);

private double ScrollExtentWidth => (ContentWidth + AdditionalMargin.Left + AdditionalMargin.Right) * ZoomLevel;
private double ScrollExtentHeight => (ContentHeight + AdditionalMargin.Top + AdditionalMargin.Bottom) * ZoomLevel;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:utu="using:Uno.Toolkit.UI">

Expand All @@ -19,7 +19,7 @@
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>

<Grid x:Name="PART_ContentGrid"
Grid.Row="0"
Grid.Column="0">
Expand Down Expand Up @@ -51,7 +51,6 @@
Minimum="{TemplateBinding VerticalMinScroll}"
Orientation="Vertical"
SmallChange="1"
Visibility="{TemplateBinding IsVerticalScrollBarVisible}"
ViewportSize="{TemplateBinding ViewportHeight}"
Value="{Binding VerticalScrollValue, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
<ScrollBar x:Name="PART_ScrollH"
Expand All @@ -65,7 +64,6 @@
Minimum="{TemplateBinding HorizontalMinScroll}"
Orientation="Horizontal"
SmallChange="1"
Visibility="{TemplateBinding IsHorizontalScrollBarVisible}"
ViewportSize="{TemplateBinding ViewportWidth}"
Value="{Binding HorizontalScrollValue, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
</Grid>
Expand Down
29 changes: 29 additions & 0 deletions src/Uno.Toolkit.UI/Extensions/TwoDExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.UI.WebUI;

namespace Uno.Toolkit.UI;

internal static partial class TwoDExtensions // Point Mathematics
{
public static Point ToPoint(this Size x) => new Point(x.Width, x.Height);
public static Point ToPoint(this Vector2 x) => new Point(x.X, x.Y);

public static Point MultiplyBy(this Point x, double scale) => new Point(x.X * scale, x.Y * scale);
public static Point MultiplyBy(this Point x, double scaleX, double scaleY) => new Point(x.X * scaleX, x.Y * scaleY);
public static Point DivideBy(this Point x, double scale) => new Point(x.X / scale, x.Y / scale);
}

internal static partial class TwoDExtensions // Size Mathematics
{
public static Size ToSize(this Point x) => new Size(x.X, x.Y);
public static Size ToSize(this Vector2 x) => new Size(x.X, x.Y);

public static Size MultiplyBy(this Size x, double scale) => new Size(x.Width * scale, x.Height * scale);
public static Size DivideBy(this Size x, double scale) => new Size(x.Width / scale, x.Height / scale);
}

0 comments on commit 3fbb235

Please sign in to comment.