From 227d5846ff5548755814d46eb1f8b1ff73fd620f Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 21 Oct 2019 19:56:15 +0200 Subject: [PATCH] Moved pan/zoom logic to seperate VM Fixed mouse offset when dragging devices and zoomed in Update background viewport when zooming/panning --- src/Artemis.UI/Artemis.UI.csproj | 1 + src/Artemis.UI/ViewModels/PanZoomViewModel.cs | 102 ++++++++++++++++++ .../Screens/SurfaceEditorViewModel.cs | 90 ++++------------ .../Views/Screens/SurfaceEditorView.xaml | 8 +- 4 files changed, 126 insertions(+), 75 deletions(-) create mode 100644 src/Artemis.UI/ViewModels/PanZoomViewModel.cs diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index d75eeb9cb..5781d1bc1 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -159,6 +159,7 @@ + diff --git a/src/Artemis.UI/ViewModels/PanZoomViewModel.cs b/src/Artemis.UI/ViewModels/PanZoomViewModel.cs new file mode 100644 index 000000000..e69696cea --- /dev/null +++ b/src/Artemis.UI/ViewModels/PanZoomViewModel.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using Stylet; + +namespace Artemis.UI.ViewModels +{ + public class PanZoomViewModel : PropertyChangedBase + { + private Point? _lastPanPosition; + public double Zoom { get; set; } = 1; + + public double ZoomPercentage + { + get => Zoom * 100; + set => SetZoomFromPercentage(value); + } + + public double PanX { get; set; } + public double PanY { get; set; } + + public Rect BackgroundViewport => new Rect(PanX, PanY, 20 * Zoom, 20 * Zoom); + + public void ProcessMouseScroll(object sender, MouseWheelEventArgs e) + { + var relative = GetRelativeMousePosition(sender, e); + var absoluteX = relative.X * Zoom + PanX; + var absoluteY = relative.Y * Zoom + PanY; + + if (e.Delta > 0) + Zoom *= 1.1; + else + Zoom *= 0.9; + + // Limit to a min of 0.1 and a max of 4 (10% - 400% in the view) + Zoom = Math.Max(0.1, Math.Min(4, Zoom)); + + // Update the PanX/Y to enable zooming relative to cursor + PanX = absoluteX - relative.X * Zoom; + PanY = absoluteY - relative.Y * Zoom; + } + + public void ProcessMouseMove(object sender, MouseEventArgs e) + { + if (e.LeftButton == MouseButtonState.Released) + { + Mouse.OverrideCursor = Cursors.Arrow; + _lastPanPosition = null; + return; + } + + if (_lastPanPosition == null) + _lastPanPosition = e.GetPosition((IInputElement) sender); + + var position = e.GetPosition((IInputElement) sender); + var delta = _lastPanPosition - position; + PanX -= delta.Value.X; + PanY -= delta.Value.Y; + + _lastPanPosition = position; + } + + public void Reset() + { + Zoom = 1; + PanX = 0; + PanY = 0; + } + + public Rect TransformContainingRect(Rect rect) + { + // Create the same transform group the view is using + var transformGroup = new TransformGroup(); + transformGroup.Children.Add(new ScaleTransform(Zoom, Zoom)); + transformGroup.Children.Add(new TranslateTransform(PanX, PanY)); + + // Apply it to the device rect + return transformGroup.TransformBounds(rect); + } + + public Point GetRelativeMousePosition(object container, MouseEventArgs e) + { + // Get the mouse position relative to the panned / zoomed panel, not very MVVM but I can't find a better way + return e.GetPosition(((Panel) container).Children[0]); + } + + private void SetZoomFromPercentage(double value) + { + var newZoom = value / 100; + // Focus towards the center of the zoomed area + PanX += newZoom - Zoom; + PanY += newZoom - Zoom; + Zoom = value / 100; + } + } +} \ No newline at end of file diff --git a/src/Artemis.UI/ViewModels/Screens/SurfaceEditorViewModel.cs b/src/Artemis.UI/ViewModels/Screens/SurfaceEditorViewModel.cs index d39100c02..b621a84cc 100644 --- a/src/Artemis.UI/ViewModels/Screens/SurfaceEditorViewModel.cs +++ b/src/Artemis.UI/ViewModels/Screens/SurfaceEditorViewModel.cs @@ -26,7 +26,8 @@ namespace Artemis.UI.ViewModels.Screens Devices = new ObservableCollection(); SurfaceConfigurations = new ObservableCollection(); SelectionRectangle = new RectangleGeometry(); - + PanZoomViewModel = new PanZoomViewModel(); + _rgbService = rgbService; _surfaceService = surfaceService; _rgbService.DeviceLoaded += RgbServiceOnDeviceLoaded; @@ -38,7 +39,7 @@ namespace Artemis.UI.ViewModels.Screens device.ZIndex = Devices.IndexOf(device) + 1; } } - + public RectangleGeometry SelectionRectangle { get; set; } public ObservableCollection Devices { get; set; } @@ -46,17 +47,8 @@ namespace Artemis.UI.ViewModels.Screens public ObservableCollection SurfaceConfigurations { get; set; } public string NewConfigurationName { get; set; } - public double Zoom { get; set; } = 1; - - public double ZoomPercentage - { - get => Zoom * 100; - set => Zoom = value / 100; - } - - public double PanX { get; set; } - public double PanY { get; set; } - + public PanZoomViewModel PanZoomViewModel { get; set; } + public string Title => "Surface Editor"; private void RgbServiceOnDeviceLoaded(object sender, DeviceEventArgs e) @@ -182,8 +174,9 @@ namespace Artemis.UI.ViewModels.Screens return; var position = e.GetPosition((IInputElement) sender); + var relative = PanZoomViewModel.GetRelativeMousePosition(sender, e); if (e.LeftButton == MouseButtonState.Pressed) - StartMouseDrag(position); + StartMouseDrag(position, relative); else StopMouseDrag(position); } @@ -199,17 +192,17 @@ namespace Artemis.UI.ViewModels.Screens } var position = e.GetPosition((IInputElement) sender); - var relative = e.GetPosition(((Grid)sender).Children[0]); + var relative = PanZoomViewModel.GetRelativeMousePosition(sender, e); if (_mouseDragStatus == MouseDragStatus.Dragging) MoveSelected(relative); else if (_mouseDragStatus == MouseDragStatus.Selecting) UpdateSelection(position); } - private void StartMouseDrag(Point position) + private void StartMouseDrag(Point position, Point relative) { // If drag started on top of a device, initialise dragging - var device = Devices.LastOrDefault(d => TransformDeviceRect(d.DeviceRectangle).Contains(position)); + var device = Devices.LastOrDefault(d => PanZoomViewModel.TransformContainingRect(d.DeviceRectangle).Contains(position)); if (device != null) { _mouseDragStatus = MouseDragStatus.Dragging; @@ -226,7 +219,7 @@ namespace Artemis.UI.ViewModels.Screens } foreach (var selectedDevice in Devices.Where(d => d.SelectionStatus == SelectionStatus.Selected)) - selectedDevice.StartMouseDrag(position); + selectedDevice.StartMouseDrag(relative); } // Start multi-selection else @@ -251,7 +244,7 @@ namespace Artemis.UI.ViewModels.Screens var selectedRect = new Rect(_mouseDragStartPoint, position); foreach (var device in Devices) { - if (TransformDeviceRect(device.DeviceRectangle).IntersectsWith(selectedRect)) + if (PanZoomViewModel.TransformContainingRect(device.DeviceRectangle).IntersectsWith(selectedRect)) device.SelectionStatus = SelectionStatus.Selected; else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift)) device.SelectionStatus = SelectionStatus.None; @@ -274,7 +267,7 @@ namespace Artemis.UI.ViewModels.Screens foreach (var device in Devices) { - if (TransformDeviceRect(device.DeviceRectangle).IntersectsWith(selectedRect)) + if (PanZoomViewModel.TransformContainingRect(device.DeviceRectangle).IntersectsWith(selectedRect)) device.SelectionStatus = SelectionStatus.Selected; else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift)) device.SelectionStatus = SelectionStatus.None; @@ -287,84 +280,39 @@ namespace Artemis.UI.ViewModels.Screens foreach (var device in Devices.Where(d => d.SelectionStatus == SelectionStatus.Selected)) device.UpdateMouseDrag(position); } - - private Rect TransformDeviceRect(Rect deviceRect) - { - // Create the same transform group the view is using - var transformGroup = new TransformGroup(); - transformGroup.Children.Add(new ScaleTransform(Zoom, Zoom)); - transformGroup.Children.Add(new TranslateTransform(PanX, PanY)); - - // Apply it to the device rect - return transformGroup.TransformBounds(deviceRect); - } - + #endregion #region Panning and zooming - private Point? _lastPanPosition; - public void EditorGridMouseWheel(object sender, MouseWheelEventArgs e) { - // Get the mouse position relative to the panned / zoomed grid, not very MVVM but I can't find a better way - var relative = e.GetPosition(((Grid) sender).Children[0]); - var absoluteX = relative.X * Zoom + PanX; - var absoluteY = relative.Y * Zoom + PanY; - - if (e.Delta > 0) - Zoom *= 1.1; - else - Zoom *= 0.9; - - // Limit to a min of 0.1 and a max of 4 (10% - 400% in the view) - Zoom = Math.Max(0.1, Math.Min(4, Zoom)); - - // Update the PanX/Y to enable zooming relative to cursor - PanX = absoluteX - relative.X * Zoom; - PanY = absoluteY - relative.Y * Zoom; + PanZoomViewModel.ProcessMouseScroll(sender, e); } public void EditorGridKeyDown(object sender, KeyEventArgs e) { - if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl) + if ((e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl) && e.IsDown) Mouse.OverrideCursor = Cursors.ScrollAll; } public void EditorGridKeyUp(object sender, KeyEventArgs e) { - if (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl) + if ((e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl) && e.IsUp) Mouse.OverrideCursor = null; } public void Pan(object sender, MouseEventArgs e) { - if (e.LeftButton == MouseButtonState.Released) - { - Mouse.OverrideCursor = Cursors.Arrow; - _lastPanPosition = null; - return; - } - - if (_lastPanPosition == null) - _lastPanPosition = e.GetPosition((IInputElement) sender); + PanZoomViewModel.ProcessMouseMove(sender, e); // Empty the selection rect since it's shown while mouse is down SelectionRectangle.Rect = Rect.Empty; - - var position = e.GetPosition((IInputElement) sender); - var delta = _lastPanPosition - position; - PanX -= delta.Value.X; - PanY -= delta.Value.Y; - - _lastPanPosition = position; } public void ResetZoomAndPan() { - Zoom = 1; - PanX = 0; - PanY = 0; + PanZoomViewModel.Reset(); } private bool IsPanKeyDown() diff --git a/src/Artemis.UI/Views/Screens/SurfaceEditorView.xaml b/src/Artemis.UI/Views/Screens/SurfaceEditorView.xaml index 264efdcee..1a0ed8071 100644 --- a/src/Artemis.UI/Views/Screens/SurfaceEditorView.xaml +++ b/src/Artemis.UI/Views/Screens/SurfaceEditorView.xaml @@ -44,7 +44,7 @@ MouseDown="{s:Action EditorGridMouseClick}" MouseMove="{s:Action EditorGridMouseMove}"> - + @@ -84,8 +84,8 @@ - - + + @@ -157,7 +157,7 @@ Maximum="400" Height="100" FocusVisualStyle="{x:Null}" - Value="{Binding ZoomPercentage}" + Value="{Binding PanZoomViewModel.ZoomPercentage}" Style="{StaticResource MaterialDesignDiscreteSlider}" />