diff --git a/src/Artemis.Core/Models/Surface/SurfaceConfiguration.cs b/src/Artemis.Core/Models/Surface/SurfaceConfiguration.cs index b59e54983..5bf1675d6 100644 --- a/src/Artemis.Core/Models/Surface/SurfaceConfiguration.cs +++ b/src/Artemis.Core/Models/Surface/SurfaceConfiguration.cs @@ -7,7 +7,7 @@ namespace Artemis.Core.Models.Surface { internal SurfaceConfiguration(string name) { - SurfaceEntity = new SurfaceEntity(); + SurfaceEntity = new SurfaceEntity {SurfacePositions = new List()}; Guid = System.Guid.NewGuid().ToString(); Name = name; diff --git a/src/Artemis.Core/Models/Surface/SurfaceDeviceConfiguration.cs b/src/Artemis.Core/Models/Surface/SurfaceDeviceConfiguration.cs index 0f35084c3..93a743718 100644 --- a/src/Artemis.Core/Models/Surface/SurfaceDeviceConfiguration.cs +++ b/src/Artemis.Core/Models/Surface/SurfaceDeviceConfiguration.cs @@ -48,7 +48,7 @@ namespace Artemis.Core.Models.Surface internal SurfacePositionEntity PositionEntity { get; set; } internal string Guid { get; } - public IRGBDevice Device { get; private set; } + public IRGBDevice Device { get; internal set; } public int DeviceId { get; } public string DeviceName { get; } public string DeviceModel { get; } @@ -66,7 +66,10 @@ namespace Artemis.Core.Models.Surface /// public void ApplyToDevice() { - Device.Location = new Point(X, Y); + if (Device != null) + { + Device.Location = new Point(X, Y); + } } /// diff --git a/src/Artemis.Core/Services/Storage/SurfaceService.cs b/src/Artemis.Core/Services/Storage/SurfaceService.cs index ea3d28453..a8d46663f 100644 --- a/src/Artemis.Core/Services/Storage/SurfaceService.cs +++ b/src/Artemis.Core/Services/Storage/SurfaceService.cs @@ -41,18 +41,39 @@ namespace Artemis.Core.Services.Storage return; _activeSurfaceConfiguration = value; + lock (_surfaceConfigurations) + { + // Mark only the new value as active + foreach (var surfaceConfiguration in _surfaceConfigurations) + surfaceConfiguration.IsActive = false; + _activeSurfaceConfiguration.IsActive = true; - // Mark only the new value as active - foreach (var surfaceConfiguration in _surfaceConfigurations) - surfaceConfiguration.IsActive = false; - _activeSurfaceConfiguration.IsActive = true; + SaveToRepository(_surfaceConfigurations, true); + } + + // Apply the active surface configuration to the devices + if (ActiveSurfaceConfiguration != null) + { + foreach (var deviceConfiguration in ActiveSurfaceConfiguration.DeviceConfigurations) + deviceConfiguration.ApplyToDevice(); + } + // Update the RGB service's graphics decorator to work with the new surface configuration + _rgbService.GraphicsDecorator.UpdateBitmap(); - SaveToRepository(_surfaceConfigurations, true); OnActiveSurfaceConfigurationChanged(new SurfaceConfigurationEventArgs(_activeSurfaceConfiguration)); } } - public ReadOnlyCollection SurfaceConfigurations => _surfaceConfigurations.AsReadOnly(); + public ReadOnlyCollection SurfaceConfigurations + { + get + { + lock (_surfaceConfigurations) + { + return _surfaceConfigurations.AsReadOnly(); + } + } + } public SurfaceConfiguration CreateSurfaceConfiguration(string name) { @@ -66,9 +87,12 @@ namespace Artemis.Core.Services.Storage configuration.DeviceConfigurations.Add(new SurfaceDeviceConfiguration(rgbDevice, deviceId, configuration)); } - _surfaceRepository.Add(configuration.SurfaceEntity); - SaveToRepository(configuration, true); - return configuration; + lock (_surfaceConfigurations) + { + _surfaceRepository.Add(configuration.SurfaceEntity); + SaveToRepository(configuration, true); + return configuration; + } } public void DeleteSurfaceConfiguration(SurfaceConfiguration surfaceConfiguration) @@ -76,20 +100,28 @@ namespace Artemis.Core.Services.Storage if (surfaceConfiguration == ActiveSurfaceConfiguration) throw new ArtemisCoreException($"Cannot delete surface configuration '{surfaceConfiguration.Name}' because it is active."); - surfaceConfiguration.Destroy(); - _surfaceConfigurations.Remove(surfaceConfiguration); + lock (_surfaceConfigurations) + { + surfaceConfiguration.Destroy(); + _surfaceConfigurations.Remove(surfaceConfiguration); - _surfaceRepository.Remove(surfaceConfiguration.SurfaceEntity); - _surfaceRepository.Save(); + _surfaceRepository.Remove(surfaceConfiguration.SurfaceEntity); + _surfaceRepository.Save(); + } } #region Event handlers private void RgbServiceOnDeviceLoaded(object sender, DeviceEventArgs e) { - // Match the newly loaded device with the current config - if (ActiveSurfaceConfiguration != null) - MatchDeviceConfiguration(e.Device, ActiveSurfaceConfiguration); + lock (_surfaceConfigurations) + { + foreach (var surfaceConfiguration in _surfaceConfigurations) + MatchDeviceConfiguration(e.Device, surfaceConfiguration); + } + + foreach (var deviceConfiguration in ActiveSurfaceConfiguration.DeviceConfigurations) + deviceConfiguration.ApplyToDevice(); } #endregion @@ -108,7 +140,10 @@ namespace Artemis.Core.Services.Storage foreach (var rgbDevice in devices) MatchDeviceConfiguration(rgbDevice, surfaceConfiguration); // Finally, add the surface config to the collection - _surfaceConfigurations.Add(surfaceConfiguration); + lock (_surfaceConfigurations) + { + _surfaceConfigurations.Add(surfaceConfiguration); + } } // When all surface configs are loaded, apply the active surface config @@ -163,6 +198,7 @@ namespace Artemis.Core.Services.Storage surfaceConfiguration.DeviceConfigurations.Add(deviceConfig); } + deviceConfig.Device = rgbDevice; deviceConfig.ApplyToDevice(); } diff --git a/src/Artemis.UI/ViewModels/Controls/SurfaceEditor/SurfaceDeviceViewModel.cs b/src/Artemis.UI/ViewModels/Controls/SurfaceEditor/SurfaceDeviceViewModel.cs index d86876553..b6bf054cb 100644 --- a/src/Artemis.UI/ViewModels/Controls/SurfaceEditor/SurfaceDeviceViewModel.cs +++ b/src/Artemis.UI/ViewModels/Controls/SurfaceEditor/SurfaceDeviceViewModel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Windows; using System.Windows.Input; +using Artemis.Core.Models.Surface; using RGB.NET.Core; using Stylet; using Point = System.Windows.Point; @@ -14,39 +15,56 @@ namespace Artemis.UI.ViewModels.Controls.SurfaceEditor private double _dragOffsetY; private readonly List _leds; - public SurfaceDeviceViewModel(IRGBDevice device) + public SurfaceDeviceViewModel(SurfaceDeviceConfiguration deviceConfiguration) { - Device = device; + DeviceConfiguration = deviceConfiguration; _leds = new List(); - foreach (var led in Device) - _leds.Add(new SurfaceLedViewModel(led)); + if (DeviceConfiguration.Device != null) + { + foreach (var led in DeviceConfiguration.Device) + _leds.Add(new SurfaceLedViewModel(led)); + } } - public IRGBDevice Device { get; } + public SurfaceDeviceConfiguration DeviceConfiguration { get; } public SelectionStatus SelectionStatus { get; set; } public Cursor Cursor { get; set; } - public int ZIndex { get; set; } + + public double X + { + get => DeviceConfiguration.X; + set => DeviceConfiguration.X = value; + } + + public double Y + { + get => DeviceConfiguration.Y; + set => DeviceConfiguration.Y = value; + } + + public int ZIndex + { + get => DeviceConfiguration.ZIndex; + set => DeviceConfiguration.ZIndex = value; + } public IReadOnlyCollection Leds => _leds.AsReadOnly(); - public Rect DeviceRectangle => new Rect(Device.Location.X, Device.Location.Y, Device.Size.Width, Device.Size.Height); + + public Rect DeviceRectangle => new Rect(X, Y, DeviceConfiguration.Device.Size.Width, DeviceConfiguration.Device.Size.Height); public void StartMouseDrag(Point mouseStartPosition) { - _dragOffsetX = Device.Location.X - mouseStartPosition.X; - _dragOffsetY = Device.Location.Y - mouseStartPosition.Y; + _dragOffsetX = X - mouseStartPosition.X; + _dragOffsetY = Y - mouseStartPosition.Y; } public void UpdateMouseDrag(Point mousePosition) { var roundedX = Math.Round((mousePosition.X + _dragOffsetX) / 10, 0, MidpointRounding.AwayFromZero) * 10; var roundedY = Math.Round((mousePosition.Y + _dragOffsetY) / 10, 0, MidpointRounding.AwayFromZero) * 10; - Device.Location = new RGB.NET.Core.Point(roundedX, roundedY); - } - - public void FinishMouseDrag(Point mouseEndPosition) - { - // TODO: Save and update + X = roundedX; + Y = roundedY; } // ReSharper disable once UnusedMember.Global - Called from view diff --git a/src/Artemis.UI/ViewModels/Controls/SurfaceEditor/SurfaceLedViewModel.cs b/src/Artemis.UI/ViewModels/Controls/SurfaceEditor/SurfaceLedViewModel.cs index 4afd874b2..3786304d1 100644 --- a/src/Artemis.UI/ViewModels/Controls/SurfaceEditor/SurfaceLedViewModel.cs +++ b/src/Artemis.UI/ViewModels/Controls/SurfaceEditor/SurfaceLedViewModel.cs @@ -1,5 +1,4 @@ -using System; -using RGB.NET.Core; +using RGB.NET.Core; using Stylet; namespace Artemis.UI.ViewModels.Controls.SurfaceEditor @@ -9,29 +8,18 @@ namespace Artemis.UI.ViewModels.Controls.SurfaceEditor public SurfaceLedViewModel(Led led) { Led = led; - Update(); + + X = Led.LedRectangle.X; + Y = Led.LedRectangle.Y; + Width = Led.LedRectangle.Width; + Height = Led.LedRectangle.Height; } public Led Led { get; } - public double X { get; private set; } - public double Y { get; private set; } - public double Width { get; private set; } - public double Height { get; private set; } - - public void Update() - { - if (Math.Abs(Led.LedRectangle.X - X) > 0.1) - X = Led.LedRectangle.X; - - if (Math.Abs(Led.LedRectangle.Y - Y) > 0.1) - Y = Led.LedRectangle.Y; - - if (Math.Abs(Led.LedRectangle.Width - Width) > 0.1) - Width = Led.LedRectangle.Width; - - if (Math.Abs(Led.LedRectangle.Height - Height) > 0.1) - Height = Led.LedRectangle.Height; - } + public double X { get; } + public double Y { get; } + public double Width { get; } + public double Height { get; } } } \ 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 0090e7a14..5f77630bd 100644 --- a/src/Artemis.UI/ViewModels/Screens/SurfaceEditorViewModel.cs +++ b/src/Artemis.UI/ViewModels/Screens/SurfaceEditorViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; @@ -27,64 +28,31 @@ namespace Artemis.UI.ViewModels.Screens SurfaceConfigurations = new ObservableCollection(); SelectionRectangle = new RectangleGeometry(); PanZoomViewModel = new PanZoomViewModel(); - + _rgbService = rgbService; _surfaceService = surfaceService; - _rgbService.DeviceLoaded += RgbServiceOnDeviceLoaded; - - foreach (var surfaceDevice in _rgbService.LoadedDevices) - { - var device = new SurfaceDeviceViewModel(surfaceDevice) {Cursor = Cursors.Hand}; - Devices.Add(device); - device.ZIndex = Devices.IndexOf(device) + 1; - } } - + public RectangleGeometry SelectionRectangle { get; set; } public ObservableCollection Devices { get; set; } - public SurfaceConfiguration SelectedSurfaceConfiguration { get; set; } + public SurfaceConfiguration SelectedSurfaceConfiguration + { + get => _selectedSurfaceConfiguration; + set + { + _selectedSurfaceConfiguration = value; + ApplySelectedSurfaceConfiguration(); + } + } + public ObservableCollection SurfaceConfigurations { get; set; } public string NewConfigurationName { get; set; } public PanZoomViewModel PanZoomViewModel { get; set; } - + public string Title => "Surface Editor"; - private void RgbServiceOnDeviceLoaded(object sender, DeviceEventArgs e) - { - Execute.OnUIThread(() => - { - if (Devices.All(d => d.Device != e.Device)) - { - var device = new SurfaceDeviceViewModel(e.Device) {Cursor = Cursors.Hand}; - Devices.Add(device); - device.ZIndex = Devices.IndexOf(device) + 1; - } - }); - } - - private async Task LoadSurfaceConfigurations() - { - await Execute.OnUIThreadAsync(async () => - { - SurfaceConfigurations.Clear(); - - // Get surface configs - var configs = await _surfaceService.GetSurfaceConfigurationsAsync(); - // Populate the UI collection - foreach (var surfaceConfiguration in configs) - SurfaceConfigurations.Add(surfaceConfiguration); - - // Select either the first active surface or the first available surface - SelectedSurfaceConfiguration = SurfaceConfigurations.FirstOrDefault(s => s.IsActive) ?? SurfaceConfigurations.FirstOrDefault(); - - // Create a default if there is none - if (SelectedSurfaceConfiguration == null) - SelectedSurfaceConfiguration = AddSurfaceConfiguration("Default"); - }); - } - public SurfaceConfiguration AddSurfaceConfiguration(string name) { var config = _surfaceService.CreateSurfaceConfiguration(name); @@ -92,6 +60,65 @@ namespace Artemis.UI.ViewModels.Screens return config; } + private void LoadSurfaceConfigurations() + { + // Get surface configs + var configs = _surfaceService.SurfaceConfigurations; + + // Get the active config, if empty, create a default config + var activeConfig = _surfaceService.ActiveSurfaceConfiguration; + if (activeConfig == null) + { + activeConfig = AddSurfaceConfiguration("Default"); + _surfaceService.ActiveSurfaceConfiguration = activeConfig; + } + + Execute.OnUIThread(() => + { + // Populate the UI collection + SurfaceConfigurations.Clear(); + foreach (var surfaceConfiguration in configs) + SurfaceConfigurations.Add(surfaceConfiguration); + + // Set the active config + SelectedSurfaceConfiguration = activeConfig; + }); + } + + private void ApplySelectedSurfaceConfiguration() + { + if (SelectedSurfaceConfiguration == null) + { + Execute.OnUIThread(Devices.Clear); + return; + } + + Task.Run(() => + { + // Create VMs for the new config outside the UI thread + var viewModels = SelectedSurfaceConfiguration.DeviceConfigurations.Select(c => new SurfaceDeviceViewModel(c)).ToList(); + // Commit the VMs to the view + Execute.OnUIThread(() => + { + Devices.Clear(); + foreach (var viewModel in viewModels.OrderBy(v => v.ZIndex)) + Devices.Add(viewModel); + }); + }); + } + + #region Overrides of Screen + + protected override void OnActivate() + { + LoadSurfaceConfigurations(); + base.OnActivate(); + } + + #endregion + + #region Configuration management + public void ConfirmationDialogClosing() { if (!string.IsNullOrWhiteSpace(NewConfigurationName)) @@ -103,14 +130,6 @@ namespace Artemis.UI.ViewModels.Screens NewConfigurationName = null; } - #region Overrides of Screen - - protected override void OnActivate() - { - Task.Run(LoadSurfaceConfigurations); - base.OnActivate(); - } - #endregion #region Context menu actions @@ -166,6 +185,7 @@ namespace Artemis.UI.ViewModels.Screens private MouseDragStatus _mouseDragStatus; private Point _mouseDragStartPoint; + private SurfaceConfiguration _selectedSurfaceConfiguration; // ReSharper disable once UnusedMember.Global - Called from view public void EditorGridMouseClick(object sender, MouseEventArgs e) @@ -251,6 +271,12 @@ namespace Artemis.UI.ViewModels.Screens device.SelectionStatus = SelectionStatus.None; } } + else + { + foreach (var device in Devices) + device.DeviceConfiguration.ApplyToDevice(); + _surfaceService.SaveToRepository(SelectedSurfaceConfiguration, true); + } Mouse.OverrideCursor = null; _mouseDragStatus = MouseDragStatus.None; @@ -281,7 +307,7 @@ namespace Artemis.UI.ViewModels.Screens foreach (var device in Devices.Where(d => d.SelectionStatus == SelectionStatus.Selected)) device.UpdateMouseDrag(position); } - + #endregion #region Panning and zooming diff --git a/src/Artemis.UI/Views/Controls/SurfaceEditor/SurfaceDeviceView.xaml b/src/Artemis.UI/Views/Controls/SurfaceEditor/SurfaceDeviceView.xaml index db32f95d5..21755f1a2 100644 --- a/src/Artemis.UI/Views/Controls/SurfaceEditor/SurfaceDeviceView.xaml +++ b/src/Artemis.UI/Views/Controls/SurfaceEditor/SurfaceDeviceView.xaml @@ -13,19 +13,19 @@ Cursor="{Binding Cursor}" MouseEnter="{s:Action MouseEnter}" MouseLeave="{s:Action MouseLeave}" - ToolTip="{Binding Device.DeviceInfo.DeviceName}"> + ToolTip="{Binding DeviceConfiguration.Device.DeviceInfo.DeviceName}"> - + - + Surface layout - + The surface is a digital representation of your LED setup. Set this up accurately and effects will seamlessly move from one device to the other. @@ -47,7 +48,8 @@ MouseDown="{s:Action EditorGridMouseClick}" MouseMove="{s:Action EditorGridMouseMove}"> - + @@ -87,7 +89,8 @@ - + @@ -99,14 +102,15 @@ - + @@ -163,7 +167,7 @@ Value="{Binding PanZoomViewModel.ZoomPercentage}" Style="{StaticResource MaterialDesignDiscreteSlider}" />