1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Profile editor - Added tools and layer visualizers

This commit is contained in:
Robert 2022-02-05 11:20:51 +01:00
parent 89beb92935
commit bc1b44069c
36 changed files with 776 additions and 225 deletions

View File

@ -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
/// <param name="includeTranslation">Whether translation should be included</param>
/// <param name="includeScale">Whether the scale should be included</param>
/// <param name="includeRotation">Whether the rotation should be included</param>
/// <param name="customBounds">Optional custom bounds to base the anchor on</param>
/// <returns>The transformation matrix containing the current transformation settings</returns>
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;

View File

@ -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<IPluginManagementService>().Dispose();
kernel.Dispose();
};
}

View File

@ -0,0 +1,45 @@
using System.Collections.Generic;
using Artemis.Core;
namespace Artemis.UI.Shared.Services.ProfileEditor.Commands;
/// <summary>
/// Represents a profile editor command that can be used to change the LEDs of a layer.
/// </summary>
public class ChangeLayerLeds : IProfileEditorCommand
{
private readonly Layer _layer;
private readonly List<ArtemisLed> _leds;
private readonly List<ArtemisLed> _originalLeds;
/// <summary>
/// Creates a new instance of the <see cref="ChangeLayerLeds" /> class.
/// </summary>
public ChangeLayerLeds(Layer layer, List<ArtemisLed> leds)
{
_layer = layer;
_leds = leds;
_originalLeds = new List<ArtemisLed>(_layer.Leds);
}
#region Implementation of IProfileEditorCommand
/// <inheritdoc />
public string DisplayName => "Change layer LEDs";
/// <inheritdoc />
public void Execute()
{
_layer.ClearLeds();
_layer.AddLeds(_leds);
}
/// <inheritdoc />
public void Undo()
{
_layer.ClearLeds();
_layer.AddLeds(_originalLeds);
}
#endregion
}

View File

@ -42,6 +42,11 @@ public interface IProfileEditorService : IArtemisSharedUIService
/// </summary>
IObservable<int> PixelsPerSecond { get; }
/// <summary>
/// Gets a source list of all available editor tools.
/// </summary>
SourceList<IToolViewModel> Tools { get; }
/// <summary>
/// Connect to the observable list of keyframes and observe any changes starting with the list's initial items.
/// </summary>

View File

@ -0,0 +1,103 @@
using System;
using System.Windows.Input;
using Material.Icons;
using ReactiveUI;
namespace Artemis.UI.Shared.Services.ProfileEditor;
/// <summary>
/// Represents a profile editor tool.
/// </summary>
public interface IToolViewModel : IDisposable
{
/// <summary>
/// Gets or sets a boolean indicating whether the tool is selected.
/// </summary>
public bool IsSelected { get; set; }
/// <summary>
/// Gets a boolean indicating whether the tool is enabled.
/// </summary>
public bool IsEnabled { get; }
/// <summary>
/// Gets a boolean indicating whether or not this tool is exclusive.
/// Exclusive tools deactivate any other exclusive tools when activated.
/// </summary>
public bool IsExclusive { get; }
/// <summary>
/// Gets or sets a boolean indicating whether this tool should be shown in the toolbar.
/// </summary>
public bool ShowInToolbar { get; }
/// <summary>
/// Gets the order in which this tool should appear in the toolbar.
/// </summary>
public int Order { get; }
/// <summary>
/// Gets the icon which this tool should show in the toolbar.
/// </summary>
public MaterialIconKind Icon { get; }
/// <summary>
/// Gets the tooltip which this tool should show in the toolbar.
/// </summary>
public string ToolTip { get; }
}
public abstract class ToolViewModel : ActivatableViewModelBase, IToolViewModel
{
private bool _isSelected;
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
}
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#region Implementation of IToolViewModel
/// <inheritdoc />
public bool IsSelected
{
get => _isSelected;
set => this.RaiseAndSetIfChanged(ref _isSelected, value);
}
/// <inheritdoc />
public abstract bool IsEnabled { get; }
/// <inheritdoc />
public abstract bool IsExclusive { get; }
/// <inheritdoc />
public abstract bool ShowInToolbar { get; }
/// <inheritdoc />
public abstract int Order { get; }
/// <inheritdoc />
public abstract MaterialIconKind Icon { get; }
/// <inheritdoc />
public abstract string ToolTip { get; }
#endregion
}

