From bc1b44069c4970d69782efb8ea4f6662808ff89a Mon Sep 17 00:00:00 2001 From: Robert Date: Sat, 5 Feb 2022 11:20:51 +0100 Subject: [PATCH] Profile editor - Added tools and layer visualizers --- src/Artemis.Core/Models/Profile/Layer.cs | 21 ++-- .../ApplicationStateManager.cs | 5 +- .../ProfileEditor/Commands/ChangeLayerLeds.cs | 45 ++++++++ .../ProfileEditor/IProfileEditorService.cs | 5 + .../Services/ProfileEditor/IToolViewModel.cs | 103 ++++++++++++++++++ .../ProfileEditor/ProfileEditorService.cs | 34 ++++-- .../Artemis.UI.Shared/ViewModelBase.cs | 26 +---- .../ApplicationStateManager.cs | 3 + .../Properties/launchSettings.json | 8 ++ src/Avalonia/Artemis.UI/Ninject/UIModule.cs | 10 ++ .../Tabs/DevicePropertiesTabViewModel.cs | 26 ++--- ...uginPrerequisitesInstallDialogViewModel.cs | 22 ++-- ...inPrerequisitesUninstallDialogViewModel.cs | 22 ++-- .../Screens/Plugins/PluginFeatureViewModel.cs | 40 ++++--- .../Plugins/PluginPrerequisiteViewModel.cs | 16 ++- .../Plugins/PluginSettingsViewModel.cs | 27 ++--- .../VisualEditor/Tools/IToolViewModel.cs | 6 - .../Tools/SelectionAddToolView.axaml | 18 +++ .../Tools/SelectionAddToolView.axaml.cs | 24 ++++ .../Tools/SelectionAddToolViewModel.cs | 68 ++++++++++++ .../Tools/SelectionRemoveToolView.axaml | 20 ++++ .../Tools/SelectionRemoveToolView.axaml.cs | 24 ++++ .../Tools/SelectionRemoveToolViewModel.cs | 64 +++++++++++ .../Tools/TransformToolView.axaml | 8 ++ .../Tools/TransformToolView.axaml.cs | 17 +++ .../Tools/TransformToolViewModel.cs | 45 ++++++++ .../VisualEditor/VisualEditorView.axaml | 2 +- .../VisualEditor/VisualEditorViewModel.cs | 16 ++- .../Visualizers/IVisualizerViewModel.cs | 4 +- .../LayerShapeVisualizerView.axaml | 3 +- .../LayerShapeVisualizerViewModel.cs | 67 +++++++++--- .../Visualizers/LayerVisualizerView.axaml | 2 +- .../Visualizers/LayerVisualizerViewModel.cs | 49 +++++++-- .../ProfileEditor/ProfileEditorView.axaml | 39 ++++--- .../ProfileEditor/ProfileEditorViewModel.cs | 27 ++++- .../Services/RegistrationService.cs | 85 ++++++++------- 36 files changed, 776 insertions(+), 225 deletions(-) create mode 100644 src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/ChangeLayerLeds.cs create mode 100644 src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/IToolViewModel.cs create mode 100644 src/Avalonia/Artemis.UI.Windows/Properties/launchSettings.json delete mode 100644 src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/IToolViewModel.cs create mode 100644 src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionAddToolView.axaml create mode 100644 src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionAddToolView.axaml.cs create mode 100644 src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionAddToolViewModel.cs create mode 100644 src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolView.axaml create mode 100644 src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolView.axaml.cs create mode 100644 src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolViewModel.cs create mode 100644 src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolView.axaml create mode 100644 src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolView.axaml.cs create mode 100644 src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolViewModel.cs diff --git a/src/Artemis.Core/Models/Profile/Layer.cs b/src/Artemis.Core/Models/Profile/Layer.cs index 8b87d2fc5..3512496f3 100644 --- a/src/Artemis.Core/Models/Profile/Layer.cs +++ b/src/Artemis.Core/Models/Profile/Layer.cs @@ -565,23 +565,24 @@ namespace Artemis.Core OnRenderPropertiesUpdated(); } - internal SKPoint GetLayerAnchorPosition(bool applyTranslation, bool zeroBased) + internal SKPoint GetLayerAnchorPosition(bool applyTranslation, bool zeroBased, SKRect? customBounds = null) { if (Disposed) throw new ObjectDisposedException("Layer"); + SKRect bounds = customBounds ?? Bounds; SKPoint positionProperty = Transform.Position.CurrentValue; // Start at the center of the shape SKPoint position = zeroBased - ? new SKPointI(Bounds.MidX - Bounds.Left, Bounds.MidY - Bounds.Top) - : new SKPointI(Bounds.MidX, Bounds.MidY); + ? new SKPoint(bounds.MidX - bounds.Left, bounds.MidY - Bounds.Top) + : new SKPoint(bounds.MidX, bounds.MidY); // Apply translation if (applyTranslation) { - position.X += positionProperty.X * Bounds.Width; - position.Y += positionProperty.Y * Bounds.Height; + position.X += positionProperty.X * bounds.Width; + position.Y += positionProperty.Y * bounds.Height; } return position; @@ -625,8 +626,9 @@ namespace Artemis.Core /// Whether translation should be included /// Whether the scale should be included /// Whether the rotation should be included + /// Optional custom bounds to base the anchor on /// The transformation matrix containing the current transformation settings - public SKMatrix GetTransformMatrix(bool zeroBased, bool includeTranslation, bool includeScale, bool includeRotation) + public SKMatrix GetTransformMatrix(bool zeroBased, bool includeTranslation, bool includeScale, bool includeRotation, SKRect? customBounds = null) { if (Disposed) throw new ObjectDisposedException("Layer"); @@ -634,15 +636,16 @@ namespace Artemis.Core if (Path == null) return SKMatrix.Empty; + SKRect bounds = customBounds ?? Bounds; SKSize sizeProperty = Transform.Scale.CurrentValue; float rotationProperty = Transform.Rotation.CurrentValue; - SKPoint anchorPosition = GetLayerAnchorPosition(true, zeroBased); + SKPoint anchorPosition = GetLayerAnchorPosition(true, zeroBased, bounds); SKPoint anchorProperty = Transform.AnchorPoint.CurrentValue; // Translation originates from the unscaled center of the shape and is tied to the anchor - float x = anchorPosition.X - (zeroBased ? Bounds.MidX - Bounds.Left : Bounds.MidX) - anchorProperty.X * Bounds.Width; - float y = anchorPosition.Y - (zeroBased ? Bounds.MidY - Bounds.Top : Bounds.MidY) - anchorProperty.Y * Bounds.Height; + float x = anchorPosition.X - (zeroBased ? bounds.MidX - bounds.Left : bounds.MidX) - anchorProperty.X * bounds.Width; + float y = anchorPosition.Y - (zeroBased ? bounds.MidY - bounds.Top : bounds.MidY) - anchorProperty.Y * bounds.Height; SKMatrix transform = SKMatrix.Empty; diff --git a/src/Avalonia/Artemis.UI.Linux/ApplicationStateManager.cs b/src/Avalonia/Artemis.UI.Linux/ApplicationStateManager.cs index 146a90751..681063b91 100644 --- a/src/Avalonia/Artemis.UI.Linux/ApplicationStateManager.cs +++ b/src/Avalonia/Artemis.UI.Linux/ApplicationStateManager.cs @@ -4,9 +4,9 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; -using System.Security.Principal; using System.Threading; using Artemis.Core; +using Artemis.Core.Services; using Artemis.UI.Shared.Services.Interfaces; using Avalonia; using Avalonia.Controls.ApplicationLifetimes; @@ -36,6 +36,9 @@ namespace Artemis.UI.Linux controlledApplicationLifetime.Exit += (_, _) => { RunForcedShutdownIfEnabled(); + + // Dispose plugins before disposing the kernel because plugins might access services during dispose + kernel.Get().Dispose(); kernel.Dispose(); }; } diff --git a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/ChangeLayerLeds.cs b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/ChangeLayerLeds.cs new file mode 100644 index 000000000..a70020101 --- /dev/null +++ b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/Commands/ChangeLayerLeds.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using Artemis.Core; + +namespace Artemis.UI.Shared.Services.ProfileEditor.Commands; + +/// +/// Represents a profile editor command that can be used to change the LEDs of a layer. +/// +public class ChangeLayerLeds : IProfileEditorCommand +{ + private readonly Layer _layer; + private readonly List _leds; + private readonly List _originalLeds; + + /// + /// Creates a new instance of the class. + /// + public ChangeLayerLeds(Layer layer, List leds) + { + _layer = layer; + _leds = leds; + _originalLeds = new List(_layer.Leds); + } + + #region Implementation of IProfileEditorCommand + + /// + public string DisplayName => "Change layer LEDs"; + + /// + public void Execute() + { + _layer.ClearLeds(); + _layer.AddLeds(_leds); + } + + /// + public void Undo() + { + _layer.ClearLeds(); + _layer.AddLeds(_originalLeds); + } + + #endregion +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs index 7be4f2843..7165d1800 100644 --- a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs +++ b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/IProfileEditorService.cs @@ -42,6 +42,11 @@ public interface IProfileEditorService : IArtemisSharedUIService /// IObservable PixelsPerSecond { get; } + /// + /// Gets a source list of all available editor tools. + /// + SourceList Tools { get; } + /// /// Connect to the observable list of keyframes and observe any changes starting with the list's initial items. /// diff --git a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/IToolViewModel.cs b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/IToolViewModel.cs new file mode 100644 index 000000000..5d01eded7 --- /dev/null +++ b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/IToolViewModel.cs @@ -0,0 +1,103 @@ +using System; +using System.Windows.Input; +using Material.Icons; +using ReactiveUI; + +namespace Artemis.UI.Shared.Services.ProfileEditor; + +/// +/// Represents a profile editor tool. +/// +public interface IToolViewModel : IDisposable +{ + /// + /// Gets or sets a boolean indicating whether the tool is selected. + /// + public bool IsSelected { get; set; } + + /// + /// Gets a boolean indicating whether the tool is enabled. + /// + public bool IsEnabled { get; } + + /// + /// Gets a boolean indicating whether or not this tool is exclusive. + /// Exclusive tools deactivate any other exclusive tools when activated. + /// + public bool IsExclusive { get; } + + /// + /// Gets or sets a boolean indicating whether this tool should be shown in the toolbar. + /// + public bool ShowInToolbar { get; } + + /// + /// Gets the order in which this tool should appear in the toolbar. + /// + public int Order { get; } + + /// + /// Gets the icon which this tool should show in the toolbar. + /// + public MaterialIconKind Icon { get; } + + /// + /// Gets the tooltip which this tool should show in the toolbar. + /// + public string ToolTip { get; } +} + +public abstract class ToolViewModel : ActivatableViewModelBase, IToolViewModel +{ + private bool _isSelected; + + /// + /// Releases the unmanaged resources used by the object and optionally releases the managed resources. + /// + /// + /// to release both managed and unmanaged resources; + /// to release only unmanaged resources. + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + } + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + #region Implementation of IToolViewModel + + /// + public bool IsSelected + { + get => _isSelected; + set => this.RaiseAndSetIfChanged(ref _isSelected, value); + } + + /// + public abstract bool IsEnabled { get; } + + /// + public abstract bool IsExclusive { get; } + + /// + public abstract bool ShowInToolbar { get; } + + /// + public abstract int Order { get; } + + /// + public abstract MaterialIconKind Icon { get; } + + /// + public abstract string ToolTip { get; } + + #endregion +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs index 858fb5427..e36a75d43 100644 --- a/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs +++ b/src/Avalonia/Artemis.UI.Shared/Services/ProfileEditor/ProfileEditorService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reactive.Linq; using System.Reactive.Subjects; @@ -9,6 +10,8 @@ using Artemis.Core.Services; using Artemis.UI.Shared.Services.Interfaces; using Artemis.UI.Shared.Services.ProfileEditor.Commands; using DynamicData; +using DynamicData.Binding; +using ReactiveUI; using Serilog; namespace Artemis.UI.Shared.Services.ProfileEditor; @@ -43,9 +46,33 @@ internal class ProfileEditorService : IProfileEditorService Playing = _playingSubject.AsObservable(); SuspendedEditing = _suspendedEditingSubject.AsObservable(); PixelsPerSecond = _pixelsPerSecondSubject.AsObservable(); + Tools = new SourceList(); + Tools.Connect().AutoRefreshOnObservable(t => t.WhenAnyValue(vm => vm.IsSelected)).Subscribe(set => + { + IToolViewModel? changed = set.FirstOrDefault()?.Item.Current; + if (changed == null) + return; + + // Disable all others if the changed one is selected and exclusive + if (changed.IsSelected && changed.IsExclusive) + { + Tools.Edit(list => + { + foreach (IToolViewModel toolViewModel in list.Where(t => t.IsExclusive && t != changed)) + toolViewModel.IsSelected = false; + }); + } + }); } public IObservable SuspendedEditing { get; } + public IObservable ProfileConfiguration { get; } + public IObservable ProfileElement { get; } + public IObservable History { get; } + public IObservable Time { get; } + public IObservable Playing { get; } + public IObservable PixelsPerSecond { get; } + public SourceList Tools { get; } private ProfileEditorHistory? GetHistory(ProfileConfiguration? profileConfiguration) { @@ -87,13 +114,6 @@ internal class ProfileEditorService : IProfileEditorService } } - public IObservable ProfileConfiguration { get; } - public IObservable ProfileElement { get; } - public IObservable History { get; } - public IObservable Time { get; } - public IObservable Playing { get; } - public IObservable PixelsPerSecond { get; } - public IObservable> ConnectToKeyframes() { return _selectedKeyframes.Connect(); diff --git a/src/Avalonia/Artemis.UI.Shared/ViewModelBase.cs b/src/Avalonia/Artemis.UI.Shared/ViewModelBase.cs index 85926ff55..0d3059328 100644 --- a/src/Avalonia/Artemis.UI.Shared/ViewModelBase.cs +++ b/src/Avalonia/Artemis.UI.Shared/ViewModelBase.cs @@ -84,34 +84,10 @@ namespace Artemis.UI.Shared /// /// Represents the base class for Artemis view models that are interested in the activated event /// - public abstract class ActivatableViewModelBase : ViewModelBase, IActivatableViewModel, IDisposable + public abstract class ActivatableViewModelBase : ViewModelBase, IActivatableViewModel { - /// - protected ActivatableViewModelBase() - { - this.WhenActivated(disposables => Disposable.Create(Dispose).DisposeWith(disposables)); - } - - /// - /// Releases the unmanaged resources used by the object and optionally releases the managed resources. - /// - /// - /// to release both managed and unmanaged resources; - /// to release only unmanaged resources. - /// - protected virtual void Dispose(bool disposing) - { - } - /// public ViewModelActivator Activator { get; } = new(); - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } } /// diff --git a/src/Avalonia/Artemis.UI.Windows/ApplicationStateManager.cs b/src/Avalonia/Artemis.UI.Windows/ApplicationStateManager.cs index aec129e73..a3c1be22d 100644 --- a/src/Avalonia/Artemis.UI.Windows/ApplicationStateManager.cs +++ b/src/Avalonia/Artemis.UI.Windows/ApplicationStateManager.cs @@ -39,6 +39,9 @@ namespace Artemis.UI.Windows controlledApplicationLifetime.Exit += (_, _) => { RunForcedShutdownIfEnabled(); + + // Dispose plugins before disposing the kernel because plugins might access services during dispose + kernel.Get().Dispose(); kernel.Dispose(); }; } diff --git a/src/Avalonia/Artemis.UI.Windows/Properties/launchSettings.json b/src/Avalonia/Artemis.UI.Windows/Properties/launchSettings.json new file mode 100644 index 000000000..a1588c6f9 --- /dev/null +++ b/src/Avalonia/Artemis.UI.Windows/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Artemis.UI.Windows": { + "commandName": "Project", + "commandLineArgs": "--force-elevation --disable-forced-shutdown --pcmr" + } + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Ninject/UIModule.cs b/src/Avalonia/Artemis.UI/Ninject/UIModule.cs index af1cfa130..e186b02a9 100644 --- a/src/Avalonia/Artemis.UI/Ninject/UIModule.cs +++ b/src/Avalonia/Artemis.UI/Ninject/UIModule.cs @@ -2,8 +2,10 @@ using Artemis.UI.Ninject.Factories; using Artemis.UI.Ninject.InstanceProviders; using Artemis.UI.Screens; +using Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools; using Artemis.UI.Services.Interfaces; using Artemis.UI.Shared; +using Artemis.UI.Shared.Services.ProfileEditor; using Avalonia.Platform; using Avalonia.Shared.PlatformSupport; using Ninject.Extensions.Conventions; @@ -39,6 +41,14 @@ namespace Artemis.UI.Ninject .BindAllBaseClasses(); }); + Kernel.Bind(x => + { + x.FromThisAssembly() + .SelectAllClasses() + .InheritedFrom() + .BindAllInterfaces(); + }); + // Bind UI factories Kernel.Bind(x => { diff --git a/src/Avalonia/Artemis.UI/Screens/Device/Tabs/DevicePropertiesTabViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Device/Tabs/DevicePropertiesTabViewModel.cs index 77edab6ea..02050d9f7 100644 --- a/src/Avalonia/Artemis.UI/Screens/Device/Tabs/DevicePropertiesTabViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/Device/Tabs/DevicePropertiesTabViewModel.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Reactive.Disposables; using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; @@ -65,8 +66,17 @@ namespace Artemis.UI.Screens.Device this.WhenAnyValue(x => x.RedScale, x => x.GreenScale, x => x.BlueScale).Subscribe(_ => ApplyScaling()); - Device.PropertyChanged += DeviceOnPropertyChanged; - _coreService.FrameRendering += OnFrameRendering; + this.WhenActivated(d => + { + Device.PropertyChanged += DeviceOnPropertyChanged; + _coreService.FrameRendering += OnFrameRendering; + + Disposable.Create(() => + { + _coreService.FrameRendering -= OnFrameRendering; + Device.PropertyChanged -= DeviceOnPropertyChanged; + }).DisposeWith(d); + }); } public ArtemisDevice Device { get; } @@ -235,18 +245,6 @@ namespace Artemis.UI.Screens.Device Device.BlueScale = _initialBlueScale; } - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - _coreService.FrameRendering -= OnFrameRendering; - Device.PropertyChanged -= DeviceOnPropertyChanged; - } - - base.Dispose(disposing); - } - private bool GetCategory(DeviceCategory category) { return _categories.Contains(category); diff --git a/src/Avalonia/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesInstallDialogViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesInstallDialogViewModel.cs index 93d811451..28fb0baff 100644 --- a/src/Avalonia/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesInstallDialogViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesInstallDialogViewModel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Reactive.Disposables; using System.Threading; using System.Threading.Tasks; using Artemis.Core; @@ -30,6 +31,15 @@ namespace Artemis.UI.Screens.Plugins CanInstall = false; Task.Run(() => CanInstall = Prerequisites.Any(p => !p.PluginPrerequisite.IsMet())); + + this.WhenActivated(d => + { + Disposable.Create(() => + { + _tokenSource?.Cancel(); + _tokenSource?.Dispose(); + }).DisposeWith(d); + }); } public ObservableCollection Prerequisites { get; } @@ -125,17 +135,5 @@ namespace Artemis.UI.Screens.Plugins { return await windowService.ShowDialogAsync(("subjects", subjects)); } - - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - _tokenSource?.Cancel(); - _tokenSource?.Dispose(); - } - - base.Dispose(disposing); - } } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesUninstallDialogViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesUninstallDialogViewModel.cs index 9eeb5c7aa..f489d2726 100644 --- a/src/Avalonia/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesUninstallDialogViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/Plugins/Dialogs/PluginPrerequisitesUninstallDialogViewModel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Reactive.Disposables; using System.Threading; using System.Threading.Tasks; using Artemis.Core; @@ -37,6 +38,15 @@ namespace Artemis.UI.Screens.Plugins // Could be slow so take it off of the UI thread Task.Run(() => CanUninstall = Prerequisites.Any(p => p.PluginPrerequisite.IsMet())); + + this.WhenActivated(d => + { + Disposable.Create(() => + { + _tokenSource?.Cancel(); + _tokenSource?.Dispose(); + }).DisposeWith(d); + }); } public string CancelLabel { get; } @@ -133,17 +143,5 @@ namespace Artemis.UI.Screens.Plugins { return await windowService.ShowDialogAsync(("subjects", subjects), ("cancelLabel", cancelLabel)); } - - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - _tokenSource?.Cancel(); - _tokenSource?.Dispose(); - } - - base.Dispose(disposing); - } } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/Plugins/PluginFeatureViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Plugins/PluginFeatureViewModel.cs index eaeda45a5..8a6678f3e 100644 --- a/src/Avalonia/Artemis.UI/Screens/Plugins/PluginFeatureViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/Plugins/PluginFeatureViewModel.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reactive.Disposables; using System.Threading.Tasks; using Artemis.Core; using Artemis.Core.Services; @@ -36,12 +37,25 @@ namespace Artemis.UI.Screens.Plugins FeatureInfo = pluginFeatureInfo; ShowShield = FeatureInfo.Plugin.Info.RequiresAdmin && showShield; - _pluginManagementService.PluginFeatureEnabling += OnFeatureEnabling; - _pluginManagementService.PluginFeatureEnabled += OnFeatureEnableStopped; - _pluginManagementService.PluginFeatureEnableFailed += OnFeatureEnableStopped; + this.WhenActivated(d => + { + _pluginManagementService.PluginFeatureEnabling += OnFeatureEnabling; + _pluginManagementService.PluginFeatureEnabled += OnFeatureEnableStopped; + _pluginManagementService.PluginFeatureEnableFailed += OnFeatureEnableStopped; - FeatureInfo.Plugin.Enabled += PluginOnToggled; - FeatureInfo.Plugin.Disabled += PluginOnToggled; + FeatureInfo.Plugin.Enabled += PluginOnToggled; + FeatureInfo.Plugin.Disabled += PluginOnToggled; + + Disposable.Create(() => + { + _pluginManagementService.PluginFeatureEnabling -= OnFeatureEnabling; + _pluginManagementService.PluginFeatureEnabled -= OnFeatureEnableStopped; + _pluginManagementService.PluginFeatureEnableFailed -= OnFeatureEnableStopped; + + FeatureInfo.Plugin.Enabled -= PluginOnToggled; + FeatureInfo.Plugin.Disabled -= PluginOnToggled; + }).DisposeWith(d); + }); } public PluginFeatureInfo FeatureInfo { get; } @@ -99,22 +113,6 @@ namespace Artemis.UI.Screens.Plugins } } - /// - protected override void Dispose(bool disposing) - { - if (disposing) - { - _pluginManagementService.PluginFeatureEnabling -= OnFeatureEnabling; - _pluginManagementService.PluginFeatureEnabled -= OnFeatureEnableStopped; - _pluginManagementService.PluginFeatureEnableFailed -= OnFeatureEnableStopped; - - FeatureInfo.Plugin.Enabled -= PluginOnToggled; - FeatureInfo.Plugin.Disabled -= PluginOnToggled; - } - - base.Dispose(disposing); - } - private async Task UpdateEnabled(bool enable) { if (IsEnabled == enable) diff --git a/src/Avalonia/Artemis.UI/Screens/Plugins/PluginPrerequisiteViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Plugins/PluginPrerequisiteViewModel.cs index 57b24ee12..85d617e1a 100644 --- a/src/Avalonia/Artemis.UI/Screens/Plugins/PluginPrerequisiteViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/Plugins/PluginPrerequisiteViewModel.cs @@ -1,6 +1,7 @@ using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; +using System.Reactive.Disposables; using System.Threading; using System.Threading.Tasks; using Artemis.Core; @@ -33,7 +34,12 @@ namespace Artemis.UI.Screens.Plugins this.WhenAnyValue(x => x.Installing, x => x.Uninstalling, (i, u) => i || u).ToProperty(this, x => x.Busy, out _busy); this.WhenAnyValue(x => x.ActiveAction, a => Actions.IndexOf(a!)).ToProperty(this, x => x.ActiveStepNumber, out _activeStepNumber); - PluginPrerequisite.PropertyChanged += PluginPrerequisiteOnPropertyChanged; + + this.WhenActivated(d => + { + PluginPrerequisite.PropertyChanged += PluginPrerequisiteOnPropertyChanged; + Disposable.Create(() => PluginPrerequisite.PropertyChanged -= PluginPrerequisiteOnPropertyChanged).DisposeWith(d); + }); // Could be slow so take it off of the UI thread Task.Run(() => IsMet = PluginPrerequisite.IsMet()); @@ -105,14 +111,6 @@ namespace Artemis.UI.Screens.Plugins } } - /// - protected override void Dispose(bool disposing) - { - if (disposing) PluginPrerequisite.PropertyChanged -= PluginPrerequisiteOnPropertyChanged; - - base.Dispose(disposing); - } - private void PluginPrerequisiteOnPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(PluginPrerequisite.CurrentAction)) diff --git a/src/Avalonia/Artemis.UI/Screens/Plugins/PluginSettingsViewModel.cs b/src/Avalonia/Artemis.UI/Screens/Plugins/PluginSettingsViewModel.cs index a1036f921..2334cfda6 100644 --- a/src/Avalonia/Artemis.UI/Screens/Plugins/PluginSettingsViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/Plugins/PluginSettingsViewModel.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Reactive; +using System.Reactive.Disposables; using System.Reactive.Linq; using System.Threading.Tasks; using Artemis.Core; @@ -49,12 +50,23 @@ namespace Artemis.UI.Screens.Plugins foreach (PluginFeatureInfo pluginFeatureInfo in Plugin.Features) PluginFeatures.Add(_settingsVmFactory.CreatePluginFeatureViewModel(pluginFeatureInfo, false)); - _pluginManagementService.PluginDisabled += PluginManagementServiceOnPluginToggled; - _pluginManagementService.PluginEnabled += PluginManagementServiceOnPluginToggled; + OpenSettings = ReactiveCommand.Create(ExecuteOpenSettings, this.WhenAnyValue(x => x.IsEnabled).Select(isEnabled => isEnabled && Plugin.ConfigurationDialog != null)); InstallPrerequisites = ReactiveCommand.CreateFromTask(ExecuteInstallPrerequisites, this.WhenAnyValue(x => x.CanInstallPrerequisites)); RemovePrerequisites = ReactiveCommand.CreateFromTask(ExecuteRemovePrerequisites, this.WhenAnyValue(x => x.CanRemovePrerequisites)); + + this.WhenActivated(d => + { + _pluginManagementService.PluginDisabled += PluginManagementServiceOnPluginToggled; + _pluginManagementService.PluginEnabled += PluginManagementServiceOnPluginToggled; + + Disposable.Create(() => + { + _pluginManagementService.PluginDisabled -= PluginManagementServiceOnPluginToggled; + _pluginManagementService.PluginEnabled -= PluginManagementServiceOnPluginToggled; + }).DisposeWith(d); + }); } public ReactiveCommand OpenSettings { get; } @@ -237,17 +249,6 @@ namespace Artemis.UI.Screens.Plugins Utilities.OpenUrl(uri.ToString()); } - protected override void Dispose(bool disposing) - { - if (disposing) - { - _pluginManagementService.PluginDisabled -= PluginManagementServiceOnPluginToggled; - _pluginManagementService.PluginEnabled -= PluginManagementServiceOnPluginToggled; - } - - base.Dispose(disposing); - } - private void PluginManagementServiceOnPluginToggled(object? sender, PluginEventArgs e) { this.RaisePropertyChanged(nameof(IsEnabled)); diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/IToolViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/IToolViewModel.cs deleted file mode 100644 index fabd8a409..000000000 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/IToolViewModel.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools -{ - public interface IToolViewModel - { - } -} diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionAddToolView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionAddToolView.axaml new file mode 100644 index 000000000..2e3c5aaae --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionAddToolView.axaml @@ -0,0 +1,18 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionAddToolView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionAddToolView.axaml.cs new file mode 100644 index 000000000..0110136ed --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionAddToolView.axaml.cs @@ -0,0 +1,24 @@ +using Artemis.UI.Shared.Events; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; +using Avalonia.Skia; + +namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools; + +public class SelectionAddToolView : ReactiveUserControl +{ + public SelectionAddToolView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private void SelectionRectangle_OnSelectionFinished(object? sender, SelectionRectangleEventArgs e) + { + ViewModel?.AddLedsInRectangle(e.Rectangle.ToSKRect()); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionAddToolViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionAddToolViewModel.cs new file mode 100644 index 000000000..77710155c --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionAddToolViewModel.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using Artemis.Core; +using Artemis.Core.Services; +using Artemis.UI.Shared.Services.ProfileEditor; +using Artemis.UI.Shared.Services.ProfileEditor.Commands; +using Avalonia.Controls.Mixins; +using Material.Icons; +using ReactiveUI; +using SkiaSharp; + +namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools; + +public class SelectionAddToolViewModel : ToolViewModel +{ + private readonly IProfileEditorService _profileEditorService; + private readonly IRgbService _rgbService; + private readonly ObservableAsPropertyHelper? _isEnabled; + private Layer? _layer; + + /// + public SelectionAddToolViewModel(IProfileEditorService profileEditorService, IRgbService rgbService) + { + _profileEditorService = profileEditorService; + _rgbService = rgbService; + _isEnabled = profileEditorService.ProfileElement.Select(p => p is Layer).ToProperty(this, vm => vm.IsEnabled); + + this.WhenActivated(d => profileEditorService.ProfileElement.Subscribe(p => _layer = p as Layer).DisposeWith(d)); + } + + /// + public override bool IsEnabled => _isEnabled?.Value ?? false; + + /// + public override bool IsExclusive => true; + + /// + public override bool ShowInToolbar => true; + + /// + public override int Order => 3; + + /// + public override MaterialIconKind Icon => MaterialIconKind.SelectionDrag; + + /// + public override string ToolTip => "Add LEDs to the current layer"; + + /// + protected override void Dispose(bool disposing) + { + if (disposing) + _isEnabled?.Dispose(); + + base.Dispose(disposing); + } + + public void AddLedsInRectangle(SKRect rect) + { + if (_layer == null) + return; + + List leds = _rgbService.EnabledDevices.SelectMany(d => d.Leds).Where(l => l.AbsoluteRectangle.IntersectsWith(rect)).ToList(); + _profileEditorService.ExecuteCommand(new ChangeLayerLeds(_layer, leds)); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolView.axaml new file mode 100644 index 000000000..0823320a9 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolView.axaml @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolView.axaml.cs new file mode 100644 index 000000000..4a618dd3f --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolView.axaml.cs @@ -0,0 +1,24 @@ +using Artemis.UI.Shared.Events; +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; +using Avalonia.Skia; + +namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools; + +public class SelectionRemoveToolView : ReactiveUserControl +{ + public SelectionRemoveToolView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private void SelectionRectangle_OnSelectionFinished(object? sender, SelectionRectangleEventArgs e) + { + ViewModel?.RemoveLedsInRectangle(e.Rectangle.ToSKRect()); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolViewModel.cs new file mode 100644 index 000000000..a36d9ce9c --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/SelectionRemoveToolViewModel.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using Artemis.Core; +using Artemis.UI.Shared.Services.ProfileEditor; +using Artemis.UI.Shared.Services.ProfileEditor.Commands; +using Avalonia.Controls.Mixins; +using Material.Icons; +using ReactiveUI; +using SkiaSharp; + +namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools; + +public class SelectionRemoveToolViewModel : ToolViewModel +{ + private readonly ObservableAsPropertyHelper? _isEnabled; + private readonly IProfileEditorService _profileEditorService; + private Layer? _layer; + + /// + public SelectionRemoveToolViewModel(IProfileEditorService profileEditorService) + { + _profileEditorService = profileEditorService; + _isEnabled = profileEditorService.ProfileElement.Select(p => p is Layer).ToProperty(this, vm => vm.IsEnabled); + this.WhenActivated(d => profileEditorService.ProfileElement.Subscribe(p => _layer = p as Layer).DisposeWith(d)); + } + + /// + public override bool IsEnabled => _isEnabled?.Value ?? false; + + /// + public override bool IsExclusive => true; + + /// + public override bool ShowInToolbar => true; + + /// + public override int Order => 3; + + /// + public override MaterialIconKind Icon => MaterialIconKind.SelectOff; + + /// + public override string ToolTip => "Remove LEDs from the current layer"; + + public void RemoveLedsInRectangle(SKRect rect) + { + if (_layer == null) + return; + + List leds = _layer.Leds.Except(_layer.Leds.Where(l => l.AbsoluteRectangle.IntersectsWith(rect))).ToList(); + _profileEditorService.ExecuteCommand(new ChangeLayerLeds(_layer, leds)); + } + + /// + protected override void Dispose(bool disposing) + { + if (disposing) + _isEnabled?.Dispose(); + + base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolView.axaml new file mode 100644 index 000000000..d92f01b02 --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolView.axaml @@ -0,0 +1,8 @@ + + Welcome to Avalonia! + \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolView.axaml.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolView.axaml.cs new file mode 100644 index 000000000..1149fe2ad --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolView.axaml.cs @@ -0,0 +1,17 @@ +using Avalonia.Markup.Xaml; +using Avalonia.ReactiveUI; + +namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools; + +public class TransformToolView : ReactiveUserControl +{ + public TransformToolView() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolViewModel.cs new file mode 100644 index 000000000..14ae1e5ec --- /dev/null +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Tools/TransformToolViewModel.cs @@ -0,0 +1,45 @@ +using System.Reactive.Linq; +using Artemis.Core; +using Artemis.UI.Shared.Services.ProfileEditor; +using Material.Icons; +using ReactiveUI; + +namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools; + +public class TransformToolViewModel : ToolViewModel +{ + private readonly ObservableAsPropertyHelper? _isEnabled; + + /// + public TransformToolViewModel(IProfileEditorService profileEditorService) + { + _isEnabled = profileEditorService.ProfileElement.Select(p => p is Layer).ToProperty(this, vm => vm.IsEnabled); + } + + /// + public override bool IsEnabled => _isEnabled?.Value ?? false; + + /// + public override bool IsExclusive => true; + + /// + public override bool ShowInToolbar => true; + + /// + public override int Order => 3; + + /// + public override MaterialIconKind Icon => MaterialIconKind.TransitConnectionVariant; + + /// + public override string ToolTip => "Transform the shape of the current layer"; + + /// + protected override void Dispose(bool disposing) + { + if (disposing) + _isEnabled?.Dispose(); + + base.Dispose(disposing); + } +} \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorView.axaml index dfa802ef5..1d7464a62 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorView.axaml +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorView.axaml @@ -65,7 +65,7 @@ - + diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorViewModel.cs index 1ad1fa430..0ed2ce175 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorViewModel.cs @@ -20,25 +20,26 @@ public class VisualEditorViewModel : ActivatableViewModelBase private readonly IProfileEditorVmFactory _vmFactory; private ObservableAsPropertyHelper? _profileConfiguration; private readonly SourceList _visualizers; + private ReadOnlyObservableCollection _tools; public VisualEditorViewModel(IProfileEditorService profileEditorService, IRgbService rgbService, IProfileEditorVmFactory vmFactory) { _vmFactory = vmFactory; _visualizers = new SourceList(); - - Devices = new ObservableCollection(rgbService.EnabledDevices); - Tools = new ObservableCollection(); - _visualizers.Connect() .Sort(SortExpressionComparer.Ascending(vm => vm.Order)) .Bind(out ReadOnlyObservableCollection visualizers) .Subscribe(); + + Devices = new ObservableCollection(rgbService.EnabledDevices); Visualizers = visualizers; this.WhenActivated(d => { _profileConfiguration = profileEditorService.ProfileConfiguration.ToProperty(this, vm => vm.ProfileConfiguration).DisposeWith(d); profileEditorService.ProfileConfiguration.Subscribe(CreateVisualizers).DisposeWith(d); + profileEditorService.Tools.Connect().AutoRefreshOnObservable(t => t.WhenAnyValue(vm => vm.IsSelected)).Filter(t => t.IsSelected).Bind(out ReadOnlyObservableCollection tools).Subscribe().DisposeWith(d); + Tools = tools; }); } @@ -46,7 +47,12 @@ public class VisualEditorViewModel : ActivatableViewModelBase public ObservableCollection Devices { get; } public ReadOnlyObservableCollection Visualizers { get; } - public ObservableCollection Tools { get; } + + public ReadOnlyObservableCollection Tools + { + get => _tools; + set => this.RaiseAndSetIfChanged(ref _tools, value); + } private void CreateVisualizers(ProfileConfiguration? profileConfiguration) { diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/IVisualizerViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/IVisualizerViewModel.cs index 385fab60f..c0134b102 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/IVisualizerViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/IVisualizerViewModel.cs @@ -2,7 +2,7 @@ public interface IVisualizerViewModel { - int X { get; } - int Y { get; } + double X { get; } + double Y { get; } int Order { get; } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerShapeVisualizerView.axaml b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerShapeVisualizerView.axaml index 0fe4b62bb..4ea1dd32d 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerShapeVisualizerView.axaml +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerShapeVisualizerView.axaml @@ -6,8 +6,7 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers.LayerShapeVisualizerView" x:DataType="visualizers:LayerShapeVisualizerViewModel" - ClipToBounds="False" - ZIndex="2"> + ClipToBounds="False">