From cceb1224868fa78b5c18d7bbed68c36caa996894 Mon Sep 17 00:00:00 2001 From: ClemensFischer Date: Thu, 12 Jun 2025 00:31:01 +0200 Subject: [PATCH] Updated mouse wheel event handling --- MapControl/Avalonia/Map.Avalonia.cs | 34 ++++++++--- MapControl/Shared/MapBase.cs | 38 ++++++------ MapControl/WPF/Map.WPF.cs | 89 +++++++++++++++++------------ MapControl/WinUI/Map.WinUI.cs | 35 ++++++++---- 4 files changed, 122 insertions(+), 74 deletions(-) diff --git a/MapControl/Avalonia/Map.Avalonia.cs b/MapControl/Avalonia/Map.Avalonia.cs index cbfed149..1b13c4e8 100644 --- a/MapControl/Avalonia/Map.Avalonia.cs +++ b/MapControl/Avalonia/Map.Avalonia.cs @@ -23,11 +23,13 @@ namespace MapControl public static readonly StyledProperty MouseWheelZoomDeltaProperty = DependencyPropertyHelper.Register(nameof(MouseWheelZoomDelta), 0.25); + public static readonly DependencyProperty MouseWheelZoomAnimatedProperty = + DependencyPropertyHelper.Register(nameof(MouseWheelZoomAnimated), true); + private IPointer pointer1; private IPointer pointer2; private Point position1; private Point position2; - private double mouseWheelDelta; public ManipulationModes ManipulationModes { @@ -36,7 +38,7 @@ namespace MapControl } /// - /// Gets or sets the amount by which the ZoomLevel property changes by a MouseWheel event. + /// Gets or sets the amount by which the ZoomLevel property changes by a PointerWheelChanged event. /// The default value is 0.25. /// public double MouseWheelZoomDelta @@ -45,20 +47,34 @@ namespace MapControl set => SetValue(MouseWheelZoomDeltaProperty, value); } + /// + /// Gets or sets a value that controls whether zooming by a PointerWheelChanged event is animated. + /// The default value is true. + /// + public bool MouseWheelZoomAnimated + { + get => (bool)GetValue(MouseWheelZoomAnimatedProperty); + set => SetValue(MouseWheelZoomAnimatedProperty, value); + } + protected override void OnPointerWheelChanged(PointerWheelEventArgs e) { - mouseWheelDelta += e.Delta.Y; + var delta = e.Delta.Y; + var zoomLevel = TargetZoomLevel + MouseWheelZoomDelta * delta; + var animated = false; - if (Math.Abs(mouseWheelDelta) >= 1d) + if (delta % 1d == 0d) { - // Zoom to integer multiple of MouseWheelZoomDelta. + // Zoom to integer multiple of MouseWheelZoomDelta when delta is an integer value, + // i.e. when the event was actually raised by a mouse wheel and not by a touch pad + // or a similar device with higher resolution. // - ZoomMap(e.GetPosition(this), - MouseWheelZoomDelta * Math.Round(TargetZoomLevel / MouseWheelZoomDelta + mouseWheelDelta)); - - mouseWheelDelta = 0d; + zoomLevel = MouseWheelZoomDelta * Math.Round(zoomLevel / MouseWheelZoomDelta); + animated = MouseWheelZoomAnimated; } + ZoomMap(e.GetPosition(this), zoomLevel, animated); + base.OnPointerWheelChanged(e); } diff --git a/MapControl/Shared/MapBase.cs b/MapControl/Shared/MapBase.cs index 66a55828..a42bf8bd 100644 --- a/MapControl/Shared/MapBase.cs +++ b/MapControl/Shared/MapBase.cs @@ -75,7 +75,7 @@ namespace MapControl } /// - /// Gets or sets the Duration of the Center, ZoomLevel and Heading animations. + /// Gets or sets the duration of the Center, ZoomLevel and Heading animations. /// The default value is 0.3 seconds. /// public TimeSpan AnimationDuration @@ -303,21 +303,18 @@ namespace MapControl else { SetTransformCenter(center); - viewCenter = new Point(viewCenter.X + translation.X, viewCenter.Y + translation.Y); if (rotation != 0d) { - var heading = (((Heading - rotation) % 360d) + 360d) % 360d; - + var heading = CoerceHeadingProperty(Heading - rotation); SetValueInternal(HeadingProperty, heading); SetValueInternal(TargetHeadingProperty, heading); } if (scale != 1d) { - var zoomLevel = Math.Min(Math.Max(ZoomLevel + Math.Log(scale, 2d), MinZoomLevel), MaxZoomLevel); - + var zoomLevel = CoerceZoomLevelProperty(ZoomLevel + Math.Log(scale, 2d)); SetValueInternal(ZoomLevelProperty, zoomLevel); SetValueInternal(TargetZoomLevelProperty, zoomLevel); } @@ -327,18 +324,30 @@ namespace MapControl } /// - /// Sets the value of the TargetZoomLevel property while retaining the specified center point - /// in view coordinates. + /// Sets the ZoomLevel or TargetZoomLevel property while retaining + /// the specified center point in view coordinates. /// - public void ZoomMap(Point center, double zoomLevel) + public void ZoomMap(Point center, double zoomLevel, bool animated = true) { zoomLevel = CoerceZoomLevelProperty(zoomLevel); - if (TargetZoomLevel != zoomLevel) + if (animated) { - SetTransformCenter(center); - - TargetZoomLevel = zoomLevel; + if (TargetZoomLevel != zoomLevel) + { + SetTransformCenter(center); + TargetZoomLevel = zoomLevel; + } + } + else + { + if (ZoomLevel != zoomLevel) + { + SetTransformCenter(center); + SetValueInternal(ZoomLevelProperty, zoomLevel); + SetValueInternal(TargetZoomLevelProperty, zoomLevel); + UpdateTransform(true); + } } } @@ -358,7 +367,6 @@ namespace MapControl if (targetCenter != null) { var scale = Math.Min(ActualWidth / mapRect.Value.Width, ActualHeight / mapRect.Value.Height); - TargetZoomLevel = ScaleToZoomLevel(scale); TargetCenter = targetCenter; TargetHeading = 0d; @@ -428,9 +436,7 @@ namespace MapControl private void SetValueInternal(DependencyProperty property, object value) { internalPropertyChange = true; - SetValue(property, value); - internalPropertyChange = false; } diff --git a/MapControl/WPF/Map.WPF.cs b/MapControl/WPF/Map.WPF.cs index 97bfc564..36ebfdb2 100644 --- a/MapControl/WPF/Map.WPF.cs +++ b/MapControl/WPF/Map.WPF.cs @@ -9,20 +9,31 @@ namespace MapControl /// public class Map : MapBase { - public static readonly DependencyProperty MouseWheelZoomDeltaProperty = - DependencyPropertyHelper.Register(nameof(MouseWheelZoomDelta), 0.25); - public static readonly DependencyProperty ManipulationModeProperty = DependencyPropertyHelper.Register(nameof(ManipulationMode), ManipulationModes.Translate | ManipulationModes.Scale); + public static readonly DependencyProperty MouseWheelZoomDeltaProperty = + DependencyPropertyHelper.Register(nameof(MouseWheelZoomDelta), 0.25); + + public static readonly DependencyProperty MouseWheelZoomAnimatedProperty = + DependencyPropertyHelper.Register(nameof(MouseWheelZoomAnimated), true); + private Point? mousePosition; - private double mouseWheelDelta; static Map() { IsManipulationEnabledProperty.OverrideMetadata(typeof(Map), new FrameworkPropertyMetadata(true)); } + /// + /// Gets or sets a value that specifies how the map control handles manipulations. + /// + public ManipulationModes ManipulationMode + { + get => (ManipulationModes)GetValue(ManipulationModeProperty); + set => SetValue(ManipulationModeProperty, value); + } + /// /// Gets or sets the amount by which the ZoomLevel property changes by a MouseWheel event. /// The default value is 0.25. @@ -34,57 +45,59 @@ namespace MapControl } /// - /// Gets or sets a value that specifies how the map control handles manipulations. + /// Gets or sets a value that controls whether zooming by a MouseWheel event is animated. + /// The default value is true. /// - public ManipulationModes ManipulationMode + public bool MouseWheelZoomAnimated { - get => (ManipulationModes)GetValue(ManipulationModeProperty); - set => SetValue(ManipulationModeProperty, value); + get => (bool)GetValue(MouseWheelZoomAnimatedProperty); + set => SetValue(MouseWheelZoomAnimatedProperty, value); } - protected override void OnManipulationStarted(ManipulationStartedEventArgs e) + protected override void OnMouseWheel(MouseWheelEventArgs e) { - base.OnManipulationStarted(e); + var zoomLevel = TargetZoomLevel + MouseWheelZoomDelta * e.Delta / 120d; + var animated = false; - Manipulation.SetManipulationMode(this, ManipulationMode); - } + if (e.Delta % 120 == 0) + { + // Zoom to integer multiple of MouseWheelZoomDelta when delta is a multiple of 120, + // i.e. when the event was actually raised by a mouse wheel and not by a touch pad + // or a similar device with higher resolution. + // + zoomLevel = MouseWheelZoomDelta * Math.Round(zoomLevel / MouseWheelZoomDelta); + animated = MouseWheelZoomAnimated; + } - protected override void OnManipulationDelta(ManipulationDeltaEventArgs e) - { - base.OnManipulationDelta(e); + ZoomMap(e.GetPosition(this), zoomLevel, animated); - TransformMap(e.ManipulationOrigin, - (Point)e.DeltaManipulation.Translation, - e.DeltaManipulation.Rotation, - e.DeltaManipulation.Scale.LengthSquared / 2d); + base.OnMouseWheel(e); } protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) { - base.OnMouseLeftButtonDown(e); - if (Keyboard.Modifiers == ModifierKeys.None && CaptureMouse()) { mousePosition = e.GetPosition(this); } + + base.OnMouseLeftButtonDown(e); } protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) { - base.OnMouseLeftButtonUp(e); - if (mousePosition.HasValue) { mousePosition = null; ReleaseMouseCapture(); } + + base.OnMouseLeftButtonUp(e); } protected override void OnMouseMove(MouseEventArgs e) { - base.OnMouseMove(e); - if (mousePosition.HasValue) { var p = e.GetPosition(this); @@ -99,25 +112,25 @@ namespace MapControl // mousePosition = e.GetPosition(this); } + + base.OnMouseMove(e); } - protected override void OnMouseWheel(MouseWheelEventArgs e) + protected override void OnManipulationStarted(ManipulationStartedEventArgs e) { - base.OnMouseWheel(e); + Manipulation.SetManipulationMode(this, ManipulationMode); - // Standard mouse wheel delta value is 120. - // - mouseWheelDelta += e.Delta / 120d; + base.OnManipulationStarted(e); + } - if (Math.Abs(mouseWheelDelta) >= 1d) - { - // Zoom to integer multiple of MouseWheelZoomDelta. - // - ZoomMap(e.GetPosition(this), - MouseWheelZoomDelta * Math.Round(TargetZoomLevel / MouseWheelZoomDelta + mouseWheelDelta)); + protected override void OnManipulationDelta(ManipulationDeltaEventArgs e) + { + TransformMap(e.ManipulationOrigin, + (Point)e.DeltaManipulation.Translation, + e.DeltaManipulation.Rotation, + e.DeltaManipulation.Scale.LengthSquared / 2d); - mouseWheelDelta = 0d; - } + base.OnManipulationDelta(e); } } } diff --git a/MapControl/WinUI/Map.WinUI.cs b/MapControl/WinUI/Map.WinUI.cs index c2ce45b3..4a3e227f 100644 --- a/MapControl/WinUI/Map.WinUI.cs +++ b/MapControl/WinUI/Map.WinUI.cs @@ -20,7 +20,9 @@ namespace MapControl public static readonly DependencyProperty MouseWheelZoomDeltaProperty = DependencyPropertyHelper.Register(nameof(MouseWheelZoomDelta), 0.25); - private double mouseWheelDelta; + public static readonly DependencyProperty MouseWheelZoomAnimatedProperty = + DependencyPropertyHelper.Register(nameof(MouseWheelZoomAnimated), true); + private bool? manipulationEnabled; public Map() @@ -48,25 +50,36 @@ namespace MapControl set => SetValue(MouseWheelZoomDeltaProperty, value); } + /// + /// Gets or sets a value that controls whether zooming by a PointerWheelChanged event is animated. + /// The default value is true. + /// + public bool MouseWheelZoomAnimated + { + get => (bool)GetValue(MouseWheelZoomAnimatedProperty); + set => SetValue(MouseWheelZoomAnimatedProperty, value); + } + private void OnPointerWheelChanged(object sender, PointerRoutedEventArgs e) { if (e.Pointer.PointerDeviceType == PointerDeviceType.Mouse) { var point = e.GetCurrentPoint(this); + var delta = point.Properties.MouseWheelDelta; + var zoomLevel = TargetZoomLevel + MouseWheelZoomDelta * delta / 120d; + var animated = false; - // Standard mouse wheel delta value is 120. - // - mouseWheelDelta += point.Properties.MouseWheelDelta / 120d; - - if (Math.Abs(mouseWheelDelta) >= 1d) + if (delta % 120 == 0) { - // Zoom to integer multiple of MouseWheelZoomDelta. + // Zoom to integer multiple of MouseWheelZoomDelta when delta is a multiple of 120, + // i.e. when the event was actually raised by a mouse wheel and not by a touch pad + // or a similar device with higher resolution. // - ZoomMap(point.Position, - MouseWheelZoomDelta * Math.Round(TargetZoomLevel / MouseWheelZoomDelta + mouseWheelDelta)); - - mouseWheelDelta = 0d; + zoomLevel = MouseWheelZoomDelta * Math.Round(zoomLevel / MouseWheelZoomDelta); + animated = MouseWheelZoomAnimated; } + + ZoomMap(point.Position, zoomLevel, animated); } }