View File

@ -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<IToolViewModel>();
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<bool> SuspendedEditing { get; }
public IObservable<ProfileConfiguration?> ProfileConfiguration { get; }
public IObservable<RenderProfileElement?> ProfileElement { get; }
public IObservable<ProfileEditorHistory?> History { get; }
public IObservable<TimeSpan> Time { get; }
public IObservable<bool> Playing { get; }
public IObservable<int> PixelsPerSecond { get; }
public SourceList<IToolViewModel> Tools { get; }
private ProfileEditorHistory? GetHistory(ProfileConfiguration? profileConfiguration)
{
@ -87,13 +114,6 @@ internal class ProfileEditorService : IProfileEditorService
}
}
public IObservable<ProfileConfiguration?> ProfileConfiguration { get; }
public IObservable<RenderProfileElement?> ProfileElement { get; }
public IObservable<ProfileEditorHistory?> History { get; }
public IObservable<TimeSpan> Time { get; }
public IObservable<bool> Playing { get; }
public IObservable<int> PixelsPerSecond { get; }
public IObservable<IChangeSet<ILayerPropertyKeyframe>> ConnectToKeyframes()
{
return _selectedKeyframes.Connect();

View File

@ -84,34 +84,10 @@ namespace Artemis.UI.Shared
/// <summary>
/// Represents the base class for Artemis view models that are interested in the activated event
/// </summary>
public abstract class ActivatableViewModelBase : ViewModelBase, IActivatableViewModel, IDisposable
public abstract class ActivatableViewModelBase : ViewModelBase, IActivatableViewModel
{
/// <inheritdoc />
protected ActivatableViewModelBase()
{
this.WhenActivated(disposables => Disposable.Create(Dispose).DisposeWith(disposables));
}
/// <summary>
/// Releases the unmanaged resources used by the object and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">
/// <see langword="true" /> to release both managed and unmanaged resources;
/// <see langword="false" /> to release only unmanaged resources.
/// </param>
protected virtual void Dispose(bool disposing)
{
}
/// <inheritdoc />
public ViewModelActivator Activator { get; } = new();
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
/// <summary>

View File

@ -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<IPluginManagementService>().Dispose();
kernel.Dispose();
};
}

View File

@ -0,0 +1,8 @@
{
"profiles": {
"Artemis.UI.Windows": {
"commandName": "Project",
"commandLineArgs": "--force-elevation --disable-forced-shutdown --pcmr"
}
}
}

View File

@ -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<IToolViewModel>()
.BindAllInterfaces();
});
// Bind UI factories
Kernel.Bind(x =>
{

View File

@ -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;
}
/// <inheritdoc />
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);

View File

@ -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<PluginPrerequisiteViewModel> Prerequisites { get; }
@ -125,17 +135,5 @@ namespace Artemis.UI.Screens.Plugins
{
return await windowService.ShowDialogAsync<PluginPrerequisitesInstallDialogViewModel, bool>(("subjects", subjects));
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (disposing)
{
_tokenSource?.Cancel();
_tokenSource?.Dispose();
}
base.Dispose(disposing);
}
}
}

View File

@ -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<PluginPrerequisitesUninstallDialogViewModel, bool>(("subjects", subjects), ("cancelLabel", cancelLabel));
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (disposing)
{
_tokenSource?.Cancel();
_tokenSource?.Dispose();
}
base.Dispose(disposing);
}
}
}

View File

@ -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
}
}
/// <inheritdoc />
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)

View File

@ -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
}
}
/// <inheritdoc />
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))

View File

@ -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<bool>(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<Unit, Unit> 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));

View File

@ -1,6 +0,0 @@
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools
{
public interface IToolViewModel
{
}
}

View File

