1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00
Artemis/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs
2021-09-04 20:52:03 +02:00

467 lines
18 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Navigation;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Extensions;
using Artemis.UI.Ninject.Factories;
using Artemis.UI.Screens.Shared;
using Artemis.UI.Screens.SurfaceEditor.Dialogs;
using Artemis.UI.Screens.SurfaceEditor.Visualization;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using SkiaSharp;
using SkiaSharp.Views.WPF;
using Stylet;
using MouseButton = System.Windows.Input.MouseButton;
namespace Artemis.UI.Screens.SurfaceEditor
{
public class SurfaceEditorViewModel : MainScreenViewModel
{
private readonly ICoreService _coreService;
private readonly IDeviceService _deviceService;
private readonly IWindowManager _windowManager;
private readonly IDeviceDebugVmFactory _deviceDebugVmFactory;
private readonly IDialogService _dialogService;
private readonly IRgbService _rgbService;
private readonly ISettingsService _settingsService;
private Cursor _cursor;
private PanZoomViewModel _panZoomViewModel;
private RectangleGeometry _selectionRectangle;
private PluginSetting<GridLength> _surfaceListWidth;
public SurfaceEditorViewModel(IRgbService rgbService,
ICoreService coreService,
IDialogService dialogService,
ISettingsService settingsService,
IDeviceService deviceService,
IWindowManager windowManager,
IDeviceDebugVmFactory deviceDebugVmFactory)
{
DisplayName = "Surface Editor";
SelectionRectangle = new RectangleGeometry();
PanZoomViewModel = new PanZoomViewModel();
Cursor = null;
ColorDevices = true;
SurfaceDeviceViewModels = new BindableCollection<SurfaceDeviceViewModel>();
ListDeviceViewModels = new BindableCollection<ListDeviceViewModel>();
_rgbService = rgbService;
_coreService = coreService;
_dialogService = dialogService;
_settingsService = settingsService;
_deviceService = deviceService;
_windowManager = windowManager;
_deviceDebugVmFactory = deviceDebugVmFactory;
}
public BindableCollection<SurfaceDeviceViewModel> SurfaceDeviceViewModels { get; }
public BindableCollection<ListDeviceViewModel> ListDeviceViewModels { get; }
public RectangleGeometry SelectionRectangle
{
get => _selectionRectangle;
set => SetAndNotify(ref _selectionRectangle, value);
}
public PanZoomViewModel PanZoomViewModel
{
get => _panZoomViewModel;
set => SetAndNotify(ref _panZoomViewModel, value);
}
public PluginSetting<GridLength> SurfaceListWidth
{
get => _surfaceListWidth;
set => SetAndNotify(ref _surfaceListWidth, value);
}
public Cursor Cursor
{
get => _cursor;
set => SetAndNotify(ref _cursor, value);
}
public bool ColorDevices
{
get => _colorDevices;
set
{
SetAndNotify(ref _colorDevices, value);
if (!value)
ColorFirstLedOnly = false;
}
}
public bool ColorFirstLedOnly
{
get => _colorFirstLedOnly;
set => SetAndNotify(ref _colorFirstLedOnly, value);
}
public double MaxTextureSize => 4096 / _settingsService.GetSetting("Core.RenderScale", 0.5).Value;
public double MaxTextureSizeIndicatorThickness => 2 / PanZoomViewModel.Zoom;
public void OpenHyperlink(object sender, RequestNavigateEventArgs e)
{
Core.Utilities.OpenUrl(e.Uri.AbsoluteUri);
}
public async Task AutoArrange()
{
bool confirmed = await _dialogService.ShowConfirmDialog("Auto-arrange layout", "Are you sure you want to auto-arrange your layout? Your current settings will be overwritten.");
if (!confirmed)
return;
_rgbService.AutoArrangeDevices();
}
private void LoadWorkspaceSettings()
{
SurfaceListWidth = _settingsService.GetSetting("SurfaceEditor.SurfaceListWidth", new GridLength(300.0));
}
private void SaveWorkspaceSettings()
{
SurfaceListWidth.Save();
}
private void CoreServiceOnFrameRendering(object sender, FrameRenderingEventArgs e)
{
if (!ColorDevices)
return;
e.Canvas.Clear(new SKColor(0, 0, 0));
foreach (ListDeviceViewModel listDeviceViewModel in ListDeviceViewModels)
{
// Order by position to accurately get the first LED
List<ArtemisLed> leds = listDeviceViewModel.Device.Leds.OrderBy(l => l.RgbLed.Location.Y).ThenBy(l => l.RgbLed.Location.X).ToList();
for (int index = 0; index < leds.Count; index++)
{
ArtemisLed artemisLed = leds[index];
if (ColorFirstLedOnly && index == 0 || !ColorFirstLedOnly)
e.Canvas.DrawRect(artemisLed.AbsoluteRectangle, new SKPaint {Color = listDeviceViewModel.Color});
}
}
}
#region Overrides of Screen
protected override void OnInitialActivate()
{
LoadWorkspaceSettings();
SurfaceDeviceViewModels.AddRange(_rgbService.EnabledDevices.OrderBy(d => d.ZIndex).Select(d => new SurfaceDeviceViewModel(d, _rgbService, _settingsService)));
ListDeviceViewModels.AddRange(_rgbService.EnabledDevices.OrderBy(d => d.ZIndex * -1).Select(d => new ListDeviceViewModel(d, this)));
List<ArtemisDevice> shuffledDevices = _rgbService.EnabledDevices.OrderBy(d => Guid.NewGuid()).ToList();
float amount = 360f / shuffledDevices.Count;
for (int i = 0; i < shuffledDevices.Count; i++)
{
ArtemisDevice rgbServiceDevice = shuffledDevices[i];
ListDeviceViewModel vm = ListDeviceViewModels.First(l => l.Device == rgbServiceDevice);
vm.Color = SKColor.FromHsv(amount * i, 100, 100);
}
_coreService.FrameRendering += CoreServiceOnFrameRendering;
PanZoomViewModel.PropertyChanged += PanZoomViewModelOnPropertyChanged;
base.OnInitialActivate();
}
protected override void OnClose()
{
SaveWorkspaceSettings();
SurfaceDeviceViewModels.Clear();
ListDeviceViewModels.Clear();
_coreService.FrameRendering -= CoreServiceOnFrameRendering;
PanZoomViewModel.PropertyChanged -= PanZoomViewModelOnPropertyChanged;
base.OnClose();
}
private void PanZoomViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(PanZoomViewModel.Zoom))
NotifyOfPropertyChange(nameof(MaxTextureSizeIndicatorThickness));
}
#endregion
#region Context menu actions
public void IdentifyDevice(ArtemisDevice device)
{
_deviceService.IdentifyDevice(device);
}
public void BringToFront(ArtemisDevice device)
{
SurfaceDeviceViewModel surfaceDeviceViewModel = SurfaceDeviceViewModels.First(d => d.Device == device);
SurfaceDeviceViewModels.Move(SurfaceDeviceViewModels.IndexOf(surfaceDeviceViewModel), SurfaceDeviceViewModels.Count - 1);
for (int i = 0; i < SurfaceDeviceViewModels.Count; i++)
{
SurfaceDeviceViewModel deviceViewModel = SurfaceDeviceViewModels[i];
deviceViewModel.Device.ZIndex = i + 1;
}
ListDeviceViewModels.Sort(l => l.Device.ZIndex * -1);
_rgbService.SaveDevices();
}
public void BringForward(ArtemisDevice device)
{
SurfaceDeviceViewModel surfaceDeviceViewModel = SurfaceDeviceViewModels.First(d => d.Device == device);
int currentIndex = SurfaceDeviceViewModels.IndexOf(surfaceDeviceViewModel);
int newIndex = Math.Min(currentIndex + 1, SurfaceDeviceViewModels.Count - 1);
SurfaceDeviceViewModels.Move(currentIndex, newIndex);
for (int i = 0; i < SurfaceDeviceViewModels.Count; i++)
{
SurfaceDeviceViewModel deviceViewModel = SurfaceDeviceViewModels[i];
deviceViewModel.Device.ZIndex = i + 1;
}
ListDeviceViewModels.Sort(l => l.Device.ZIndex * -1);
_rgbService.SaveDevices();
}
public void SendToBack(ArtemisDevice device)
{
SurfaceDeviceViewModel surfaceDeviceViewModel = SurfaceDeviceViewModels.First(d => d.Device == device);
SurfaceDeviceViewModels.Move(SurfaceDeviceViewModels.IndexOf(surfaceDeviceViewModel), 0);
for (int i = 0; i < SurfaceDeviceViewModels.Count; i++)
{
SurfaceDeviceViewModel deviceViewModel = SurfaceDeviceViewModels[i];
deviceViewModel.Device.ZIndex = i + 1;
}
ListDeviceViewModels.Sort(l => l.Device.ZIndex * -1);
_rgbService.SaveDevices();
}
public void SendBackward(ArtemisDevice device)
{
SurfaceDeviceViewModel surfaceDeviceViewModel = SurfaceDeviceViewModels.First(d => d.Device == device);
int currentIndex = SurfaceDeviceViewModels.IndexOf(surfaceDeviceViewModel);
int newIndex = Math.Max(currentIndex - 1, 0);
SurfaceDeviceViewModels.Move(currentIndex, newIndex);
for (int i = 0; i < SurfaceDeviceViewModels.Count; i++)
{
SurfaceDeviceViewModel deviceViewModel = SurfaceDeviceViewModels[i];
deviceViewModel.Device.ZIndex = i + 1;
}
ListDeviceViewModels.Sort(l => l.Device.ZIndex * -1);
_rgbService.SaveDevices();
}
public void ViewProperties(ArtemisDevice device)
{
_windowManager.ShowWindow(_deviceDebugVmFactory.DeviceDialogViewModel(device));
}
public async Task DetectInput(ArtemisDevice device)
{
object madeChanges = await _dialogService.ShowDialog<SurfaceDeviceDetectInputViewModel>(new Dictionary<string, object> {{"device", device}});
if ((bool) madeChanges)
_rgbService.SaveDevice(device);
}
#endregion
#region Selection
private MouseDragStatus _mouseDragStatus;
private Point _mouseDragStartPoint;
private bool _colorDevices;
private bool _colorFirstLedOnly;
// ReSharper disable once UnusedMember.Global - Called from view
public void EditorGridMouseClick(object sender, MouseButtonEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
((IInputElement) sender).CaptureMouse();
else
((IInputElement) sender).ReleaseMouseCapture();
if (IsPanKeyDown() || e.ChangedButton == MouseButton.Right)
return;
Point position = e.GetPosition((IInputElement) sender);
Point relative = PanZoomViewModel.GetRelativeMousePosition(sender, e);
if (e.LeftButton == MouseButtonState.Pressed)
StartMouseDrag(sender, position, relative);
else
StopMouseDrag(position);
}
// ReSharper disable once UnusedMember.Global - Called from view
public void EditorGridMouseMove(object sender, MouseEventArgs e)
{
// If holding down Ctrl, pan instead of move/select
if (IsPanKeyDown())
{
Pan(sender, e);
return;
}
Point position = e.GetPosition((IInputElement) sender);
Point relative = PanZoomViewModel.GetRelativeMousePosition(sender, e);
if (_mouseDragStatus == MouseDragStatus.Dragging)
MoveSelected(relative);
else if (_mouseDragStatus == MouseDragStatus.Selecting)
UpdateSelection(position);
}
private void StartMouseDrag(object sender, Point position, Point relative)
{
// If drag started on top of a device, initialise dragging
RectangleGeometry selectedRect = new(new Rect(position, new Size(1, 1)));
SurfaceDeviceViewModel device = HitTestUtilities.GetHitViewModels<SurfaceDeviceViewModel>((Visual) sender, selectedRect).FirstOrDefault();
if (device != null)
{
_rgbService.SetRenderPaused(true);
_mouseDragStatus = MouseDragStatus.Dragging;
// If the device is not selected, deselect others and select only this one (if shift not held)
if (device.SelectionStatus != SelectionStatus.Selected)
{
if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
foreach (SurfaceDeviceViewModel others in SurfaceDeviceViewModels)
others.SelectionStatus = SelectionStatus.None;
device.SelectionStatus = SelectionStatus.Selected;
}
foreach (SurfaceDeviceViewModel selectedDevice in SurfaceDeviceViewModels.Where(d => d.SelectionStatus == SelectionStatus.Selected))
selectedDevice.StartMouseDrag(relative);
}
// Start multi-selection
else
{
_mouseDragStatus = MouseDragStatus.Selecting;
_mouseDragStartPoint = position;
}
// Any time dragging starts, start with a new rect
SelectionRectangle.Rect = new Rect();
ApplySurfaceSelection();
}
private void StopMouseDrag(Point position)
{
if (_mouseDragStatus != MouseDragStatus.Dragging)
{
SKRect hitTestRect = PanZoomViewModel.UnTransformContainingRect(new Rect(_mouseDragStartPoint, position)).ToSKRect();
foreach (SurfaceDeviceViewModel device in SurfaceDeviceViewModels)
{
if (device.Device.Rectangle.IntersectsWith(hitTestRect))
device.SelectionStatus = SelectionStatus.Selected;
else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
device.SelectionStatus = SelectionStatus.None;
}
}
else
{
_rgbService.SaveDevices();
}
_mouseDragStatus = MouseDragStatus.None;
_rgbService.SetRenderPaused(false);
ApplySurfaceSelection();
}
private void UpdateSelection(Point position)
{
if (IsPanKeyDown())
return;
Rect selectedRect = new(_mouseDragStartPoint, position);
SelectionRectangle.Rect = selectedRect;
SKRect hitTestRect = PanZoomViewModel.UnTransformContainingRect(new Rect(_mouseDragStartPoint, position)).ToSKRect();
foreach (SurfaceDeviceViewModel device in SurfaceDeviceViewModels)
{
if (device.Device.Rectangle.IntersectsWith(hitTestRect))
device.SelectionStatus = SelectionStatus.Selected;
else if (!Keyboard.IsKeyDown(Key.LeftShift) && !Keyboard.IsKeyDown(Key.RightShift))
device.SelectionStatus = SelectionStatus.None;
}
ApplySurfaceSelection();
}
private void MoveSelected(Point position)
{
foreach (SurfaceDeviceViewModel device in SurfaceDeviceViewModels.Where(d => d.SelectionStatus == SelectionStatus.Selected))
device.UpdateMouseDrag(position);
}
private void ApplySurfaceSelection()
{
foreach (ListDeviceViewModel viewModel in ListDeviceViewModels)
viewModel.IsSelected = SurfaceDeviceViewModels.Any(s => s.Device == viewModel.Device && s.SelectionStatus == SelectionStatus.Selected);
}
#endregion
#region Panning and zooming
public void EditorGridMouseWheel(object sender, MouseWheelEventArgs e)
{
PanZoomViewModel.ProcessMouseScroll(sender, e);
}
public void EditorGridKeyDown(object sender, KeyEventArgs e)
{
if ((e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl) && e.IsDown)
Cursor = Cursors.ScrollAll;
}
public void EditorGridKeyUp(object sender, KeyEventArgs e)
{
if ((e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl) && e.IsUp)
Cursor = null;
}
public void Pan(object sender, MouseEventArgs e)
{
PanZoomViewModel.ProcessMouseMove(sender, e);
// Empty the selection rect since it's shown while mouse is down
SelectionRectangle.Rect = Rect.Empty;
}
public void ResetZoomAndPan()
{
PanZoomViewModel.Reset();
}
private bool IsPanKeyDown()
{
return Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
}
#endregion
}
internal enum MouseDragStatus
{
None,
Selecting,
Dragging
}
}