using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using SkiaSharp; namespace Artemis.Core { /// /// Represents an element of a /// public abstract class ProfileElement : BreakableModel, IDisposable { private Guid _entityId; private string? _name; private int _order; private ProfileElement? _parent; private Profile _profile; private bool _suspended; internal readonly List ChildrenList; internal ProfileElement(Profile profile) { _profile = profile; ChildrenList = new List(); Children = new(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; } /// /// 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}"; } #region Overrides of BreakableModel /// public override string BrokenDisplayName => Name ?? GetType().Name; #endregion #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); 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(); } /// /// 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(); } private void StreamlineOrder() { for (int index = 0; index < ChildrenList.Count; index++) ChildrenList[index].Order = index; } /// /// 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; } #endregion #region Storage internal abstract void Load(); internal abstract void Save(); #endregion #region IDisposable /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Disposes the profile element /// protected virtual void Dispose(bool disposing) { if (disposing) { } } #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() { ChildAdded?.Invoke(this, EventArgs.Empty); } /// /// Invokes the event /// protected virtual void OnChildRemoved() { ChildRemoved?.Invoke(this, EventArgs.Empty); } #endregion } }