@ -0,0 +1,18 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools.SelectionAddToolView" ClipToBounds="False">
<Grid>
<controls:SelectionRectangle InputElement="{Binding $parent[ZoomBorder]}"
BorderBrush="{DynamicResource SystemAccentColor}"
BorderRadius="8"
SelectionFinished="SelectionRectangle_OnSelectionFinished">
<controls:SelectionRectangle.Background>
<SolidColorBrush Color="{DynamicResource SystemAccentColorLight1}" Opacity="0.2"></SolidColorBrush>
</controls:SelectionRectangle.Background>
</controls:SelectionRectangle>
</Grid>
</UserControl>

View File

@ -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<SelectionAddToolViewModel>
{
public SelectionAddToolView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void SelectionRectangle_OnSelectionFinished(object? sender, SelectionRectangleEventArgs e)
{
ViewModel?.AddLedsInRectangle(e.Rectangle.ToSKRect());
}
}

View File

@ -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<bool>? _isEnabled;
private Layer? _layer;
/// <inheritdoc />
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));
}
/// <inheritdoc />
public override bool IsEnabled => _isEnabled?.Value ?? false;
/// <inheritdoc />
public override bool IsExclusive => true;
/// <inheritdoc />
public override bool ShowInToolbar => true;
/// <inheritdoc />
public override int Order => 3;
/// <inheritdoc />
public override MaterialIconKind Icon => MaterialIconKind.SelectionDrag;
/// <inheritdoc />
public override string ToolTip => "Add LEDs to the current layer";
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (disposing)
_isEnabled?.Dispose();
base.Dispose(disposing);
}
public void AddLedsInRectangle(SKRect rect)
{
if (_layer == null)
return;
List<ArtemisLed> leds = _rgbService.EnabledDevices.SelectMany(d => d.Leds).Where(l => l.AbsoluteRectangle.IntersectsWith(rect)).ToList();
_profileEditorService.ExecuteCommand(new ChangeLayerLeds(_layer, leds));
}
}

View File

@ -0,0 +1,20 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Artemis.UI.Shared.Controls;assembly=Artemis.UI.Shared"
xmlns:paz="clr-namespace:Avalonia.Controls.PanAndZoom;assembly=Avalonia.Controls.PanAndZoom"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools.SelectionRemoveToolView"
ClipToBounds="False">
<Grid>
<controls:SelectionRectangle InputElement="{Binding $parent[paz:ZoomBorder]}"
BorderBrush="{DynamicResource SystemAccentColor}"
BorderRadius="8"
SelectionFinished="SelectionRectangle_OnSelectionFinished">
<controls:SelectionRectangle.Background>
<SolidColorBrush Color="{DynamicResource SystemAccentColorLight1}" Opacity="0.2"></SolidColorBrush>
</controls:SelectionRectangle.Background>
</controls:SelectionRectangle>
</Grid>
</UserControl>

View File

@ -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<SelectionRemoveToolViewModel>
{
public SelectionRemoveToolView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void SelectionRectangle_OnSelectionFinished(object? sender, SelectionRectangleEventArgs e)
{
ViewModel?.RemoveLedsInRectangle(e.Rectangle.ToSKRect());
}
}

View File

@ -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<bool>? _isEnabled;
private readonly IProfileEditorService _profileEditorService;
private Layer? _layer;
/// <inheritdoc />
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));
}
/// <inheritdoc />
public override bool IsEnabled => _isEnabled?.Value ?? false;
/// <inheritdoc />
public override bool IsExclusive => true;
/// <inheritdoc />
public override bool ShowInToolbar => true;
/// <inheritdoc />
public override int Order => 3;
/// <inheritdoc />
public override MaterialIconKind Icon => MaterialIconKind.SelectOff;
/// <inheritdoc />
public override string ToolTip => "Remove LEDs from the current layer";
public void RemoveLedsInRectangle(SKRect rect)
{
if (_layer == null)
return;
List<ArtemisLed> leds = _layer.Leds.Except(_layer.Leds.Where(l => l.AbsoluteRectangle.IntersectsWith(rect))).ToList();
_profileEditorService.ExecuteCommand(new ChangeLayerLeds(_layer, leds));
}
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (disposing)
_isEnabled?.Dispose();
base.Dispose(disposing);
}
}

