using System; using System.Collections.Generic; using System.Linq; using System.Windows; using System.Windows.Input; using Artemis.Core; using Artemis.Core.Services; using Artemis.UI.Events; using Artemis.UI.Ninject.Factories; using Artemis.UI.Screens.ProfileEditor.Visualization.Tools; using Artemis.UI.Screens.Shared; using Artemis.UI.Shared.Services; using SkiaSharp; using Stylet; namespace Artemis.UI.Screens.ProfileEditor.Visualization { public class ProfileViewModel : Conductor.Collection.AllActive, IProfileEditorPanelViewModel, IHandle { private readonly ICoreService _coreService; private readonly IProfileEditorService _profileEditorService; private readonly IProfileLayerVmFactory _profileLayerVmFactory; private readonly IRgbService _rgbService; private readonly ISettingsService _settingsService; private readonly IVisualizationToolVmFactory _visualizationToolVmFactory; private int _activeToolIndex; private VisualizationToolViewModel _activeToolViewModel; private bool _canApplyToLayer; private bool _canSelectEditTool; private BindableCollection _devices; private BindableCollection _highlightedLeds; private DateTime _lastUpdate; private PanZoomViewModel _panZoomViewModel; private Layer _previousSelectedLayer; private int _previousTool; public ProfileViewModel(IProfileEditorService profileEditorService, IRgbService rgbService, ICoreService coreService, ISettingsService settingsService, IEventAggregator eventAggregator, IVisualizationToolVmFactory visualizationToolVmFactory, IProfileLayerVmFactory profileLayerVmFactory) { _profileEditorService = profileEditorService; _rgbService = rgbService; _coreService = coreService; _settingsService = settingsService; _visualizationToolVmFactory = visualizationToolVmFactory; _profileLayerVmFactory = profileLayerVmFactory; eventAggregator.Subscribe(this); } public bool CanSelectEditTool { get => _canSelectEditTool; set => SetAndNotify(ref _canSelectEditTool, value); } public PanZoomViewModel PanZoomViewModel { get => _panZoomViewModel; set => SetAndNotify(ref _panZoomViewModel, value); } public BindableCollection Devices { get => _devices; set => SetAndNotify(ref _devices, value); } public BindableCollection HighlightedLeds { get => _highlightedLeds; set => SetAndNotify(ref _highlightedLeds, value); } public VisualizationToolViewModel ActiveToolViewModel { get => _activeToolViewModel; private set { // Remove the tool from the canvas if (_activeToolViewModel != null) lock (Items) { Items.Remove(_activeToolViewModel); NotifyOfPropertyChange(() => Items); } // Set the new tool SetAndNotify(ref _activeToolViewModel, value); // Add the new tool to the canvas if (_activeToolViewModel != null) lock (Items) { Items.Add(_activeToolViewModel); NotifyOfPropertyChange(() => Items); } } } public int ActiveToolIndex { get => _activeToolIndex; set { if (!SetAndNotify(ref _activeToolIndex, value)) return; ActivateToolByIndex(value); } } public bool CanApplyToLayer { get => _canApplyToLayer; set => SetAndNotify(ref _canApplyToLayer, value); } public bool SuspendedEditing => _profileEditorService.SuspendEditing; protected override void OnInitialActivate() { PanZoomViewModel = new PanZoomViewModel { LimitToZero = false, PanX = _settingsService.GetSetting("ProfileEditor.PanX", 0d).Value, PanY = _settingsService.GetSetting("ProfileEditor.PanY", 0d).Value, Zoom = _settingsService.GetSetting("ProfileEditor.Zoom", 0d).Value }; Devices = new BindableCollection(); HighlightedLeds = new BindableCollection(); ApplyDevices(); ActivateToolByIndex(0); ApplyActiveProfile(); _lastUpdate = DateTime.Now; _coreService.FrameRendered += OnFrameRendered; _rgbService.DeviceAdded += RgbServiceOnDevicesModified; _rgbService.DeviceRemoved += RgbServiceOnDevicesModified; _profileEditorService.SuspendEditingChanged += ProfileEditorServiceOnSuspendEditingChanged; _profileEditorService.SelectedProfileChanged += OnSelectedProfileChanged; _profileEditorService.SelectedProfileElementChanged += OnSelectedProfileElementChanged; _profileEditorService.SelectedProfileElementSaved += OnSelectedProfileElementSaved; base.OnInitialActivate(); } protected override void OnClose() { _coreService.FrameRendered -= OnFrameRendered; _rgbService.DeviceAdded -= RgbServiceOnDevicesModified; _rgbService.DeviceRemoved -= RgbServiceOnDevicesModified; _profileEditorService.SuspendEditingChanged -= ProfileEditorServiceOnSuspendEditingChanged; _profileEditorService.SelectedProfileChanged -= OnSelectedProfileChanged; _profileEditorService.SelectedProfileElementChanged -= OnSelectedProfileElementChanged; _profileEditorService.SelectedProfileElementSaved -= OnSelectedProfileElementSaved; if (_previousSelectedLayer != null) _previousSelectedLayer.LayerBrushUpdated -= SelectedLayerOnLayerBrushUpdated; _settingsService.GetSetting("ProfileEditor.PanX", 0d).Value = PanZoomViewModel.PanX; _settingsService.GetSetting("ProfileEditor.PanX", 0d).Save(); _settingsService.GetSetting("ProfileEditor.PanY", 0d).Value = PanZoomViewModel.PanY; _settingsService.GetSetting("ProfileEditor.PanY", 0d).Save(); _settingsService.GetSetting("ProfileEditor.Zoom", 0d).Value = PanZoomViewModel.Zoom; _settingsService.GetSetting("ProfileEditor.Zoom", 0d).Save(); base.OnClose(); } private void RgbServiceOnDevicesModified(object sender, DeviceEventArgs e) { ApplyDevices(); } private void ApplyActiveProfile() { List layerViewModels = Items.Where(vm => vm is ProfileLayerViewModel).Cast().ToList(); List layers = _profileEditorService.SelectedProfile?.GetAllLayers() ?? new List(); // Add new layers missing a VM foreach (Layer layer in layers) { if (layerViewModels.All(vm => vm.Layer != layer)) Items.Add(_profileLayerVmFactory.Create(layer, PanZoomViewModel)); } // Remove layers that no longer exist IEnumerable toRemove = layerViewModels.Where(vm => !layers.Contains(vm.Layer)); foreach (ProfileLayerViewModel profileLayerViewModel in toRemove) Items.Remove(profileLayerViewModel); } private void ApplyDevices() { Devices.Clear(); Devices.AddRange(_rgbService.EnabledDevices.Where(d => d.IsEnabled).OrderBy(d => d.ZIndex)); } private void UpdateLedsDimStatus() { HighlightedLeds.Clear(); if (_profileEditorService.SelectedProfileElement is Layer layer) HighlightedLeds.AddRange(layer.Leds); } private void UpdateCanSelectEditTool() { if (SuspendedEditing) { CanApplyToLayer = false; CanSelectEditTool = false; return; } if (_profileEditorService.SelectedProfileElement is Layer layer) { CanApplyToLayer = true; CanSelectEditTool = (layer.LayerBrush == null || layer.LayerBrush.SupportsTransformation) && layer.Leds.Any(); } else { CanApplyToLayer = false; CanSelectEditTool = false; } if (CanSelectEditTool == false && ActiveToolIndex == 1) ActivateToolByIndex(2); } #region Buttons private void ActivateToolByIndex(int value) { if (value == 1 && !CanSelectEditTool) return; switch (value) { case 0: ActiveToolViewModel = _visualizationToolVmFactory.ViewpointMoveToolViewModel(PanZoomViewModel); break; case 1: ActiveToolViewModel = _visualizationToolVmFactory.EditToolViewModel(PanZoomViewModel); break; case 2: ActiveToolViewModel = _visualizationToolVmFactory.SelectionToolViewModel(PanZoomViewModel); break; case 3: ActiveToolViewModel = _visualizationToolVmFactory.SelectionRemoveToolViewModel(PanZoomViewModel); break; } ActiveToolIndex = value; } public void ResetZoomAndPan() { if (!Devices.Any()) { PanZoomViewModel.Reset(); return; } // Create a rect surrounding all devices SKRect rect = new( Devices.Min(d => d.Rectangle.Left), Devices.Min(d => d.Rectangle.Top), Devices.Max(d => d.Rectangle.Right), Devices.Max(d => d.Rectangle.Bottom) ); PanZoomViewModel.Reset(rect); } #endregion #region Mouse public void CanvasMouseDown(object sender, MouseButtonEventArgs e) { ((IInputElement) sender).CaptureMouse(); ActiveToolViewModel?.MouseDown(sender, e); e.Handled = false; } public void CanvasMouseUp(object sender, MouseButtonEventArgs e) { ((IInputElement) sender).ReleaseMouseCapture(); ActiveToolViewModel?.MouseUp(sender, e); e.Handled = false; } public void CanvasMouseMove(object sender, MouseEventArgs e) { ActiveToolViewModel?.MouseMove(sender, e); } public void CanvasMouseWheel(object sender, MouseWheelEventArgs e) { PanZoomViewModel.ProcessMouseScroll(sender, e); ActiveToolViewModel?.MouseWheel(sender, e); } #endregion #region Event handlers private void OnFrameRendered(object sender, FrameRenderedEventArgs e) { TimeSpan delta = DateTime.Now - _lastUpdate; _lastUpdate = DateTime.Now; if (SuspendedEditing || !_settingsService.GetSetting("ProfileEditor.AlwaysApplyDataBindings", true).Value || _profileEditorService.SelectedProfile == null) return; foreach (IDataBindingRegistration dataBindingRegistration in _profileEditorService.SelectedProfile.GetAllFolders() .SelectMany(f => f.GetAllLayerProperties(), (f, p) => p) .SelectMany(p => p.GetAllDataBindingRegistrations())) dataBindingRegistration.GetDataBinding()?.UpdateWithDelta(delta); foreach (IDataBindingRegistration dataBindingRegistration in _profileEditorService.SelectedProfile.GetAllLayers() .SelectMany(f => f.GetAllLayerProperties(), (f, p) => p) .SelectMany(p => p.GetAllDataBindingRegistrations())) dataBindingRegistration.GetDataBinding()?.UpdateWithDelta(delta); // TODO: Only update when there are data bindings if (!_profileEditorService.Playing) _profileEditorService.UpdateProfilePreview(); } private void HighlightSelectedLayerOnSettingChanged(object sender, EventArgs e) { UpdateLedsDimStatus(); } private void OnSelectedProfileChanged(object sender, EventArgs e) { ApplyActiveProfile(); } private void OnSelectedProfileElementChanged(object sender, EventArgs e) { if (_previousSelectedLayer != null) { _previousSelectedLayer.LayerBrushUpdated -= SelectedLayerOnLayerBrushUpdated; _previousSelectedLayer.Transform.LayerPropertyOnCurrentValueSet -= TransformValueChanged; } if (_profileEditorService.SelectedProfileElement is Layer layer) { _previousSelectedLayer = layer; _previousSelectedLayer.LayerBrushUpdated += SelectedLayerOnLayerBrushUpdated; _previousSelectedLayer.Transform.LayerPropertyOnCurrentValueSet += TransformValueChanged; } else { _previousSelectedLayer = null; } ApplyActiveProfile(); UpdateLedsDimStatus(); UpdateCanSelectEditTool(); } private void SelectedLayerOnLayerBrushUpdated(object sender, EventArgs e) { UpdateCanSelectEditTool(); } private void TransformValueChanged(object sender, LayerPropertyEventArgs e) { if (ActiveToolIndex != 1) ActivateToolByIndex(1); } private void OnSelectedProfileElementSaved(object sender, EventArgs e) { ApplyActiveProfile(); UpdateLedsDimStatus(); if (_profileEditorService.SelectedProfileElement is Layer layer) { CanApplyToLayer = true; CanSelectEditTool = layer.Leds.Any(); } else { CanApplyToLayer = false; CanSelectEditTool = false; } if (CanSelectEditTool == false && ActiveToolIndex == 1) ActivateToolByIndex(2); } private void ProfileEditorServiceOnSuspendEditingChanged(object sender, EventArgs e) { NotifyOfPropertyChange(nameof(SuspendedEditing)); UpdateCanSelectEditTool(); } public void Handle(MainWindowKeyEvent message) { if (message.KeyDown) { if (ActiveToolIndex != 0) { _previousTool = ActiveToolIndex; if ((message.EventArgs.Key == Key.LeftCtrl || message.EventArgs.Key == Key.RightCtrl) && message.EventArgs.IsDown) ActivateToolByIndex(0); } ActiveToolViewModel?.KeyDown(message.EventArgs); // If T is pressed while Ctrl is down, that makes it Ctrl+T > swap to transformation tool on Ctrl release if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.LeftCtrl)) { if (message.EventArgs.Key == Key.T) _previousTool = 1; else if (message.EventArgs.Key == Key.Q) _previousTool = 2; else if (message.EventArgs.Key == Key.W) _previousTool = 3; } } else { if ((message.EventArgs.Key == Key.LeftCtrl || message.EventArgs.Key == Key.RightCtrl) && message.EventArgs.IsUp) ActivateToolByIndex(_previousTool); ActiveToolViewModel?.KeyUp(message.EventArgs); } } #endregion } }