From 212d13c1c807e3091970373d8fbe9643ce4cdd23 Mon Sep 17 00:00:00 2001 From: Robert Date: Sun, 21 Aug 2022 20:15:43 +0200 Subject: [PATCH 1/2] Device dialog - Recreate tabs on device reload --- .../Screens/Device/DevicePropertiesView.axaml | 23 ++++++--- .../Device/DevicePropertiesViewModel.cs | 50 +++++++++++++++---- .../SurfaceEditor/SurfaceEditorViewModel.cs | 32 +++++++++++- 3 files changed, 86 insertions(+), 19 deletions(-) diff --git a/src/Artemis.UI/Screens/Device/DevicePropertiesView.axaml b/src/Artemis.UI/Screens/Device/DevicePropertiesView.axaml index 4c1b080a6..4cc1f6a55 100644 --- a/src/Artemis.UI/Screens/Device/DevicePropertiesView.axaml +++ b/src/Artemis.UI/Screens/Device/DevicePropertiesView.axaml @@ -37,7 +37,7 @@ ShowColors="True" Margin="20" LedClicked="DeviceVisualizer_OnLedClicked" - Clicked="DeviceVisualizer_OnClicked"/> + Clicked="DeviceVisualizer_OnClicked" /> - - - - - - - + + + + + + + + + + Waiting for device... + + + + diff --git a/src/Artemis.UI/Screens/Device/DevicePropertiesViewModel.cs b/src/Artemis.UI/Screens/Device/DevicePropertiesViewModel.cs index b29985ac1..98ea28983 100644 --- a/src/Artemis.UI/Screens/Device/DevicePropertiesViewModel.cs +++ b/src/Artemis.UI/Screens/Device/DevicePropertiesViewModel.cs @@ -14,20 +14,22 @@ namespace Artemis.UI.Screens.Device; public class DevicePropertiesViewModel : DialogViewModelBase { - public DevicePropertiesViewModel(ArtemisDevice device, ICoreService coreService, IDeviceVmFactory deviceVmFactory) + private readonly IDeviceVmFactory _deviceVmFactory; + private ArtemisDevice _device; + + public DevicePropertiesViewModel(ArtemisDevice device, ICoreService coreService, IRgbService rgbService, IDeviceVmFactory deviceVmFactory) { - Device = device; + _deviceVmFactory = deviceVmFactory; + _device = device; + SelectedLeds = new ObservableCollection(); Tabs = new ObservableCollection(); - Tabs.Add(deviceVmFactory.DevicePropertiesTabViewModel(device)); - Tabs.Add(deviceVmFactory.DeviceInfoTabViewModel(device)); - if (Device.DeviceType == RGBDeviceType.Keyboard) - Tabs.Add(deviceVmFactory.InputMappingsTabViewModel(device, SelectedLeds)); - Tabs.Add(deviceVmFactory.DeviceLedsTabViewModel(device, SelectedLeds)); - + AddTabs(); this.WhenActivated(d => { + rgbService.DeviceAdded += RgbServiceOnDeviceAdded; + rgbService.DeviceRemoved += RgbServiceOnDeviceRemoved; coreService.FrameRendering += CoreServiceOnFrameRendering; Disposable.Create(() => coreService.FrameRendering -= CoreServiceOnFrameRendering).DisposeWith(d); }); @@ -35,12 +37,40 @@ public class DevicePropertiesViewModel : DialogViewModelBase ClearSelectedLeds = ReactiveCommand.Create(ExecuteClearSelectedLeds); } - public ArtemisDevice Device { get; } + private void RgbServiceOnDeviceAdded(object? sender, DeviceEventArgs e) + { + if (e.Device.Identifier != Device.Identifier || Device == e.Device) + return; + + Device = e.Device; + AddTabs(); + } + + private void RgbServiceOnDeviceRemoved(object? sender, DeviceEventArgs e) + { + Tabs.Clear(); + SelectedLeds.Clear(); + } + + public ArtemisDevice Device + { + get => _device; + set => RaiseAndSetIfChanged(ref _device, value); + } + public ObservableCollection SelectedLeds { get; } public ObservableCollection Tabs { get; } - public ReactiveCommand ClearSelectedLeds { get; } + private void AddTabs() + { + Tabs.Add(_deviceVmFactory.DevicePropertiesTabViewModel(Device)); + Tabs.Add(_deviceVmFactory.DeviceInfoTabViewModel(Device)); + if (Device.DeviceType == RGBDeviceType.Keyboard) + Tabs.Add(_deviceVmFactory.InputMappingsTabViewModel(Device, SelectedLeds)); + Tabs.Add(_deviceVmFactory.DeviceLedsTabViewModel(Device, SelectedLeds)); + } + private void ExecuteClearSelectedLeds() { SelectedLeds.Clear(); diff --git a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs index 23be61398..c642578a5 100644 --- a/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs +++ b/src/Artemis.UI/Screens/SurfaceEditor/SurfaceEditorViewModel.cs @@ -21,6 +21,7 @@ public class SurfaceEditorViewModel : MainScreenViewModel private readonly IDeviceService _deviceService; private readonly IDeviceVmFactory _deviceVmFactory; private readonly IRgbService _rgbService; + private readonly ISurfaceVmFactory _surfaceVmFactory; private readonly ISettingsService _settingsService; private readonly IWindowService _windowService; private bool _colorDevices; @@ -39,6 +40,7 @@ public class SurfaceEditorViewModel : MainScreenViewModel IDeviceService deviceService) : base(hostScreen, "surface-editor") { _rgbService = rgbService; + _surfaceVmFactory = surfaceVmFactory; _settingsService = settingsService; _deviceVmFactory = deviceVmFactory; _windowService = windowService; @@ -59,10 +61,38 @@ public class SurfaceEditorViewModel : MainScreenViewModel this.WhenActivated(d => { coreService.FrameRendering += CoreServiceOnFrameRendering; - Disposable.Create(() => coreService.FrameRendering -= CoreServiceOnFrameRendering).DisposeWith(d); + _rgbService.DeviceAdded += RgbServiceOnDeviceAdded; + _rgbService.DeviceRemoved += RgbServiceOnDeviceRemoved; + Disposable.Create(() => + { + coreService.FrameRendering -= CoreServiceOnFrameRendering; + _rgbService.DeviceAdded -= RgbServiceOnDeviceAdded; + _rgbService.DeviceRemoved -= RgbServiceOnDeviceRemoved; + }).DisposeWith(d); }); } + private void RgbServiceOnDeviceAdded(object? sender, DeviceEventArgs e) + { + if (!e.Device.IsEnabled) + return; + + SurfaceDeviceViewModels.Add(_surfaceVmFactory.SurfaceDeviceViewModel(e.Device, this)); + ListDeviceViewModels.Add(_surfaceVmFactory.ListDeviceViewModel(e.Device, this)); + SurfaceDeviceViewModels.Sort(l => l.Device.ZIndex * -1); + ListDeviceViewModels.Sort(l => l.Device.ZIndex * -1); + } + + private void RgbServiceOnDeviceRemoved(object? sender, DeviceEventArgs e) + { + SurfaceDeviceViewModel? surfaceVm = SurfaceDeviceViewModels.FirstOrDefault(vm => vm.Device == e.Device); + ListDeviceViewModel? listVm = ListDeviceViewModels.FirstOrDefault(vm => vm.Device == e.Device); + if (surfaceVm != null) + SurfaceDeviceViewModels.Remove(surfaceVm); + if (listVm != null) + ListDeviceViewModels.Remove(listVm); + } + public bool ColorDevices { get => _colorDevices; From 3efb97eedd3970f7a3e244ff2209fd60a4314395 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 22 Aug 2022 20:24:08 +0200 Subject: [PATCH 2/2] Color picker - Avoid overlapping colors --- .../Models/Profile/Colors/ColorGradient.cs | 3 + .../Profile/Colors/ColorGradientStop.cs | 32 +++++++++- .../LostFocusNumericUpDownBindingBehavior.cs | 63 +++++++++++++++++++ .../Styles/Controls/GradientPicker.axaml | 9 ++- 4 files changed, 104 insertions(+), 3 deletions(-) create mode 100644 src/Artemis.UI.Shared/Behaviors/LostFocusNumericUpDownBindingBehavior.cs diff --git a/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs b/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs index bd9fe7c7a..18c570c9a 100644 --- a/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs +++ b/src/Artemis.Core/Models/Profile/Colors/ColorGradient.cs @@ -51,6 +51,7 @@ public class ColorGradient : IList, IList, INotifyCollectionC { ColorGradientStop stop = new(colorGradientStop.Color, colorGradientStop.Position); stop.PropertyChanged += ItemOnPropertyChanged; + stop.ColorGradient = this; _stops.Add(stop); } } @@ -66,6 +67,7 @@ public class ColorGradient : IList, IList, INotifyCollectionC { ColorGradientStop stop = new(colorGradientStop.Color, colorGradientStop.Position); stop.PropertyChanged += ItemOnPropertyChanged; + stop.ColorGradient = this; _stops.Add(stop); } } @@ -465,6 +467,7 @@ public class ColorGradient : IList, IList, INotifyCollectionC public void Add(ColorGradientStop item) { _stops.Add(item); + item.ColorGradient = this; item.PropertyChanged += ItemOnPropertyChanged; OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _stops.IndexOf(item))); diff --git a/src/Artemis.Core/Models/Profile/Colors/ColorGradientStop.cs b/src/Artemis.Core/Models/Profile/Colors/ColorGradientStop.cs index 8ccddbf24..96af9f2b6 100644 --- a/src/Artemis.Core/Models/Profile/Colors/ColorGradientStop.cs +++ b/src/Artemis.Core/Models/Profile/Colors/ColorGradientStop.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using SkiaSharp; namespace Artemis.Core; @@ -35,9 +36,11 @@ public class ColorGradientStop : CorePropertyChanged public float Position { get => _position; - set => SetAndNotify(ref _position, value); + set => SetAndNotify(ref _position, GetUpdatedPosition(value)); } + internal ColorGradient? ColorGradient { get; set; } + #region Equality members /// @@ -64,5 +67,32 @@ public class ColorGradientStop : CorePropertyChanged return HashCode.Combine(_color, _position); } + /// + /// Gets the position of the given color stop in a safe manner that avoids overlap with other stops. + /// + /// The new position. + private float GetUpdatedPosition(float stopPosition) + { + if (ColorGradient == null) + return stopPosition; + // Find the first available spot going down + while (ColorGradient.Any(s => !ReferenceEquals(s, this) && Math.Abs(s.Position - stopPosition) < 0.001f)) + stopPosition -= 0.001f; + + // If we ran out of space, try going up + if (stopPosition < 0) + { + stopPosition = 0; + while (ColorGradient.Any(s => !ReferenceEquals(s, this) && Math.Abs(s.Position - stopPosition) < 0.001f)) + stopPosition += 0.001f; + } + + // If we ran out of space there too, movement isn't possible + if (stopPosition > 1) + stopPosition = Position; + + return stopPosition; + } + #endregion } \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Behaviors/LostFocusNumericUpDownBindingBehavior.cs b/src/Artemis.UI.Shared/Behaviors/LostFocusNumericUpDownBindingBehavior.cs new file mode 100644 index 000000000..3c73456a7 --- /dev/null +++ b/src/Artemis.UI.Shared/Behaviors/LostFocusNumericUpDownBindingBehavior.cs @@ -0,0 +1,63 @@ +using System; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Interactivity; +using Avalonia.Xaml.Interactivity; +using FluentAvalonia.UI.Controls; + +namespace Artemis.UI.Shared.Behaviors; + +/// +/// Represents a behavior that can be used to make a numeric up down only update it's binding on focus loss. +/// +public class LostFocusNumericUpDownBindingBehavior : Behavior +{ + /// + /// Gets or sets the value of the binding. + /// + public static readonly StyledProperty ValueProperty = AvaloniaProperty.Register( + nameof(Value), defaultBindingMode: BindingMode.TwoWay); + + static LostFocusNumericUpDownBindingBehavior() + { + ValueProperty.Changed.Subscribe(e => ((LostFocusNumericUpDownBindingBehavior) e.Sender).OnBindingValueChanged()); + } + + /// + /// Gets or sets the value of the binding. + /// + public double Value + { + get => GetValue(ValueProperty); + set => SetValue(ValueProperty, value); + } + + /// + protected override void OnAttached() + { + if (AssociatedObject != null) + AssociatedObject.LostFocus += OnLostFocus; + base.OnAttached(); + } + + /// + protected override void OnDetaching() + { + if (AssociatedObject != null) + AssociatedObject.LostFocus -= OnLostFocus; + base.OnDetaching(); + } + + private void OnLostFocus(object? sender, RoutedEventArgs e) + { + if (AssociatedObject != null) + Value = AssociatedObject.Value; + } + + private void OnBindingValueChanged() + { + if (AssociatedObject != null) + AssociatedObject.Value = Value; + } +} \ No newline at end of file diff --git a/src/Artemis.UI.Shared/Styles/Controls/GradientPicker.axaml b/src/Artemis.UI.Shared/Styles/Controls/GradientPicker.axaml index aa096987b..5d0a4e113 100644 --- a/src/Artemis.UI.Shared/Styles/Controls/GradientPicker.axaml +++ b/src/Artemis.UI.Shared/Styles/Controls/GradientPicker.axaml @@ -4,7 +4,8 @@ xmlns:core="clr-namespace:Artemis.Core;assembly=Artemis.Core" xmlns:fluent="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters" - xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker"> + xmlns:gradientPicker="clr-namespace:Artemis.UI.Shared.Controls.GradientPicker" + xmlns:behaviors="clr-namespace:Artemis.UI.Shared.Behaviors"> @@ -198,7 +199,11 @@ - + + + + +