View File

@ -0,0 +1,8 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools.TransformToolView">
Welcome to Avalonia!
</UserControl>

View File

@ -0,0 +1,17 @@
using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Tools;
public class TransformToolView : ReactiveUserControl<TransformToolViewModel>
{
public TransformToolView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

View File

@ -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<bool>? _isEnabled;
/// <inheritdoc />
public TransformToolViewModel(IProfileEditorService profileEditorService)
{
_isEnabled = profileEditorService.ProfileElement.Select(p => p is Layer).ToProperty(this, vm => vm.IsEnabled);
}
/// <inheritdoc />
public override bool IsEnabled => _isEnabled?.Value ?? false;
/// <inheritdoc />
public override bool IsExclusive => true;
/// <inheritdoc />
public override bool ShowInToolbar => true;
/// <inheritdoc />
public override int Order => 3;
/// <inheritdoc />
public override MaterialIconKind Icon => MaterialIconKind.TransitConnectionVariant;
/// <inheritdoc />
public override string ToolTip => "Transform the shape of the current layer";
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (disposing)
_isEnabled?.Dispose();
base.Dispose(disposing);
}
}

View File

@ -65,7 +65,7 @@
<ItemsControl Items="{CompiledBinding Tools}" ClipToBounds="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>

View File

@ -20,25 +20,26 @@ public class VisualEditorViewModel : ActivatableViewModelBase
private readonly IProfileEditorVmFactory _vmFactory;
private ObservableAsPropertyHelper<ProfileConfiguration?>? _profileConfiguration;
private readonly SourceList<IVisualizerViewModel> _visualizers;
private ReadOnlyObservableCollection<IToolViewModel> _tools;
public VisualEditorViewModel(IProfileEditorService profileEditorService, IRgbService rgbService, IProfileEditorVmFactory vmFactory)
{
_vmFactory = vmFactory;
_visualizers = new SourceList<IVisualizerViewModel>();
Devices = new ObservableCollection<ArtemisDevice>(rgbService.EnabledDevices);
Tools = new ObservableCollection<IToolViewModel>();
_visualizers.Connect()
.Sort(SortExpressionComparer<IVisualizerViewModel>.Ascending(vm => vm.Order))
.Bind(out ReadOnlyObservableCollection<IVisualizerViewModel> visualizers)
.Subscribe();
Devices = new ObservableCollection<ArtemisDevice>(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<IToolViewModel> tools).Subscribe().DisposeWith(d);
Tools = tools;
});
}
@ -46,7 +47,12 @@ public class VisualEditorViewModel : ActivatableViewModelBase
public ObservableCollection<ArtemisDevice> Devices { get; }
public ReadOnlyObservableCollection<IVisualizerViewModel> Visualizers { get; }
public ObservableCollection<IToolViewModel> Tools { get; }
public ReadOnlyObservableCollection<IToolViewModel> Tools
{
get => _tools;
set => this.RaiseAndSetIfChanged(ref _tools, value);
}
private void CreateVisualizers(ProfileConfiguration? profileConfiguration)
{

View File

@ -2,7 +2,7 @@
public interface IVisualizerViewModel
{
int X { get; }
int Y { get; }
double X { get; }
double Y { get; }
int Order { get; }
}

View File

@ -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">
<UserControl.Styles>
<Style Selector="Path.layer-visualizer">
<Setter Property="Stroke" Value="{StaticResource ButtonBorderBrushDisabled}" />

View File

@ -7,13 +7,18 @@ using Artemis.UI.Shared.Services.ProfileEditor;
using Avalonia;
using Avalonia.Controls.Mixins;
using Avalonia.Media;
using Avalonia.Skia;
using ReactiveUI;
using SkiaSharp;
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
public class LayerShapeVisualizerViewModel : ActivatableViewModelBase, IVisualizerViewModel
{
private ObservableAsPropertyHelper<bool>? _selected;
private Rect _layerBounds;
private double _x;
private double _y;
private Geometry? _shapeGeometry;
public LayerShapeVisualizerViewModel(Layer layer, IProfileEditorService profileEditorService)
@ -40,13 +45,29 @@ public class LayerShapeVisualizerViewModel : ActivatableViewModelBase, IVisualiz
profileEditorService.Time.Subscribe(_ => UpdateTransform()).DisposeWith(d);
Update();
UpdateTransform();
});
}
public Layer Layer { get; }
public bool Selected => _selected?.Value ?? false;
public Rect LayerBounds => Layer.Bounds.ToRect();
public Rect LayerBounds
{
get => _layerBounds;
private set => this.RaiseAndSetIfChanged(ref _layerBounds, value);
}
public double X
{
get => _x;
set => this.RaiseAndSetIfChanged(ref _x, value);
}
public double Y
{
get => _y;
set => this.RaiseAndSetIfChanged(ref _y, value);
}
public Geometry? ShapeGeometry
{
@ -54,25 +75,43 @@ public class LayerShapeVisualizerViewModel : ActivatableViewModelBase, IVisualiz
set => this.RaiseAndSetIfChanged(ref _shapeGeometry, value);
}
public int Order => 2;
private void Update()
{
if (Layer.General.ShapeType.CurrentValue == LayerShapeType.Rectangle)
ShapeGeometry = new RectangleGeometry(new Rect(0, 0, Layer.Bounds.Width, Layer.Bounds.Height));
else
ShapeGeometry = new EllipseGeometry(new Rect(0, 0, Layer.Bounds.Width, Layer.Bounds.Height));
UpdateLayerBounds();
this.RaisePropertyChanged(nameof(X));
this.RaisePropertyChanged(nameof(Y));
this.RaisePropertyChanged(nameof(LayerBounds));
if (Layer.General.ShapeType.CurrentValue == LayerShapeType.Rectangle)
ShapeGeometry = new RectangleGeometry(LayerBounds);
else
ShapeGeometry = new EllipseGeometry(LayerBounds);
UpdateTransform();
}
private void UpdateLayerBounds()
{
// Create accurate bounds based on the RgbLeds and not the rounded ArtemisLeds
SKPath path = new();
foreach (ArtemisLed artemisLed in Layer.Leds)
{
path.AddRect(SKRect.Create(
artemisLed.RgbLed.AbsoluteBoundary.Location.X,
artemisLed.RgbLed.AbsoluteBoundary.Location.Y,
artemisLed.RgbLed.AbsoluteBoundary.Size.Width,
artemisLed.RgbLed.AbsoluteBoundary.Size.Height)
);
}
SKRect bounds = path.Bounds;
LayerBounds = new Rect(0, 0, bounds.Width, bounds.Height);
X = bounds.Left;
Y = bounds.Top;
}
private void UpdateTransform()
{
if (ShapeGeometry != null)
ShapeGeometry.Transform = new MatrixTransform(Layer.GetTransformMatrix(true, true, true, true).ToMatrix());
ShapeGeometry.Transform = new MatrixTransform(Layer.GetTransformMatrix(false, true, true, true, LayerBounds.ToSKRect()).ToMatrix());
}
public int X => Layer.Bounds.Left;
public int Y => Layer.Bounds.Top;
public int Order => 2;
}

