using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Core.LayerEffects;
using Artemis.Storage.Entities.Profile;
using Artemis.Storage.Entities.Profile.Abstract;
using SkiaSharp;
namespace Artemis.Core;
///
/// Represents a folder in a
///
public sealed class Folder : RenderProfileElement
{
private bool _isExpanded;
///
/// Creates a new instance of the class and adds itself to the child collection of the provided
///
///
/// The parent of the folder
/// The name of the folder
public Folder(ProfileElement parent, string name) : base(parent, parent.Profile)
{
FolderEntity = new FolderEntity();
EntityId = Guid.NewGuid();
Profile = Parent.Profile;
Name = name;
}
///
/// Creates a new instance of the class based on the provided folder entity
///
/// The profile the folder belongs to
/// The parent of the folder
/// The entity of the folder
public Folder(Profile profile, ProfileElement parent, FolderEntity folderEntity) : base(parent, parent.Profile)
{
FolderEntity = folderEntity;
EntityId = folderEntity.Id;
Profile = profile;
Name = folderEntity.Name;
IsExpanded = folderEntity.IsExpanded;
Suspended = folderEntity.Suspended;
Order = folderEntity.Order;
Load();
}
///
/// Gets a boolean indicating whether this folder is at the root of the profile tree
///
public bool IsRootFolder => Parent == Profile;
///
/// Gets or sets a boolean indicating whether this folder is expanded
///
public bool IsExpanded
{
get => _isExpanded;
set => SetAndNotify(ref _isExpanded, value);
}
///
/// Gets the folder entity this folder uses for persistent storage
///
public FolderEntity FolderEntity { get; internal set; }
///
public override bool ShouldBeEnabled => !Suspended && DisplayConditionMet && !Timeline.IsFinished;
internal override RenderElementEntity RenderElementEntity => FolderEntity;
///
public override List GetAllLayerProperties()
{
List result = new();
foreach (BaseLayerEffect layerEffect in LayerEffects)
{
if (layerEffect.BaseProperties != null)
result.AddRange(layerEffect.BaseProperties.GetAllLayerProperties());
}
return result;
}
///
public override void Update(double deltaTime)
{
if (Disposed)
throw new ObjectDisposedException("Folder");
if (Timeline.IsOverridden)
{
Timeline.ClearOverride();
return;
}
try
{
UpdateDisplayCondition();
UpdateTimeline(deltaTime);
if (ShouldBeEnabled)
Enable();
else if (Timeline.IsFinished)
Disable();
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
baseLayerEffect.InternalUpdate(Timeline);
foreach (ProfileElement child in Children)
child.Update(deltaTime);
}
finally
{
Timeline.ClearDelta();
}
}
///
public override void Reset()
{
UpdateDisplayCondition();
if (DisplayConditionMet)
Timeline.JumpToStart();
else
Timeline.JumpToEnd();
foreach (ProfileElement child in Children)
child.Reset();
}
///
public override void AddChild(ProfileElement child, int? order = null)
{
if (Disposed)
throw new ObjectDisposedException("Folder");
base.AddChild(child, order);
CalculateRenderProperties();
}
///
public override void RemoveChild(ProfileElement child)
{
if (Disposed)
throw new ObjectDisposedException("Folder");
base.RemoveChild(child);
CalculateRenderProperties();
}
///
/// Creates a deep copy of the folder
///
/// The newly created copy
public Folder CreateCopy()
{
if (Parent == null)
throw new ArtemisCoreException("Cannot create a copy of a folder without a parent");
FolderEntity entityCopy = CoreJson.Deserialize(CoreJson.Serialize(FolderEntity))!;
entityCopy.Id = Guid.NewGuid();
entityCopy.Name += " - Copy";
// TODO Children
return new Folder(Profile, Parent, entityCopy);
}
///
public override string ToString()
{
return $"[Folder] {nameof(Name)}: {Name}, {nameof(Order)}: {Order}";
}
///
public override IEnumerable GetFeatureDependencies()
{
return LayerEffects.SelectMany(e => e.GetFeatureDependencies())
.Concat(Children.SelectMany(c => c.GetFeatureDependencies()))
.Concat(DisplayCondition.GetFeatureDependencies());
}
#region Rendering
///
public override void Render(SKCanvas canvas, SKPointI basePosition, ProfileElement? editorFocus)
{
if (Disposed)
throw new ObjectDisposedException("Folder");
// Ensure the folder is ready
if (!Enabled || Path == null)
return;
// No point rendering if all children are disabled
if (!Children.Any(c => c is RenderProfileElement {Enabled: true}))
return;
// If the editor focus is on this folder, discard further focus for children to effectively focus the entire folder and all descendants
if (editorFocus == this)
editorFocus = null;
SKPaint layerPaint = new() {FilterQuality = SKFilterQuality.Low};
try
{
SKRectI rendererBounds = SKRectI.Create(0, 0, Bounds.Width, Bounds.Height);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
{
if (!baseLayerEffect.Suspended)
baseLayerEffect.InternalPreProcess(canvas, rendererBounds, layerPaint);
}
// No point rendering if the alpha was set to zero by one of the effects
if (layerPaint.Color.Alpha == 0)
return;
canvas.SaveLayer(layerPaint);
canvas.Translate(Bounds.Left - basePosition.X, Bounds.Top - basePosition.Y);
// Iterate the children in reverse because the first layer must be rendered last to end up on top
for (int index = Children.Count - 1; index > -1; index--)
Children[index].Render(canvas, new SKPointI(Bounds.Left, Bounds.Top), editorFocus);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
{
if (!baseLayerEffect.Suspended)
baseLayerEffect.InternalPostProcess(canvas, rendererBounds, layerPaint);
}
}
finally
{
canvas.Restore();
layerPaint.DisposeSelfAndProperties();
}
}
#endregion
///
public override void Enable()
{
if (Enabled)
return;
// Not enabling children, they'll enable themselves during their own Update
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.InternalEnable();
Enabled = true;
}
///
public override void Disable()
{
if (!Enabled)
return;
foreach (BaseLayerEffect baseLayerEffect in LayerEffects)
baseLayerEffect.InternalDisable();
// Disabling children since their Update won't get called with their parent disabled
foreach (ProfileElement profileElement in Children)
{
if (profileElement is RenderProfileElement renderProfileElement)
renderProfileElement.Disable();
}
Enabled = false;
}
///
public override void OverrideTimelineAndApply(TimeSpan position)
{
DisplayCondition.OverrideTimeline(position);
foreach (BaseLayerEffect baseLayerEffect in LayerEffects.Where(e => !e.Suspended))
baseLayerEffect.InternalUpdate(Timeline);
;
}
///
/// Occurs when a property affecting the rendering properties of this folder has been updated
///
public event EventHandler? RenderPropertiesUpdated;
#region Overrides of BreakableModel
///
public override IEnumerable GetBrokenHierarchy()
{
return LayerEffects.Where(e => e.BrokenState != null);
}
#endregion
///
protected override void Dispose(bool disposing)
{
Disposed = true;
Disable();
foreach (ProfileElement profileElement in Children)
profileElement.Dispose();
base.Dispose(disposing);
}
internal void CalculateRenderProperties()
{
if (Disposed)
throw new ObjectDisposedException("Folder");
SKPath path = new() {FillType = SKPathFillType.Winding};
foreach (ProfileElement child in Children)
{
if (child is RenderProfileElement effectChild && effectChild.Path != null)
path.AddPath(effectChild.Path);
}
Path = path;
// Folder render properties are based on child paths and thus require an update
if (Parent is Folder folder)
folder.CalculateRenderProperties();
OnRenderPropertiesUpdated();
}
internal override void Load()
{
Reset();
// Load child folders
foreach (FolderEntity childFolder in Profile.ProfileEntity.Folders.Where(f => f.ParentId == EntityId))
ChildrenList.Add(new Folder(Profile, this, childFolder));
// Load child layers
foreach (LayerEntity childLayer in Profile.ProfileEntity.Layers.Where(f => f.ParentId == EntityId))
ChildrenList.Add(new Layer(Profile, this, childLayer));
// Ensure order integrity, should be unnecessary but no one is perfect specially me
ChildrenList.Sort((a, b) => a.Order.CompareTo(b.Order));
for (int index = 0; index < ChildrenList.Count; index++)
ChildrenList[index].Order = index + 1;
LoadRenderElement();
}
internal override void Save()
{
if (Disposed)
throw new ObjectDisposedException("Folder");
FolderEntity.Id = EntityId;
FolderEntity.ParentId = Parent?.EntityId ?? new Guid();
FolderEntity.Order = Order;
FolderEntity.Name = Name;
FolderEntity.IsExpanded = IsExpanded;
FolderEntity.Suspended = Suspended;
SaveRenderElement();
}
private void OnRenderPropertiesUpdated()
{
RenderPropertiesUpdated?.Invoke(this, EventArgs.Empty);
}
}