From 220222d102f6fdc74c35916412ce68b36f814165 Mon Sep 17 00:00:00 2001 From: SpoinkyNL Date: Sun, 6 Dec 2020 23:40:53 +0100 Subject: [PATCH] Surface editor - Added auto-arrange --- .../Storage/Interfaces/ISurfaceService.cs | 5 + .../Storage/Models/SurfaceArrangement.cs | 103 +++++++++++++++++- .../Models/SurfaceArrangementConfiguration.cs | 99 +++++++++++++++++ .../Models/SurfaceArrangementDevice.cs | 43 -------- .../Storage/Models/SurfaceArrangementType.cs | 95 ++++++++++++++++ .../Services/Storage/SurfaceService.cs | 11 +- .../SurfaceEditor/SurfaceEditorView.xaml | 35 +++--- .../SurfaceEditor/SurfaceEditorViewModel.cs | 10 ++ 8 files changed, 333 insertions(+), 68 deletions(-) create mode 100644 src/Artemis.Core/Services/Storage/Models/SurfaceArrangementConfiguration.cs delete mode 100644 src/Artemis.Core/Services/Storage/Models/SurfaceArrangementDevice.cs create mode 100644 src/Artemis.Core/Services/Storage/Models/SurfaceArrangementType.cs diff --git a/src/Artemis.Core/Services/Storage/Interfaces/ISurfaceService.cs b/src/Artemis.Core/Services/Storage/Interfaces/ISurfaceService.cs index 8258b3e19..23a6c87bf 100644 --- a/src/Artemis.Core/Services/Storage/Interfaces/ISurfaceService.cs +++ b/src/Artemis.Core/Services/Storage/Interfaces/ISurfaceService.cs @@ -44,6 +44,11 @@ namespace Artemis.Core.Services /// The surface entity to delete, may not be the active surface entity void DeleteSurfaceConfiguration(ArtemisSurface surface); + /// + /// Applies auto-arranging logic to the current active surface + /// + void AutoArrange(); + /// /// Occurs when the active device entity has been changed /// diff --git a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangement.cs b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangement.cs index 464a78519..3f7d726ac 100644 --- a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangement.cs +++ b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangement.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using RGB.NET.Core; namespace Artemis.Core.Services.Models @@ -7,18 +8,108 @@ namespace Artemis.Core.Services.Models { public SurfaceArrangement() { - Devices = new List(); + Types = new List(); } - public List Devices { get; } + public List Types { get; } internal static SurfaceArrangement GetDefaultArrangement() { SurfaceArrangement arrangement = new SurfaceArrangement(); - arrangement.Devices.Add(new SurfaceArrangementDevice(null, RGBDeviceType.Keyboard, ArrangementPosition.Right)); + + SurfaceArrangementType keypad = new SurfaceArrangementType(RGBDeviceType.Keypad); + keypad.Configurations.Add(new SurfaceArrangementConfiguration(null, HorizontalArrangementPosition.Equal, VerticalArrangementPosition.Equal, 20)); + arrangement.Types.Add(keypad); + + SurfaceArrangementType keyboard = new SurfaceArrangementType(RGBDeviceType.Keyboard); + keyboard.Configurations.Add(new SurfaceArrangementConfiguration(keypad, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 20)); + arrangement.Types.Add(keyboard); + + SurfaceArrangementType mousepad = new SurfaceArrangementType(RGBDeviceType.Mousepad); + mousepad.Configurations.Add(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 10)); + arrangement.Types.Add(mousepad); + + SurfaceArrangementType mouse = new SurfaceArrangementType(RGBDeviceType.Mouse); + mouse.Configurations.Add(new SurfaceArrangementConfiguration(mousepad, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Center, 0)); + mouse.Configurations.Add(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Center, 100)); + arrangement.Types.Add(mouse); + + SurfaceArrangementType headset = new SurfaceArrangementType(RGBDeviceType.Headset); + headset.Configurations.Add(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Bottom, 100)); + arrangement.Types.Add(headset); + + SurfaceArrangementType headsetStand = new SurfaceArrangementType(RGBDeviceType.HeadsetStand); + headsetStand.Configurations.Add(new SurfaceArrangementConfiguration(mousepad, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Top, 100)); + headsetStand.Configurations.Add(new SurfaceArrangementConfiguration(mouse, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Top, 100)); + headsetStand.Configurations.Add(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Top, 100)); + arrangement.Types.Add(headsetStand); + + SurfaceArrangementType mainboard = new SurfaceArrangementType(RGBDeviceType.Mainboard); + mainboard.Configurations.Add(new SurfaceArrangementConfiguration(mousepad, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Bottom, 500)); + mainboard.Configurations.Add(new SurfaceArrangementConfiguration(mouse, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Bottom, 500)); + mainboard.Configurations.Add(new SurfaceArrangementConfiguration(keyboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Bottom, 500)); + arrangement.Types.Add(mainboard); + + SurfaceArrangementType cooler = new SurfaceArrangementType(RGBDeviceType.Cooler); + cooler.Configurations.Add(new SurfaceArrangementConfiguration(mainboard, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Center, 0)); + arrangement.Types.Add(cooler); + + SurfaceArrangementType dram = new SurfaceArrangementType(RGBDeviceType.DRAM); + dram.Configurations.Add(new SurfaceArrangementConfiguration(cooler, HorizontalArrangementPosition.Left, VerticalArrangementPosition.Equal, 10)); + dram.Configurations.Add(new SurfaceArrangementConfiguration(mainboard, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Center, 0)); + arrangement.Types.Add(dram); + + SurfaceArrangementType graphicsCard = new SurfaceArrangementType(RGBDeviceType.GraphicsCard); + graphicsCard.Configurations.Add(new SurfaceArrangementConfiguration(cooler, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Bottom, 10)); + graphicsCard.Configurations.Add(new SurfaceArrangementConfiguration(dram, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Bottom, 10)); + graphicsCard.Configurations.Add(new SurfaceArrangementConfiguration(mainboard, HorizontalArrangementPosition.Center, VerticalArrangementPosition.Center, 0)); + arrangement.Types.Add(graphicsCard); + + SurfaceArrangementType fan = new SurfaceArrangementType(RGBDeviceType.Fan); + fan.Configurations.Add(new SurfaceArrangementConfiguration(mainboard, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 100)); + arrangement.Types.Add(fan); + + SurfaceArrangementType ledStripe = new SurfaceArrangementType(RGBDeviceType.LedStripe); + ledStripe.Configurations.Add(new SurfaceArrangementConfiguration(fan, HorizontalArrangementPosition.Right, VerticalArrangementPosition.Equal, 100)); + arrangement.Types.Add(ledStripe); + + SurfaceArrangementType speaker = new SurfaceArrangementType(RGBDeviceType.Speaker); + arrangement.Types.Add(speaker); + return arrangement; } + + public void Arrange(ArtemisSurface surface) + { + foreach (ArtemisDevice surfaceDevice in surface.Devices) + { + surfaceDevice.X = 0; + surfaceDevice.X = 0; + surfaceDevice.ApplyToRgbDevice(); + } + + foreach (SurfaceArrangementType surfaceArrangementType in Types) + surfaceArrangementType.Arrange(surface); + + // See if we need to move the surface to keep X and Y values positive + double x = surface.Devices.Min(d => d.RgbDevice.Location.X); + double y = surface.Devices.Min(d => d.RgbDevice.Location.Y); + if (x < 0) + { + foreach (ArtemisDevice surfaceDevice in surface.Devices) + { + surfaceDevice.X += x * -1; + surfaceDevice.ApplyToRgbDevice(); + } + } + if (y < 0) + { + foreach (ArtemisDevice surfaceDevice in surface.Devices) + { + surfaceDevice.Y += y * -1; + surfaceDevice.ApplyToRgbDevice(); + } + } + } } - - -} +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementConfiguration.cs b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementConfiguration.cs new file mode 100644 index 000000000..c35a0cce1 --- /dev/null +++ b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementConfiguration.cs @@ -0,0 +1,99 @@ +using System.Collections.Generic; +using System.Linq; +using RGB.NET.Core; + +namespace Artemis.Core.Services.Models +{ + internal class SurfaceArrangementConfiguration + { + public SurfaceArrangementConfiguration(SurfaceArrangementType? anchor, HorizontalArrangementPosition horizontalPosition, VerticalArrangementPosition verticalPosition, + int margin) + { + Anchor = anchor; + HorizontalPosition = horizontalPosition; + VerticalPosition = verticalPosition; + + MarginLeft = margin; + MarginTop = margin; + MarginRight = margin; + MarginBottom = margin; + } + + public SurfaceArrangementConfiguration(SurfaceArrangementType? anchor, HorizontalArrangementPosition horizontalPosition, VerticalArrangementPosition verticalPosition, + int marginLeft, int marginTop, int marginRight, int marginBottom) + { + Anchor = anchor; + HorizontalPosition = horizontalPosition; + VerticalPosition = verticalPosition; + + MarginLeft = marginLeft; + MarginTop = marginTop; + MarginRight = marginRight; + MarginBottom = marginBottom; + } + + public SurfaceArrangementType? Anchor { get; } + public HorizontalArrangementPosition HorizontalPosition { get; } + public VerticalArrangementPosition VerticalPosition { get; } + + public int MarginLeft { get; } + public int MarginTop { get; } + public int MarginRight { get; } + public int MarginBottom { get; } + + public bool Apply(List devices, ArtemisSurface surface) + { + if (Anchor != null && !Anchor.HasDevices(surface)) + return false; + + // Start at the edge of the anchor, if there is no anchor start at any device + Point startPoint = Anchor?.GetEdge(HorizontalPosition, VerticalPosition, surface) ?? + new SurfaceArrangementType(RGBDeviceType.All).GetEdge(HorizontalPosition, VerticalPosition, surface); + + // Stack multiple devices of the same type vertically if they are wider than they are tall + bool stackVertically = devices.Average(d => d.RgbDevice.Size.Width) >= devices.Average(d => d.RgbDevice.Size.Height); + + ArtemisDevice? previous = null; + foreach (ArtemisDevice artemisDevice in devices) + { + if (previous != null) + { + if (stackVertically) + { + artemisDevice.X = previous.X; + artemisDevice.Y = previous.RgbDevice.Location.Y + previous.RgbDevice.Size.Height + MarginTop / 2.0; + } + else + { + artemisDevice.X = previous.RgbDevice.Location.X + previous.RgbDevice.Size.Width + MarginLeft / 2.0; + artemisDevice.Y = previous.Y; + } + } + else + { + artemisDevice.X = HorizontalPosition switch + { + HorizontalArrangementPosition.Left => startPoint.X - artemisDevice.RgbDevice.Size.Width - MarginRight, + HorizontalArrangementPosition.Right => startPoint.X + MarginLeft, + HorizontalArrangementPosition.Center => startPoint.X - artemisDevice.RgbDevice.Size.Width / 2, + HorizontalArrangementPosition.Equal => startPoint.X, + _ => artemisDevice.X + }; + artemisDevice.Y = VerticalPosition switch + { + VerticalArrangementPosition.Top => startPoint.Y - artemisDevice.RgbDevice.Size.Height - MarginBottom, + VerticalArrangementPosition.Bottom => startPoint.Y + MarginTop, + VerticalArrangementPosition.Center => startPoint.Y - artemisDevice.RgbDevice.Size.Height / 2, + VerticalArrangementPosition.Equal => startPoint.Y, + _ => artemisDevice.X + }; + } + + artemisDevice.ApplyToRgbDevice(); + previous = artemisDevice; + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementDevice.cs b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementDevice.cs deleted file mode 100644 index 945f03754..000000000 --- a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementDevice.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using RGB.NET.Core; - -namespace Artemis.Core.Services.Models -{ - internal class SurfaceArrangementDevice - { - public SurfaceArrangementDevice? Anchor { get; } - public RGBDeviceType DeviceType { get; } - public ArrangementPosition Position { get; } - - public SurfaceArrangementDevice(SurfaceArrangementDevice? anchor, RGBDeviceType deviceType, ArrangementPosition position) - { - Anchor = anchor; - DeviceType = deviceType; - Position = position; - } - - public void Apply(ArtemisSurface surface) - { - List devices = surface.Devices.Where(d => d.RgbDevice.DeviceInfo.DeviceType == DeviceType).ToList(); - ArtemisDevice? previous = null; - foreach (ArtemisDevice artemisDevice in devices) - { - if (previous != null) - { - - } - previous = artemisDevice; - } - } - } - - internal enum ArrangementPosition - { - Left, - Right, - Top, - Bottom, - Center - } -} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementType.cs b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementType.cs new file mode 100644 index 000000000..47d92fac9 --- /dev/null +++ b/src/Artemis.Core/Services/Storage/Models/SurfaceArrangementType.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using RGB.NET.Core; + +namespace Artemis.Core.Services.Models +{ + internal class SurfaceArrangementType + { + public SurfaceArrangementType(RGBDeviceType deviceType) + { + DeviceType = deviceType; + Configurations = new List(); + } + + public RGBDeviceType DeviceType { get; } + public List Configurations { get; } + public SurfaceArrangementConfiguration? AppliedConfiguration { get; set; } + + public bool HasDevices(ArtemisSurface surface) + { + return surface.Devices.Any(d => d.RgbDevice.DeviceInfo.DeviceType == DeviceType); + } + + public void Arrange(ArtemisSurface surface) + { + List devices = surface.Devices.Where(d => d.RgbDevice.DeviceInfo.DeviceType == DeviceType).ToList(); + if (!devices.Any()) + return; + + AppliedConfiguration = null; + foreach (SurfaceArrangementConfiguration configuration in Configurations) + { + bool applied = configuration.Apply(devices, surface); + if (applied) + { + AppliedConfiguration = configuration; + return; + } + } + + // If nothing applied fall back to just basing on whatever is the furthers to the right + SurfaceArrangementConfiguration fallback = new SurfaceArrangementConfiguration( + null, + HorizontalArrangementPosition.Right, + VerticalArrangementPosition.Equal, + 10 + ); + fallback.Apply(devices, surface); + AppliedConfiguration = fallback; + } + + public Point GetEdge(HorizontalArrangementPosition horizontalPosition, VerticalArrangementPosition verticalPosition, ArtemisSurface surface) + { + List devices = surface.Devices.Where(d => d.RgbDevice.DeviceInfo.DeviceType == DeviceType || DeviceType == RGBDeviceType.All).ToList(); + if (!devices.Any()) + return new Point(); + + double x = horizontalPosition switch + { + HorizontalArrangementPosition.Left => devices.Min(d => d.RgbDevice.Location.X) - (AppliedConfiguration?.MarginLeft ?? 0.0), + HorizontalArrangementPosition.Right => devices.Max(d => d.RgbDevice.Location.X + d.RgbDevice.Size.Width) + (AppliedConfiguration?.MarginRight ?? 0.0), + HorizontalArrangementPosition.Center => devices.First().RgbDevice.DeviceRectangle.Center.X, + HorizontalArrangementPosition.Equal => devices.First().RgbDevice.Location.X, + _ => throw new ArgumentOutOfRangeException(nameof(horizontalPosition), horizontalPosition, null) + }; + double y = verticalPosition switch + { + VerticalArrangementPosition.Top => devices.Min(d => d.RgbDevice.Location.Y) - (AppliedConfiguration?.MarginTop ?? 0.0), + VerticalArrangementPosition.Bottom => devices.Max(d => d.RgbDevice.Location.Y + d.RgbDevice.Size.Height) + (AppliedConfiguration?.MarginBottom ?? 0.0), + VerticalArrangementPosition.Center => devices.First().RgbDevice.DeviceRectangle.Center.Y, + VerticalArrangementPosition.Equal => devices.First().RgbDevice.Location.Y, + _ => throw new ArgumentOutOfRangeException(nameof(verticalPosition), verticalPosition, null) + }; + + return new Point(x, y); + } + } + + internal enum HorizontalArrangementPosition + { + Left, + Right, + Equal, + Center + } + + internal enum VerticalArrangementPosition + { + Top, + Bottom, + Equal, + Center + } +} \ No newline at end of file diff --git a/src/Artemis.Core/Services/Storage/SurfaceService.cs b/src/Artemis.Core/Services/Storage/SurfaceService.cs index efccae435..21eab6998 100644 --- a/src/Artemis.Core/Services/Storage/SurfaceService.cs +++ b/src/Artemis.Core/Services/Storage/SurfaceService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Artemis.Core.DeviceProviders; +using Artemis.Core.Services.Models; using Artemis.Storage.Entities.Surface; using Artemis.Storage.Repositories.Interfaces; using RGB.NET.Core; @@ -202,13 +203,11 @@ namespace Artemis.Core.Services #region AutoLayout - public void AutoLayout() + public void AutoArrange() { - // Phase one, bottom layer - // Keyboard - - // Phase two, top layer - + SurfaceArrangement surfaceArrangement = SurfaceArrangement.GetDefaultArrangement(); + surfaceArrangement.Arrange(ActiveSurface); + UpdateSurfaceConfiguration(ActiveSurface, true); } #endregion diff --git a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.xaml b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.xaml index 233224940..dea9900c5 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.xaml +++ b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorView.xaml @@ -178,23 +178,32 @@ - - - + + + + + diff --git a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs index 2d0d4d2f1..ab3098cab 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs +++ b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs @@ -114,6 +114,16 @@ namespace Artemis.UI.Screens.SurfaceEditor return config; } + 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; + + _surfaceService.AutoArrange(); + + } + private void LoadWorkspaceSettings() { SurfaceListWidth = _settingsService.GetSetting("SurfaceEditor.SurfaceListWidth", new GridLength(300.0));