View File

@ -6,7 +6,7 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers.LayerVisualizerView"
x:DataType="visualizers:LayerVisualizerViewModel"
ZIndex="1">
ClipToBounds="False">
<UserControl.Styles>
<Style Selector="Path.layer-visualizer">
<Setter Property="Stroke" Value="{StaticResource ButtonBorderBrushDisabled}" />

View File

@ -6,12 +6,16 @@ using Artemis.UI.Shared.Services.ProfileEditor;
using Avalonia;
using Avalonia.Controls.Mixins;
using ReactiveUI;
using ShimSkiaSharp;
namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
public class LayerVisualizerViewModel : ActivatableViewModelBase, IVisualizerViewModel
{
private ObservableAsPropertyHelper<bool>? _selected;
private Rect _layerBounds;
private double _x;
private double _y;
public LayerVisualizerViewModel(Layer layer, IProfileEditorService profileEditorService)
{
@ -25,21 +29,50 @@ public class LayerVisualizerViewModel : ActivatableViewModelBase, IVisualizerVie
.Select(p => p == Layer)
.ToProperty(this, vm => vm.Selected)
.DisposeWith(d);
Update();
});
}
public Layer Layer { get; }
public bool Selected => _selected?.Value ?? false;
public Rect LayerBounds => new(0, 0, Layer.Bounds.Width, Layer.Bounds.Height);
public Rect LayerBounds
{
get => _layerBounds;
private set => this.RaiseAndSetIfChanged(ref _layerBounds, value);
}
public double X
{
get => _x;
set => this.RaiseAndSetIfChanged(ref _x, value);
}
public double Y
{
get => _y;
set => this.RaiseAndSetIfChanged(ref _y, value);
}
public int Order => 1;
private void Update()
{
this.RaisePropertyChanged(nameof(X));
this.RaisePropertyChanged(nameof(Y));
this.RaisePropertyChanged(nameof(LayerBounds));
}
// Create accurate bounds based on the RgbLeds and not the rounded ArtemisLeds
SKPath path = new();
foreach (ArtemisLed artemisLed in Layer.Leds)
{
path.AddRect(SKRect.Create(
artemisLed.RgbLed.AbsoluteBoundary.Location.X,
artemisLed.RgbLed.AbsoluteBoundary.Location.Y,
artemisLed.RgbLed.AbsoluteBoundary.Size.Width,
artemisLed.RgbLed.AbsoluteBoundary.Size.Height)
);
}
public int X => Layer.Bounds.Left;
public int Y => Layer.Bounds.Top;
public int Order => 1;
LayerBounds = new Rect(0, 0, path.Bounds.Width, path.Bounds.Height);
X = path.Bounds.Left;
Y = path.Bounds.Top;
}
}

