using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Text.RegularExpressions; using SkiaSharp; namespace Artemis.Core { /// /// Represents an element of a /// public abstract class ProfileElement : BreakableModel, IDisposable { internal readonly List ChildrenList; private Guid _entityId; private string? _name; private int _order; private ProfileElement? _parent; private Profile _profile; private bool _suspended; internal ProfileElement(Profile profile) { _profile = profile; ChildrenList = new List(); Children = new ReadOnlyCollection(ChildrenList); } /// /// Gets the unique ID of this profile element /// public Guid EntityId { get => _entityId; internal set => SetAndNotify(ref _entityId, value); } /// /// Gets the profile this element belongs to /// public Profile Profile { get => _profile; internal set => SetAndNotify(ref _profile, value); } /// /// Gets the parent of this element /// public ProfileElement? Parent { get => _parent; internal set => SetAndNotify(ref _parent, value); } /// /// The element's children /// public ReadOnlyCollection Children { get; } /// /// The order in which this element appears in the update loop and editor /// public int Order { get => _order; internal set => SetAndNotify(ref _order, value); } /// /// The name which appears in the editor /// public string? Name { get => _name; set => SetAndNotify(ref _name, value); } /// /// Gets or sets the suspended state, if suspended the element is skipped in render and update /// public bool Suspended { get => _suspended; set => SetAndNotify(ref _suspended, value); } /// /// Gets a boolean indicating whether the profile element is disposed /// public bool Disposed { get; protected set; } #region Overrides of BreakableModel /// public override string BrokenDisplayName => Name ?? GetType().Name; #endregion /// /// Updates the element /// /// public abstract void Update(double deltaTime); /// /// Renders the element /// public abstract void Render(SKCanvas canvas, SKPointI basePosition); /// /// Resets the internal state of the element /// public abstract void Reset(); /// public override string ToString() { 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; /// /// Occurs when a child was added to the list of this element or any of it's descendents. /// public event EventHandler? DescendentAdded; /// /// Occurs when a child was removed from the list of this element or any of it's descendents. /// public event EventHandler? DescendentRemoved; /// /// 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)); } /// /// Invokes the event /// protected virtual void OnDescendentAdded(ProfileElement child) { DescendentAdded?.Invoke(this, new ProfileElementEventArgs(child)); Parent?.OnDescendentAdded(child); } /// /// Invokes the event /// protected virtual void OnDescendentRemoved(ProfileElement child) { DescendentRemoved?.Invoke(this, new ProfileElementEventArgs(child)); Parent?.OnDescendentRemoved(child); } /// /// Disposes the profile element /// protected virtual void Dispose(bool disposing) { if (disposing) { } } /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #region Hierarchy /// /// Adds a profile element to the collection, optionally at the given position (0-based) /// /// The profile element to add /// The order where to place the child (0-based), defaults to the end of the collection public virtual void AddChild(ProfileElement child, int? order = null) { if (Disposed) throw new ObjectDisposedException(GetType().Name); ProfileElement? current = this; while (current != null) { if (ReferenceEquals(child, this)) throw new ArtemisCoreException("Cannot make an element a child of itself"); current = current.Parent; } lock (ChildrenList) { if (ChildrenList.Contains(child)) return; // Add to the end of the list if (order == null) { ChildrenList.Add(child); } // Insert at the given index else { if (order < 0) order = 0; if (order > ChildrenList.Count) order = ChildrenList.Count; ChildrenList.Insert(order.Value, child); } child.Parent = this; StreamlineOrder(); } OnChildAdded(child); OnDescendentAdded(child); } /// /// Removes a profile element from the collection /// /// The profile element to remove public virtual void RemoveChild(ProfileElement child) { if (Disposed) throw new ObjectDisposedException(GetType().Name); lock (ChildrenList) { ChildrenList.Remove(child); StreamlineOrder(); child.Parent = null; } OnChildRemoved(child); OnDescendentRemoved(child); } private void StreamlineOrder() { for (int index = 0; index < ChildrenList.Count; index++) ChildrenList[index].Order = index + 1; } /// /// Returns a flattened list of all child render elements /// /// public List GetAllRenderElements() { if (Disposed) throw new ObjectDisposedException(GetType().Name); List elements = new(); foreach (RenderProfileElement childElement in Children.Where(c => c is RenderProfileElement).Cast()) { // Add all folders in this element elements.Add(childElement); // Add all folders in folders inside this element elements.AddRange(childElement.GetAllRenderElements()); } return elements; } /// /// Returns a flattened list of all child folders /// /// public List GetAllFolders() { if (Disposed) throw new ObjectDisposedException(GetType().Name); List folders = new(); foreach (Folder childFolder in Children.Where(c => c is Folder).Cast()) { // Add all folders in this element folders.Add(childFolder); // Add all folders in folders inside this element folders.AddRange(childFolder.GetAllFolders()); } return folders; } /// /// Returns a flattened list of all child layers /// /// public List GetAllLayers() { if (Disposed) throw new ObjectDisposedException(GetType().Name); List layers = new(); // Add all layers in this element layers.AddRange(Children.Where(c => c is Layer).Cast()); // Add all layers in folders inside this element foreach (Folder childFolder in Children.Where(c => c is Folder).Cast()) layers.AddRange(childFolder.GetAllLayers()); 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(string baseName = "New layer") { if (!Children.Any(c => c is Layer && c.Name == baseName)) return baseName; int current = 2; while (true) { if (Children.Where(c => c is Layer).All(c => c.Name != $"{baseName} ({current})")) return $"{baseName} ({current})"; current++; } } /// /// 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(string baseName = "New folder") { if (!Children.Any(c => c is Folder && c.Name == baseName)) return baseName; int current = 2; while (true) { if (Children.Where(c => c is Folder).All(c => c.Name != $"{baseName} ({current})")) return $"{baseName} ({current})"; current++; } } #endregion #region Storage internal abstract void Load(); internal abstract void Save(); #endregion } }