mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
467 lines
18 KiB
C#
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
|
|
}
|
|
} |