View File

@ -6,6 +6,7 @@
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:converters="clr-namespace:Artemis.UI.Converters"
xmlns:profileEditor="clr-namespace:Artemis.UI.Screens.ProfileEditor"
xmlns:shared="clr-namespace:Artemis.UI.Shared.Services.ProfileEditor;assembly=Artemis.UI.Shared"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Artemis.UI.Screens.ProfileEditor.ProfileEditorView"
x:DataType="profileEditor:ProfileEditorViewModel">
@ -45,7 +46,7 @@
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="{CompiledBinding TreeWidth.Value, Mode=TwoWay, Converter={StaticResource DoubleToGridLengthConverter}}"/>
<ColumnDefinition Width="{CompiledBinding TreeWidth.Value, Mode=TwoWay, Converter={StaticResource DoubleToGridLengthConverter}}" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
@ -56,25 +57,23 @@
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="{CompiledBinding PropertiesHeight.Value, Mode=TwoWay, Converter={StaticResource DoubleToGridLengthConverter}}"/>
<RowDefinition Height="{CompiledBinding PropertiesHeight.Value, Mode=TwoWay, Converter={StaticResource DoubleToGridLengthConverter}}" />
</Grid.RowDefinitions>
<Border Grid.Row="0" Classes="card" Padding="0" Margin="4 0 4 4" ClipToBounds="True">
<Grid ColumnDefinitions="Auto,*">
<Border Grid.Column="0" Background="{DynamicResource CardStrokeColorDefaultSolidBrush}">
<StackPanel Orientation="Vertical">
<ToggleButton Classes="icon-button editor-sidebar-button">
<avalonia:MaterialIcon Kind="HandLeft" />
</ToggleButton>
<ToggleButton Classes="icon-button editor-sidebar-button">
<avalonia:MaterialIcon Kind="TransitConnectionVariant" />
</ToggleButton>
<ToggleButton Classes="icon-button editor-sidebar-button">
<avalonia:MaterialIcon Kind="SelectionDrag" />
</ToggleButton>
<ToggleButton Classes="icon-button editor-sidebar-button">
<avalonia:MaterialIcon Kind="SelectOff" />
</ToggleButton>
</StackPanel>
<ItemsControl Items="{CompiledBinding Tools}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="shared:IToolViewModel">
<ToggleButton Classes="icon-button editor-sidebar-button"
ToolTip.Tip="{CompiledBinding ToolTip}"
IsEnabled="{CompiledBinding IsEnabled}"
IsChecked="{CompiledBinding IsSelected}">
<avalonia:MaterialIcon Kind="{CompiledBinding Icon}" />
</ToggleButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<ContentControl Grid.Column="1" Content="{CompiledBinding VisualEditorViewModel}" />
</Grid>
@ -83,7 +82,7 @@
<GridSplitter Grid.Row="1" Classes="editor-grid-splitter-horizontal" />
<Border Grid.Row="2" Classes="card card-condensed" Margin="4" Padding="0" ClipToBounds="True">
<ContentControl Content="{CompiledBinding PropertiesViewModel}"/>
<ContentControl Content="{CompiledBinding PropertiesViewModel}" />
</Border>
</Grid>
@ -91,9 +90,9 @@
<Grid Grid.Row="0" Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="{CompiledBinding ConditionsHeight.Value, Mode=TwoWay, Converter={StaticResource DoubleToGridLengthConverter}}"/>
<RowDefinition Height="{CompiledBinding ConditionsHeight.Value, Mode=TwoWay, Converter={StaticResource DoubleToGridLengthConverter}}" />
</Grid.RowDefinitions>
<Border Grid.Row="0" Classes="card card-condensed" Margin="4 0 4 4">
<ContentControl Content="{CompiledBinding ProfileTreeViewModel}" />
@ -106,6 +105,6 @@
</Border>
</Grid>
<ContentControl Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Content="{CompiledBinding StatusBarViewModel}"/>
<ContentControl Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Content="{CompiledBinding StatusBarViewModel}" />
</Grid>
</UserControl>

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.ObjectModel;
using System.Reactive.Disposables;
using Artemis.Core;
using Artemis.Core.Services;
@ -7,7 +8,10 @@ using Artemis.UI.Screens.ProfileEditor.ProfileTree;
using Artemis.UI.Screens.ProfileEditor.Properties;
using Artemis.UI.Screens.ProfileEditor.StatusBar;
using Artemis.UI.Screens.ProfileEditor.VisualEditor;
using Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;
using Artemis.UI.Shared.Services.ProfileEditor;
using DynamicData;
using DynamicData.Binding;
using Ninject;
using ReactiveUI;
@ -18,6 +22,7 @@ namespace Artemis.UI.Screens.ProfileEditor
private readonly ISettingsService _settingsService;
private ObservableAsPropertyHelper<ProfileConfiguration?>? _profileConfiguration;
private ObservableAsPropertyHelper<ProfileEditorHistory?>? _history;
private ReadOnlyObservableCollection<IToolViewModel> _tools;
/// <inheritdoc />
public ProfileEditorViewModel(IScreen hostScreen,
@ -26,7 +31,7 @@ namespace Artemis.UI.Screens.ProfileEditor
VisualEditorViewModel visualEditorViewModel,
ProfileTreeViewModel profileTreeViewModel,
ProfileEditorTitleBarViewModel profileEditorTitleBarViewModel,
MenuBarViewModel menuBarViewModel,
MenuBarViewModel menuBarViewModel,
PropertiesViewModel propertiesViewModel,
StatusBarViewModel statusBarViewModel)
: base(hostScreen, "profile-editor")
@ -42,8 +47,18 @@ namespace Artemis.UI.Screens.ProfileEditor
else
MenuBarViewModel = menuBarViewModel;
this.WhenActivated(d => _profileConfiguration = profileEditorService.ProfileConfiguration.ToProperty(this, vm => vm.ProfileConfiguration).DisposeWith(d));
this.WhenActivated(d => _history = profileEditorService.History.ToProperty(this, vm => vm.History).DisposeWith(d));
this.WhenActivated(d =>
{
_profileConfiguration = profileEditorService.ProfileConfiguration.ToProperty(this, vm => vm.ProfileConfiguration).DisposeWith(d);
_history = profileEditorService.History.ToProperty(this, vm => vm.History).DisposeWith(d);
profileEditorService.Tools.Connect()
.Filter(t => t.ShowInToolbar)
.Sort(SortExpressionComparer<IToolViewModel>.Ascending(vm => vm.Order))
.Bind(out ReadOnlyObservableCollection<IToolViewModel> tools)
.Subscribe()
.DisposeWith(d);
Tools = tools;
});
}
public VisualEditorViewModel VisualEditorViewModel { get; }
@ -52,6 +67,12 @@ namespace Artemis.UI.Screens.ProfileEditor
public PropertiesViewModel PropertiesViewModel { get; }
public StatusBarViewModel StatusBarViewModel { get; }
public ReadOnlyObservableCollection<IToolViewModel> Tools
{
get => _tools;
set => this.RaiseAndSetIfChanged(ref _tools, value);
}
public ProfileConfiguration? ProfileConfiguration => _profileConfiguration?.Value;
public ProfileEditorHistory? History => _history?.Value;
public PluginSetting<double> TreeWidth => _settingsService.GetSetting("ProfileEditor.TreeWidth", 350.0);

