diff --git a/src/Artemis.Core/Models/Profile/ProfileElement.cs b/src/Artemis.Core/Models/Profile/ProfileElement.cs index 9d97fd638..01c8f5ab8 100644 --- a/src/Artemis.Core/Models/Profile/ProfileElement.cs +++ b/src/Artemis.Core/Models/Profile/ProfileElement.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Text.RegularExpressions; using SkiaSharp; namespace Artemis.Core @@ -11,6 +12,7 @@ namespace Artemis.Core /// public abstract class ProfileElement : BreakableModel, IDisposable { + internal readonly List ChildrenList; private Guid _entityId; private string? _name; private int _order; @@ -18,8 +20,6 @@ namespace Artemis.Core private Profile _profile; private bool _suspended; - internal readonly List ChildrenList; - internal ProfileElement(Profile profile) { _profile = profile; @@ -120,6 +120,49 @@ namespace Artemis.Core return $"{nameof(EntityId)}: {EntityId}, {nameof(Order)}: {Order}, {nameof(Name)}: {Name}"; } + /// + /// Occurs when a child was added to the list + /// + public event EventHandler? ChildAdded; + + /// + /// Occurs when a child was removed from the list + /// + public event EventHandler? ChildRemoved; + + /// + /// Invokes the event + /// + protected virtual void OnChildAdded(ProfileElement child) + { + ChildAdded?.Invoke(this, new ProfileElementEventArgs(child)); + } + + /// + /// Invokes the event + /// + protected virtual void OnChildRemoved(ProfileElement child) + { + ChildRemoved?.Invoke(this, new ProfileElementEventArgs(child)); + } + + /// + /// Disposes the profile element + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + } + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #region Hierarchy /// @@ -248,6 +291,48 @@ namespace Artemis.Core return layers; } + /// + /// Returns a name for a new layer according to any other layers with a default name similar to creating new folders in + /// Explorer + /// + /// The resulting name i.e. New layer or New layer (2) + public string GetNewLayerName() + { + if (!Children.Any(c => c is Layer)) + return "New layer"; + + // Grab existing unnamed layers and get the first available number + // Looks slow but it's not https://stackoverflow.com/a/8865806/5015269 + Regex regex = new(@"New layer \((\d+)\)"); + int firstAvailable = Enumerable.Range(1, int.MaxValue) + .Except(Children.Where(c => c is Layer && c.Name != null && regex.IsMatch(c.Name)) + .Select(c => int.Parse(regex.Match(c.Name!).Groups[1].Value)) + .OrderBy(i => i)) + .First(); + return $"New layer ({firstAvailable})"; + } + + /// + /// Returns a name for a new folder according to any other folders with a default name similar to creating new folders + /// in Explorer + /// + /// The resulting name i.e. New folder or New folder (2) + public string GetNewFolderName() + { + if (!Children.Any(c => c is Folder)) + return "New folder"; + + // Grab existing unnamed layers and get the first available number + // Looks slow but it's not https://stackoverflow.com/a/8865806/5015269 + Regex regex = new(@"New folder \((\d+)\)"); + int firstAvailable = Enumerable.Range(1, int.MaxValue) + .Except(Children.Where(c => c is Folder && c.Name != null && regex.IsMatch(c.Name)) + .Select(c => int.Parse(regex.Match(c.Name!).Groups[1].Value)) + .OrderBy(i => i)) + .First(); + return $"New folder ({firstAvailable})"; + } + #endregion #region Storage @@ -256,56 +341,5 @@ namespace Artemis.Core internal abstract void Save(); #endregion - - #region Events - - /// - /// Occurs when a child was added to the list - /// - public event EventHandler? ChildAdded; - - /// - /// Occurs when a child was removed from the list - /// - public event EventHandler? ChildRemoved; - - /// - /// Invokes the event - /// - protected virtual void OnChildAdded(ProfileElement child) - { - ChildAdded?.Invoke(this, new ProfileElementEventArgs(child)); - } - - /// - /// Invokes the event - /// - protected virtual void OnChildRemoved(ProfileElement child) - { - ChildRemoved?.Invoke(this, new ProfileElementEventArgs(child)); - } - - #endregion - - #region IDisposable - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Disposes the profile element - /// - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - } - } - - #endregion } } \ No newline at end of file diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs index 1c0cb235b..2a9d57e8f 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/ProfileTree/TreeItemViewModel.cs @@ -42,13 +42,13 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree { if (ProfileElement is Layer targetLayer) { - Layer layer = new(targetLayer.Parent, "New layer"); + Layer layer = new(targetLayer.Parent, targetLayer.GetNewLayerName()); layer.AddLeds(rgbService.EnabledDevices.SelectMany(d => d.Leds)); profileEditorService.ExecuteCommand(new AddProfileElement(layer, targetLayer.Parent, targetLayer.Order)); } else if (ProfileElement != null) { - Layer layer = new(ProfileElement, "New layer"); + Layer layer = new(ProfileElement, ProfileElement.GetNewLayerName()); layer.AddLeds(rgbService.EnabledDevices.SelectMany(d => d.Leds)); profileEditorService.ExecuteCommand(new AddProfileElement(layer, ProfileElement, 0)); } @@ -57,9 +57,9 @@ namespace Artemis.UI.Screens.ProfileEditor.ProfileTree AddFolder = ReactiveCommand.Create(() => { if (ProfileElement is Layer targetLayer) - profileEditorService.ExecuteCommand(new AddProfileElement(new Folder(targetLayer.Parent, "New folder"), targetLayer.Parent, targetLayer.Order)); + profileEditorService.ExecuteCommand(new AddProfileElement(new Folder(targetLayer.Parent, targetLayer.Parent.GetNewFolderName()), targetLayer.Parent, targetLayer.Order)); else if (ProfileElement != null) - profileEditorService.ExecuteCommand(new AddProfileElement(new Folder(ProfileElement, "New folder"), ProfileElement, 0)); + profileEditorService.ExecuteCommand(new AddProfileElement(new Folder(ProfileElement, ProfileElement.GetNewFolderName()), ProfileElement, 0)); }); Rename = ReactiveCommand.Create(() => 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 43ca7c63c..489305eb8 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/VisualEditorViewModel.cs @@ -56,6 +56,7 @@ public class VisualEditorViewModel : ActivatableViewModelBase private void CreateVisualizers(ProfileConfiguration? profileConfiguration) { + // TODO: Monitor and respond to new layers/folders and deletions _visualizers.Edit(list => { list.Clear(); diff --git a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerVisualizerViewModel.cs b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerVisualizerViewModel.cs index f7e934dc6..b673579bf 100644 --- a/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerVisualizerViewModel.cs +++ b/src/Avalonia/Artemis.UI/Screens/ProfileEditor/Panels/VisualEditor/Visualizers/LayerVisualizerViewModel.cs @@ -7,7 +7,6 @@ using Artemis.UI.Shared.Services.ProfileEditor; using Avalonia; using Avalonia.Controls.Mixins; using ReactiveUI; -using ShimSkiaSharp; using SKRect = SkiaSharp.SKRect; namespace Artemis.UI.Screens.ProfileEditor.VisualEditor.Visualizers;