mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Moved pan/zoom logic to seperate VM
Fixed mouse offset when dragging devices and zoomed in Update background viewport when zooming/panning
This commit is contained in:
parent
720af4ed73
commit
227d5846ff
@ -159,6 +159,7 @@
|
|||||||
<Compile Include="ViewModels\Controls\ProfileEditor\ProfileLedViewModel.cs" />
|
<Compile Include="ViewModels\Controls\ProfileEditor\ProfileLedViewModel.cs" />
|
||||||
<Compile Include="ViewModels\Controls\SurfaceEditor\SurfaceLedViewModel.cs" />
|
<Compile Include="ViewModels\Controls\SurfaceEditor\SurfaceLedViewModel.cs" />
|
||||||
<Compile Include="ViewModels\Controls\SurfaceEditor\SurfaceDeviceViewModel.cs" />
|
<Compile Include="ViewModels\Controls\SurfaceEditor\SurfaceDeviceViewModel.cs" />
|
||||||
|
<Compile Include="ViewModels\PanZoomViewModel.cs" />
|
||||||
<Compile Include="ViewModels\Screens\SurfaceEditorViewModel.cs" />
|
<Compile Include="ViewModels\Screens\SurfaceEditorViewModel.cs" />
|
||||||
<Compile Include="ViewModels\Interfaces\IEditorViewModel.cs" />
|
<Compile Include="ViewModels\Interfaces\IEditorViewModel.cs" />
|
||||||
<Compile Include="ViewModels\Controls\Settings\RgbDeviceSettingsViewModel.cs" />
|
<Compile Include="ViewModels\Controls\Settings\RgbDeviceSettingsViewModel.cs" />
|
||||||
|
|||||||
102
src/Artemis.UI/ViewModels/PanZoomViewModel.cs
Normal file
102
src/Artemis.UI/ViewModels/PanZoomViewModel.cs
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -26,7 +26,8 @@ namespace Artemis.UI.ViewModels.Screens
|
|||||||
Devices = new ObservableCollection<SurfaceDeviceViewModel>();
|
Devices = new ObservableCollection<SurfaceDeviceViewModel>();
|
||||||
SurfaceConfigurations = new ObservableCollection<SurfaceConfiguration>();
|
SurfaceConfigurations = new ObservableCollection<SurfaceConfiguration>();
|
||||||
SelectionRectangle = new RectangleGeometry();
|
SelectionRectangle = new RectangleGeometry();
|
||||||
|
PanZoomViewModel = new PanZoomViewModel();
|
||||||
|
|
||||||
_rgbService = rgbService;
|
_rgbService = rgbService;
|
||||||
_surfaceService = surfaceService;
|
_surfaceService = surfaceService;
|
||||||
_rgbService.DeviceLoaded += RgbServiceOnDeviceLoaded;
|
_rgbService.DeviceLoaded += RgbServiceOnDeviceLoaded;
|
||||||
@ -38,7 +39,7 @@ namespace Artemis.UI.ViewModels.Screens
|
|||||||
device.ZIndex = Devices.IndexOf(device) + 1;
|
device.ZIndex = Devices.IndexOf(device) + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public RectangleGeometry SelectionRectangle { get; set; }
|
public RectangleGeometry SelectionRectangle { get; set; }
|
||||||
public ObservableCollection<SurfaceDeviceViewModel> Devices { get; set; }
|
public ObservableCollection<SurfaceDeviceViewModel> Devices { get; set; }
|
||||||
|
|
||||||
@ -46,17 +47,8 @@ namespace Artemis.UI.ViewModels.Screens
|
|||||||
public ObservableCollection<SurfaceConfiguration> SurfaceConfigurations { get; set; }
|
public ObservableCollection<SurfaceConfiguration> SurfaceConfigurations { get; set; }
|
||||||
public string NewConfigurationName { get; set; }
|
public string NewConfigurationName { get; set; }
|
||||||
|
|
||||||
public double Zoom { get; set; } = 1;
|
public PanZoomViewModel PanZoomViewModel { get; set; }
|
||||||
|
|
||||||
public double ZoomPercentage
|
|
||||||
{
|
|
||||||
get => Zoom * 100;
|
|
||||||
set => Zoom = value / 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double PanX { get; set; }
|
|
||||||
public double PanY { get; set; }
|
|
||||||
|
|
||||||
public string Title => "Surface Editor";
|
public string Title => "Surface Editor";
|
||||||
|
|
||||||
private void RgbServiceOnDeviceLoaded(object sender, DeviceEventArgs e)
|
private void RgbServiceOnDeviceLoaded(object sender, DeviceEventArgs e)
|
||||||
@ -182,8 +174,9 @@ namespace Artemis.UI.ViewModels.Screens
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var position = e.GetPosition((IInputElement) sender);
|
var position = e.GetPosition((IInputElement) sender);
|
||||||
|
var relative = PanZoomViewModel.GetRelativeMousePosition(sender, e);
|
||||||
if (e.LeftButton == MouseButtonState.Pressed)
|
if (e.LeftButton == MouseButtonState.Pressed)
|
||||||
StartMouseDrag(position);
|
StartMouseDrag(position, relative);
|
||||||
else
|
else
|
||||||
StopMouseDrag(position);
|
StopMouseDrag(position);
|
||||||
}
|
}
|
||||||
@ -199,17 +192,17 @@ namespace Artemis.UI.ViewModels.Screens
|
|||||||
}
|
}
|
||||||
|
|
||||||
var position = e.GetPosition((IInputElement) sender);
|
var position = e.GetPosition((IInputElement) sender);
|
||||||
var relative = e.GetPosition(((Grid)sender).Children[0]);
|
var relative = PanZoomViewModel.GetRelativeMousePosition(sender, e);
|
||||||
if (_mouseDragStatus == MouseDragStatus.Dragging)
|
if (_mouseDragStatus == MouseDragStatus.Dragging)
|
||||||
MoveSelected(relative);
|
MoveSelected(relative);
|
||||||
else if (_mouseDragStatus == MouseDragStatus.Selecting)
|
else if (_mouseDragStatus == MouseDragStatus.Selecting)
|
||||||
UpdateSelection(position);
|
UpdateSelection(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartMouseDrag(Point position)
|
private void StartMouseDrag(Point position, Point relative)
|
||||||
{
|
{
|
||||||
// If drag started on top of a device, initialise dragging
|
// 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)
|
if (device != null)
|
||||||
{
|
{
|
||||||
_mouseDragStatus = MouseDragStatus.Dragging;
|
_mouseDragStatus = MouseDragStatus.Dragging;
|
||||||
@ -226,7 +219,7 @@ namespace Artemis.UI.ViewModels.Screens
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach (var selectedDevice in Devices.Where(d => d.SelectionStatus == SelectionStatus.Selected))
|
foreach (var selectedDevice in Devices.Where(d => d.SelectionStatus == SelectionStatus.Selected))
|
||||||
selectedDevice.StartMouseDrag(position);
|
selectedDevice.StartMouseDrag(relative);
|
||||||
}
|
}
|
||||||
// Start multi-selection
|
// Start multi-selection
|
||||||
else
|
else
|
||||||
@ -251,7 +244,7 @@ namespace Artemis.UI.ViewModels.Screens
|
|||||||
var selectedRect = new Rect(_mouseDragStartPoint, position);
|
var selectedRect = new Rect(_mouseDragStartPoint, position);
|
||||||
foreach (var device in Devices)
|
foreach (var device in Devices)
|
||||||
{
|
{
|
||||||
if (TransformDeviceRect(device.DeviceRectangle).IntersectsWith(selectedRect))
|
if (PanZoomViewModel.TransformContainingRect(device.DeviceRectangle).IntersectsWith(selectedRect))
|
||||||
device.SelectionStatus = SelectionStatus.Selected;
|
device.SelectionStatus = SelectionStatus.Selected;
|
||||||
else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
|
else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
|
||||||
device.SelectionStatus = SelectionStatus.None;
|
device.SelectionStatus = SelectionStatus.None;
|
||||||
@ -274,7 +267,7 @@ namespace Artemis.UI.ViewModels.Screens
|
|||||||
|
|
||||||
foreach (var device in Devices)
|
foreach (var device in Devices)
|
||||||
{
|
{
|
||||||
if (TransformDeviceRect(device.DeviceRectangle).IntersectsWith(selectedRect))
|
if (PanZoomViewModel.TransformContainingRect(device.DeviceRectangle).IntersectsWith(selectedRect))
|
||||||
device.SelectionStatus = SelectionStatus.Selected;
|
device.SelectionStatus = SelectionStatus.Selected;
|
||||||
else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
|
else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
|
||||||
device.SelectionStatus = SelectionStatus.None;
|
device.SelectionStatus = SelectionStatus.None;
|
||||||
@ -287,84 +280,39 @@ namespace Artemis.UI.ViewModels.Screens
|
|||||||
foreach (var device in Devices.Where(d => d.SelectionStatus == SelectionStatus.Selected))
|
foreach (var device in Devices.Where(d => d.SelectionStatus == SelectionStatus.Selected))
|
||||||
device.UpdateMouseDrag(position);
|
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
|
#endregion
|
||||||
|
|
||||||
#region Panning and zooming
|
#region Panning and zooming
|
||||||
|
|
||||||
private Point? _lastPanPosition;
|
|
||||||
|
|
||||||
public void EditorGridMouseWheel(object sender, MouseWheelEventArgs e)
|
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
|
PanZoomViewModel.ProcessMouseScroll(sender, e);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EditorGridKeyDown(object sender, KeyEventArgs 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;
|
Mouse.OverrideCursor = Cursors.ScrollAll;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EditorGridKeyUp(object sender, KeyEventArgs e)
|
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;
|
Mouse.OverrideCursor = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Pan(object sender, MouseEventArgs e)
|
public void Pan(object sender, MouseEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.LeftButton == MouseButtonState.Released)
|
PanZoomViewModel.ProcessMouseMove(sender, e);
|
||||||
{
|
|
||||||
Mouse.OverrideCursor = Cursors.Arrow;
|
|
||||||
_lastPanPosition = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_lastPanPosition == null)
|
|
||||||
_lastPanPosition = e.GetPosition((IInputElement) sender);
|
|
||||||
|
|
||||||
// Empty the selection rect since it's shown while mouse is down
|
// Empty the selection rect since it's shown while mouse is down
|
||||||
SelectionRectangle.Rect = Rect.Empty;
|
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()
|
public void ResetZoomAndPan()
|
||||||
{
|
{
|
||||||
Zoom = 1;
|
PanZoomViewModel.Reset();
|
||||||
PanX = 0;
|
|
||||||
PanY = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsPanKeyDown()
|
private bool IsPanKeyDown()
|
||||||
|
|||||||
@ -44,7 +44,7 @@
|
|||||||
MouseDown="{s:Action EditorGridMouseClick}"
|
MouseDown="{s:Action EditorGridMouseClick}"
|
||||||
MouseMove="{s:Action EditorGridMouseMove}">
|
MouseMove="{s:Action EditorGridMouseMove}">
|
||||||
<Grid.Background>
|
<Grid.Background>
|
||||||
<VisualBrush TileMode="Tile" Stretch="Uniform" Viewport="20,20,20,20" ViewportUnits="Absolute">
|
<VisualBrush TileMode="Tile" Stretch="Uniform" Viewport="{Binding PanZoomViewModel.BackgroundViewport}" ViewportUnits="Absolute">
|
||||||
<VisualBrush.Visual>
|
<VisualBrush.Visual>
|
||||||
<Grid Width="20" Height="20">
|
<Grid Width="20" Height="20">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
@ -84,8 +84,8 @@
|
|||||||
<Grid Name="EditorDisplayGrid">
|
<Grid Name="EditorDisplayGrid">
|
||||||
<Grid.RenderTransform>
|
<Grid.RenderTransform>
|
||||||
<TransformGroup>
|
<TransformGroup>
|
||||||
<ScaleTransform ScaleX="{Binding Zoom}" ScaleY="{Binding Zoom}" />
|
<ScaleTransform ScaleX="{Binding PanZoomViewModel.Zoom}" ScaleY="{Binding PanZoomViewModel.Zoom}" />
|
||||||
<TranslateTransform X="{Binding PanX}" Y="{Binding PanY}" />
|
<TranslateTransform X="{Binding PanZoomViewModel.PanX}" Y="{Binding PanZoomViewModel.PanY}" />
|
||||||
</TransformGroup>
|
</TransformGroup>
|
||||||
</Grid.RenderTransform>
|
</Grid.RenderTransform>
|
||||||
<ItemsControl ItemsSource="{Binding Devices}">
|
<ItemsControl ItemsSource="{Binding Devices}">
|
||||||
@ -157,7 +157,7 @@
|
|||||||
Maximum="400"
|
Maximum="400"
|
||||||
Height="100"
|
Height="100"
|
||||||
FocusVisualStyle="{x:Null}"
|
FocusVisualStyle="{x:Null}"
|
||||||
Value="{Binding ZoomPercentage}"
|
Value="{Binding PanZoomViewModel.ZoomPercentage}"
|
||||||
Style="{StaticResource MaterialDesignDiscreteSlider}" />
|
Style="{StaticResource MaterialDesignDiscreteSlider}" />
|
||||||
<Button Command="{s:Action ResetZoomAndPan}"
|
<Button Command="{s:Action ResetZoomAndPan}"
|
||||||
Style="{StaticResource MaterialDesignFloatingActionMiniButton}"
|
Style="{StaticResource MaterialDesignFloatingActionMiniButton}"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user