View File

@ -1,56 +1,61 @@
using Artemis.Core;
using System.Collections.Generic;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.DefaultTypes.PropertyInput;
using Artemis.UI.Services.Interfaces;
using Artemis.UI.Shared.Services.ProfileEditor;
using Artemis.UI.Shared.Services.PropertyInput;
using DynamicData;
namespace Artemis.UI.Services
namespace Artemis.UI.Services;
public class RegistrationService : IRegistrationService
{
public class RegistrationService : IRegistrationService
private readonly IInputService _inputService;
private readonly IPropertyInputService _propertyInputService;
private bool _registeredBuiltInPropertyEditors;
public RegistrationService(IInputService inputService, IPropertyInputService propertyInputService, IProfileEditorService profileEditorService, IEnumerable<IToolViewModel> toolViewModels)
{
private readonly IInputService _inputService;
private readonly IPropertyInputService _propertyInputService;
private bool _registeredBuiltInPropertyEditors;
_inputService = inputService;
_propertyInputService = propertyInputService;
public RegistrationService(IInputService inputService, IPropertyInputService propertyInputService)
{
_inputService = inputService;
_propertyInputService = propertyInputService;
}
public void RegisterBuiltInDataModelDisplays()
{
}
profileEditorService.Tools.AddRange(toolViewModels);
}
public void RegisterBuiltInDataModelInputs()
{
}
public void RegisterBuiltInDataModelDisplays()
{
}
public void RegisterBuiltInPropertyEditors()
{
if (_registeredBuiltInPropertyEditors)
return;
public void RegisterBuiltInDataModelInputs()
{
}
_propertyInputService.RegisterPropertyInput<BrushPropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<ColorGradientPropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<FloatPropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<IntPropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<SKColorPropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<SKPointPropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<SKSizePropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput(typeof(EnumPropertyInputViewModel<>), Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<BoolPropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<FloatRangePropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<IntRangePropertyInputViewModel>(Constants.CorePlugin);
public void RegisterBuiltInPropertyEditors()
{
if (_registeredBuiltInPropertyEditors)
return;
_registeredBuiltInPropertyEditors = true;
}
_propertyInputService.RegisterPropertyInput<BrushPropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<ColorGradientPropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<FloatPropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<IntPropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<SKColorPropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<SKPointPropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<SKSizePropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput(typeof(EnumPropertyInputViewModel<>), Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<BoolPropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<FloatRangePropertyInputViewModel>(Constants.CorePlugin);
_propertyInputService.RegisterPropertyInput<IntRangePropertyInputViewModel>(Constants.CorePlugin);
public void RegisterControllers()
{
}
_registeredBuiltInPropertyEditors = true;
}
public void ApplyPreferredGraphicsContext()
{
}
public void RegisterControllers()
{
}
public void ApplyPreferredGraphicsContext()
{
}
}