From a0ac0f6e5d2ff6747fb35c8cd34184a1cee2cd64 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Tue, 21 Mar 2023 21:27:52 +0900 Subject: [PATCH 01/84] =?UTF-8?q?LogicalTree=E3=82=92Hierarchical=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Collections/HierarchicalList.cs | 14 ++ src/Beutl.Core/Collections/LogicalList.cs | 14 -- ...tExtensions.cs => CoreObjectExtensions.cs} | 0 src/Beutl.Core/Element.cs | 120 ---------- src/Beutl.Core/Hierarchy/Hierarchical.cs | 199 ++++++++++++++++ .../Hierarchy/HierarchicalExtensions.cs | 112 +++++++++ .../Hierarchy/HierarchyAttachmentEventArgs.cs | 14 ++ .../Hierarchy/HierarchyException.cs | 15 ++ src/Beutl.Core/Hierarchy/IHierarchical.cs | 15 ++ src/Beutl.Core/Hierarchy/IHierarchicalRoot.cs | 5 + .../Hierarchy/IModifiableHierarchical.cs | 14 ++ src/Beutl.Core/ILogicalElement.cs | 15 -- src/Beutl.Core/ITopLevel.cs | 6 - .../LogicalTreeAttachmentEventArgs.cs | 11 - src/Beutl.Core/LogicalTreeException.cs | 15 -- src/Beutl.Core/LogicalTreeExtensions.cs | 125 ---------- src/Beutl.Core/PropertyChangeTracker.cs | 8 +- src/Beutl.Framework/IWorkspace.cs | 4 +- src/Beutl.Framework/IWorkspaceItem.cs | 2 +- src/Beutl.Graphics/Audio/Sound.cs | 8 +- src/Beutl.Graphics/Audio/SourceSound.cs | 8 +- src/Beutl.Graphics/Graphics/Canvas.cs | 36 --- src/Beutl.Graphics/Graphics/Drawable.cs | 2 +- .../Graphics/Drawables/VideoFrame.cs | 10 +- src/Beutl.Graphics/Graphics/ICanvas.cs | 3 - src/Beutl.Graphics/Graphics/NullCanvas.cs | 3 - .../Graphics/Shapes/TextBlock.cs | 29 +-- .../Graphics/Shapes/TextElements.cs | 43 +--- src/Beutl.Graphics/Graphics/SourceImage.cs | 10 +- src/Beutl.Graphics/LogicalElementImpl.cs | 38 --- .../TextFormatting/Compat/FormattedText.cs | 152 ------------ .../Compat/FormattedTextParser.cs | 204 ---------------- .../TextFormatting/Compat/TextElement.cs | 168 ------------- .../Media/TextFormatting/Compat/TextLine.cs | 135 ----------- .../Rendering/RenderLayerSpan.cs | 2 +- src/Beutl.Graphics/Rendering/Renderables.cs | 4 +- src/Beutl.Graphics/Styling/IStyleable.cs | 2 +- src/Beutl.Graphics/Styling/Styleable.cs | 224 ++++++++++++------ .../Configure/ConfigureOperator.cs | 4 +- .../Handler/DefaultSourceHandler.cs | 10 +- src/Beutl.ProjectSystem/NodeTree/INodeItem.cs | 2 +- .../NodeTree/LayerNodeTreeModel.cs | 2 +- src/Beutl.ProjectSystem/NodeTree/Node.cs | 12 +- src/Beutl.ProjectSystem/NodeTree/NodeItem.cs | 2 +- .../NodeTree/NodeTreeSpace.cs | 8 +- .../NodeTree/Nodes/Group/GroupNode.cs | 10 +- .../NodeTree/Nodes/OutputNode.cs | 4 +- .../Operation/SourceOperator.cs | 2 +- .../Operation/SourceOperators.cs | 21 +- .../Operation/SourceStyler.cs | 4 +- .../ProjectSystem/Layer.cs | 45 ++-- .../ProjectSystem/Layers.cs | 4 +- .../ProjectSystem/Project.cs | 8 +- .../ProjectSystem/Scene.cs | 15 +- src/Beutl.ProjectSystem/SceneRenderer.cs | 2 +- .../GraphEditorKeyFrameViewModel.cs | 4 +- .../ViewModels/InlineKeyFrameViewModel.cs | 2 +- .../NodeTree/NodeTreeTabViewModel.cs | 4 +- .../ViewModels/NodeTree/NodeViewModel.cs | 2 +- src/Beutl/ViewModels/PlayerViewModel.cs | 2 +- .../ViewModels/TimelineLayerViewModel.cs | 6 +- src/Beutl/Views/GraphEditorView.axaml.cs | 6 +- src/Beutl/Views/InlineAnimationLayer.axaml.cs | 2 +- src/Beutl/Views/Timeline.axaml.cs | 6 +- src/Beutl/Views/TimelineLayer.axaml.cs | 2 +- .../Views/Tools/SourceOperatorView.axaml.cs | 4 +- .../PropertyChangeTrackerTests.cs | 10 +- 67 files changed, 672 insertions(+), 1322 deletions(-) create mode 100644 src/Beutl.Core/Collections/HierarchicalList.cs delete mode 100644 src/Beutl.Core/Collections/LogicalList.cs rename src/Beutl.Core/{ElementExtensions.cs => CoreObjectExtensions.cs} (100%) delete mode 100644 src/Beutl.Core/Element.cs create mode 100644 src/Beutl.Core/Hierarchy/Hierarchical.cs create mode 100644 src/Beutl.Core/Hierarchy/HierarchicalExtensions.cs create mode 100644 src/Beutl.Core/Hierarchy/HierarchyAttachmentEventArgs.cs create mode 100644 src/Beutl.Core/Hierarchy/HierarchyException.cs create mode 100644 src/Beutl.Core/Hierarchy/IHierarchical.cs create mode 100644 src/Beutl.Core/Hierarchy/IHierarchicalRoot.cs create mode 100644 src/Beutl.Core/Hierarchy/IModifiableHierarchical.cs delete mode 100644 src/Beutl.Core/ILogicalElement.cs delete mode 100644 src/Beutl.Core/ITopLevel.cs delete mode 100644 src/Beutl.Core/LogicalTreeAttachmentEventArgs.cs delete mode 100644 src/Beutl.Core/LogicalTreeException.cs delete mode 100644 src/Beutl.Core/LogicalTreeExtensions.cs delete mode 100644 src/Beutl.Graphics/LogicalElementImpl.cs delete mode 100644 src/Beutl.Graphics/Media/TextFormatting/Compat/FormattedText.cs delete mode 100644 src/Beutl.Graphics/Media/TextFormatting/Compat/FormattedTextParser.cs delete mode 100644 src/Beutl.Graphics/Media/TextFormatting/Compat/TextElement.cs delete mode 100644 src/Beutl.Graphics/Media/TextFormatting/Compat/TextLine.cs diff --git a/src/Beutl.Core/Collections/HierarchicalList.cs b/src/Beutl.Core/Collections/HierarchicalList.cs new file mode 100644 index 000000000..31e9317f9 --- /dev/null +++ b/src/Beutl.Core/Collections/HierarchicalList.cs @@ -0,0 +1,14 @@ +namespace Beutl.Collections; + +public class HierarchicalList : CoreList + where T : IHierarchical +{ + public HierarchicalList(IModifiableHierarchical parent) + { + Parent = parent; + Attached += item => parent.AddChild(item); + Detached += item => parent.RemoveChild(item); + } + + public IModifiableHierarchical Parent { get; } +} diff --git a/src/Beutl.Core/Collections/LogicalList.cs b/src/Beutl.Core/Collections/LogicalList.cs deleted file mode 100644 index 0b6bee869..000000000 --- a/src/Beutl.Core/Collections/LogicalList.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Beutl.Collections; - -public class LogicalList : CoreList - where T : ILogicalElement -{ - public LogicalList(ILogicalElement parent) - { - Parent = parent; - Attached += item => item.NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(Parent)); - Detached += item => item.NotifyDetachedFromLogicalTree(new LogicalTreeAttachmentEventArgs(Parent)); - } - - public ILogicalElement Parent { get; } -} diff --git a/src/Beutl.Core/ElementExtensions.cs b/src/Beutl.Core/CoreObjectExtensions.cs similarity index 100% rename from src/Beutl.Core/ElementExtensions.cs rename to src/Beutl.Core/CoreObjectExtensions.cs diff --git a/src/Beutl.Core/Element.cs b/src/Beutl.Core/Element.cs deleted file mode 100644 index 85b4be605..000000000 --- a/src/Beutl.Core/Element.cs +++ /dev/null @@ -1,120 +0,0 @@ -namespace Beutl; - -public abstract class Element : CoreObject, ILogicalElement -{ - public static readonly CoreProperty ParentProperty; - private ILogicalElement? _parent; - - static Element() - { - ParentProperty = ConfigureProperty(nameof(Parent)) - .Accessor(o => o.Parent, (o, v) => o.Parent = v) - .Register(); - } - - public Element? Parent - { - get => _parent as Element; - private set - { - var parent = Parent; - SetAndRaise(ParentProperty, ref parent, value); - _parent = parent; - } - } - - ILogicalElement? ILogicalElement.LogicalParent => _parent; - - IEnumerable ILogicalElement.LogicalChildren => OnEnumerateChildren(); - - public event EventHandler? AttachedToLogicalTree; - - public event EventHandler? DetachedFromLogicalTree; - - protected static void LogicalChild( - CoreProperty? property1 = null, - CoreProperty? property2 = null, - CoreProperty? property3 = null, - CoreProperty? property4 = null) - where T : Element - { - static void onNext(CorePropertyChangedEventArgs e) - { - if (e.Sender is T s) - { - if (e.OldValue is ILogicalElement oldLogical) - { - oldLogical.NotifyDetachedFromLogicalTree(new LogicalTreeAttachmentEventArgs(s)); - } - - if (e.NewValue is ILogicalElement newLogical) - { - newLogical.NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(s)); - } - } - } - - property1?.Changed.Subscribe(onNext); - property2?.Changed.Subscribe(onNext); - property3?.Changed.Subscribe(onNext); - property4?.Changed.Subscribe(onNext); - } - - protected static void LogicalChild(params CoreProperty[] properties) - where T : Element - { - static void onNext(CorePropertyChangedEventArgs e) - { - if (e.Sender is T s) - { - if (e.OldValue is ILogicalElement oldLogical) - { - oldLogical.NotifyDetachedFromLogicalTree(new LogicalTreeAttachmentEventArgs(s)); - } - - if (e.NewValue is ILogicalElement newLogical) - { - newLogical.NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(s)); - } - } - } - - foreach (CoreProperty? item in properties) - { - item.Changed.Subscribe(onNext); - } - } - - protected virtual void OnAttachedToLogicalTree(in LogicalTreeAttachmentEventArgs args) - { - } - - protected virtual void OnDetachedFromLogicalTree(in LogicalTreeAttachmentEventArgs args) - { - } - - protected virtual IEnumerable OnEnumerateChildren() - { - yield break; - } - - void ILogicalElement.NotifyAttachedToLogicalTree(in LogicalTreeAttachmentEventArgs e) - { - if (_parent is { }) - throw new LogicalTreeException("This logical element already has a parent element."); - - OnAttachedToLogicalTree(e); - _parent = e.Parent; - AttachedToLogicalTree?.Invoke(this, e); - } - - void ILogicalElement.NotifyDetachedFromLogicalTree(in LogicalTreeAttachmentEventArgs e) - { - if (!ReferenceEquals(e.Parent, _parent)) - throw new LogicalTreeException("The detach source element and the parent element do not match."); - - OnDetachedFromLogicalTree(e); - _parent = null; - DetachedFromLogicalTree?.Invoke(this, e); - } -} diff --git a/src/Beutl.Core/Hierarchy/Hierarchical.cs b/src/Beutl.Core/Hierarchy/Hierarchical.cs new file mode 100644 index 000000000..d65d80fa0 --- /dev/null +++ b/src/Beutl.Core/Hierarchy/Hierarchical.cs @@ -0,0 +1,199 @@ +using System.Collections; +using System.Collections.Specialized; + +using Beutl.Collections; + +namespace Beutl; + +public abstract class Hierarchical : CoreObject, IHierarchical, IModifiableHierarchical +{ + public static readonly CoreProperty HierarchicalParentProperty; + private readonly CoreList _hierarchicalChildren; + private IHierarchical? _parent; + private IHierarchicalRoot? _root; + + static Hierarchical() + { + HierarchicalParentProperty = ConfigureProperty(nameof(HierarchicalParent)) + .Accessor(o => o.HierarchicalParent, (o, v) => o.HierarchicalParent = v) + .Register(); + } + + public Hierarchical() + { + _root = this as IHierarchicalRoot; + _hierarchicalChildren = new CoreList() + { + ResetBehavior = ResetBehavior.Remove + }; + _hierarchicalChildren.CollectionChanged += HierarchicalChildrenCollectionChanged; + } + + public IHierarchical? HierarchicalParent + { + get => _parent; + private set => SetAndRaise(HierarchicalParentProperty, ref _parent, value); + } + + protected ICoreList HierarchicalChildren => _hierarchicalChildren; + + IHierarchical? IHierarchical.HierarchicalParent => _parent; + + IHierarchicalRoot? IHierarchical.HierarchicalRoot => _root; + + ICoreReadOnlyList IHierarchical.HierarchicalChildren => HierarchicalChildren; + + public event EventHandler? AttachedToHierarchy; + + public event EventHandler? DetachedFromHierarchy; + + protected virtual void HierarchicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + void SetParent(IList children) + { + int count = children.Count; + + for (int i = 0; i < count; i++) + { + var logical = (IHierarchical)children[i]!; + + if (logical.HierarchicalParent is null) + { + (logical as IModifiableHierarchical)?.SetParent(this); + } + } + } + + void ClearParent(IList children) + { + int count = children.Count; + + for (int i = 0; i < count; i++) + { + var logical = (IHierarchical)children[i]!; + + if (logical.HierarchicalParent == this) + { + (logical as IModifiableHierarchical)?.SetParent(null); + } + } + } + + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + SetParent(e.NewItems!); + break; + + case NotifyCollectionChangedAction.Remove: + ClearParent(e.OldItems!); + break; + + case NotifyCollectionChangedAction.Replace: + ClearParent(e.OldItems!); + SetParent(e.NewItems!); + break; + + case NotifyCollectionChangedAction.Reset: + break; + } + } + + protected virtual void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs args) + { + } + + protected virtual void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) + { + } + + private void OnAttachedToHierarchyCore(in HierarchyAttachmentEventArgs e) + { + if (_parent == null && this is not IHierarchicalRoot) + { + throw new InvalidOperationException( + $"AttachedToLogicalTreeCore called for '{GetType().Name}' but element has no logical parent."); + } + + if (_root == null) + { + _root = e.Root; + OnAttachedToHierarchy(e); + AttachedToHierarchy?.Invoke(this, e); + } + + _ = HierarchicalChildren; + foreach (IHierarchical item in _hierarchicalChildren!.GetMarshal().Value) + { + (item as IModifiableHierarchical)?.NotifyAttachedToHierarchy(e); + } + } + + private void OnDetachedFromHierarchyCore(in HierarchyAttachmentEventArgs e) + { + if (_root != null) + { + _root = null; + OnDetachedFromHierarchy(e); + DetachedFromHierarchy?.Invoke(this, e); + + _ = HierarchicalChildren; + foreach (IHierarchical item in _hierarchicalChildren!.GetMarshal().Value) + { + (item as IModifiableHierarchical)?.NotifyDetachedFromHierarchy(e); + } + } + } + + void IModifiableHierarchical.NotifyAttachedToHierarchy(in HierarchyAttachmentEventArgs e) + { + OnAttachedToHierarchyCore(e); + } + + void IModifiableHierarchical.NotifyDetachedFromHierarchy(in HierarchyAttachmentEventArgs e) + { + OnDetachedFromHierarchyCore(e); + } + + void IModifiableHierarchical.SetParent(IHierarchical? parent) + { + IHierarchical? old = _parent; + + if (parent != old) + { + if (old != null && parent != null) + { + throw new InvalidOperationException("This logical element already has a parent."); + } + + if (_root != null) + { + var e = new HierarchyAttachmentEventArgs(_root, old); + OnDetachedFromHierarchyCore(e); + } + + _parent = parent; + IHierarchicalRoot? newRoot = this.FindHierarchicalRoot(); + + if (newRoot != null) + { + var e = new HierarchyAttachmentEventArgs(newRoot, parent); + OnAttachedToHierarchyCore(e); + } + + // Raise PropertyChanged + _parent = old; + HierarchicalParent = parent; + } + } + + void IModifiableHierarchical.AddChild(IHierarchical child) + { + HierarchicalChildren.Add(child); + } + + void IModifiableHierarchical.RemoveChild(IHierarchical child) + { + HierarchicalChildren.Remove(child); + } +} diff --git a/src/Beutl.Core/Hierarchy/HierarchicalExtensions.cs b/src/Beutl.Core/Hierarchy/HierarchicalExtensions.cs new file mode 100644 index 000000000..3ed5e34bb --- /dev/null +++ b/src/Beutl.Core/Hierarchy/HierarchicalExtensions.cs @@ -0,0 +1,112 @@ +namespace Beutl; + +public static class HierarchicalExtensions +{ + public static T FindRequiredHierarchicalParent(this IHierarchical self, bool includeSelf = false) + { + T? parent = self.FindHierarchicalParent(includeSelf); + if (parent == null) throw new HierarchyException("Cannot get parent."); + + return parent; + } + + public static T? FindHierarchicalParent(this IHierarchical self, bool includeSelf = false) + { + try + { + IHierarchical? obj = includeSelf ? self : self.HierarchicalParent; + + while (obj is not T) + { + if (obj is null) + { + return default; + } + + obj = obj.HierarchicalParent; + } + + if (obj is T result) + { + return result; + } + else + { + return default; + } + } + catch + { + return default; + } + } + + public static IHierarchical FindRequiredHierarchicalParent(this IHierarchical self, Type type, bool includeSelf = false) + { + IHierarchical? parent = self.FindHierarchicalParent(type, includeSelf); + if (parent == null) throw new HierarchyException("Cannot get parent."); + + return parent; + } + + public static IHierarchical? FindHierarchicalParent(this IHierarchical self, Type type, bool includeSelf = false) + { + try + { + IHierarchical? obj = includeSelf ? self : self.HierarchicalParent; + Type? objType = obj?.GetType(); + + while (objType?.IsAssignableTo(type) != true) + { + if (obj is null) + { + return default; + } + + obj = obj.HierarchicalParent; + objType = obj?.GetType(); + } + + if (obj != null && objType?.IsAssignableTo(type) == true) + { + return obj; + } + else + { + return default; + } + } + catch + { + return default; + } + } + + public static IHierarchicalRoot? FindHierarchicalRoot(this IHierarchical self) + { + while (self != null) + { + if (self is IHierarchicalRoot root) + { + return root; + } + + self = self.HierarchicalParent!; + } + + return null; + } + + public static IEnumerable EnumerateAllChildren(this IHierarchical self) + { + foreach (IHierarchical? item in self.HierarchicalChildren) + { + foreach (TResult? innerItem in EnumerateAllChildren(item)) + { + yield return innerItem; + } + + if (item is TResult t) yield return t; + } + } +} diff --git a/src/Beutl.Core/Hierarchy/HierarchyAttachmentEventArgs.cs b/src/Beutl.Core/Hierarchy/HierarchyAttachmentEventArgs.cs new file mode 100644 index 000000000..be4ce970c --- /dev/null +++ b/src/Beutl.Core/Hierarchy/HierarchyAttachmentEventArgs.cs @@ -0,0 +1,14 @@ +namespace Beutl; + +public readonly struct HierarchyAttachmentEventArgs +{ + public HierarchyAttachmentEventArgs(IHierarchicalRoot root, IHierarchical? parent) + { + Root = root; + Parent = parent; + } + + public IHierarchicalRoot Root { get; } + + public IHierarchical? Parent { get; } +} diff --git a/src/Beutl.Core/Hierarchy/HierarchyException.cs b/src/Beutl.Core/Hierarchy/HierarchyException.cs new file mode 100644 index 000000000..0d64eb835 --- /dev/null +++ b/src/Beutl.Core/Hierarchy/HierarchyException.cs @@ -0,0 +1,15 @@ +namespace Beutl; + +[Serializable] +public class HierarchyException : Exception +{ + public HierarchyException() { } + + public HierarchyException(string message) : base(message) { } + + public HierarchyException(string message, Exception inner) : base(message, inner) { } + + protected HierarchyException( + System.Runtime.Serialization.SerializationInfo info, + System.Runtime.Serialization.StreamingContext context) : base(info, context) { } +} diff --git a/src/Beutl.Core/Hierarchy/IHierarchical.cs b/src/Beutl.Core/Hierarchy/IHierarchical.cs new file mode 100644 index 000000000..cb4b30d83 --- /dev/null +++ b/src/Beutl.Core/Hierarchy/IHierarchical.cs @@ -0,0 +1,15 @@ +using Beutl.Collections; + +namespace Beutl; + +public interface IHierarchical +{ + IHierarchical? HierarchicalParent { get; } + + IHierarchicalRoot? HierarchicalRoot { get; } + + ICoreReadOnlyList HierarchicalChildren { get; } + + event EventHandler AttachedToHierarchy; + event EventHandler DetachedFromHierarchy; +} diff --git a/src/Beutl.Core/Hierarchy/IHierarchicalRoot.cs b/src/Beutl.Core/Hierarchy/IHierarchicalRoot.cs new file mode 100644 index 000000000..60d82cbcf --- /dev/null +++ b/src/Beutl.Core/Hierarchy/IHierarchicalRoot.cs @@ -0,0 +1,5 @@ +namespace Beutl; + +public interface IHierarchicalRoot : IHierarchical +{ +} diff --git a/src/Beutl.Core/Hierarchy/IModifiableHierarchical.cs b/src/Beutl.Core/Hierarchy/IModifiableHierarchical.cs new file mode 100644 index 000000000..b9fef963d --- /dev/null +++ b/src/Beutl.Core/Hierarchy/IModifiableHierarchical.cs @@ -0,0 +1,14 @@ +namespace Beutl; + +public interface IModifiableHierarchical : IHierarchical +{ + void AddChild(IHierarchical child); + + void RemoveChild(IHierarchical child); + + void SetParent(IHierarchical? parent); + + void NotifyAttachedToHierarchy(in HierarchyAttachmentEventArgs e); + + void NotifyDetachedFromHierarchy(in HierarchyAttachmentEventArgs e); +} diff --git a/src/Beutl.Core/ILogicalElement.cs b/src/Beutl.Core/ILogicalElement.cs deleted file mode 100644 index 4d09f83af..000000000 --- a/src/Beutl.Core/ILogicalElement.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Beutl; - -public interface ILogicalElement -{ - ILogicalElement? LogicalParent { get; } - - IEnumerable LogicalChildren { get; } - - event EventHandler AttachedToLogicalTree; - event EventHandler DetachedFromLogicalTree; - - void NotifyAttachedToLogicalTree(in LogicalTreeAttachmentEventArgs e); - - void NotifyDetachedFromLogicalTree(in LogicalTreeAttachmentEventArgs e); -} diff --git a/src/Beutl.Core/ITopLevel.cs b/src/Beutl.Core/ITopLevel.cs deleted file mode 100644 index 3b142d1c5..000000000 --- a/src/Beutl.Core/ITopLevel.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Beutl; - -public interface ITopLevel : ILogicalElement -{ - string RootDirectory { get; } -} diff --git a/src/Beutl.Core/LogicalTreeAttachmentEventArgs.cs b/src/Beutl.Core/LogicalTreeAttachmentEventArgs.cs deleted file mode 100644 index 983a1ea4f..000000000 --- a/src/Beutl.Core/LogicalTreeAttachmentEventArgs.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Beutl; - -public readonly struct LogicalTreeAttachmentEventArgs -{ - public LogicalTreeAttachmentEventArgs(ILogicalElement? parent) - { - Parent = parent; - } - - public ILogicalElement? Parent { get; } -} diff --git a/src/Beutl.Core/LogicalTreeException.cs b/src/Beutl.Core/LogicalTreeException.cs deleted file mode 100644 index 03aaae412..000000000 --- a/src/Beutl.Core/LogicalTreeException.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Beutl; - -[Serializable] -public class LogicalTreeException : Exception -{ - public LogicalTreeException() { } - - public LogicalTreeException(string message) : base(message) { } - - public LogicalTreeException(string message, Exception inner) : base(message, inner) { } - - protected LogicalTreeException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) : base(info, context) { } -} diff --git a/src/Beutl.Core/LogicalTreeExtensions.cs b/src/Beutl.Core/LogicalTreeExtensions.cs deleted file mode 100644 index c2e990171..000000000 --- a/src/Beutl.Core/LogicalTreeExtensions.cs +++ /dev/null @@ -1,125 +0,0 @@ -namespace Beutl; - -public static class LogicalTreeExtensions -{ - public static T FindRequiredLogicalParent(this ILogicalElement self, bool includeSelf = false) - { - T? parent = FindLogicalParent(self, includeSelf); - if (parent == null) throw new LogicalTreeException("Cannot get parent."); - - return parent; - } - - public static T? FindLogicalParent(this ILogicalElement self, bool includeSelf = false) - { - try - { - ILogicalElement? obj = includeSelf ? self : self.LogicalParent; - - while (obj is not T) - { - if (obj is null) - { - return default; - } - - obj = obj.LogicalParent; - } - - if (obj is T result) - { - return result; - } - else - { - return default; - } - } - catch - { - return default; - } - } - - public static ILogicalElement FindRequiredLogicalParent(this ILogicalElement self, Type type, bool includeSelf = false) - { - ILogicalElement? parent = FindLogicalParent(self, type, includeSelf); - if (parent == null) throw new LogicalTreeException("Cannot get parent."); - - return parent; - } - - public static ILogicalElement? FindLogicalParent(this ILogicalElement self, Type type, bool includeSelf = false) - { - try - { - ILogicalElement? obj = includeSelf ? self : self.LogicalParent; - Type? objType = obj?.GetType(); - - while (objType?.IsAssignableTo(type) != true) - { - if (obj is null) - { - return default; - } - - obj = obj.LogicalParent; - objType = obj?.GetType(); - } - - if (obj != null && objType?.IsAssignableTo(type) == true) - { - return obj; - } - else - { - return default; - } - } - catch - { - return default; - } - } - - public static ILogicalElement GetRoot(this ILogicalElement self) - { - ILogicalElement? current = self; - - while (true) - { - ILogicalElement? next; - - try - { - next = current.LogicalParent; - } - catch - { - return current; - } - - if (next is null) - { - return current; - } - else - { - current = next; - } - } - } - - public static IEnumerable EnumerateAllChildren(this ILogicalElement self) - { - foreach (ILogicalElement? item in self.LogicalChildren) - { - foreach (TResult? innerItem in EnumerateAllChildren(item)) - { - yield return innerItem; - } - - if (item is TResult t) yield return t; - } - } -} diff --git a/src/Beutl.Core/PropertyChangeTracker.cs b/src/Beutl.Core/PropertyChangeTracker.cs index 7af6bc68d..dbf59e714 100644 --- a/src/Beutl.Core/PropertyChangeTracker.cs +++ b/src/Beutl.Core/PropertyChangeTracker.cs @@ -40,9 +40,9 @@ private void AddHandlers(ICoreObject obj, int currentDepth) _trackingElement.Add(obj); obj.PropertyChanged += OnPropertyChanged; - if (obj is ILogicalElement elm) + if (obj is IHierarchical elm) { - foreach (ILogicalElement item in elm.LogicalChildren) + foreach (IHierarchical item in elm.HierarchicalChildren) { AddHandlers(item, currentDepth + 1); } @@ -50,7 +50,7 @@ private void AddHandlers(ICoreObject obj, int currentDepth) } } - private void AddHandlers(ILogicalElement elm, int currentDepth) + private void AddHandlers(IHierarchical elm, int currentDepth) { if (MaxDepth == -1 || currentDepth <= MaxDepth) { @@ -60,7 +60,7 @@ private void AddHandlers(ILogicalElement elm, int currentDepth) obj.PropertyChanged += OnPropertyChanged; } - foreach (ILogicalElement item in elm.LogicalChildren) + foreach (IHierarchical item in elm.HierarchicalChildren) { AddHandlers(item, currentDepth + 1); } diff --git a/src/Beutl.Framework/IWorkspace.cs b/src/Beutl.Framework/IWorkspace.cs index 250f2f3d9..2611bf3c0 100644 --- a/src/Beutl.Framework/IWorkspace.cs +++ b/src/Beutl.Framework/IWorkspace.cs @@ -2,7 +2,7 @@ namespace Beutl.Framework; -public interface IWorkspace : ITopLevel, IDisposable, IStorable +public interface IWorkspace : IHierarchicalRoot, IDisposable, IStorable { ICoreList Items { get; } @@ -11,4 +11,6 @@ public interface IWorkspace : ITopLevel, IDisposable, IStorable Version AppVersion { get; } Version MinAppVersion { get; } + + string RootDirectory { get; } } diff --git a/src/Beutl.Framework/IWorkspaceItem.cs b/src/Beutl.Framework/IWorkspaceItem.cs index 2f2d9d407..2d6006dc8 100644 --- a/src/Beutl.Framework/IWorkspaceItem.cs +++ b/src/Beutl.Framework/IWorkspaceItem.cs @@ -1,5 +1,5 @@ namespace Beutl.Framework; -public interface IWorkspaceItem : IStorable, ILogicalElement, ICoreObject +public interface IWorkspaceItem : IStorable, IHierarchicalRoot, ICoreObject { } diff --git a/src/Beutl.Graphics/Audio/Sound.cs b/src/Beutl.Graphics/Audio/Sound.cs index 7ea67e442..05998a4a3 100644 --- a/src/Beutl.Graphics/Audio/Sound.cs +++ b/src/Beutl.Graphics/Audio/Sound.cs @@ -125,15 +125,15 @@ public void Time(TimeSpan available) protected abstract TimeSpan TimeCore(TimeSpan available); - protected override void OnAttachedToLogicalTree(in LogicalTreeAttachmentEventArgs args) + protected override void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs args) { - base.OnAttachedToLogicalTree(args); + base.OnAttachedToHierarchy(args); _layerSpan = args.Parent as RenderLayerSpan; } - protected override void OnDetachedFromLogicalTree(in LogicalTreeAttachmentEventArgs args) + protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) { - base.OnDetachedFromLogicalTree(args); + base.OnDetachedFromHierarchy(args); _layerSpan = null; } diff --git a/src/Beutl.Graphics/Audio/SourceSound.cs b/src/Beutl.Graphics/Audio/SourceSound.cs index f7b540a06..8d067126d 100644 --- a/src/Beutl.Graphics/Audio/SourceSound.cs +++ b/src/Beutl.Graphics/Audio/SourceSound.cs @@ -31,15 +31,15 @@ public ISoundSource? Source set => SetAndRaise(SourceProperty, ref _source, value); } - protected override void OnAttachedToLogicalTree(in LogicalTreeAttachmentEventArgs args) + protected override void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs args) { - base.OnAttachedToLogicalTree(args); + base.OnAttachedToHierarchy(args); Open(); } - protected override void OnDetachedFromLogicalTree(in LogicalTreeAttachmentEventArgs args) + protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) { - base.OnDetachedFromLogicalTree(args); + base.OnDetachedFromHierarchy(args); Close(); } diff --git a/src/Beutl.Graphics/Graphics/Canvas.cs b/src/Beutl.Graphics/Graphics/Canvas.cs index c111b141f..bbf978f57 100644 --- a/src/Beutl.Graphics/Graphics/Canvas.cs +++ b/src/Beutl.Graphics/Graphics/Canvas.cs @@ -217,42 +217,6 @@ public void DrawText(FormattedText text) } } - [Obsolete("Use 'DrawText(FormattedText)'.")] - public void DrawText(Media.TextFormatting.Compat.TextElement text, Size size) - { - VerifyAccess(); - _sharedPaint.Reset(); - ConfigurePaint(_sharedPaint, size); - _sharedPaint.TextSize = text.Size; - _sharedPaint.Typeface = text.Typeface.ToSkia(); - _sharedPaint.Style = SKPaintStyle.Fill; - Span sc = stackalloc char[1]; - float prevRight = 0; - - foreach (char item in text.Text) - { - sc[0] = item; - var bounds = default(SKRect); - float w = _sharedPaint.MeasureText(sc, ref bounds); - - _canvas.Save(); - _canvas.Translate(prevRight + bounds.Left, 0); - - SKPath path = _sharedPaint.GetTextPath( - sc, - (bounds.Width / 2) - bounds.MidX, - 0/*-_paint.FontMetrics.Ascent*/); - - _canvas.DrawPath(path, _sharedPaint); - path.Dispose(); - - prevRight += text.Spacing; - prevRight += w; - - _canvas.Restore(); - } - } - public void FillCircle(Size size) { VerifyAccess(); diff --git a/src/Beutl.Graphics/Graphics/Drawable.cs b/src/Beutl.Graphics/Graphics/Drawable.cs index 7651379fe..dee6b540e 100644 --- a/src/Beutl.Graphics/Graphics/Drawable.cs +++ b/src/Beutl.Graphics/Graphics/Drawable.cs @@ -9,7 +9,7 @@ namespace Beutl.Graphics; -public abstract class Drawable : Renderable, IDrawable, ILogicalElement +public abstract class Drawable : Renderable, IDrawable, IHierarchical { public static readonly CoreProperty WidthProperty; public static readonly CoreProperty HeightProperty; diff --git a/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs b/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs index 24998913c..877b194ee 100644 --- a/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs +++ b/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs @@ -138,15 +138,15 @@ public override void ApplyAnimations(IClock clock) } } - protected override void OnAttachedToLogicalTree(in LogicalTreeAttachmentEventArgs args) + protected override void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs args) { - base.OnAttachedToLogicalTree(args); - _layerNode = args.Parent?.FindLogicalParent(true); + base.OnAttachedToHierarchy(args); + _layerNode = args.Parent?.FindHierarchicalParent(true); } - protected override void OnDetachedFromLogicalTree(in LogicalTreeAttachmentEventArgs args) + protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) { - base.OnDetachedFromLogicalTree(args); + base.OnDetachedFromHierarchy(args); _layerNode = null; } diff --git a/src/Beutl.Graphics/Graphics/ICanvas.cs b/src/Beutl.Graphics/Graphics/ICanvas.cs index 2a5a211ac..9efceb9f6 100644 --- a/src/Beutl.Graphics/Graphics/ICanvas.cs +++ b/src/Beutl.Graphics/Graphics/ICanvas.cs @@ -39,9 +39,6 @@ public interface ICanvas : IDisposable void DrawText(FormattedText text); - [Obsolete("Use 'DrawText(FormattedText)'.")] - void DrawText(Media.TextFormatting.Compat.TextElement text, Size size); - void FillCircle(Size size); void FillRect(Size size); diff --git a/src/Beutl.Graphics/Graphics/NullCanvas.cs b/src/Beutl.Graphics/Graphics/NullCanvas.cs index 2527da9b5..e032b2f47 100644 --- a/src/Beutl.Graphics/Graphics/NullCanvas.cs +++ b/src/Beutl.Graphics/Graphics/NullCanvas.cs @@ -60,9 +60,6 @@ public Matrix Transform public void DrawText(FormattedText text) => throw new NotImplementedException(); - [Obsolete("Use 'DrawText(FormattedText)'.")] - public void DrawText(Media.TextFormatting.Compat.TextElement text, Size size) => throw new NotImplementedException(); - public void FillCircle(Size size) => throw new NotImplementedException(); public void FillRect(Size size) => throw new NotImplementedException(); diff --git a/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs b/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs index f438baf26..f874db02c 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs @@ -6,6 +6,8 @@ using Beutl.Media; using Beutl.Media.TextFormatting; +using DynamicData; + namespace Beutl.Graphics.Shapes; public class TextBlock : Drawable @@ -92,7 +94,6 @@ static TextBlock() .Register(); AffectsRender(ElementsProperty); - LogicalChild(ElementsProperty); } public TextBlock() @@ -237,25 +238,25 @@ protected override void OnDraw(ICanvas canvas) } } - protected override IEnumerable OnEnumerateChildren() + protected override void OnPropertyChanged(PropertyChangedEventArgs args) { - foreach (ILogicalElement item in base.OnEnumerateChildren()) - { - yield return item; - } - - if (_elements != null) + base.OnPropertyChanged(args); + if (args.PropertyName is nameof(Elements)) { - foreach (TextElement item in _elements) + if (args is CorePropertyChangedEventArgs typedargs) { - yield return item; + if (typedargs.OldValue != null) + { + HierarchicalChildren.RemoveMany(typedargs.OldValue); + } + + if (typedargs.NewValue != null) + { + HierarchicalChildren.AddRange(typedargs.NewValue); + } } } - } - protected override void OnPropertyChanged(PropertyChangedEventArgs args) - { - base.OnPropertyChanged(args); if (args.PropertyName is nameof(Text) or nameof(Size) or nameof(FontFamily) or nameof(FontStyle) or nameof(FontWeight) or nameof(Foreground) or nameof(Spacing) or nameof(Margin)) { OnUpdateText(); diff --git a/src/Beutl.Graphics/Graphics/Shapes/TextElements.cs b/src/Beutl.Graphics/Graphics/Shapes/TextElements.cs index 5cdf2aa2b..9f4a594e3 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/TextElements.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/TextElements.cs @@ -8,10 +8,9 @@ namespace Beutl.Graphics.Shapes; -public class TextElements : IReadOnlyList, ILogicalElement +public class TextElements : IReadOnlyList { private readonly TextElement[] _array; - private ILogicalElement? _parent; public TextElements(IEnumerable items) : this(items.ToArray()) @@ -30,22 +29,6 @@ internal TextElements(TextElement[] array) public LineEnumerable Lines { get; } - ILogicalElement? ILogicalElement.LogicalParent => _parent; - - IEnumerable ILogicalElement.LogicalChildren => _array; - - event EventHandler ILogicalElement.AttachedToLogicalTree - { - add { } - remove { } - } - - event EventHandler ILogicalElement.DetachedFromLogicalTree - { - add { } - remove { } - } - public IEnumerator GetEnumerator() { return ((IEnumerable)_array).GetEnumerator(); @@ -56,30 +39,6 @@ IEnumerator IEnumerable.GetEnumerator() return _array.GetEnumerator(); } - void ILogicalElement.NotifyAttachedToLogicalTree(in LogicalTreeAttachmentEventArgs e) - { - if (_parent is { }) - throw new LogicalTreeException("This logical element already has a parent element."); - - _parent = e.Parent; - foreach (TextElement item in _array) - { - (item as ILogicalElement).NotifyAttachedToLogicalTree(e); - } - } - - void ILogicalElement.NotifyDetachedFromLogicalTree(in LogicalTreeAttachmentEventArgs e) - { - if (!ReferenceEquals(e.Parent, _parent)) - throw new LogicalTreeException("The detach source element and the parent element do not match."); - - _parent = null; - foreach (TextElement item in _array) - { - (item as ILogicalElement).NotifyDetachedFromLogicalTree(e); - } - } - public readonly struct LineEnumerable { private readonly TextElement[] _array; diff --git a/src/Beutl.Graphics/Graphics/SourceImage.cs b/src/Beutl.Graphics/Graphics/SourceImage.cs index 299982b39..9add017e2 100644 --- a/src/Beutl.Graphics/Graphics/SourceImage.cs +++ b/src/Beutl.Graphics/Graphics/SourceImage.cs @@ -39,7 +39,7 @@ public override void ReadFromJson(JsonNode json) && fileNode is JsonValue fileValue && fileValue.TryGetValue(out string? fileStr)) { - if (Parent != null && _sourceName != fileStr) + if (HierarchicalParent != null && _sourceName != fileStr) { Close(); _sourceName = fileStr; @@ -66,15 +66,15 @@ public override void WriteToJson(ref JsonNode json) } } - protected override void OnAttachedToLogicalTree(in LogicalTreeAttachmentEventArgs args) + protected override void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs args) { - base.OnAttachedToLogicalTree(args); + base.OnAttachedToHierarchy(args); Open(); } - protected override void OnDetachedFromLogicalTree(in LogicalTreeAttachmentEventArgs args) + protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) { - base.OnDetachedFromLogicalTree(args); + base.OnDetachedFromHierarchy(args); Close(); } diff --git a/src/Beutl.Graphics/LogicalElementImpl.cs b/src/Beutl.Graphics/LogicalElementImpl.cs deleted file mode 100644 index 0ad086ad3..000000000 --- a/src/Beutl.Graphics/LogicalElementImpl.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Beutl.Styling; - -namespace Beutl; - -internal struct LogicalElementImpl : ILogicalElement -{ - public ILogicalElement? LogicalParent { get; set; } - - public IEnumerable LogicalChildren => Enumerable.Empty(); - - public event EventHandler? AttachedToLogicalTree; - - public event EventHandler? DetachedFromLogicalTree; - - public void VerifyAttachedToLogicalTree() - { - if (LogicalParent is { }) - throw new LogicalTreeException("This logical element already has a parent element."); - } - - public void VerifyDetachedFromLogicalTree(in LogicalTreeAttachmentEventArgs e) - { - if (!ReferenceEquals(e.Parent, LogicalParent)) - throw new LogicalTreeException("The detach source element and the parent element do not match."); - } - - public void NotifyAttachedToLogicalTree(in LogicalTreeAttachmentEventArgs e) - { - LogicalParent = e.Parent; - AttachedToLogicalTree?.Invoke(this, e); - } - - public void NotifyDetachedFromLogicalTree(in LogicalTreeAttachmentEventArgs e) - { - LogicalParent = null; - DetachedFromLogicalTree?.Invoke(this, e); - } -} diff --git a/src/Beutl.Graphics/Media/TextFormatting/Compat/FormattedText.cs b/src/Beutl.Graphics/Media/TextFormatting/Compat/FormattedText.cs deleted file mode 100644 index 56cc8cf66..000000000 --- a/src/Beutl.Graphics/Media/TextFormatting/Compat/FormattedText.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System.Text.Json.Nodes; - -using Beutl.Graphics; -using Beutl.Styling; - -namespace Beutl.Media.TextFormatting.Compat; - -[Obsolete("Use TextBlock APIs.")] -public sealed class TextLines : AffectsRenders -{ - -} - -[Obsolete("Use TextBlock APIs.")] -public class FormattedText : Drawable -{ - public static readonly CoreProperty LinesProperty; - private readonly TextLines _lines; - - static FormattedText() - { - LinesProperty = ConfigureProperty(nameof(Lines)) - .Accessor(o => o.Lines, (o, v) => o.Lines = v) - .PropertyFlags(PropertyFlags.All) - .Register(); - } - - public FormattedText() - { - _lines = new(); - _lines.Attached += item => - { - (item as ILogicalElement)?.NotifyAttachedToLogicalTree(new(this)); - }; - _lines.Detached += item => - { - (item as ILogicalElement)?.NotifyDetachedFromLogicalTree(new(this)); - }; - _lines.Invalidated += (_, _) => Invalidate(); - } - - private FormattedText(List lines) - : this() - { - _lines.AddRange(lines); - } - - public TextLines Lines - { - get => _lines; - set => _lines.Replace(value); - } - - public static FormattedText Parse(string s, FormattedTextInfo info) - { - var tokenizer = new Compat.FormattedTextParser(s); - List lines = tokenizer.ToLines(info); - - return new FormattedText(lines); - } - - public void Load(string s, FormattedTextInfo info) - { - var tokenizer = new Compat.FormattedTextParser(s); - List lines = tokenizer.ToLines(info); - - _lines.Replace(lines); - Invalidate(); - } - - protected override void OnDraw(ICanvas canvas) - { - DrawCore(canvas); - } - - private void DrawCore(ICanvas canvas) - { - float prevBottom = 0; - for (int i = 0; i < Lines.Count; i++) - { - TextLine line = Lines[i]; - line.Measure(canvas.Size.ToSize(1)); - Rect lineBounds = line.Bounds; - - using (canvas.PushTransform(Matrix.CreateTranslation(0, prevBottom))) - { - line.Draw(canvas); - prevBottom += lineBounds.Height; - } - } - } - - protected override Size MeasureCore(Size availableSize) - { - float width = 0; - float height = 0; - - foreach (TextLine line in Lines) - { - line.Measure(availableSize); - Rect bounds = line.Bounds; - width = MathF.Max(bounds.Width, width); - height += bounds.Height; - } - - return new Size(width, height); - } - - public override void ReadFromJson(JsonNode json) - { - base.ReadFromJson(json); - if (json is JsonObject jobject) - { - if (jobject.TryGetPropertyValue("lines", out JsonNode? childrenNode) - && childrenNode is JsonArray childrenArray) - { - _lines.Clear(); - if (_lines.Capacity < childrenArray.Count) - { - _lines.Capacity = childrenArray.Count; - } - - foreach (JsonObject childJson in childrenArray.OfType()) - { - var item = new TextLine(); - item.ReadFromJson(childJson); - _lines.Add(item); - } - } - } - } - - public override void WriteToJson(ref JsonNode json) - { - base.WriteToJson(ref json); - - if (json is JsonObject jobject) - { - var array = new JsonArray(); - - foreach (TextLine item in _lines.AsSpan()) - { - JsonNode node = new JsonObject(); - item.WriteToJson(ref node); - - array.Add(node); - } - - jobject["lines"] = array; - } - } -} diff --git a/src/Beutl.Graphics/Media/TextFormatting/Compat/FormattedTextParser.cs b/src/Beutl.Graphics/Media/TextFormatting/Compat/FormattedTextParser.cs deleted file mode 100644 index 90845c6f6..000000000 --- a/src/Beutl.Graphics/Media/TextFormatting/Compat/FormattedTextParser.cs +++ /dev/null @@ -1,204 +0,0 @@ -using System.Runtime.InteropServices; - -using Beutl.Graphics; - -using static Beutl.Media.TextFormatting.FormattedTextTokenizer; -using static Beutl.Media.TextFormatting.FormattedTextParser; - -namespace Beutl.Media.TextFormatting.Compat; - -[Obsolete("Use TextBlock APIs.")] -public readonly struct FormattedTextParser -{ - private readonly string _s; - - public FormattedTextParser(string s) - { - _s = s; - } - - public List ToLines(FormattedTextInfo defaultProps) - { - var tokenizer = new FormattedTextTokenizer(_s) - { - CompatMode = true - }; - tokenizer.Tokenize(); - List tokens = tokenizer.Result; - Span spanTokens = CollectionsMarshal.AsSpan(tokens); - var lines = new List(tokenizer.LineCount); - - // 行を追加 - foreach (Token token in spanTokens) - { - if (token.Type == TokenType.NewLine) - { - lines.Add(new TextLine()); - } - } - - int lineNum = 0; - var font = new Stack(); - var fontWeight = new Stack(); - var fontStyle = new Stack(); - var size = new Stack(); - var color = new Stack(); - var space = new Stack(); - var margin = new Stack(); - FontFamily curFont = defaultProps.Typeface.FontFamily; - FontWeight curWeight = defaultProps.Typeface.Weight; - FontStyle curStyle = defaultProps.Typeface.Style; - float curSize = defaultProps.Size; - IBrush curColor = defaultProps.Brush; - float curSpace = defaultProps.Space; - Thickness curMargin = defaultProps.Margin; - bool noParse = false; - - foreach (Token token in spanTokens) - { - if (!noParse && token.Type == TokenType.TagStart && - TryParseTag(token.Text, out TagInfo tag)) - { - // 開始タグ - if (tag.TryGetFont(out FontFamily font1)) - { - font.Push(curFont); - curFont = font1; - } - else if (tag.TryGetSize(out float size1)) - { - size.Push(curSize); - curSize = size1; - } - else if (tag.TryGetColor(out Color color1)) - { - color.Push(curColor); - curColor = color1.ToImmutableBrush(); - } - else if (tag.TryGetCharSpace(out float space1)) - { - space.Push(curSpace); - curSpace = space1; - } - else if (tag.TryGetMargin(out Thickness margin1)) - { - margin.Push(curMargin); - curMargin = margin1; - } - else if (tag.TryGetFontStyle(out FontStyle fontStyle1)) - { - fontStyle.Push(curStyle); - curStyle = fontStyle1; - } - else if (tag.TryGetFontWeight(out FontWeight fontWeight1)) - { - fontWeight.Push(curWeight); - curWeight = fontWeight1; - } - else if (tag.Type == TagType.NoParse) - { - noParse = true; - } - else - { - throw new Exception($"{tag.Value} is invalid tag."); - } - - continue; - } - else if (token.Type == TokenType.TagClose) - { - TagType closeTagType = GetCloseTagType(token.Text); - if (closeTagType == TagType.NoParse) - { - noParse = false; - } - - if (!noParse) - { - switch (closeTagType) - { - case TagType.Invalid: - goto default; - case TagType.Font: - curFont = font.PopOrDefault(defaultProps.Typeface.FontFamily); - break; - case TagType.Size: - curSize = size.PopOrDefault(defaultProps.Size); - break; - case TagType.Color: - case TagType.ColorHash: - curColor = color.PopOrDefault(defaultProps.Brush); - break; - case TagType.CharSpace: - curSpace = space.PopOrDefault(defaultProps.Space); - break; - case TagType.Margin: - curMargin = margin.PopOrDefault(defaultProps.Margin); - break; - case TagType.FontWeightBold: - case TagType.FontWeight: - curWeight = fontWeight.PopOrDefault(defaultProps.Typeface.Weight); - break; - case TagType.FontStyle: - case TagType.FontStyleItalic: - curStyle = fontStyle.PopOrDefault(defaultProps.Typeface.Style); - break; - case TagType.NoParse: - noParse = false; - break; - default: - throw new Exception($"{token.Text} is invalid tag."); - } - } - } - - if (token.Type == TokenType.Content) - { - lines[lineNum].Elements.Add(new TextElement() - { - Text = token.Text.AsSpan().ToString(), - Typeface = new Typeface(curFont, curStyle, curWeight), - Size = curSize, - Foreground = curColor, - Spacing = curSpace, - Margin = curMargin, - }); - } - else if (token.Type == TokenType.NewLine) - { - lineNum++; - } - else if (noParse) - { - lines[lineNum].Elements.Add(new TextElement() - { - Text = token.ToString(), - Typeface = new Typeface(curFont, curStyle, curWeight), - Size = curSize, - Foreground = curColor, - Spacing = curSpace, - Margin = curMargin, - }); - } - } - - lines.RemoveAll(i => - { - if (i.Elements.Count < 1) - { - foreach (TextElement item in i.Elements.AsSpan()) - { - item._paint.Dispose(); - } - return true; - } - else - { - return false; - } - }); - - return lines; - } -} diff --git a/src/Beutl.Graphics/Media/TextFormatting/Compat/TextElement.cs b/src/Beutl.Graphics/Media/TextFormatting/Compat/TextElement.cs deleted file mode 100644 index d4c82b568..000000000 --- a/src/Beutl.Graphics/Media/TextFormatting/Compat/TextElement.cs +++ /dev/null @@ -1,168 +0,0 @@ -using Beutl.Graphics; - -using SkiaSharp; - -namespace Beutl.Media.TextFormatting.Compat; - -[Obsolete("Use TextBlock APIs.")] -public class TextElement : Drawable -{ - public static readonly CoreProperty TypefaceProperty; - public static readonly CoreProperty SizeProperty; - public static readonly CoreProperty SpacingProperty; - public static readonly CoreProperty TextProperty; - public static readonly CoreProperty MarginProperty; - internal readonly SKPaint _paint = new(); - private Typeface _typeface = new(FontFamily.Default, FontStyle.Normal, FontWeight.Regular); - private float _size; - private bool _isDirty = true; - private FontMetrics _fontMetrics; - private float _spacing; - private string _text = string.Empty; - private Thickness _margin; - - static TextElement() - { - TypefaceProperty = ConfigureProperty(nameof(Typeface)) - .Accessor(o => o.Typeface, (o, v) => o.Typeface = v) - .PropertyFlags(PropertyFlags.All) - .DefaultValue(new(FontFamily.Default, FontStyle.Normal, FontWeight.Regular)) - .SerializeName("typeface") - .Register(); - - SizeProperty = ConfigureProperty(nameof(Size)) - .Accessor(o => o.Size, (o, v) => o.Size = v) - .PropertyFlags(PropertyFlags.All) - .DefaultValue(0) - .SerializeName("size") - .Register(); - - SpacingProperty = ConfigureProperty(nameof(Spacing)) - .Accessor(o => o.Spacing, (o, v) => o.Spacing = v) - .PropertyFlags(PropertyFlags.All) - .DefaultValue(0) - .SerializeName("spacing") - .Register(); - - TextProperty = ConfigureProperty(nameof(Text)) - .Accessor(o => o.Text, (o, v) => o.Text = v) - .PropertyFlags(PropertyFlags.All) - .DefaultValue(string.Empty) - .SerializeName("text") - .Register(); - - MarginProperty = ConfigureProperty(nameof(Margin)) - .Accessor(o => o.Margin, (o, v) => o.Margin = v) - .PropertyFlags(PropertyFlags.All) - .DefaultValue(new Thickness()) - .SerializeName("margin") - .Register(); - - AffectsRender(TypefaceProperty, SizeProperty, SpacingProperty, TextProperty, MarginProperty); - } - - ~TextElement() - { - _paint.Dispose(); - } - - public FontWeight Weight - { - get => _typeface.Weight; - set => Typeface = new Typeface(_typeface.FontFamily, _typeface.Style, value); - } - - public FontStyle Style - { - get => _typeface.Style; - set => Typeface = new Typeface(_typeface.FontFamily, value, _typeface.Weight); - } - - public FontFamily Font - { - get => _typeface.FontFamily; - set => Typeface = new Typeface(value, _typeface.Style, _typeface.Weight); - } - - public Typeface Typeface - { - get => _typeface; - set - { - if (SetAndRaise(TypefaceProperty, ref _typeface, value)) - { - _isDirty = true; - } - } - } - - public float Size - { - get => _size; - set - { - if (SetAndRaise(SizeProperty, ref _size, value)) - { - _isDirty = true; - } - } - } - - public float Spacing - { - get => _spacing; - set => SetAndRaise(SpacingProperty, ref _spacing, value); - } - - public string Text - { - get => _text; - set => SetAndRaise(TextProperty, ref _text, value); - } - - public Thickness Margin - { - get => _margin; - set => SetAndRaise(MarginProperty, ref _margin, value); - } - - public FontMetrics FontMetrics - { - get - { - if (_isDirty) - { - _paint.TextSize = Size; - _paint.Typeface = Typeface.ToSkia(); - _fontMetrics = _paint.FontMetrics.ToFontMetrics(); - _isDirty = false; - } - - return _fontMetrics; - } - } - - protected override Size MeasureCore(Size availableSize) - { - _ = FontMetrics; - float w = _paint.MeasureText(Text); - - return new Size( - w + (Text.Length - 1) * Spacing, - FontMetrics.Descent - FontMetrics.Ascent); - } - - protected override void OnDraw(ICanvas canvas) - { - DrawInternal(canvas); - } - - internal void DrawInternal(ICanvas canvas) - { - using (canvas.PushTransform(Matrix.CreateTranslation(Margin.Left, Margin.Top))) - { - canvas.DrawText(this, MeasureCore(Graphics.Size.Infinity)); - } - } -} - diff --git a/src/Beutl.Graphics/Media/TextFormatting/Compat/TextLine.cs b/src/Beutl.Graphics/Media/TextFormatting/Compat/TextLine.cs deleted file mode 100644 index 4f31cef8c..000000000 --- a/src/Beutl.Graphics/Media/TextFormatting/Compat/TextLine.cs +++ /dev/null @@ -1,135 +0,0 @@ -using System.Text.Json.Nodes; - -using Beutl.Graphics; - -namespace Beutl.Media.TextFormatting.Compat; - -[Obsolete("Use TextBlock APIs.")] -public sealed class TextElements : AffectsRenders -{ - -} - -[Obsolete("Use TextBlock APIs.")] -public sealed class TextLine : Drawable, ILogicalElement -{ - public static readonly CoreProperty ElementsProperty; - private readonly TextElements _elements; - - static TextLine() - { - ElementsProperty = ConfigureProperty(nameof(Elements)) - .Accessor(o => o.Elements, (o, v) => o.Elements = v) - .PropertyFlags(PropertyFlags.All) - .Register(); - } - - public TextLine() - { - _elements = new(); - _elements.Attached += item => (item as ILogicalElement).NotifyAttachedToLogicalTree(new(this)); - _elements.Detached += item => (item as ILogicalElement).NotifyDetachedFromLogicalTree(new(this)); - _elements.Invalidated += (_, _) => Invalidate(); - } - - public TextElements Elements - { - get => _elements; - set => _elements.Replace(value); - } - - IEnumerable ILogicalElement.LogicalChildren => Elements; - - public float MinAscent() - { - float ascent = 0; - foreach (TextElement item in Elements) - { - ascent = MathF.Min(item.FontMetrics.Ascent, ascent); - } - - return ascent; - } - - public override void ReadFromJson(JsonNode json) - { - base.ReadFromJson(json); - if (json is JsonObject jobject) - { - if (jobject.TryGetPropertyValue("elements", out JsonNode? childrenNode) - && childrenNode is JsonArray childrenArray) - { - _elements.Clear(); - if (_elements.Capacity < childrenArray.Count) - { - _elements.Capacity = childrenArray.Count; - } - - foreach (JsonObject childJson in childrenArray.OfType()) - { - var item = new TextElement(); - item.ReadFromJson(childJson); - _elements.Add(item); - } - } - } - } - - public override void WriteToJson(ref JsonNode json) - { - base.WriteToJson(ref json); - - if (json is JsonObject jobject) - { - var array = new JsonArray(); - - foreach (TextElement item in _elements.AsSpan()) - { - JsonNode node = new JsonObject(); - item.WriteToJson(ref node); - - array.Add(node); - } - - jobject["elements"] = array; - } - } - - protected override Size MeasureCore(Size availableSize) - { - float width = 0; - float height = 0; - - foreach (TextElement element in Elements) - { - element.Measure(availableSize); - Rect bounds = element.Bounds; - width += bounds.Width; - width += element.Margin.Left + element.Margin.Right; - - height = MathF.Max(bounds.Height + element.Margin.Top + element.Margin.Bottom, height); - } - - return new Size(width, height); - - } - - protected override void OnDraw(ICanvas canvas) - { - float ascent = MinAscent(); - - using (canvas.PushTransform(Matrix.CreateTranslation(0, -ascent))) - { - float prevRight = 0; - foreach (TextElement element in Elements) - { - canvas.Translate(new(prevRight, 0)); - Rect elementBounds = element.Bounds; - - element.Draw(canvas); - - prevRight = elementBounds.Width + element.Margin.Right; - } - } - } -} diff --git a/src/Beutl.Graphics/Rendering/RenderLayerSpan.cs b/src/Beutl.Graphics/Rendering/RenderLayerSpan.cs index 88b21c78f..308b98133 100644 --- a/src/Beutl.Graphics/Rendering/RenderLayerSpan.cs +++ b/src/Beutl.Graphics/Rendering/RenderLayerSpan.cs @@ -3,7 +3,7 @@ namespace Beutl.Rendering; -public sealed class RenderLayerSpan : Element, IAffectsRender +public sealed class RenderLayerSpan : Hierarchical, IAffectsRender { public static readonly CoreProperty StartProperty; public static readonly CoreProperty DurationProperty; diff --git a/src/Beutl.Graphics/Rendering/Renderables.cs b/src/Beutl.Graphics/Rendering/Renderables.cs index c7554c44f..5305162ac 100644 --- a/src/Beutl.Graphics/Rendering/Renderables.cs +++ b/src/Beutl.Graphics/Rendering/Renderables.cs @@ -6,9 +6,9 @@ namespace Beutl.Rendering; -public sealed class Renderables : LogicalList, IAffectsRender +public sealed class Renderables : HierarchicalList, IAffectsRender { - public Renderables(ILogicalElement parent) + public Renderables(IModifiableHierarchical parent) : base(parent) { ResetBehavior = ResetBehavior.Remove; diff --git a/src/Beutl.Graphics/Styling/IStyleable.cs b/src/Beutl.Graphics/Styling/IStyleable.cs index b5fb10b50..1093231d8 100644 --- a/src/Beutl.Graphics/Styling/IStyleable.cs +++ b/src/Beutl.Graphics/Styling/IStyleable.cs @@ -1,6 +1,6 @@ namespace Beutl.Styling; -public interface IStyleable : ICoreObject, ILogicalElement +public interface IStyleable : ICoreObject, IHierarchical { Styles Styles { get; set; } diff --git a/src/Beutl.Graphics/Styling/Styleable.cs b/src/Beutl.Graphics/Styling/Styleable.cs index 8a2d0f4ed..23ab5ae21 100644 --- a/src/Beutl.Graphics/Styling/Styleable.cs +++ b/src/Beutl.Graphics/Styling/Styleable.cs @@ -1,14 +1,15 @@ -using System.Text.Json.Nodes; +using System.Collections; +using System.Collections.Specialized; +using System.Text.Json.Nodes; using Beutl.Animation; +using Beutl.Collections; namespace Beutl.Styling; -public abstract class Styleable : Animatable, IStyleable +public abstract class Styleable : Animatable, IStyleable, IModifiableHierarchical { public static readonly CoreProperty StylesProperty; - public static readonly CoreProperty ParentProperty; - private ILogicalElement? _parent; private readonly Styles _styles; private IStyleInstance? _styleInstance; @@ -18,8 +19,8 @@ static Styleable() .Accessor(o => o.Styles, (o, v) => o.Styles = v) .Register(); - ParentProperty = ConfigureProperty(nameof(Parent)) - .Accessor(o => o.Parent, (o, v) => o.Parent = v) + HierarchicalParentProperty = ConfigureProperty(nameof(HierarchicalParent)) + .Accessor(o => o.HierarchicalParent, (o, v) => o.HierarchicalParent = v) .Register(); } @@ -35,28 +36,20 @@ protected Styleable() item.Invalidated -= Style_Invalidated; }; _styles.CollectionChanged += Style_Invalidated; - } - - public event EventHandler? AttachedToLogicalTree; - public event EventHandler? DetachedFromLogicalTree; + _root = this as IHierarchicalRoot; + _hierarchicalChildren = new CoreList() + { + ResetBehavior = ResetBehavior.Remove + }; + _hierarchicalChildren.CollectionChanged += HierarchicalChildrenCollectionChanged; + } private void Style_Invalidated(object? sender, EventArgs e) { _styleInstance = null; } - public Styleable? Parent - { - get => _parent as Styleable; - private set - { - Styleable? parent = Parent; - SetAndRaise(ParentProperty, ref parent, value); - _parent = parent; - } - } - public Styles Styles { get => _styles; @@ -69,10 +62,6 @@ public Styles Styles } } - ILogicalElement? ILogicalElement.LogicalParent => _parent; - - IEnumerable ILogicalElement.LogicalChildren => OnEnumerateChildren(); - public void InvalidateStyles() { if (_styleInstance != null) @@ -159,90 +148,179 @@ public override void WriteToJson(ref JsonNode json) } } - protected static void LogicalChild( - CoreProperty? property1 = null, - CoreProperty? property2 = null, - CoreProperty? property3 = null, - CoreProperty? property4 = null) - where T : Styleable + #region IHierarchical + + public static readonly CoreProperty HierarchicalParentProperty; + private readonly CoreList _hierarchicalChildren; + private IHierarchical? _parent; + private IHierarchicalRoot? _root; + + public IHierarchical? HierarchicalParent + { + get => _parent; + private set => SetAndRaise(HierarchicalParentProperty, ref _parent, value); + } + + protected ICoreList HierarchicalChildren => _hierarchicalChildren; + + IHierarchical? IHierarchical.HierarchicalParent => _parent; + + IHierarchicalRoot? IHierarchical.HierarchicalRoot => _root; + + ICoreReadOnlyList IHierarchical.HierarchicalChildren => HierarchicalChildren; + + public event EventHandler? AttachedToHierarchy; + + public event EventHandler? DetachedFromHierarchy; + + protected virtual void HierarchicalChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { - static void onNext(CorePropertyChangedEventArgs e) + void SetParent(IList children) { - if (e.Sender is T s) + int count = children.Count; + + for (int i = 0; i < count; i++) { - if (e.OldValue is ILogicalElement oldLogical) + var logical = (IHierarchical)children[i]!; + + if (logical.HierarchicalParent is null) { - oldLogical.NotifyDetachedFromLogicalTree(new LogicalTreeAttachmentEventArgs(s)); + (logical as IModifiableHierarchical)?.SetParent(this); } + } + } + + void ClearParent(IList children) + { + int count = children.Count; + + for (int i = 0; i < count; i++) + { + var logical = (IHierarchical)children[i]!; - if (e.NewValue is ILogicalElement newLogical) + if (logical.HierarchicalParent == this) { - newLogical.NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(s)); + (logical as IModifiableHierarchical)?.SetParent(null); } } } - property1?.Changed.Subscribe(onNext); - property2?.Changed.Subscribe(onNext); - property3?.Changed.Subscribe(onNext); - property4?.Changed.Subscribe(onNext); + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + SetParent(e.NewItems!); + break; + + case NotifyCollectionChangedAction.Remove: + ClearParent(e.OldItems!); + break; + + case NotifyCollectionChangedAction.Replace: + ClearParent(e.OldItems!); + SetParent(e.NewItems!); + break; + + case NotifyCollectionChangedAction.Reset: + break; + } } - protected static void LogicalChild(params CoreProperty[] properties) - where T : Styleable + protected virtual void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs args) { - static void onNext(CorePropertyChangedEventArgs e) + } + + protected virtual void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) + { + } + + private void OnAttachedToHierarchyCore(in HierarchyAttachmentEventArgs e) + { + if (_parent == null && this is not IHierarchicalRoot) { - if (e.Sender is T s) - { - if (e.OldValue is ILogicalElement oldLogical) - { - oldLogical.NotifyDetachedFromLogicalTree(new LogicalTreeAttachmentEventArgs(s)); - } + throw new InvalidOperationException( + $"AttachedToLogicalTreeCore called for '{GetType().Name}' but element has no logical parent."); + } - if (e.NewValue is ILogicalElement newLogical) - { - newLogical.NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(s)); - } - } + if (_root == null) + { + _root = e.Root; + OnAttachedToHierarchy(e); + AttachedToHierarchy?.Invoke(this, e); } - foreach (CoreProperty? item in properties) + _ = HierarchicalChildren; + foreach (IHierarchical item in _hierarchicalChildren!.GetMarshal().Value) { - item.Changed.Subscribe(onNext); + (item as IModifiableHierarchical)?.NotifyAttachedToHierarchy(e); } } - protected virtual void OnAttachedToLogicalTree(in LogicalTreeAttachmentEventArgs args) + private void OnDetachedFromHierarchyCore(in HierarchyAttachmentEventArgs e) { + if (_root != null) + { + _root = null; + OnDetachedFromHierarchy(e); + DetachedFromHierarchy?.Invoke(this, e); + + _ = HierarchicalChildren; + foreach (IHierarchical item in _hierarchicalChildren!.GetMarshal().Value) + { + (item as IModifiableHierarchical)?.NotifyDetachedFromHierarchy(e); + } + } } - protected virtual void OnDetachedFromLogicalTree(in LogicalTreeAttachmentEventArgs args) + void IModifiableHierarchical.NotifyAttachedToHierarchy(in HierarchyAttachmentEventArgs e) { + OnAttachedToHierarchyCore(e); } - protected virtual IEnumerable OnEnumerateChildren() + void IModifiableHierarchical.NotifyDetachedFromHierarchy(in HierarchyAttachmentEventArgs e) { - yield break; + OnDetachedFromHierarchyCore(e); } - void ILogicalElement.NotifyAttachedToLogicalTree(in LogicalTreeAttachmentEventArgs e) + void IModifiableHierarchical.SetParent(IHierarchical? parent) { - if (_parent is { }) - throw new LogicalTreeException("This logical element already has a parent element."); + IHierarchical? old = _parent; + + if (parent != old) + { + if (old != null && parent != null) + { + throw new InvalidOperationException("This logical element already has a parent."); + } + + if (_root != null) + { + var e = new HierarchyAttachmentEventArgs(_root, old); + OnDetachedFromHierarchyCore(e); + } - OnAttachedToLogicalTree(e); - _parent = e.Parent; - AttachedToLogicalTree?.Invoke(this, e); + _parent = parent; + IHierarchicalRoot? newRoot = this.FindHierarchicalRoot(); + + if (newRoot != null) + { + var e = new HierarchyAttachmentEventArgs(newRoot, parent); + OnAttachedToHierarchyCore(e); + } + + // Raise PropertyChanged + _parent = old; + HierarchicalParent = parent; + } } - void ILogicalElement.NotifyDetachedFromLogicalTree(in LogicalTreeAttachmentEventArgs e) + void IModifiableHierarchical.AddChild(IHierarchical child) { - if (!ReferenceEquals(e.Parent, _parent)) - throw new LogicalTreeException("The detach source element and the parent element do not match."); + HierarchicalChildren.Add(child); + } - OnDetachedFromLogicalTree(e); - _parent = null; - DetachedFromLogicalTree?.Invoke(this, e); + void IModifiableHierarchical.RemoveChild(IHierarchical child) + { + HierarchicalChildren.Remove(child); } + #endregion } diff --git a/src/Beutl.Operators/Configure/ConfigureOperator.cs b/src/Beutl.Operators/Configure/ConfigureOperator.cs index fdfd0c9ed..8ee7e65b0 100644 --- a/src/Beutl.Operators/Configure/ConfigureOperator.cs +++ b/src/Beutl.Operators/Configure/ConfigureOperator.cs @@ -149,9 +149,9 @@ protected virtual void PostSelect(TTarget target, TValue value) protected abstract void OnDetached(TTarget target, TValue value); - protected override void OnDetachedFromLogicalTree(in LogicalTreeAttachmentEventArgs args) + protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) { - base.OnDetachedFromLogicalTree(args); + base.OnDetachedFromHierarchy(args); if (_snapshot != null) { diff --git a/src/Beutl.Operators/Handler/DefaultSourceHandler.cs b/src/Beutl.Operators/Handler/DefaultSourceHandler.cs index e81b0fa9d..d389562c9 100644 --- a/src/Beutl.Operators/Handler/DefaultSourceHandler.cs +++ b/src/Beutl.Operators/Handler/DefaultSourceHandler.cs @@ -13,7 +13,7 @@ private static void Detach(Layer layer, IList renderables) { foreach (Renderable item in renderables) { - if ((item as ILogicalElement).LogicalParent is RenderLayerSpan span + if ((item as IHierarchical).HierarchicalParent is RenderLayerSpan span && layer.Span != span) { span.Value.Remove(item); @@ -55,15 +55,15 @@ public override void Exit() } } - protected override void OnAttachedToLogicalTree(in LogicalTreeAttachmentEventArgs args) + protected override void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs args) { - base.OnAttachedToLogicalTree(args); + base.OnAttachedToHierarchy(args); _layer = args.Parent as Layer; } - protected override void OnDetachedFromLogicalTree(in LogicalTreeAttachmentEventArgs args) + protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) { - base.OnDetachedFromLogicalTree(args); + base.OnDetachedFromHierarchy(args); if (_layer != null) { diff --git a/src/Beutl.ProjectSystem/NodeTree/INodeItem.cs b/src/Beutl.ProjectSystem/NodeTree/INodeItem.cs index 30c13cc41..125e5d30d 100644 --- a/src/Beutl.ProjectSystem/NodeTree/INodeItem.cs +++ b/src/Beutl.ProjectSystem/NodeTree/INodeItem.cs @@ -8,7 +8,7 @@ internal interface ISupportSetValueNodeItem void SetThrough(INodeItem nodeItem); } -public interface INodeItem : ICoreObject, ILogicalElement, IAffectsRender +public interface INodeItem : ICoreObject, IHierarchical, IAffectsRender { int LocalId { get; } diff --git a/src/Beutl.ProjectSystem/NodeTree/LayerNodeTreeModel.cs b/src/Beutl.ProjectSystem/NodeTree/LayerNodeTreeModel.cs index 1304e2112..53e6e2686 100644 --- a/src/Beutl.ProjectSystem/NodeTree/LayerNodeTreeModel.cs +++ b/src/Beutl.ProjectSystem/NodeTree/LayerNodeTreeModel.cs @@ -129,7 +129,7 @@ private void BuildNode(Node node, PooledList stack) INodeItem? item = node.Items[i]; if (item is IInputSocket { Connection.Output: { } outputSocket }) { - Node? node2 = outputSocket.FindLogicalParent(); + Node? node2 = outputSocket.FindHierarchicalParent(); if (node2 != null) { BuildNode(node2, stack); diff --git a/src/Beutl.ProjectSystem/NodeTree/Node.cs b/src/Beutl.ProjectSystem/NodeTree/Node.cs index 6bcfd1e89..6b0c30865 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Node.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Node.cs @@ -8,11 +8,11 @@ namespace Beutl.NodeTree; -public abstract class Node : Element +public abstract class Node : Hierarchical { public static readonly CoreProperty IsExpandedProperty; public static readonly CoreProperty<(double X, double Y)> PositionProperty; - private readonly LogicalList _items; + private readonly HierarchicalList _items; private (double X, double Y) _position; private NodeTreeSpace? _nodeTree; @@ -478,9 +478,9 @@ public override void WriteToJson(ref JsonNode json) json["items"] = array; } - protected override void OnAttachedToLogicalTree(in LogicalTreeAttachmentEventArgs args) + protected override void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs args) { - base.OnAttachedToLogicalTree(args); + base.OnAttachedToHierarchy(args); if (args.Parent is NodeTreeSpace nodeTree) { _nodeTree = nodeTree; @@ -491,9 +491,9 @@ protected override void OnAttachedToLogicalTree(in LogicalTreeAttachmentEventArg } } - protected override void OnDetachedFromLogicalTree(in LogicalTreeAttachmentEventArgs args) + protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) { - base.OnDetachedFromLogicalTree(args); + base.OnDetachedFromHierarchy(args); if (args.Parent is NodeTreeSpace nodeTree) { _nodeTree = null; diff --git a/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs b/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs index 8b1697d0a..f58c23dfb 100644 --- a/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs +++ b/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs @@ -4,7 +4,7 @@ namespace Beutl.NodeTree; -public abstract class NodeItem : Element +public abstract class NodeItem : Hierarchical { public static readonly CoreProperty IsValidProperty; public static readonly CoreProperty LocalIdProperty; diff --git a/src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs b/src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs index fd4ee1c5c..59b084786 100644 --- a/src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs +++ b/src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs @@ -162,7 +162,7 @@ private void BuildNode(Node node, Stack stack) INodeItem? item = node.Items[i]; if (item is IInputSocket { Connection.Output: { } outputSocket }) { - Node? node2 = outputSocket.FindLogicalParent(); + Node? node2 = outputSocket.FindHierarchicalParent(); if (node2 != null) { BuildNode(node2, stack); @@ -207,15 +207,15 @@ protected override void OnPropertyChanged(PropertyChangedEventArgs args) } // Todo: NodeTreeModel -public abstract class NodeTreeSpace : Element, IAffectsRender +public abstract class NodeTreeSpace : Hierarchical, IAffectsRender { - private readonly LogicalList _nodes; + private readonly HierarchicalList _nodes; public event EventHandler? Invalidated; public NodeTreeSpace() { - _nodes = new LogicalList(this); + _nodes = new HierarchicalList(this); } public ICoreList Nodes => _nodes; diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupNode.cs index 4b4607a84..d65617b47 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupNode.cs @@ -21,7 +21,7 @@ public GroupNode() { Name = "Group" }; - (Group as ILogicalElement).NotifyAttachedToLogicalTree(new(this)); + HierarchicalChildren.Add(Group); Group.Invalidated += OnGroupInvalidated; this.GetObservable(NameProperty).Subscribe(v => Group.Name = string.IsNullOrWhiteSpace(v) ? "Group" : v); @@ -108,9 +108,9 @@ public override void PostEvaluate(NodeEvaluationContext context) base.PostEvaluate(context); } - protected override void OnAttachedToLogicalTree(in LogicalTreeAttachmentEventArgs args) + protected override void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs args) { - base.OnAttachedToLogicalTree(args); + base.OnAttachedToHierarchy(args); Group.GetPropertyChangedObservable(NodeGroup.OutputProperty) .Subscribe(e => OnOutputChanged(e.NewValue, e.OldValue)) .DisposeWith(_disposables); @@ -120,9 +120,9 @@ protected override void OnAttachedToLogicalTree(in LogicalTreeAttachmentEventArg .DisposeWith(_disposables); } - protected override void OnDetachedFromLogicalTree(in LogicalTreeAttachmentEventArgs args) + protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) { - base.OnDetachedFromLogicalTree(args); + base.OnDetachedFromHierarchy(args); _disposables.Clear(); } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/OutputNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/OutputNode.cs index 9d0e4d663..0cb72c0f4 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/OutputNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/OutputNode.cs @@ -41,9 +41,9 @@ public override void Evaluate(NodeEvaluationContext context) protected abstract void Detach(Drawable drawable); - protected override void OnDetachedFromLogicalTree(in LogicalTreeAttachmentEventArgs args) + protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) { - base.OnDetachedFromLogicalTree(args); + base.OnDetachedFromHierarchy(args); if (_prevDrawable != null) { Detach(_prevDrawable); diff --git a/src/Beutl.ProjectSystem/Operation/SourceOperator.cs b/src/Beutl.ProjectSystem/Operation/SourceOperator.cs index bcc7637d0..b64e9c45f 100644 --- a/src/Beutl.ProjectSystem/Operation/SourceOperator.cs +++ b/src/Beutl.ProjectSystem/Operation/SourceOperator.cs @@ -11,7 +11,7 @@ public interface ISourceOperator : IAffectsRender ICoreList Properties { get; } } -public class SourceOperator : Element, ISourceOperator +public class SourceOperator : Hierarchical, ISourceOperator { public static readonly CoreProperty IsEnabledProperty; private bool _isEnabled = true; diff --git a/src/Beutl.ProjectSystem/Operation/SourceOperators.cs b/src/Beutl.ProjectSystem/Operation/SourceOperators.cs index ba00101cb..1f1cc9ab2 100644 --- a/src/Beutl.ProjectSystem/Operation/SourceOperators.cs +++ b/src/Beutl.ProjectSystem/Operation/SourceOperators.cs @@ -19,40 +19,39 @@ public SourceOperators(Layer parent) private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { Span span = GetMarshal().Value; - var args = new LogicalTreeAttachmentEventArgs(Parent); switch (e.Action) { case NotifyCollectionChangedAction.Add: for (int i = 0; i < e.NewStartingIndex; i++) { - (span[i] as ILogicalElement).NotifyDetachedFromLogicalTree(args); + (span[i] as IModifiableHierarchical).SetParent(null); } for (int i = e.NewStartingIndex + e.NewItems!.Count; i < Count; i++) { - (span[i] as ILogicalElement).NotifyDetachedFromLogicalTree(args); + (span[i] as IModifiableHierarchical).SetParent(null); } foreach (SourceOperator item in span) { - (item as ILogicalElement).NotifyAttachedToLogicalTree(args); + (item as IModifiableHierarchical).SetParent(Parent); } break; case NotifyCollectionChangedAction.Remove: for (int i = 0; i < e.OldItems!.Count; i++) { - (e.OldItems![i] as ILogicalElement)?.NotifyDetachedFromLogicalTree(args); + (e.OldItems![i] as IModifiableHierarchical)!.SetParent(null); } foreach (SourceOperator item in span) { - (item as ILogicalElement).NotifyDetachedFromLogicalTree(args); + (item as IModifiableHierarchical).SetParent(null); } foreach (SourceOperator item in span) { - (item as ILogicalElement).NotifyAttachedToLogicalTree(args); + (item as IModifiableHierarchical).SetParent(Parent); } break; @@ -60,22 +59,22 @@ private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArg case NotifyCollectionChangedAction.Move: for (int i = 0; i < e.OldItems!.Count; i++) { - (e.OldItems![i] as ILogicalElement)?.NotifyDetachedFromLogicalTree(args); + (e.OldItems![i] as IModifiableHierarchical)!.SetParent(null); } for (int i = 0; i < e.NewStartingIndex; i++) { - (span[i] as ILogicalElement).NotifyDetachedFromLogicalTree(args); + (span[i] as IModifiableHierarchical).SetParent(null); } for (int i = e.NewStartingIndex + e.NewItems!.Count; i < Count; i++) { - (span[i] as ILogicalElement).NotifyDetachedFromLogicalTree(args); + (span[i] as IModifiableHierarchical).SetParent(null); } foreach (SourceOperator item in span) { - (item as ILogicalElement).NotifyAttachedToLogicalTree(args); + (item as IModifiableHierarchical).SetParent(Parent); } break; diff --git a/src/Beutl.ProjectSystem/Operation/SourceStyler.cs b/src/Beutl.ProjectSystem/Operation/SourceStyler.cs index 4deb2dec4..923fb2a02 100644 --- a/src/Beutl.ProjectSystem/Operation/SourceStyler.cs +++ b/src/Beutl.ProjectSystem/Operation/SourceStyler.cs @@ -70,9 +70,9 @@ protected virtual void ApplyStyle(IStyleInstance instance, IRenderable value, IC instance.End(); } - protected override void OnDetachedFromLogicalTree(in LogicalTreeAttachmentEventArgs args) + protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) { - base.OnDetachedFromLogicalTree(args); + base.OnDetachedFromHierarchy(args); foreach (KeyValuePair item in _table) { item.Value.Dispose(); diff --git a/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs b/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs index 268395ef3..395f7a09d 100644 --- a/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs +++ b/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs @@ -13,7 +13,7 @@ namespace Beutl.ProjectSystem; -public class Layer : Element, IStorable, ILogicalElement +public class Layer : Hierarchical, IStorable { public static readonly CoreProperty StartProperty; public static readonly CoreProperty LengthProperty; @@ -108,7 +108,7 @@ static Layer() ZIndexProperty.Changed.Subscribe(args => { - if (args.Sender is Layer layer && layer.Parent is Scene { Renderer: { IsDisposed: false } renderer }) + if (args.Sender is Layer layer && layer.HierarchicalParent is Scene { Renderer: { IsDisposed: false } renderer }) { renderer[args.OldValue]?.RemoveSpan(layer.Span); if (args.NewValue >= 0) @@ -178,10 +178,11 @@ public Layer() Operators.Detached += item => item.Invalidated -= Operator_Invalidated; Operators.CollectionChanged += OnOperatorsCollectionChanged; - (Span as ILogicalElement).NotifyAttachedToLogicalTree(new(this)); - Space = new LayerNodeTreeModel(); Space.Invalidated += (_, _) => ForceRender(); + + HierarchicalChildren.Add(Span); + HierarchicalChildren.Add(Space); } private void OnOperatorsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) @@ -425,9 +426,9 @@ public IRecordableCommand InsertChild(int index, SourceOperator @operator) .ToCommand(); } - protected override void OnAttachedToLogicalTree(in LogicalTreeAttachmentEventArgs args) + protected override void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs args) { - base.OnAttachedToLogicalTree(args); + base.OnAttachedToHierarchy(args); if (args.Parent is Scene { Renderer: { IsDisposed: false } renderer } && ZIndex >= 0) { IRenderLayer? context = renderer[ZIndex]; @@ -442,9 +443,9 @@ protected override void OnAttachedToLogicalTree(in LogicalTreeAttachmentEventArg } } - protected override void OnDetachedFromLogicalTree(in LogicalTreeAttachmentEventArgs args) + protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) { - base.OnDetachedFromLogicalTree(args); + base.OnDetachedFromHierarchy(args); if (args.Parent is Scene { Renderer: { IsDisposed: false } renderer } && ZIndex >= 0) { renderer[ZIndex]?.RemoveSpan(Span); @@ -453,24 +454,6 @@ protected override void OnDetachedFromLogicalTree(in LogicalTreeAttachmentEventA } } - protected override IEnumerable OnEnumerateChildren() - { - foreach (ILogicalElement item in base.OnEnumerateChildren()) - { - yield return item; - } - - foreach (SourceOperator item in Operators) - { - yield return item; - } - - if (Span != null) - { - yield return Span; - } - } - internal bool InRange(TimeSpan ts) { return Start <= ts && ts < Length + Start; @@ -478,7 +461,7 @@ internal bool InRange(TimeSpan ts) private void ForceRender() { - Scene? scene = this.FindLogicalParent(); + Scene? scene = this.FindHierarchicalParent(); if (IsEnabled && scene != null && Start <= scene.CurrentFrame @@ -508,7 +491,7 @@ private void Operator_Invalidated(object? sender, EventArgs e) internal Layer? GetBefore(int zindex, TimeSpan start) { - if (Parent is Scene scene) + if (HierarchicalParent is Scene scene) { Layer? tmp = null; foreach (Layer? item in scene.Children.GetMarshal().Value) @@ -529,7 +512,7 @@ private void Operator_Invalidated(object? sender, EventArgs e) internal Layer? GetAfter(int zindex, TimeSpan end) { - if (Parent is Scene scene) + if (HierarchicalParent is Scene scene) { Layer? tmp = null; foreach (Layer? item in scene.Children.GetMarshal().Value) @@ -550,7 +533,7 @@ private void Operator_Invalidated(object? sender, EventArgs e) internal (Layer? Before, Layer? After, Layer? Cover) GetBeforeAndAfterAndCover(int zindex, TimeSpan start, TimeSpan end) { - if (Parent is Scene scene) + if (HierarchicalParent is Scene scene) { Layer? beforeTmp = null; Layer? afterTmp = null; @@ -587,7 +570,7 @@ private void Operator_Invalidated(object? sender, EventArgs e) internal (Layer? Before, Layer? After, Layer? Cover) GetBeforeAndAfterAndCover(int zindex, TimeSpan start, Layer[] excludes) { - if (Parent is Scene scene) + if (HierarchicalParent is Scene scene) { Layer? beforeTmp = null; Layer? afterTmp = null; diff --git a/src/Beutl.ProjectSystem/ProjectSystem/Layers.cs b/src/Beutl.ProjectSystem/ProjectSystem/Layers.cs index 020c61d55..8dbd1f98f 100644 --- a/src/Beutl.ProjectSystem/ProjectSystem/Layers.cs +++ b/src/Beutl.ProjectSystem/ProjectSystem/Layers.cs @@ -3,9 +3,9 @@ namespace Beutl.ProjectSystem; -public sealed class Layers : LogicalList +public sealed class Layers : HierarchicalList { - public Layers(ILogicalElement parent) + public Layers(IModifiableHierarchical parent) : base(parent) { } diff --git a/src/Beutl.ProjectSystem/ProjectSystem/Project.cs b/src/Beutl.ProjectSystem/ProjectSystem/Project.cs index 5491c8386..4af6395c3 100644 --- a/src/Beutl.ProjectSystem/ProjectSystem/Project.cs +++ b/src/Beutl.ProjectSystem/ProjectSystem/Project.cs @@ -18,7 +18,7 @@ public static class ProjectVariableKeys } // Todo: IResourceProviderを実装 -public sealed class Project : Element, IStorable, ILogicalElement, IWorkspace +public sealed class Project : Hierarchical, IStorable, IWorkspace { public static readonly CoreProperty AppVersionProperty; public static readonly CoreProperty MinAppVersionProperty; @@ -26,7 +26,7 @@ public sealed class Project : Element, IStorable, ILogicalElement, IWorkspace private string? _fileName; private EventHandler? _saved; private EventHandler? _restored; - private readonly LogicalList _items; + private readonly HierarchicalList _items; private readonly Dictionary _variables = new(); static Project() @@ -44,7 +44,7 @@ static Project() public Project() { MinAppVersion = new Version(0, 3); - _items = new LogicalList(this); + _items = new HierarchicalList(this); _items.CollectionChanged += Items_CollectionChanged; } @@ -70,8 +70,6 @@ event EventHandler IStorable.Restored public DateTime LastSavedTime { get; private set; } - IEnumerable ILogicalElement.LogicalChildren => _items; - public ICoreList Items => _items; public IDictionary Variables => _variables; diff --git a/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs b/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs index 5c4d95150..99f1833e4 100644 --- a/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs +++ b/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs @@ -12,7 +12,7 @@ namespace Beutl.ProjectSystem; -public class Scene : Element, IStorable, IWorkspaceItem +public class Scene : Hierarchical, IStorable, IWorkspaceItem { public static readonly CoreProperty WidthProperty; public static readonly CoreProperty HeightProperty; @@ -371,19 +371,6 @@ public void Restore(string filename) _restored?.Invoke(this, EventArgs.Empty); } - protected override IEnumerable OnEnumerateChildren() - { - foreach (ILogicalElement item in base.OnEnumerateChildren()) - { - yield return item; - } - - foreach (Layer item in Children) - { - yield return item; - } - } - private void SyncronizeLayers(IEnumerable pathToLayer) { string baseDir = Path.GetDirectoryName(FileName)!; diff --git a/src/Beutl.ProjectSystem/SceneRenderer.cs b/src/Beutl.ProjectSystem/SceneRenderer.cs index 8a87a9a54..becd2aac9 100644 --- a/src/Beutl.ProjectSystem/SceneRenderer.cs +++ b/src/Beutl.ProjectSystem/SceneRenderer.cs @@ -86,7 +86,7 @@ void Detach(IList renderables) { foreach (Renderable item in renderables) { - if ((item as ILogicalElement).LogicalParent is RenderLayerSpan span + if (item.HierarchicalParent is RenderLayerSpan span && layer.Span != span) { span.Value.Remove(item); diff --git a/src/Beutl/ViewModels/GraphEditorKeyFrameViewModel.cs b/src/Beutl/ViewModels/GraphEditorKeyFrameViewModel.cs index bfef72bca..1829223f4 100644 --- a/src/Beutl/ViewModels/GraphEditorKeyFrameViewModel.cs +++ b/src/Beutl/ViewModels/GraphEditorKeyFrameViewModel.cs @@ -256,7 +256,7 @@ public void SubmitControlPoint2(float oldX, float oldY) public void SubmitCrossed(TimeSpan timeSpan) { - int rate = _parent.Parent.Scene.Parent is Project proj ? proj.GetFrameRate() : 30; + int rate = _parent.Parent.Scene.FindHierarchicalParent() is { } proj ? proj.GetFrameRate() : 30; Model.KeyTime = timeSpan.RoundToRate(rate); } @@ -266,7 +266,7 @@ public void SubmitKeyTimeAndValue(TimeSpan oldKeyTime) IKeyFrameAnimation animation = parent2.Animation; float scale = parent2.Options.Value.Scale; - int rate = parent2.Scene.Parent is Project proj ? proj.GetFrameRate() : 30; + int rate = parent2.Scene.FindHierarchicalParent() is { } proj ? proj.GetFrameRate() : 30; if (_parent.TryConvertFromDouble(Model.Value, EndY.Value / parent2.ScaleY.Value, animation.Property.PropertyType, out object? obj)) { diff --git a/src/Beutl/ViewModels/InlineKeyFrameViewModel.cs b/src/Beutl/ViewModels/InlineKeyFrameViewModel.cs index 8b5743793..4d8442723 100644 --- a/src/Beutl/ViewModels/InlineKeyFrameViewModel.cs +++ b/src/Beutl/ViewModels/InlineKeyFrameViewModel.cs @@ -47,7 +47,7 @@ public InlineKeyFrameViewModel(IKeyFrame keyframe, IKeyFrameAnimation animation, public void UpdateKeyTime() { float scale = Timeline.Options.Value.Scale; - Project? proj = Timeline.Scene.FindLogicalParent(); + Project? proj = Timeline.Scene.FindHierarchicalParent(); int rate = proj?.GetFrameRate() ?? 30; TimeSpan time = Left.Value.ToTimeSpan(scale).RoundToRate(rate); diff --git a/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs b/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs index 3083c89cc..45f2d3952 100644 --- a/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs @@ -141,7 +141,7 @@ public void NavigateTo(NodeTreeSpace nodeTree) { using var stack = new PooledList(); - ILogicalElement? current = nodeTree; + IHierarchical? current = nodeTree; while (current != null) { @@ -152,7 +152,7 @@ public void NavigateTo(NodeTreeSpace nodeTree) try { - current = current?.LogicalParent; + current = current?.HierarchicalParent; } catch { diff --git a/src/Beutl/ViewModels/NodeTree/NodeViewModel.cs b/src/Beutl/ViewModels/NodeTree/NodeViewModel.cs index c048194fc..4f6094e67 100644 --- a/src/Beutl/ViewModels/NodeTree/NodeViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/NodeViewModel.cs @@ -65,7 +65,7 @@ public NodeViewModel(Node node) Delete.Subscribe(() => { - NodeTreeSpace? tree = Node.FindLogicalParent(); + NodeTreeSpace? tree = Node.FindHierarchicalParent(); if (tree != null) { new RemoveCommand(tree.Nodes, Node) diff --git a/src/Beutl/ViewModels/PlayerViewModel.cs b/src/Beutl/ViewModels/PlayerViewModel.cs index 7dee1d56c..ae5236a69 100644 --- a/src/Beutl/ViewModels/PlayerViewModel.cs +++ b/src/Beutl/ViewModels/PlayerViewModel.cs @@ -92,7 +92,7 @@ public PlayerViewModel(Scene scene, ReactivePropertySlim isEnabled) public Scene Scene { get; } - public Project? Project => Scene.FindLogicalParent(); + public Project? Project => Scene.FindHierarchicalParent(); public ReactivePropertySlim PreviewImage { get; } = new(); diff --git a/src/Beutl/ViewModels/TimelineLayerViewModel.cs b/src/Beutl/ViewModels/TimelineLayerViewModel.cs index 16b1dc1be..1ff3d05f1 100644 --- a/src/Beutl/ViewModels/TimelineLayerViewModel.cs +++ b/src/Beutl/ViewModels/TimelineLayerViewModel.cs @@ -47,7 +47,7 @@ public TimelineLayerViewModel(Layer sceneLayer, TimelineViewModel timeline) Split.Where(func => func != null).Subscribe(func => { - int rate = Scene.Parent is Project proj ? proj.GetFrameRate() : 30; + int rate = Scene.FindHierarchicalParent() is { } proj ? proj.GetFrameRate() : 30; TimeSpan absTime = func!().RoundToRate(rate); TimeSpan forwardLength = absTime - Model.Start; TimeSpan backwardLength = Model.Length - forwardLength; @@ -141,7 +141,7 @@ public TimelineLayerViewModel(Layer sceneLayer, TimelineViewModel timeline) public Layer Model { get; } - public Scene Scene => (Scene)Model.Parent!; + public Scene Scene => (Scene)Model.HierarchicalParent!; public ReactiveProperty Margin { get; } @@ -222,7 +222,7 @@ public async Task SyncModelToViewModel() PrepareAnimationContext context = PrepareAnimation(); float scale = Timeline.Options.Value.Scale; - int rate = Scene.Parent is Project proj ? proj.GetFrameRate() : 30; + int rate = Scene.FindHierarchicalParent() is { } proj ? proj.GetFrameRate() : 30; int zindex = Timeline.ToLayerNumber(Margin.Value); TimeSpan start = BorderMargin.Value.Left.ToTimeSpan(scale).RoundToRate(rate); TimeSpan length = Width.Value.ToTimeSpan(scale).RoundToRate(rate); diff --git a/src/Beutl/Views/GraphEditorView.axaml.cs b/src/Beutl/Views/GraphEditorView.axaml.cs index 0eaf195e1..65cd3882e 100644 --- a/src/Beutl/Views/GraphEditorView.axaml.cs +++ b/src/Beutl/Views/GraphEditorView.axaml.cs @@ -192,7 +192,7 @@ private void OnContentPointerMoved(object? sender, PointerEventArgs e) PointerPoint pointerPt = e.GetCurrentPoint(graphPanel); _pointerFrame = pointerPt.Position.X.ToTimeSpan(viewModel.Options.Value.Scale) - .RoundToRate(viewModel.Scene.Parent is Project proj ? proj.GetFrameRate() : 30); + .RoundToRate(viewModel.Scene.FindHierarchicalParent() is { } proj ? proj.GetFrameRate() : 30); if (_pressed) { @@ -223,7 +223,7 @@ private void OnContentPointerPressed(object? sender, PointerPressedEventArgs e) _pressed = true; viewModel.Scene.CurrentFrame = pointerPt.Position.X.ToTimeSpan(viewModel.Options.Value.Scale) - .RoundToRate(viewModel.Scene.Parent is Project proj ? proj.GetFrameRate() : 30); + .RoundToRate(viewModel.Scene.FindHierarchicalParent() is { } proj ? proj.GetFrameRate() : 30); e.Handled = true; } @@ -379,7 +379,7 @@ private void OnGraphPanelPointerMoved(object? sender, PointerEventArgs e) itemViewModel.EndY.Value -= delta.Y; float scale = viewModel.Options.Value.Scale; - int rate = viewModel.Scene.Parent is Project proj ? proj.GetFrameRate() : 30; + int rate = viewModel.Scene.FindHierarchicalParent() is { } proj ? proj.GetFrameRate() : 30; double right = itemViewModel.Right.Value + delta.X; var timeSpan = right.ToTimeSpan(scale); diff --git a/src/Beutl/Views/InlineAnimationLayer.axaml.cs b/src/Beutl/Views/InlineAnimationLayer.axaml.cs index 852c4eefb..c334e1748 100644 --- a/src/Beutl/Views/InlineAnimationLayer.axaml.cs +++ b/src/Beutl/Views/InlineAnimationLayer.axaml.cs @@ -36,7 +36,7 @@ private void OnDrap(object? sender, DragEventArgs e) if (e.Data.Get("Easing") is Easing easing && DataContext is InlineAnimationLayerViewModel { Timeline: { Options.Value.Scale: { } scale, Scene:{ }scene } } viewModel) { - Project? proj = scene.FindLogicalParent(); + Project? proj = scene.FindHierarchicalParent(); int rate = proj?.GetFrameRate() ?? 30; TimeSpan time = e.GetPosition(this).X.ToTimeSpan(scale).RoundToRate(rate); diff --git a/src/Beutl/Views/Timeline.axaml.cs b/src/Beutl/Views/Timeline.axaml.cs index 6336fc63d..038745eab 100644 --- a/src/Beutl/Views/Timeline.axaml.cs +++ b/src/Beutl/Views/Timeline.axaml.cs @@ -214,7 +214,7 @@ private void TimelinePanel_PointerMoved(object? sender, PointerEventArgs e) TimelineViewModel viewModel = ViewModel; PointerPoint pointerPt = e.GetCurrentPoint(TimelinePanel); _pointerFrame = pointerPt.Position.X.ToTimeSpan(viewModel.Options.Value.Scale) - .RoundToRate(viewModel.Scene.Parent is Project proj ? proj.GetFrameRate() : 30); + .RoundToRate(viewModel.Scene.FindHierarchicalParent() is { } proj ? proj.GetFrameRate() : 30); if (ReferenceEquals(sender, TimelinePanel)) { @@ -284,7 +284,7 @@ private void TimelinePanel_PointerPressed(object? sender, PointerPressedEventArg TimelineViewModel viewModel = ViewModel; PointerPoint pointerPt = e.GetCurrentPoint(TimelinePanel); viewModel.ClickedFrame = pointerPt.Position.X.ToTimeSpan(viewModel.Options.Value.Scale) - .RoundToRate(viewModel.Scene.Parent is Project proj ? proj.GetFrameRate() : 30); + .RoundToRate(viewModel.Scene.FindHierarchicalParent() is { } proj ? proj.GetFrameRate() : 30); if (ReferenceEquals(sender, TimelinePanel)) { @@ -323,7 +323,7 @@ private async void TimelinePanel_Drop(object? sender, DragEventArgs e) Point pt = e.GetPosition(TimelinePanel); viewModel.ClickedFrame = pt.X.ToTimeSpan(viewModel.Options.Value.Scale) - .RoundToRate(viewModel.Scene.Parent is Project proj ? proj.GetFrameRate() : 30); + .RoundToRate(viewModel.Scene.FindHierarchicalParent() is { } proj ? proj.GetFrameRate() : 30); viewModel.ClickedLayer = viewModel.ToLayerNumber(pt.Y); if (e.Data.Get("SourceOperator") is OperatorRegistry.RegistryItem item2) diff --git a/src/Beutl/Views/TimelineLayer.axaml.cs b/src/Beutl/Views/TimelineLayer.axaml.cs index ae224cbea..61c56d042 100644 --- a/src/Beutl/Views/TimelineLayer.axaml.cs +++ b/src/Beutl/Views/TimelineLayer.axaml.cs @@ -436,7 +436,7 @@ private async void OnBorderPointerReleased(object? sender, PointerReleasedEventA else { float scale = viewModel.Timeline.Options.Value.Scale; - int rate = viewModel.Scene.Parent is Project proj ? proj.GetFrameRate() : 30; + int rate = viewModel.Scene.FindHierarchicalParent() is { } proj ? proj.GetFrameRate() : 30; TimeSpan newStart = viewModel.BorderMargin.Value.Left.ToTimeSpan(scale).RoundToRate(rate); int newIndex = viewModel.Timeline.ToLayerNumber(viewModel.Margin.Value); TimeSpan deltaStart = newStart - viewModel.Model.Start; diff --git a/src/Beutl/Views/Tools/SourceOperatorView.axaml.cs b/src/Beutl/Views/Tools/SourceOperatorView.axaml.cs index 79c9d8117..41910f54a 100644 --- a/src/Beutl/Views/Tools/SourceOperatorView.axaml.cs +++ b/src/Beutl/Views/Tools/SourceOperatorView.axaml.cs @@ -35,7 +35,7 @@ public void Remove_Click(object? sender, RoutedEventArgs e) if (DataContext is SourceOperatorViewModel viewModel2) { SourceOperator operation = viewModel2.Model; - Layer layer = operation.FindRequiredLogicalParent(); + Layer layer = operation.FindRequiredHierarchicalParent(); layer.RemoveChild(operation) .DoAndRecord(CommandRecorder.Default); } @@ -47,7 +47,7 @@ private void Drop(object? sender, DragEventArgs e) && DataContext is SourceOperatorViewModel viewModel2) { SourceOperator operation = viewModel2.Model; - Layer layer = operation.FindRequiredLogicalParent(); + Layer layer = operation.FindRequiredHierarchicalParent(); Rect bounds = Bounds; Point position = e.GetPosition(this); double half = bounds.Height / 2; diff --git a/tests/Beutl.Core.UnitTests/PropertyChangeTrackerTests.cs b/tests/Beutl.Core.UnitTests/PropertyChangeTrackerTests.cs index c16cd76d1..04b9dabcd 100644 --- a/tests/Beutl.Core.UnitTests/PropertyChangeTrackerTests.cs +++ b/tests/Beutl.Core.UnitTests/PropertyChangeTrackerTests.cs @@ -23,7 +23,7 @@ public void Test1() elm_1 } }; - var array = new Element[] { elm }; + var array = new Hierarchical[] { elm }; const string Foo = "Foo"; const string Bar = "Bar"; @@ -47,13 +47,13 @@ public void Test1() } } -public class TestElement : Element, ILogicalElement +public class TestElement : Hierarchical, IHierarchical { public static readonly CoreProperty StringProperty; public TestElement() { - Children = new LogicalList(this); + Children = new HierarchicalList(this); } static TestElement() @@ -70,7 +70,7 @@ public string String set => SetValue(StringProperty, value); } - public LogicalList Children { get; set; } + public HierarchicalList Children { get; set; } - IEnumerable ILogicalElement.LogicalChildren => Children; + IEnumerable IHierarchical.HierarchicalChildren => Children; } From 68fa7aae62bc966266770a52de6fd9fa71d6e7e8 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Tue, 21 Mar 2023 21:40:37 +0900 Subject: [PATCH 02/84] =?UTF-8?q?Project=E3=82=92Beutl.Core=E3=81=AB?= =?UTF-8?q?=E7=A7=BB=E5=8B=95=E3=80=81Workspace=E5=9E=8B=E5=90=8D=E3=82=92?= =?UTF-8?q?=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Api/Services/ExtensionProvider.cs | 8 +-- .../IProjectItemContainer.cs} | 10 ++-- .../ProjectSystem => Beutl.Core}/Project.cs | 25 ++++----- src/Beutl.Core/ProjectItem.cs | 53 +++++++++++++++++++ src/Beutl.Framework/EditorExtension.cs | 6 +-- src/Beutl.Framework/IWorkspace.cs | 16 ------ src/Beutl.Framework/IWorkspaceItem.cs | 5 -- src/Beutl.Framework/ProjectItemExtension.cs | 20 +++++++ .../Services/IProjectService.cs | 8 +-- src/Beutl.Framework/WorkspaceItemExtension.cs | 23 -------- .../ProjectSystem/Layer.cs | 45 ++-------------- .../ProjectSystem/Scene.cs | 34 ++---------- src/Beutl/App.axaml.cs | 2 +- src/Beutl/Helper.cs | 8 +-- .../PrimitiveImpls/SceneEditorExtension.cs | 2 +- .../SceneWorkspaceItemExtension.cs | 10 ++-- ...emContainer.cs => ProjectItemContainer.cs} | 36 ++++++------- src/Beutl/Services/ProjectService.cs | 16 +++--- .../Dialogs/CreateNewSceneViewModel.cs | 4 +- src/Beutl/ViewModels/EditPageViewModel.cs | 12 ++--- src/Beutl/ViewModels/MainViewModel.cs | 4 +- src/Beutl/ViewModels/OutputViewModel.cs | 2 +- src/Beutl/Views/MainView.axaml.cs | 14 ++--- 23 files changed, 162 insertions(+), 201 deletions(-) rename src/{Beutl.Framework/IWorkspaceItemContainer.cs => Beutl.Core/IProjectItemContainer.cs} (60%) rename src/{Beutl.ProjectSystem/ProjectSystem => Beutl.Core}/Project.cs (87%) create mode 100644 src/Beutl.Core/ProjectItem.cs delete mode 100644 src/Beutl.Framework/IWorkspace.cs delete mode 100644 src/Beutl.Framework/IWorkspaceItem.cs create mode 100644 src/Beutl.Framework/ProjectItemExtension.cs delete mode 100644 src/Beutl.Framework/WorkspaceItemExtension.cs rename src/Beutl/Services/{WorkspaceItemContainer.cs => ProjectItemContainer.cs} (61%) diff --git a/src/Beutl.Api/Services/ExtensionProvider.cs b/src/Beutl.Api/Services/ExtensionProvider.cs index 97dacb1f4..059731237 100644 --- a/src/Beutl.Api/Services/ExtensionProvider.cs +++ b/src/Beutl.Api/Services/ExtensionProvider.cs @@ -74,11 +74,11 @@ public TExtension[] GetExtensions() return null; } - public WorkspaceItemExtension? MatchWorkspaceItemExtension(string file) + public ProjectItemExtension? MatchProjectItemExtension(string file) { foreach (Extension extension in AllExtensions) { - if (extension is WorkspaceItemExtension wsiExtension && + if (extension is ProjectItemExtension wsiExtension && wsiExtension.IsSupported(file)) { return wsiExtension; @@ -88,11 +88,11 @@ public TExtension[] GetExtensions() return null; } - public IEnumerable MatchWorkspaceItemExtensions(string file) + public IEnumerable MatchProjectItemExtensions(string file) { foreach (Extension extension in AllExtensions) { - if (extension is WorkspaceItemExtension wsiExtension && + if (extension is ProjectItemExtension wsiExtension && wsiExtension.IsSupported(file)) { yield return wsiExtension; diff --git a/src/Beutl.Framework/IWorkspaceItemContainer.cs b/src/Beutl.Core/IProjectItemContainer.cs similarity index 60% rename from src/Beutl.Framework/IWorkspaceItemContainer.cs rename to src/Beutl.Core/IProjectItemContainer.cs index 65b6d34be..c0188dda5 100644 --- a/src/Beutl.Framework/IWorkspaceItemContainer.cs +++ b/src/Beutl.Core/IProjectItemContainer.cs @@ -1,17 +1,17 @@ using System.Diagnostics.CodeAnalysis; -namespace Beutl.Framework; +namespace Beutl; -public interface IWorkspaceItemContainer +public interface IProjectItemContainer { bool TryGetOrCreateItem(string file, [NotNullWhen(true)] out T? item) - where T : class, IWorkspaceItem; + where T : ProjectItem; - bool TryGetOrCreateItem(string file, [NotNullWhen(true)] out IWorkspaceItem? item); + bool TryGetOrCreateItem(string file, [NotNullWhen(true)] out ProjectItem? item); bool IsCreated(string file); bool Remove(string file); - void Add(IWorkspaceItem item); + void Add(ProjectItem item); } diff --git a/src/Beutl.ProjectSystem/ProjectSystem/Project.cs b/src/Beutl.Core/Project.cs similarity index 87% rename from src/Beutl.ProjectSystem/ProjectSystem/Project.cs rename to src/Beutl.Core/Project.cs index 4af6395c3..f51db57ba 100644 --- a/src/Beutl.ProjectSystem/ProjectSystem/Project.cs +++ b/src/Beutl.Core/Project.cs @@ -1,15 +1,12 @@ using System.Collections.Specialized; -using System.ComponentModel; using System.Reflection; -using System.Text.Json; using System.Text.Json.Nodes; using Beutl.Collections; -using Beutl.Framework; using Microsoft.Extensions.DependencyInjection; -namespace Beutl.ProjectSystem; +namespace Beutl; public static class ProjectVariableKeys { @@ -18,7 +15,7 @@ public static class ProjectVariableKeys } // Todo: IResourceProviderを実装 -public sealed class Project : Hierarchical, IStorable, IWorkspace +public sealed class Project : Hierarchical, IStorable, IHierarchicalRoot { public static readonly CoreProperty AppVersionProperty; public static readonly CoreProperty MinAppVersionProperty; @@ -26,7 +23,7 @@ public sealed class Project : Hierarchical, IStorable, IWorkspace private string? _fileName; private EventHandler? _saved; private EventHandler? _restored; - private readonly HierarchicalList _items; + private readonly HierarchicalList _items; private readonly Dictionary _variables = new(); static Project() @@ -44,7 +41,7 @@ static Project() public Project() { MinAppVersion = new Version(0, 3); - _items = new HierarchicalList(this); + _items = new HierarchicalList(this); _items.CollectionChanged += Items_CollectionChanged; } @@ -70,7 +67,7 @@ event EventHandler IStorable.Restored public DateTime LastSavedTime { get; private set; } - public ICoreList Items => _items; + public ICoreList Items => _items; public IDictionary Variables => _variables; @@ -144,7 +141,7 @@ public override void WriteToJson(ref JsonNode json) jobject["minAppVersion"] = JsonValue.Create(MinAppVersion); var items = new JsonArray(); - foreach (IWorkspaceItem item in Items) + foreach (ProjectItem item in Items) { string path = Path.GetRelativePath(RootDirectory, item.FileName).Replace('\\', '/'); var value = JsonValue.Create(path); @@ -181,21 +178,21 @@ private void SyncronizeScenes(IEnumerable pathToItem) pathToItem = pathToItem.Select(x => Path.GetFullPath(x, RootDirectory)).ToArray(); // 削除するシーン - IEnumerable toRemoveItems = _items.ExceptBy(pathToItem, x => x.FileName); + IEnumerable toRemoveItems = _items.ExceptBy(pathToItem, x => x.FileName); // 追加するシーン IEnumerable toAddItems = pathToItem.Except(_items.Select(x => x.FileName)); - foreach (IWorkspaceItem? item in toRemoveItems) + foreach (ProjectItem? item in toRemoveItems) { _items.Remove(item); } - IWorkspaceItemContainer resolver = ServiceLocator.Current.GetRequiredService(); + IProjectItemContainer resolver = ServiceLocator.Current.GetRequiredService(); foreach (string item in toAddItems) { - if (resolver.TryGetOrCreateItem(item, out IWorkspaceItem? workspaceItem)) + if (resolver.TryGetOrCreateItem(item, out ProjectItem? projectItem)) { - _items.Add(workspaceItem); + _items.Add(projectItem); } } diff --git a/src/Beutl.Core/ProjectItem.cs b/src/Beutl.Core/ProjectItem.cs new file mode 100644 index 000000000..33bbb166d --- /dev/null +++ b/src/Beutl.Core/ProjectItem.cs @@ -0,0 +1,53 @@ +namespace Beutl; + +public abstract class ProjectItem : Hierarchical, IStorable +{ + public static readonly CoreProperty FileNameProperty; + private string? _fileName; + + static ProjectItem() + { + FileNameProperty = ConfigureProperty(nameof(FileName)) + .Accessor(o => o.FileName, (o, v) => o.FileName = v) + .PropertyFlags(PropertyFlags.NotifyChanged) + .Register(); + } + + public string FileName + { + get => _fileName!; + set => SetAndRaise(FileNameProperty, ref _fileName!, value); + } + + public DateTime LastSavedTime { get; private set; } + + public event EventHandler? Saved; + public event EventHandler? Restored; + + public virtual void Restore(string filename) + { + RestoreCore(filename); + FileName = filename; + LastSavedTime = File.GetLastWriteTimeUtc(filename); + + Restored?.Invoke(this, EventArgs.Empty); + } + + public virtual void Save(string filename) + { + FileName = filename; + LastSavedTime = DateTime.UtcNow; + SaveCore(filename); + File.SetLastWriteTimeUtc(filename, LastSavedTime); + + Saved?.Invoke(this, EventArgs.Empty); + } + + protected virtual void RestoreCore(string filename) + { + } + + protected virtual void SaveCore(string filename) + { + } +} diff --git a/src/Beutl.Framework/EditorExtension.cs b/src/Beutl.Framework/EditorExtension.cs index 2c232871b..7b746fb67 100644 --- a/src/Beutl.Framework/EditorExtension.cs +++ b/src/Beutl.Framework/EditorExtension.cs @@ -45,8 +45,8 @@ public abstract bool TryCreateEditor( string file, [NotNullWhen(true)] out IEditor? editor); - // NOTE: ここからIWorkspaceItemを取得する場合、 - // IWorkspaceItemContainerから取得すればいい + // NOTE: ここからProjectItemを取得する場合、 + // ProjectItemContainerから取得すればいい public abstract bool TryCreateContext( string file, [NotNullWhen(true)] out IEditorContext? context); @@ -60,7 +60,7 @@ public virtual bool IsSupported(string file) public abstract bool MatchFileExtension(string ext); // 'ServiceLocator'から'IProjectService'を取得し、Projectのインスタンスを取得します。 - protected static IWorkspace? GetCurrentProject() + protected static Project? GetCurrentProject() { return ServiceLocator.Current.GetRequiredService().CurrentProject.Value; } diff --git a/src/Beutl.Framework/IWorkspace.cs b/src/Beutl.Framework/IWorkspace.cs deleted file mode 100644 index 2611bf3c0..000000000 --- a/src/Beutl.Framework/IWorkspace.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Beutl.Collections; - -namespace Beutl.Framework; - -public interface IWorkspace : IHierarchicalRoot, IDisposable, IStorable -{ - ICoreList Items { get; } - - IDictionary Variables { get; } - - Version AppVersion { get; } - - Version MinAppVersion { get; } - - string RootDirectory { get; } -} diff --git a/src/Beutl.Framework/IWorkspaceItem.cs b/src/Beutl.Framework/IWorkspaceItem.cs deleted file mode 100644 index 2d6006dc8..000000000 --- a/src/Beutl.Framework/IWorkspaceItem.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Beutl.Framework; - -public interface IWorkspaceItem : IStorable, IHierarchicalRoot, ICoreObject -{ -} diff --git a/src/Beutl.Framework/ProjectItemExtension.cs b/src/Beutl.Framework/ProjectItemExtension.cs new file mode 100644 index 000000000..bb0f0b184 --- /dev/null +++ b/src/Beutl.Framework/ProjectItemExtension.cs @@ -0,0 +1,20 @@ +using System.Diagnostics.CodeAnalysis; + +using Avalonia.Platform.Storage; + +using FluentAvalonia.UI.Controls; + +namespace Beutl.Framework; + +public abstract class ProjectItemExtension : Extension +{ + public abstract FilePickerFileType GetFilePickerFileType(); + + public abstract IconSource? GetIcon(); + + public abstract bool TryCreateItem( + string file, + [NotNullWhen(true)] out ProjectItem? context); + + public abstract bool IsSupported(string file); +} diff --git a/src/Beutl.Framework/Services/IProjectService.cs b/src/Beutl.Framework/Services/IProjectService.cs index 9d882caa6..5e15e267d 100644 --- a/src/Beutl.Framework/Services/IProjectService.cs +++ b/src/Beutl.Framework/Services/IProjectService.cs @@ -4,15 +4,15 @@ namespace Beutl.Framework.Services; public interface IProjectService { - IObservable<(IWorkspace? New, IWorkspace? Old)> ProjectObservable { get; } + IObservable<(Project? New, Project? Old)> ProjectObservable { get; } - IReactiveProperty CurrentProject { get; } + IReactiveProperty CurrentProject { get; } IReadOnlyReactiveProperty IsOpened { get; } - IWorkspace? OpenProject(string file); + Project? OpenProject(string file); void CloseProject(); - IWorkspace? CreateProject(int width, int height, int framerate, int samplerate, string name, string location); + Project? CreateProject(int width, int height, int framerate, int samplerate, string name, string location); } diff --git a/src/Beutl.Framework/WorkspaceItemExtension.cs b/src/Beutl.Framework/WorkspaceItemExtension.cs deleted file mode 100644 index c1a9d5ef3..000000000 --- a/src/Beutl.Framework/WorkspaceItemExtension.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -using Avalonia.Platform.Storage; - -using FluentAvalonia.UI.Controls; - -namespace Beutl.Framework; - -// NOTE: EditorExtension内にIWorkspaceItemを作成するメソッドを追加すると、 -// WorkspaceItemのエディターを固定してしまい拡張性が悪くなる(例えばSceneのエディターを変更できないなど)ので、 -// WorkspaceItemExtensisonとEditorExtensionを分けた -public abstract class WorkspaceItemExtension : Extension -{ - public abstract FilePickerFileType GetFilePickerFileType(); - - public abstract IconSource? GetIcon(); - - public abstract bool TryCreateItem( - string file, - [NotNullWhen(true)] out IWorkspaceItem? context); - - public abstract bool IsSupported(string file); -} diff --git a/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs b/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs index 395f7a09d..a988ee0a5 100644 --- a/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs +++ b/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs @@ -13,7 +13,7 @@ namespace Beutl.ProjectSystem; -public class Layer : Hierarchical, IStorable +public class Layer : ProjectItem { public static readonly CoreProperty StartProperty; public static readonly CoreProperty LengthProperty; @@ -25,15 +25,11 @@ public class Layer : Hierarchical, IStorable public static readonly CoreProperty OperatorsProperty; public static readonly CoreProperty SpaceProperty; public static readonly CoreProperty UseNodeProperty; - public static readonly CoreProperty FileNameProperty; private TimeSpan _start; private TimeSpan _length; private int _zIndex; - private string? _fileName; private bool _isEnabled = true; private bool _allowOutflow = false; - private EventHandler? _saved; - private EventHandler? _restored; private IDisposable? _disposable; private bool _useNode; @@ -99,11 +95,6 @@ static Layer() .SerializeName("useNode") .Register(); - FileNameProperty = ConfigureProperty(nameof(FileName)) - .Accessor(o => o.FileName, (o, v) => o.FileName = v) - .PropertyFlags(PropertyFlags.NotifyChanged) - .Register(); - NameProperty.OverrideMetadata(new CorePropertyMetadata("name")); ZIndexProperty.Changed.Subscribe(args => @@ -209,18 +200,6 @@ private void UpdateName() Name = sb.ToString(); } - event EventHandler IStorable.Saved - { - add => _saved += value; - remove => _saved -= value; - } - - event EventHandler IStorable.Restored - { - add => _restored += value; - remove => _restored -= value; - } - // 0以上 public TimeSpan Start { @@ -265,14 +244,6 @@ public bool AllowOutflow [Obsolete("Use 'Layer.Span'.")] public RenderLayerSpan Node => Span; - public string FileName - { - get => _fileName!; - set => SetAndRaise(FileNameProperty, ref _fileName!, value); - } - - public DateTime LastSavedTime { get; private set; } - public SourceOperators Operators { get; } public LayerNodeTreeModel Space { get; } @@ -283,24 +254,14 @@ public bool UseNode set => SetAndRaise(UseNodeProperty, ref _useNode, value); } - public void Save(string filename) + protected override void SaveCore(string filename) { - _fileName = filename; - LastSavedTime = DateTime.UtcNow; this.JsonSave(filename); - File.SetLastWriteTimeUtc(filename, LastSavedTime); - - _saved?.Invoke(this, EventArgs.Empty); } - public void Restore(string filename) + protected override void RestoreCore(string filename) { - _fileName = filename; - this.JsonRestore(filename); - LastSavedTime = File.GetLastWriteTimeUtc(filename); - - _restored?.Invoke(this, EventArgs.Empty); } public override void ReadFromJson(JsonNode json) diff --git a/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs b/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs index 99f1833e4..afa4ea8af 100644 --- a/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs +++ b/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs @@ -12,7 +12,7 @@ namespace Beutl.ProjectSystem; -public class Scene : Hierarchical, IStorable, IWorkspaceItem +public class Scene : ProjectItem, IHierarchicalRoot { public static readonly CoreProperty WidthProperty; public static readonly CoreProperty HeightProperty; @@ -98,18 +98,6 @@ static Scene() }); } - event EventHandler IStorable.Saved - { - add => _saved += value; - remove => _saved -= value; - } - - event EventHandler IStorable.Restored - { - add => _restored += value; - remove => _restored -= value; - } - public int Width => Renderer.Graphics.Size.Width; public int Height => Renderer.Graphics.Size.Height; @@ -158,10 +146,6 @@ public IRenderer Renderer private set => SetAndRaise(RendererProperty, ref _renderer, value); } - public string FileName => _fileName ?? throw new Exception("The file name is not set."); - - public DateTime LastSavedTime { get; private set; } - [MemberNotNull("_renderer")] public void Initialize(int width, int height) { @@ -344,11 +328,9 @@ static void Process(JsonObject jobject, string jsonName, List list) json["layers"] = layersNode; } - public void Save(string filename) + protected override void SaveCore(string filename) { - _fileName = filename; - LastSavedTime = DateTime.UtcNow; - string? directory = Path.GetDirectoryName(_fileName); + string? directory = Path.GetDirectoryName(filename); if (directory != null && !Directory.Exists(directory)) { @@ -356,19 +338,11 @@ public void Save(string filename) } this.JsonSave(filename); - File.SetLastWriteTimeUtc(filename, LastSavedTime); - - _saved?.Invoke(this, EventArgs.Empty); } - public void Restore(string filename) + protected override void RestoreCore(string filename) { - _fileName = filename; - this.JsonRestore(filename); - LastSavedTime = File.GetLastWriteTimeUtc(filename); - - _restored?.Invoke(this, EventArgs.Empty); } private void SyncronizeLayers(IEnumerable pathToLayer) diff --git a/src/Beutl/App.axaml.cs b/src/Beutl/App.axaml.cs index 6123ca5e1..705228221 100644 --- a/src/Beutl/App.axaml.cs +++ b/src/Beutl/App.axaml.cs @@ -92,7 +92,7 @@ public override void RegisterServices() .BindToSelfSingleton() .BindToSelfSingleton() .Bind().ToSingleton() - .BindToSelf(new WorkspaceItemContainer()) + .BindToSelf(new ProjectItemContainer()) .BindToSelf(new ProjectService()) .BindToSelf(new NotificationService()); diff --git a/src/Beutl/Helper.cs b/src/Beutl/Helper.cs index 67ef6c2a0..9bb2d1051 100644 --- a/src/Beutl/Helper.cs +++ b/src/Beutl/Helper.cs @@ -22,17 +22,17 @@ static Helper() LayerHeight = (double)(Application.Current?.FindResource("LayerHeight") ?? 25); } - public static int GetFrameRate(this IWorkspace workspace) + public static int GetFrameRate(this Project project) { - return workspace.Variables.TryGetValue(ProjectVariableKeys.FrameRate, out string? value) + return project.Variables.TryGetValue(ProjectVariableKeys.FrameRate, out string? value) && int.TryParse(value, out int rate) ? rate : 30; } - public static int GetSampleRate(this IWorkspace workspace) + public static int GetSampleRate(this Project project) { - return workspace.Variables.TryGetValue(ProjectVariableKeys.SampleRate, out string? value) + return project.Variables.TryGetValue(ProjectVariableKeys.SampleRate, out string? value) && int.TryParse(value, out int rate) ? rate : 44100; diff --git a/src/Beutl/Services/PrimitiveImpls/SceneEditorExtension.cs b/src/Beutl/Services/PrimitiveImpls/SceneEditorExtension.cs index 95a995f77..f9b406b0e 100644 --- a/src/Beutl/Services/PrimitiveImpls/SceneEditorExtension.cs +++ b/src/Beutl/Services/PrimitiveImpls/SceneEditorExtension.cs @@ -43,7 +43,7 @@ public override bool TryCreateEditor(string file, [NotNullWhen(true)] out IEdito public override bool TryCreateContext(string file, [NotNullWhen(true)] out IEditorContext? context) { if (file.EndsWith($".{Constants.SceneFileExtension}") - && ServiceLocator.Current.GetRequiredService().TryGetOrCreateItem(file, out Scene? model)) + && ServiceLocator.Current.GetRequiredService().TryGetOrCreateItem(file, out Scene? model)) { context = new EditViewModel(model); return true; diff --git a/src/Beutl/Services/PrimitiveImpls/SceneWorkspaceItemExtension.cs b/src/Beutl/Services/PrimitiveImpls/SceneWorkspaceItemExtension.cs index 7ff0e212d..84bd7a3cd 100644 --- a/src/Beutl/Services/PrimitiveImpls/SceneWorkspaceItemExtension.cs +++ b/src/Beutl/Services/PrimitiveImpls/SceneWorkspaceItemExtension.cs @@ -14,13 +14,13 @@ namespace Beutl.Services.PrimitiveImpls; [PrimitiveImpl] -public sealed class SceneWorkspaceItemExtension : WorkspaceItemExtension +public sealed class SceneProjectItemExtension : ProjectItemExtension { - public static readonly SceneWorkspaceItemExtension Instance = new(); + public static readonly SceneProjectItemExtension Instance = new(); - public override string Name => "Make the scene a workspace item."; + public override string Name => "Make the scene a project item."; - public override string DisplayName => "Make the scene a workspace item."; + public override string DisplayName => "Make the scene a project item."; public override FilePickerFileType GetFilePickerFileType() { @@ -46,7 +46,7 @@ public override bool IsSupported(string file) return file.EndsWith($".{Constants.SceneFileExtension}"); } - public override bool TryCreateItem(string file, [NotNullWhen(true)] out IWorkspaceItem? result) + public override bool TryCreateItem(string file, [NotNullWhen(true)] out ProjectItem? result) { result = null; if (file.EndsWith($".{Constants.SceneFileExtension}")) diff --git a/src/Beutl/Services/WorkspaceItemContainer.cs b/src/Beutl/Services/ProjectItemContainer.cs similarity index 61% rename from src/Beutl/Services/WorkspaceItemContainer.cs rename to src/Beutl/Services/ProjectItemContainer.cs index c31a4d57a..41f06eab1 100644 --- a/src/Beutl/Services/WorkspaceItemContainer.cs +++ b/src/Beutl/Services/ProjectItemContainer.cs @@ -8,19 +8,19 @@ namespace Beutl.Services; -public sealed class WorkspaceItemContainer : IWorkspaceItemContainer +public sealed class ProjectItemContainer : IProjectItemContainer { - private readonly List> _items = new(); + private readonly List> _items = new(); public bool IsCreated(string file) { - return _items.Any(i => i.TryGetTarget(out IWorkspaceItem? target) && target.FileName == file); + return _items.Any(i => i.TryGetTarget(out ProjectItem? target) && target.FileName == file); } public bool Remove(string file) { - WeakReference? item - = _items.Find(i => i.TryGetTarget(out IWorkspaceItem? target) && target.FileName == file); + WeakReference? item + = _items.Find(i => i.TryGetTarget(out ProjectItem? target) && target.FileName == file); if (item != null) return _items.Remove(item); else @@ -28,11 +28,11 @@ public bool Remove(string file) } public bool TryGetOrCreateItem(string file, [NotNullWhen(true)] out T? item) - where T : class, IWorkspaceItem + where T : ProjectItem { item = default; - item = _items.Find(i => i.TryGetTarget(out IWorkspaceItem? target) && target.FileName == file && target is T) - ?.TryGetTarget(out IWorkspaceItem? target) ?? false + item = _items.Find(i => i.TryGetTarget(out ProjectItem? target) && target.FileName == file && target is T) + ?.TryGetTarget(out ProjectItem? target) ?? false ? target as T : null; @@ -49,9 +49,9 @@ public bool TryGetOrCreateItem(string file, [NotNullWhen(true)] out T? item) } var extensionProvider = ServiceLocator.Current.GetRequiredService(); - foreach (WorkspaceItemExtension ext in extensionProvider.MatchWorkspaceItemExtensions(file)) + foreach (ProjectItemExtension ext in extensionProvider.MatchProjectItemExtensions(file)) { - if (ext.TryCreateItem(file, out IWorkspaceItem? result) && result is T typed) + if (ext.TryCreateItem(file, out ProjectItem? result) && result is T typed) { Add(typed); item = typed; @@ -62,11 +62,11 @@ public bool TryGetOrCreateItem(string file, [NotNullWhen(true)] out T? item) return false; } - public bool TryGetOrCreateItem(string file, [NotNullWhen(true)] out IWorkspaceItem? item) + public bool TryGetOrCreateItem(string file, [NotNullWhen(true)] out ProjectItem? item) { item = default; - item = _items.Find(i => i.TryGetTarget(out IWorkspaceItem? target) && target.FileName == file) - ?.TryGetTarget(out IWorkspaceItem? target) ?? false + item = _items.Find(i => i.TryGetTarget(out ProjectItem? target) && target.FileName == file) + ?.TryGetTarget(out ProjectItem? target) ?? false ? target : default; if (item != null) @@ -82,9 +82,9 @@ public bool TryGetOrCreateItem(string file, [NotNullWhen(true)] out IWorkspaceIt } var extensionProvider = ServiceLocator.Current.GetRequiredService(); - foreach (WorkspaceItemExtension ext in extensionProvider.MatchWorkspaceItemExtensions(file)) + foreach (ProjectItemExtension ext in extensionProvider.MatchProjectItemExtensions(file)) { - if (ext.TryCreateItem(file, out IWorkspaceItem? result)) + if (ext.TryCreateItem(file, out ProjectItem? result)) { Add(result); item = result; @@ -95,9 +95,9 @@ public bool TryGetOrCreateItem(string file, [NotNullWhen(true)] out IWorkspaceIt return false; } - public void Add(IWorkspaceItem item) + public void Add(ProjectItem item) { - foreach (WeakReference wref in _items) + foreach (WeakReference wref in _items) { if (!wref.TryGetTarget(out _)) { @@ -106,6 +106,6 @@ public void Add(IWorkspaceItem item) } } - _items.Add(new WeakReference(item)); + _items.Add(new WeakReference(item)); } } diff --git a/src/Beutl/Services/ProjectService.cs b/src/Beutl/Services/ProjectService.cs index b21c0f359..20003e8d4 100644 --- a/src/Beutl/Services/ProjectService.cs +++ b/src/Beutl/Services/ProjectService.cs @@ -14,8 +14,8 @@ namespace Beutl.Services; public sealed class ProjectService : IProjectService { - private readonly Subject<(IWorkspace? New, IWorkspace? Old)> _projectObservable = new(); - private readonly ReactivePropertySlim _currentProject = new(); + private readonly Subject<(Project? New, Project? Old)> _projectObservable = new(); + private readonly ReactivePropertySlim _currentProject = new(); private readonly ReadOnlyReactivePropertySlim _isOpened; public ProjectService() @@ -23,20 +23,20 @@ public ProjectService() _isOpened = CurrentProject.Select(v => v != null).ToReadOnlyReactivePropertySlim(); } - public IObservable<(IWorkspace? New, IWorkspace? Old)> ProjectObservable => _projectObservable; + public IObservable<(Project? New, Project? Old)> ProjectObservable => _projectObservable; - public IReactiveProperty CurrentProject => _currentProject; + public IReactiveProperty CurrentProject => _currentProject; public IReadOnlyReactiveProperty IsOpened => _isOpened; - public IWorkspace? OpenProject(string file) + public Project? OpenProject(string file) { try { var project = new Project(); project.Restore(file); - IWorkspace? old = CurrentProject.Value; + Project? old = CurrentProject.Value; CurrentProject.Value = project; // 値を発行 _projectObservable.OnNext((New: project, old)); @@ -63,12 +63,12 @@ public void CloseProject() } } - public IWorkspace? CreateProject(int width, int height, int framerate, int samplerate, string name, string location) + public Project? CreateProject(int width, int height, int framerate, int samplerate, string name, string location) { try { location = Path.Combine(location, name); - IWorkspaceItemContainer container = ServiceLocator.Current.GetRequiredService(); + IProjectItemContainer container = ServiceLocator.Current.GetRequiredService(); var scene = new Scene(width, height, name); container.Add(scene); var project = new Project() diff --git a/src/Beutl/ViewModels/Dialogs/CreateNewSceneViewModel.cs b/src/Beutl/ViewModels/Dialogs/CreateNewSceneViewModel.cs index e513fde2a..7b7da3e59 100644 --- a/src/Beutl/ViewModels/Dialogs/CreateNewSceneViewModel.cs +++ b/src/Beutl/ViewModels/Dialogs/CreateNewSceneViewModel.cs @@ -14,7 +14,7 @@ namespace Beutl.ViewModels.Dialogs; public sealed class CreateNewSceneViewModel { - private readonly IWorkspace? _proj; + private readonly Project? _proj; public CreateNewSceneViewModel() { @@ -63,7 +63,7 @@ public CreateNewSceneViewModel() Create = new ReactiveCommand(CanCreate); Create.Subscribe(() => { - IWorkspaceItemContainer container = ServiceLocator.Current.GetRequiredService(); + IProjectItemContainer container = ServiceLocator.Current.GetRequiredService(); EditorService editPage = ServiceLocator.Current.GetRequiredService(); var scene = new Scene(Size.Value.Width, Size.Value.Height, Name.Value); container.Add(scene); diff --git a/src/Beutl/ViewModels/EditPageViewModel.cs b/src/Beutl/ViewModels/EditPageViewModel.cs index 0f2137da1..5b3eb1354 100644 --- a/src/Beutl/ViewModels/EditPageViewModel.cs +++ b/src/Beutl/ViewModels/EditPageViewModel.cs @@ -25,7 +25,7 @@ public EditPageViewModel() _projectService.ProjectObservable.Subscribe(item => ProjectChanged(item.New, item.Old)); } - public IReactiveProperty Project => _projectService.CurrentProject; + public IReactiveProperty Project => _projectService.CurrentProject; public IReadOnlyReactiveProperty IsProjectOpened => _projectService.IsOpened; @@ -37,13 +37,13 @@ public EditPageViewModel() public string Header => Strings.Edit; - private void ProjectChanged(IWorkspace? @new, IWorkspace? old) + private void ProjectChanged(Project? @new, Project? old) { // プロジェクトが開いた if (@new != null) { @new.Items.CollectionChanged += Project_Items_CollectionChanged; - foreach (IWorkspaceItem item in @new.Items) + foreach (ProjectItem item in @new.Items) { SelectOrAddTabItem(item.FileName, TabOpenMode.FromProject); } @@ -53,7 +53,7 @@ private void ProjectChanged(IWorkspace? @new, IWorkspace? old) if (old != null) { old.Items.CollectionChanged -= Project_Items_CollectionChanged; - foreach (IWorkspaceItem item in old.Items) + foreach (ProjectItem item in old.Items) { CloseTabItem(item.FileName, TabOpenMode.FromProject); } @@ -65,7 +65,7 @@ private void Project_Items_CollectionChanged(object? sender, NotifyCollectionCha if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null) { - foreach (IWorkspaceItem item in e.NewItems.OfType()) + foreach (ProjectItem item in e.NewItems.OfType()) { SelectOrAddTabItem(item.FileName, TabOpenMode.FromProject); } @@ -73,7 +73,7 @@ private void Project_Items_CollectionChanged(object? sender, NotifyCollectionCha else if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null) { - foreach (IWorkspaceItem item in e.OldItems.OfType()) + foreach (ProjectItem item in e.OldItems.OfType()) { CloseTabItem(item.FileName, TabOpenMode.FromProject); } diff --git a/src/Beutl/ViewModels/MainViewModel.cs b/src/Beutl/ViewModels/MainViewModel.cs index 007c5d6cc..296845f1e 100644 --- a/src/Beutl/ViewModels/MainViewModel.cs +++ b/src/Beutl/ViewModels/MainViewModel.cs @@ -127,7 +127,7 @@ public MainViewModel() SaveAll.Subscribe(async () => { - IWorkspace? project = _projectService.CurrentProject.Value; + Project? project = _projectService.CurrentProject.Value; int itemsCount = 0; try @@ -329,7 +329,7 @@ public Task RunSplachScreenTask(Func, Task> sh { SceneEditorExtension.Instance, SceneOutputExtension.Instance, - SceneWorkspaceItemExtension.Instance, + SceneProjectItemExtension.Instance, TimelineTabExtension.Instance, ObjectPropertyTabExtension.Instance, StyleEditorTabExtension.Instance, diff --git a/src/Beutl/ViewModels/OutputViewModel.cs b/src/Beutl/ViewModels/OutputViewModel.cs index e7fe6b7ee..8ec8113cb 100644 --- a/src/Beutl/ViewModels/OutputViewModel.cs +++ b/src/Beutl/ViewModels/OutputViewModel.cs @@ -302,7 +302,7 @@ public sealed class OutputViewModel : IOutputContext private readonly ReactivePropertySlim _progress = new(); private readonly ReadOnlyObservableCollection _encoders; private readonly IDisposable _disposable1; - private readonly IWorkspaceItemContainer _itemContainer = ServiceLocator.Current.GetRequiredService(); + private readonly IProjectItemContainer _itemContainer = ServiceLocator.Current.GetRequiredService(); private CancellationTokenSource? _lastCts; public OutputViewModel(SceneFile model) diff --git a/src/Beutl/Views/MainView.axaml.cs b/src/Beutl/Views/MainView.axaml.cs index 0e2f5fe57..e3551f6ea 100644 --- a/src/Beutl/Views/MainView.axaml.cs +++ b/src/Beutl/Views/MainView.axaml.cs @@ -95,7 +95,7 @@ public sealed partial class MainView : UserControl private readonly EditorService _editorService = ServiceLocator.Current.GetRequiredService(); private readonly IProjectService _projectService = ServiceLocator.Current.GetRequiredService(); private readonly INotificationService _notificationService = ServiceLocator.Current.GetRequiredService(); - private readonly IWorkspaceItemContainer _workspaceItemContainer = ServiceLocator.Current.GetRequiredService(); + private readonly IProjectItemContainer _projectItemContainer = ServiceLocator.Current.GetRequiredService(); private readonly Avalonia.Animation.Animation _animation = new() { Easing = new SplineEasing(0.1, 0.9, 0.2, 1.0), @@ -461,14 +461,14 @@ private void InitCommands(MainViewModel viewModel) if (files.Count > 0) { bool? addToProject = null; - IWorkspace? project = _projectService.CurrentProject.Value; + Project? project = _projectService.CurrentProject.Value; foreach (IStorageFile file in files) { if (file.TryGetUri(out Uri? uri) && uri.IsFile) { string path = uri.LocalPath; - if (project != null && _workspaceItemContainer.TryGetOrCreateItem(path, out IWorkspaceItem? item)) + if (project != null && _projectItemContainer.TryGetOrCreateItem(path, out ProjectItem? item)) { if (!addToProject.HasValue) { @@ -523,7 +523,7 @@ private void InitCommands(MainViewModel viewModel) viewModel.AddToProject.Subscribe(() => { - IWorkspace? project = _projectService.CurrentProject.Value; + Project? project = _projectService.CurrentProject.Value; EditorTabItem? selectedTabItem = _editorService.SelectedTabItem.Value; if (project != null && selectedTabItem != null) @@ -532,7 +532,7 @@ private void InitCommands(MainViewModel viewModel) if (project.Items.Any(i => i.FileName == filePath)) return; - if (_workspaceItemContainer.TryGetOrCreateItem(filePath, out IWorkspaceItem? workspaceItem)) + if (_projectItemContainer.TryGetOrCreateItem(filePath, out ProjectItem? workspaceItem)) { project.Items.Add(workspaceItem); } @@ -541,13 +541,13 @@ private void InitCommands(MainViewModel viewModel) viewModel.RemoveFromProject.Subscribe(async () => { - IWorkspace? project = _projectService.CurrentProject.Value; + Project? project = _projectService.CurrentProject.Value; EditorTabItem? selectedTabItem = _editorService.SelectedTabItem.Value; if (project != null && selectedTabItem != null) { string filePath = selectedTabItem.FilePath.Value; - IWorkspaceItem? wsItem = project.Items.FirstOrDefault(i => i.FileName == filePath); + ProjectItem? wsItem = project.Items.FirstOrDefault(i => i.FileName == filePath); if (wsItem == null) return; From b5fc5cdba0cf21d4b4958adfee15ee8d20ff8640 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Tue, 21 Mar 2023 22:58:12 +0900 Subject: [PATCH 03/84] =?UTF-8?q?Layer=E3=81=AEOperators=E3=82=92=E3=83=AA?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=81=A7=E3=81=AF=E3=81=AA=E3=81=8F=E3=82=AA?= =?UTF-8?q?=E3=83=96=E3=82=B8=E3=82=A7=E3=82=AF=E3=83=88=E3=81=AB=E3=81=97?= =?UTF-8?q?=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Graphics/Animation/Animatable.cs | 1 + .../BitmapEffect/BitmapEffectOperator.cs | 2 +- .../Configure/ConfigureOperator.cs | 66 +++-- .../ImageFilter/ImageFilterOperator.cs | 2 +- .../SoundEffect/SoundEffectOperator.cs | 2 +- .../Configure/Transform/TransformOperator.cs | 2 +- .../{NodeTree => }/EvaluationContext.cs | 4 +- .../Operation/OperatorEvaluationContext.cs | 28 ++ .../Operation/SourceOperation.cs | 264 ++++++++++++++++++ .../Operation/SourceOperator.cs | 44 +++ .../Operation/SourceOperators.cs | 88 ------ .../ProjectSystem/Layer.cs | 171 +++--------- .../ProjectSystem/Scene.cs | 3 - src/Beutl.ProjectSystem/SceneRenderer.cs | 112 +------- .../ViewModels/Dialogs/AddLayerViewModel.cs | 2 +- .../NodeTree/NodeTreeInputViewModel.cs | 8 +- .../NodeTree/NodeTreeTabViewModel.cs | 4 +- src/Beutl/ViewModels/TimelineViewModel.cs | 2 +- .../Tools/SourceOperatorsTabViewModel.cs | 4 +- .../Views/NodeTree/NodeInputView.axaml.cs | 2 +- .../Views/Tools/SourceOperatorView.axaml.cs | 10 +- .../Views/Tools/SourceOperatorsTab.axaml.cs | 2 +- 22 files changed, 434 insertions(+), 389 deletions(-) rename src/Beutl.ProjectSystem/{NodeTree => }/EvaluationContext.cs (86%) create mode 100644 src/Beutl.ProjectSystem/Operation/OperatorEvaluationContext.cs create mode 100644 src/Beutl.ProjectSystem/Operation/SourceOperation.cs delete mode 100644 src/Beutl.ProjectSystem/Operation/SourceOperators.cs diff --git a/src/Beutl.Graphics/Animation/Animatable.cs b/src/Beutl.Graphics/Animation/Animatable.cs index b8219475d..c51cea723 100644 --- a/src/Beutl.Graphics/Animation/Animatable.cs +++ b/src/Beutl.Graphics/Animation/Animatable.cs @@ -19,6 +19,7 @@ public event EventHandler AnimationInvalidated remove => Animations.Invalidated -= value; } + // Todo: オブジェクトを作成したものがIClockを指定する public virtual void ApplyAnimations(IClock clock) { foreach (IAnimation? item in Animations.GetMarshal().Value) diff --git a/src/Beutl.Operators/Configure/BitmapEffect/BitmapEffectOperator.cs b/src/Beutl.Operators/Configure/BitmapEffect/BitmapEffectOperator.cs index 7611b14a5..6456c39a1 100644 --- a/src/Beutl.Operators/Configure/BitmapEffect/BitmapEffectOperator.cs +++ b/src/Beutl.Operators/Configure/BitmapEffect/BitmapEffectOperator.cs @@ -11,7 +11,7 @@ namespace Beutl.Operators.Configure.BitmapEffect; public abstract class BitmapEffectOperator : ConfigureOperator, ISourceTransformer where T : BitmapEffect, new() { - protected override void PreSelect(Drawable target, T value) + protected override void PreProcess(Drawable target, T value) { value.IsEnabled = IsEnabled; } diff --git a/src/Beutl.Operators/Configure/ConfigureOperator.cs b/src/Beutl.Operators/Configure/ConfigureOperator.cs index 8ee7e65b0..a594922ac 100644 --- a/src/Beutl.Operators/Configure/ConfigureOperator.cs +++ b/src/Beutl.Operators/Configure/ConfigureOperator.cs @@ -53,27 +53,38 @@ public void Transform(IList value, IClock clock) if (_snapshot != null) { - foreach (TTarget item in _snapshot.Take(_snapshotCount).Except(value).OfType()) + var set = new HashSet(value); + + foreach (Renderable item in _snapshot.AsSpan().Slice(_snapshotCount)) { - PreSelect(item, Value); - OnDetached(item, Value); - PostSelect(item, Value); + if (set.Add(item) && item is TTarget target) + { + // valueにない、つまり削除された。 + PreProcess(target, Value); + OnDetached(target, Value); + PostProcess(target, Value); + } } - foreach (TTarget item in value.Except(_snapshot.Take(_snapshotCount)).OfType()) + set.IntersectWith(_snapshot.Take(_snapshotCount)); + foreach (Renderable item in value) { - PreSelect(item, Value); - OnAttached(item, Value); - PostSelect(item, Value); + if (set.Add(item) && item is TTarget target) + { + // _snapshotになくてvalueにある。(追加された) + PreProcess(target, Value); + OnAttached(target, Value); + PostProcess(target, Value); + } } } else { foreach (TTarget item in value.OfType()) { - PreSelect(item, Value); + PreProcess(item, Value); OnAttached(item, Value); - PostSelect(item, Value); + PostProcess(item, Value); } } } @@ -99,6 +110,21 @@ public void Transform(IList value, IClock clock) } } + public override void UninitializeForContext(OperatorEvaluationContext context) + { + base.UninitializeForContext(context); + if (_snapshot != null) + { + foreach (TTarget item in _snapshot.Take(_snapshotCount).OfType()) + { + OnDetached(item, Value); + } + + ArrayPool.Shared.Return(_snapshot, true); + _snapshot = null; + } + } + public override void ReadFromJson(JsonNode json) { base.ReadFromJson(json); @@ -137,11 +163,11 @@ public override void Exit() } } - protected virtual void PreSelect(TTarget target, TValue value) + protected virtual void PreProcess(TTarget target, TValue value) { } - protected virtual void PostSelect(TTarget target, TValue value) + protected virtual void PostProcess(TTarget target, TValue value) { } @@ -149,22 +175,6 @@ protected virtual void PostSelect(TTarget target, TValue value) protected abstract void OnDetached(TTarget target, TValue value); - protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) - { - base.OnDetachedFromHierarchy(args); - - if (_snapshot != null) - { - foreach (TTarget item in _snapshot.Take(_snapshotCount).OfType()) - { - OnDetached(item, Value); - } - - ArrayPool.Shared.Return(_snapshot, true); - _snapshot = null; - } - } - protected virtual IEnumerable GetProperties() { yield break; diff --git a/src/Beutl.Operators/Configure/ImageFilter/ImageFilterOperator.cs b/src/Beutl.Operators/Configure/ImageFilter/ImageFilterOperator.cs index 060dbb490..3cfd886a6 100644 --- a/src/Beutl.Operators/Configure/ImageFilter/ImageFilterOperator.cs +++ b/src/Beutl.Operators/Configure/ImageFilter/ImageFilterOperator.cs @@ -9,7 +9,7 @@ namespace Beutl.Operators.Configure.ImageFilter; public abstract class ImageFilterOperator : ConfigureOperator where T : ImageFilter, new() { - protected override void PreSelect(Drawable target, T value) + protected override void PreProcess(Drawable target, T value) { value.IsEnabled = IsEnabled; } diff --git a/src/Beutl.Operators/Configure/SoundEffect/SoundEffectOperator.cs b/src/Beutl.Operators/Configure/SoundEffect/SoundEffectOperator.cs index e8595c678..5bb00f1b5 100644 --- a/src/Beutl.Operators/Configure/SoundEffect/SoundEffectOperator.cs +++ b/src/Beutl.Operators/Configure/SoundEffect/SoundEffectOperator.cs @@ -16,7 +16,7 @@ namespace Beutl.Operators.Configure.SoundEffect; public abstract class SoundEffectOperator : ConfigureOperator, ISourceTransformer where T : SoundEffect, new() { - protected override void PreSelect(Sound target, T value) + protected override void PreProcess(Sound target, T value) { value.IsEnabled = IsEnabled; } diff --git a/src/Beutl.Operators/Configure/Transform/TransformOperator.cs b/src/Beutl.Operators/Configure/Transform/TransformOperator.cs index ed19b4e13..747313fa9 100644 --- a/src/Beutl.Operators/Configure/Transform/TransformOperator.cs +++ b/src/Beutl.Operators/Configure/Transform/TransformOperator.cs @@ -9,7 +9,7 @@ namespace Beutl.Operators.Configure.Transform; public abstract class TransformOperator : ConfigureOperator where T : Transform, new() { - protected override void PreSelect(Drawable target, T value) + protected override void PreProcess(Drawable target, T value) { value.IsEnabled = IsEnabled; } diff --git a/src/Beutl.ProjectSystem/NodeTree/EvaluationContext.cs b/src/Beutl.ProjectSystem/EvaluationContext.cs similarity index 86% rename from src/Beutl.ProjectSystem/NodeTree/EvaluationContext.cs rename to src/Beutl.ProjectSystem/EvaluationContext.cs index 28b81d2ff..30066b3d6 100644 --- a/src/Beutl.ProjectSystem/NodeTree/EvaluationContext.cs +++ b/src/Beutl.ProjectSystem/EvaluationContext.cs @@ -1,7 +1,7 @@ using Beutl.Animation; using Beutl.Rendering; -namespace Beutl.NodeTree; +namespace Beutl; // Todo: public class EvaluationContext @@ -26,7 +26,7 @@ public EvaluationContext() public IRenderer Renderer { get; internal set; } - public IReadOnlyList List { get; internal set; } + public IReadOnlyList List { get; internal set; } public void AddRenderable(Renderable renderable) { diff --git a/src/Beutl.ProjectSystem/Operation/OperatorEvaluationContext.cs b/src/Beutl.ProjectSystem/Operation/OperatorEvaluationContext.cs new file mode 100644 index 000000000..0b7da2b5f --- /dev/null +++ b/src/Beutl.ProjectSystem/Operation/OperatorEvaluationContext.cs @@ -0,0 +1,28 @@ +using Beutl.Rendering; + +namespace Beutl.Operation; + +public sealed class OperatorEvaluationContext : EvaluationContext +{ + public OperatorEvaluationContext(SourceOperator sourceOperator, EvaluationContext context) + : base(context) + { + Operator = sourceOperator; + } + + public OperatorEvaluationContext(SourceOperator sourceOperator) + { + Operator = sourceOperator; + } + + public IList GlobalRenderables { get; internal set; } = null!; + + public SourceOperator Operator { get; } + + // AllowOutflowがfalseでもOutflowができる + public void AddGlobalRenderable(Renderable renderable) + { + AddRenderable(renderable); + GlobalRenderables.Add(renderable); + } +} diff --git a/src/Beutl.ProjectSystem/Operation/SourceOperation.cs b/src/Beutl.ProjectSystem/Operation/SourceOperation.cs new file mode 100644 index 000000000..f4274fba3 --- /dev/null +++ b/src/Beutl.ProjectSystem/Operation/SourceOperation.cs @@ -0,0 +1,264 @@ +using System.Buffers; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Nodes; + +using Avalonia.Collections.Pooled; + +using Beutl.Collections; +using Beutl.Media; +using Beutl.ProjectSystem; +using Beutl.Rendering; + +namespace Beutl.Operation; + +public sealed class SourceOperation : Hierarchical, IAffectsRender +{ + private readonly HierarchicalList _children; + private OperatorEvaluationContext[]? _contexts; + private int _contextsLength; + private bool _isDirty = true; + + public SourceOperation() + { + _children = new HierarchicalList(this); + _children.Attached += OnOperatorAttached; + _children.Detached += OnOperatorDetached; + _children.CollectionChanged += OnOperatorsCollectionChanged; + } + + public event EventHandler? Invalidated; + + public ICoreList Children => _children; + + public override void ReadFromJson(JsonNode json) + { + base.ReadFromJson(json); + + if (json is JsonObject jobject) + { + if (jobject.TryGetPropertyValue("children", out JsonNode? childrenNode) + && childrenNode is JsonArray childrenArray) + { + foreach (JsonObject operatorJson in childrenArray.OfType()) + { + if (operatorJson.TryGetPropertyValue("@type", out JsonNode? atTypeNode) + && atTypeNode is JsonValue atTypeValue + && atTypeValue.TryGetValue(out string? atType)) + { + var type = TypeFormat.ToType(atType); + SourceOperator? @operator = null; + + if (type?.IsAssignableTo(typeof(SourceOperator)) ?? false) + { + @operator = Activator.CreateInstance(type) as SourceOperator; + } + + @operator ??= new SourceOperator(); + @operator.ReadFromJson(operatorJson); + Children.Add(@operator); + } + } + } + + } + } + + public override void WriteToJson(ref JsonNode json) + { + base.WriteToJson(ref json); + + if (json is JsonObject jobject) + { + Span children = _children.GetMarshal().Value; + if (children.Length > 0) + { + var array = new JsonArray(); + + foreach (SourceOperator item in children) + { + JsonNode node = new JsonObject(); + item.WriteToJson(ref node); + node["@type"] = TypeFormat.ToString(item.GetType()); + + array.Add(node); + } + + jobject["children"] = array; + } + + } + } + + // NodeTreeとSourceOperationの違い + + // NodeTreeは評価コンテキストにAddRenderableしたらすべて、レイヤーに追加される。 + // SourceOperationは編集者がハンドラ(自動ハンドラもある)を指定して、それが受け取ったRenderableをレイヤーに追加する。 + + // NodeTreeは流れるオブジェクトが何でもあり + // SourceOperationはRenderableのみ + public void Evaluate(IRenderer renderer, Layer layer, IList unhandled) + { + void Detach(IList renderables) + { + foreach (Renderable item in renderables) + { + if (item.HierarchicalParent is RenderLayerSpan span + && layer.Span != span) + { + span.Value.Remove(item); + } + } + } + + Initialize(renderer); + if (_contexts != null) + { + PooledList? pooled = null; + IList list = layer.AllowOutflow ? unhandled : (pooled = new PooledList()); + + foreach (OperatorEvaluationContext? item in _contexts.AsSpan().Slice(0, _contextsLength)) + { + item._renderables = list; + item.GlobalRenderables = unhandled; + item.Operator.Evaluate(item); + } + + if (!layer.AllowOutflow) + { + Detach(list); + layer.Span.Value.Replace(list); + + foreach (Renderable item in layer.Span.Value.GetMarshal().Value) + { + item.ApplyStyling(renderer.Clock); + item.ApplyAnimations(renderer.Clock); + item.IsVisible = layer.IsEnabled; + while (!item.EndBatchUpdate()) + { + } + } + + pooled?.Dispose(); + } + } + else + { + layer.Span.Value.Clear(); + } + } + + [MemberNotNull(nameof(_contexts))] + private Memory RentContextsArray(int size) + { + if (_contexts != null) + throw new InvalidOperationException("ReturnContextsArray has not yet been called."); + + _contexts = ArrayPool.Shared.Rent(size); + _contextsLength = size; + + return _contexts.AsMemory().Slice(0, size); + } + + private void ReturnContextsArray() + { + if (_contexts != null) + { + ArrayPool.Shared.Return(_contexts, true); + _contexts = null; + _contextsLength = -1; + } + } + + private void Uninitialize() + { + if (_contexts != null) + { + foreach (OperatorEvaluationContext? item in _contexts.AsSpan().Slice(0, _contextsLength)) + { + item.Operator.UninitializeForContext(item); + } + + ReturnContextsArray(); + } + } + + private void Initialize(IRenderer renderer) + { + if (_isDirty) + { + Uninitialize(); + Span contexts = RentContextsArray(Children.Count).Span; + + int index = 0; + foreach (SourceOperator item in Children.GetMarshal().Value) + { + if (item.IsEnabled) + { + contexts[index++] = new OperatorEvaluationContext(item) + { + Clock = renderer.Clock, + Renderer = renderer, + List = _contexts + }; + } + } + + foreach (OperatorEvaluationContext item in contexts) + { + item.Operator.InitializeForContext(item); + } + + _isDirty = false; + } + } + + public IRecordableCommand AddChild(SourceOperator @operator) + { + ArgumentNullException.ThrowIfNull(@operator); + + return Children.BeginRecord() + .Add(@operator) + .ToCommand(); + } + + public IRecordableCommand RemoveChild(SourceOperator @operator) + { + ArgumentNullException.ThrowIfNull(@operator); + + return Children.BeginRecord() + .Remove(@operator) + .ToCommand(); + } + + public IRecordableCommand InsertChild(int index, SourceOperator @operator) + { + ArgumentNullException.ThrowIfNull(@operator); + + return Children.BeginRecord() + .Insert(index, @operator) + .ToCommand(); + } + + private void OnOperatorsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + _isDirty = true; + Invalidated?.Invoke(this, new RenderInvalidatedEventArgs(this)); + } + + private void OnOperatorAttached(SourceOperator obj) + { + obj.Invalidated += OnOperatorInvalidated; + } + + private void OnOperatorDetached(SourceOperator obj) + { + obj.Invalidated -= OnOperatorInvalidated; + } + + private void OnOperatorInvalidated(object? sender, RenderInvalidatedEventArgs e) + { + Invalidated?.Invoke(this, e); + } +} diff --git a/src/Beutl.ProjectSystem/Operation/SourceOperator.cs b/src/Beutl.ProjectSystem/Operation/SourceOperator.cs index b64e9c45f..67a97779b 100644 --- a/src/Beutl.ProjectSystem/Operation/SourceOperator.cs +++ b/src/Beutl.ProjectSystem/Operation/SourceOperator.cs @@ -1,6 +1,9 @@ using Beutl.Collections; using Beutl.Framework; using Beutl.Media; +using Beutl.Rendering; + +using DynamicData; namespace Beutl.Operation; @@ -42,6 +45,47 @@ public bool IsEnabled public event EventHandler? Invalidated; + public virtual void InitializeForContext(OperatorEvaluationContext context) + { + } + + public virtual void UninitializeForContext(OperatorEvaluationContext context) + { + } + + public virtual void Evaluate(OperatorEvaluationContext context) + { + switch (this) + { + case ISourceTransformer selector: + selector.Transform(context._renderables, context.Clock); + break; + case ISourcePublisher source: + if (source.Publish(context.Clock) is Renderable renderable) + { + context.AddRenderable(renderable); + } + break; + case ISourceFilter filter: + if (filter.Scope == SourceFilterScope.Local) + { + context._renderables = filter.Filter((IReadOnlyList)context._renderables, context.Clock); + } + else + { + context._renderables = filter.Filter((IReadOnlyList)context._renderables, context.Clock); + context.GlobalRenderables.Clear(); + context.GlobalRenderables.AddRange(context._renderables); + } + break; + case ISourceHandler handler: + handler.Handle(context._renderables, context.Clock); + break; + default: + break; + } + } + public virtual void Enter() { } diff --git a/src/Beutl.ProjectSystem/Operation/SourceOperators.cs b/src/Beutl.ProjectSystem/Operation/SourceOperators.cs deleted file mode 100644 index 1f1cc9ab2..000000000 --- a/src/Beutl.ProjectSystem/Operation/SourceOperators.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System.Collections.Specialized; - -using Beutl.Collections; -using Beutl.ProjectSystem; - -namespace Beutl.Operation; - -public sealed class SourceOperators : CoreList -{ - public SourceOperators(Layer parent) - { - Parent = parent; - ResetBehavior = ResetBehavior.Remove; - CollectionChanged += OnCollectionChanged; - } - - public Layer Parent { get; } - - private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) - { - Span span = GetMarshal().Value; - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - for (int i = 0; i < e.NewStartingIndex; i++) - { - (span[i] as IModifiableHierarchical).SetParent(null); - } - - for (int i = e.NewStartingIndex + e.NewItems!.Count; i < Count; i++) - { - (span[i] as IModifiableHierarchical).SetParent(null); - } - - foreach (SourceOperator item in span) - { - (item as IModifiableHierarchical).SetParent(Parent); - } - break; - - case NotifyCollectionChangedAction.Remove: - for (int i = 0; i < e.OldItems!.Count; i++) - { - (e.OldItems![i] as IModifiableHierarchical)!.SetParent(null); - } - - foreach (SourceOperator item in span) - { - (item as IModifiableHierarchical).SetParent(null); - } - - foreach (SourceOperator item in span) - { - (item as IModifiableHierarchical).SetParent(Parent); - } - break; - - case NotifyCollectionChangedAction.Replace: - case NotifyCollectionChangedAction.Move: - for (int i = 0; i < e.OldItems!.Count; i++) - { - (e.OldItems![i] as IModifiableHierarchical)!.SetParent(null); - } - - for (int i = 0; i < e.NewStartingIndex; i++) - { - (span[i] as IModifiableHierarchical).SetParent(null); - } - - for (int i = e.NewStartingIndex + e.NewItems!.Count; i < Count; i++) - { - (span[i] as IModifiableHierarchical).SetParent(null); - } - - foreach (SourceOperator item in span) - { - (item as IModifiableHierarchical).SetParent(Parent); - } - break; - - case NotifyCollectionChangedAction.Reset: - break; - - default: - break; - } - } -} diff --git a/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs b/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs index a988ee0a5..5fb775c1c 100644 --- a/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs +++ b/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs @@ -22,8 +22,8 @@ public class Layer : ProjectItem public static readonly CoreProperty IsEnabledProperty; public static readonly CoreProperty AllowOutflowProperty; public static readonly CoreProperty SpanProperty; - public static readonly CoreProperty OperatorsProperty; - public static readonly CoreProperty SpaceProperty; + public static readonly CoreProperty OperationProperty; + public static readonly CoreProperty NodeTreeProperty; public static readonly CoreProperty UseNodeProperty; private TimeSpan _start; private TimeSpan _length; @@ -80,12 +80,12 @@ static Layer() .PropertyFlags(PropertyFlags.NotifyChanged) .Register(); - OperatorsProperty = ConfigureProperty(nameof(Operators)) - .Accessor(o => o.Operators, null) + OperationProperty = ConfigureProperty(nameof(Operation)) + .Accessor(o => o.Operation, null) .Register(); - SpaceProperty = ConfigureProperty(nameof(Space)) - .Accessor(o => o.Space, null) + NodeTreeProperty = ConfigureProperty(nameof(NodeTree)) + .Accessor(o => o.NodeTree, null) .Register(); UseNodeProperty = ConfigureProperty(nameof(UseNode)) @@ -115,14 +115,6 @@ static Layer() } }); - //RenderableProperty.Changed.Subscribe(args => - //{ - // if (args.Sender is Layer layer && layer.Parent is Scene { Renderer: { IsDisposed: false } renderer } && layer.ZIndex >= 0) - // { - // renderer[layer.ZIndex] = args.NewValue; - // } - //}); - IsEnabledProperty.Changed.Subscribe(args => { if (args.Sender is Layer layer) @@ -164,16 +156,17 @@ static Layer() public Layer() { - Operators = new SourceOperators(this); - Operators.Attached += item => item.Invalidated += Operator_Invalidated; - Operators.Detached += item => item.Invalidated -= Operator_Invalidated; - Operators.CollectionChanged += OnOperatorsCollectionChanged; - - Space = new LayerNodeTreeModel(); - Space.Invalidated += (_, _) => ForceRender(); - + Operation = new SourceOperation(); + Operation.Invalidated += (_, _) => ForceRender(); +#if DEBUG + Operation.Children.CollectionChanged += OnOperatorsCollectionChanged; +#endif + NodeTree = new LayerNodeTreeModel(); + NodeTree.Invalidated += (_, _) => ForceRender(); + + HierarchicalChildren.Add(Operation); HierarchicalChildren.Add(Span); - HierarchicalChildren.Add(Space); + HierarchicalChildren.Add(NodeTree); } private void OnOperatorsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) @@ -185,9 +178,9 @@ private void OnOperatorsCollectionChanged(object? sender, NotifyCollectionChange private void UpdateName() { var sb = new StringBuilder(); - for (int i = 0; i < Operators.Count; i++) + for (int i = 0; i < Operation.Children.Count; i++) { - SourceOperator op = Operators[i]; + SourceOperator op = Operation.Children[i]; if (op.IsEnabled) { Type type = op.GetType(); @@ -241,12 +234,9 @@ public bool AllowOutflow public RenderLayerSpan Span { get; } = new(); - [Obsolete("Use 'Layer.Span'.")] - public RenderLayerSpan Node => Span; + public SourceOperation Operation { get; } - public SourceOperators Operators { get; } - - public LayerNodeTreeModel Space { get; } + public LayerNodeTreeModel NodeTree { get; } public bool UseNode { @@ -270,55 +260,16 @@ public override void ReadFromJson(JsonNode json) if (json is JsonObject jobject) { - // NOTE: リリース時に削除。互換性を保つためのコードなので - if (!jobject.ContainsKey("zIndex") && jobject.TryGetPropertyValue("layer", out JsonNode? layerNode) - && layerNode is JsonValue layerValue - && layerValue.TryGetValue(out int layer)) + if (jobject.TryGetPropertyValue("operation", out JsonNode? operationNode) + && operationNode != null) { - ZIndex = layer; + Operation.ReadFromJson(operationNode); } - //if (jobject.TryGetPropertyValue("renderable", out JsonNode? renderableNode) - // && renderableNode is JsonObject renderableObj - // && renderableObj.TryGetPropertyValue("@type", out JsonNode? renderableTypeNode) - // && renderableTypeNode is JsonValue renderableTypeValue - // && renderableTypeValue.TryGetValue(out string? renderableTypeStr) - // && TypeFormat.ToType(renderableTypeStr) is Type renderableType - // && renderableType.IsAssignableTo(typeof(Renderable)) - // && Activator.CreateInstance(renderableType) is Renderable renderable) - //{ - // renderable.ReadFromJson(renderableObj); - // Span.Value = renderable; - //} - - if (jobject.TryGetPropertyValue("operators", out JsonNode? operatorsNode) - && operatorsNode is JsonArray operatorsArray) + if (jobject.TryGetPropertyValue("nodeTree", out JsonNode? nodeTreeNode) + && nodeTreeNode != null) { - foreach (JsonObject operatorJson in operatorsArray.OfType()) - { - if (operatorJson.TryGetPropertyValue("@type", out JsonNode? atTypeNode) - && atTypeNode is JsonValue atTypeValue - && atTypeValue.TryGetValue(out string? atType)) - { - var type = TypeFormat.ToType(atType); - SourceOperator? @operator = null; - - if (type?.IsAssignableTo(typeof(SourceOperator)) ?? false) - { - @operator = Activator.CreateInstance(type) as SourceOperator; - } - - @operator ??= new SourceOperator(); - @operator.ReadFromJson(operatorJson); - Operators.Add(@operator); - } - } - } - - if (jobject.TryGetPropertyValue("space", out JsonNode? spaceNode) - && spaceNode != null) - { - Space.ReadFromJson(spaceNode); + NodeTree.ReadFromJson(nodeTreeNode); } } } @@ -329,64 +280,16 @@ public override void WriteToJson(ref JsonNode json) if (json is JsonObject jobject) { - //if (Span.Value is Renderable renderable) - //{ - // JsonNode node = new JsonObject(); - // renderable.WriteToJson(ref node); - // node["@type"] = TypeFormat.ToString(renderable.GetType()); - // jobject["renderable"] = node; - //} - - Span operators = Operators.GetMarshal().Value; - if (operators.Length > 0) - { - var array = new JsonArray(); - - foreach (SourceOperator item in operators) - { - JsonNode node = new JsonObject(); - item.WriteToJson(ref node); - node["@type"] = TypeFormat.ToString(item.GetType()); - - array.Add(node); - } - - jobject["operators"] = array; - } + JsonNode operationNode = new JsonObject(); + Operation.WriteToJson(ref operationNode); + jobject["operation"] = operationNode; - JsonNode spaceNode = new JsonObject(); - Space.WriteToJson(ref spaceNode); - jobject["space"] = spaceNode; + JsonNode nodeTreeNode = new JsonObject(); + NodeTree.WriteToJson(ref nodeTreeNode); + jobject["nodeTree"] = nodeTreeNode; } } - public IRecordableCommand AddChild(SourceOperator @operator) - { - ArgumentNullException.ThrowIfNull(@operator); - - return Operators.BeginRecord() - .Add(@operator) - .ToCommand(); - } - - public IRecordableCommand RemoveChild(SourceOperator @operator) - { - ArgumentNullException.ThrowIfNull(@operator); - - return Operators.BeginRecord() - .Remove(@operator) - .ToCommand(); - } - - public IRecordableCommand InsertChild(int index, SourceOperator @operator) - { - ArgumentNullException.ThrowIfNull(@operator); - - return Operators.BeginRecord() - .Insert(index, @operator) - .ToCommand(); - } - protected override void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs args) { base.OnAttachedToHierarchy(args); @@ -415,11 +318,6 @@ protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs } } - internal bool InRange(TimeSpan ts) - { - return Start <= ts && ts < Length + Start; - } - private void ForceRender() { Scene? scene = this.FindHierarchicalParent(); @@ -445,11 +343,6 @@ private IDisposable SubscribeToLayerNode() .Subscribe(_ => ForceRender()); } - private void Operator_Invalidated(object? sender, EventArgs e) - { - ForceRender(); - } - internal Layer? GetBefore(int zindex, TimeSpan start) { if (HierarchicalParent is Scene scene) diff --git a/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs b/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs index afa4ea8af..4433e9dc5 100644 --- a/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs +++ b/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs @@ -27,13 +27,10 @@ public class Scene : ProjectItem, IHierarchicalRoot }; private readonly List _excludeLayers = new(); private readonly Layers _children; - private string? _fileName; private TimeSpan _duration = TimeSpan.FromMinutes(5); private TimeSpan _currentFrame; private PreviewOptions? _previewOptions; private IRenderer _renderer; - private EventHandler? _saved; - private EventHandler? _restored; public Scene() : this(1920, 1080, string.Empty) diff --git a/src/Beutl.ProjectSystem/SceneRenderer.cs b/src/Beutl.ProjectSystem/SceneRenderer.cs index becd2aac9..849343db8 100644 --- a/src/Beutl.ProjectSystem/SceneRenderer.cs +++ b/src/Beutl.ProjectSystem/SceneRenderer.cs @@ -15,7 +15,6 @@ internal sealed class SceneRenderer : private readonly List _end = new(); private readonly List _layers = new(); private readonly List _unhandleds = new(); - private readonly List _sharedList = new(); private TimeSpan _recentTime = TimeSpan.MinValue; public SceneRenderer(Scene scene, int width, int height) @@ -52,11 +51,11 @@ protected override void RenderGraphicsCore() { if (layer.UseNode) { - InvokeNode(layer); + layer.NodeTree.Evaluate(this, layer); } else { - EvaluateLayer(layer); + layer.Operation.Evaluate(this, layer, _unhandleds); } } @@ -66,7 +65,7 @@ protected override void RenderGraphicsCore() private static void EnterSourceOperators(Layer layer) { - foreach (SourceOperator item in layer.Operators.GetMarshal().Value) + foreach (SourceOperator item in layer.Operation.Children.GetMarshal().Value) { item.Enter(); } @@ -74,115 +73,12 @@ private static void EnterSourceOperators(Layer layer) private static void ExitSourceOperators(Layer layer) { - foreach (SourceOperator item in layer.Operators.GetMarshal().Value) + foreach (SourceOperator item in layer.Operation.Children.GetMarshal().Value) { item.Exit(); } } - private void EvaluateLayer(Layer layer) - { - void Detach(IList renderables) - { - foreach (Renderable item in renderables) - { - if (item.HierarchicalParent is RenderLayerSpan span - && layer.Span != span) - { - span.Value.Remove(item); - } - } - } - - void DefaultHandler(IList renderables) - { - if (renderables.Count > 0) - { - RenderLayerSpan span = layer.Span; - Detach(renderables); - - span.Value.Replace(renderables); - - foreach (Renderable item in span.Value.GetMarshal().Value) - { - item.ApplyStyling(Clock); - item.ApplyAnimations(Clock); - item.IsVisible = layer.IsEnabled; - while (!item.EndBatchUpdate()) - { - } - } - - renderables.Clear(); - } - } - - void EvaluateSourceOperators(List unhandleds) - { - foreach (SourceOperator? item in layer.Operators.GetMarshal().Value) - { - if (item is ISourceTransformer selector) - { - selector.Transform(unhandleds, Clock); - } - else if (item is ISourcePublisher source) - { - if (source.Publish(Clock) is Renderable renderable) - { - unhandleds.Add(renderable); - // Todo: Publish内でBeginBatchUpdateするようにする - renderable.BeginBatchUpdate(); - } - } - else if (item is ISourceFilter { IsEnabled: true } filter) - { - if (filter.Scope == SourceFilterScope.Local) - { - unhandleds = filter.Filter(unhandleds, Clock); - } - else - { - unhandleds = filter.Filter(unhandleds, Clock); - _unhandleds.Clear(); - _unhandleds.AddRange(unhandleds); - } - } - - if (item is ISourceHandler handler) - { - handler.Handle(unhandleds, Clock); - // 差分を取ってEndBatchUpdateする - } - } - } - - if (layer.AllowOutflow) - { - EvaluateSourceOperators(_unhandleds); - } - else - { - EvaluateSourceOperators(_sharedList); - - DefaultHandler(_sharedList); - } - - //span.Value = result; - //span.Value?.ApplyStyling(Clock); - //span.Value?.ApplyAnimations(Clock); - - //if (prevResult != null) - //{ - // prevResult.IsVisible = layer.IsEnabled; - //} - //span.Value?.EndBatchUpdate(); - } - - private void InvokeNode(Layer layer) - { - layer.Space.Evaluate(this, layer); - } - // Layersを振り分ける private void SortLayers(TimeSpan timeSpan) { diff --git a/src/Beutl/ViewModels/Dialogs/AddLayerViewModel.cs b/src/Beutl/ViewModels/Dialogs/AddLayerViewModel.cs index d7a3ac54e..f0389c948 100644 --- a/src/Beutl/ViewModels/Dialogs/AddLayerViewModel.cs +++ b/src/Beutl/ViewModels/Dialogs/AddLayerViewModel.cs @@ -84,7 +84,7 @@ public AddLayerViewModel(Scene scene, LayerDescription desc) if (_layerDescription.InitialOperator != null) { - sLayer.AddChild((SourceOperator)Activator.CreateInstance(_layerDescription.InitialOperator.Type)!).Do(); + sLayer.Operation.AddChild((SourceOperator)Activator.CreateInstance(_layerDescription.InitialOperator.Type)!).Do(); } sLayer.Save(sLayer.FileName); diff --git a/src/Beutl/ViewModels/NodeTree/NodeTreeInputViewModel.cs b/src/Beutl/ViewModels/NodeTree/NodeTreeInputViewModel.cs index 645eaa598..15a4db26a 100644 --- a/src/Beutl/ViewModels/NodeTree/NodeTreeInputViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/NodeTreeInputViewModel.cs @@ -24,17 +24,17 @@ public NodeTreeInputViewModel(Layer layer) .DoAndRecord(CommandRecorder.Default)) .DisposeWith(_disposables); - layer.Space.Nodes.ForEachItem( + layer.NodeTree.Nodes.ForEachItem( (originalIdx, item) => { if (item is LayerInputNode layerInput) { int idx = ConvertFromOriginalIndex(originalIdx); - Items.Insert(idx, new NodeInputViewModel(layerInput, originalIdx, Model.Space)); + Items.Insert(idx, new NodeInputViewModel(layerInput, originalIdx, Model.NodeTree)); for (int i = idx; i < Items.Count; i++) { - Items[i].OriginalIndex = Model.Space.Nodes.IndexOf(Items[i].Node); + Items[i].OriginalIndex = Model.NodeTree.Nodes.IndexOf(Items[i].Node); } } }, @@ -48,7 +48,7 @@ public NodeTreeInputViewModel(Layer layer) for (int i = idx; i < Items.Count; i++) { - Items[i].OriginalIndex = Model.Space.Nodes.IndexOf(Items[i].Node); + Items[i].OriginalIndex = Model.NodeTree.Nodes.IndexOf(Items[i].Node); } } }, diff --git a/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs b/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs index 45f2d3952..9cab99ab1 100644 --- a/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs @@ -71,14 +71,14 @@ public NodeTreeTabViewModel(EditViewModel editViewModel) if (v != null) { - NodeTree.Value = new NodeTreeViewModel(v.Space); + NodeTree.Value = new NodeTreeViewModel(v.NodeTree); IObservable name = v.GetObservable(CoreObject.NameProperty); IObservable fileName = v.GetObservable(ProjectSystem.Layer.FileNameProperty) .Select(x => Path.GetFileNameWithoutExtension(x)); Items.Add(new NodeTreeNavigationItem( viewModel: NodeTree.Value, - nodeTree: v.Space, + nodeTree: v.NodeTree, name: name.CombineLatest(fileName) .Select(x => string.IsNullOrWhiteSpace(x.First) ? x.Second : x.First) .ToReadOnlyReactivePropertySlim()!)); diff --git a/src/Beutl/ViewModels/TimelineViewModel.cs b/src/Beutl/ViewModels/TimelineViewModel.cs index b17ea058f..d7d88ae7d 100644 --- a/src/Beutl/ViewModels/TimelineViewModel.cs +++ b/src/Beutl/ViewModels/TimelineViewModel.cs @@ -68,7 +68,7 @@ public TimelineViewModel(EditViewModel editViewModel) if (item.InitialOperator != null) { sLayer.AccentColor = item.InitialOperator.AccentColor; - sLayer.AddChild((SourceOperator)Activator.CreateInstance(item.InitialOperator.Type)!).Do(); + sLayer.Operation.AddChild((SourceOperator)Activator.CreateInstance(item.InitialOperator.Type)!).Do(); } sLayer.Save(sLayer.FileName); diff --git a/src/Beutl/ViewModels/Tools/SourceOperatorsTabViewModel.cs b/src/Beutl/ViewModels/Tools/SourceOperatorsTabViewModel.cs index 6e90cce20..02d40ae7f 100644 --- a/src/Beutl/ViewModels/Tools/SourceOperatorsTabViewModel.cs +++ b/src/Beutl/ViewModels/Tools/SourceOperatorsTabViewModel.cs @@ -40,8 +40,8 @@ public SourceOperatorsTabViewModel(EditViewModel editViewModel) { _disposable1?.Dispose(); - Items.AddRange(layer.Operators.Select(x => new SourceOperatorViewModel(x, _editViewModel))); - _disposable1 = layer.Operators.CollectionChangedAsObservable() + Items.AddRange(layer.Operation.Children.Select(x => new SourceOperatorViewModel(x, _editViewModel))); + _disposable1 = layer.Operation.Children.CollectionChangedAsObservable() .Subscribe(e => { static void RemoveItems(CoreList items, int index, int count) diff --git a/src/Beutl/Views/NodeTree/NodeInputView.axaml.cs b/src/Beutl/Views/NodeTree/NodeInputView.axaml.cs index b7a1c69c1..cb6157154 100644 --- a/src/Beutl/Views/NodeTree/NodeInputView.axaml.cs +++ b/src/Beutl/Views/NodeTree/NodeInputView.axaml.cs @@ -61,7 +61,7 @@ protected override void OnMoveDraggedItem(ItemsControl? itemsControl, int oldInd { oldIndex = viewModel.ConvertToOriginalIndex(oldIndex); newIndex = viewModel.ConvertToOriginalIndex(newIndex); - viewModel.Model.Space.Nodes.BeginRecord() + viewModel.Model.NodeTree.Nodes.BeginRecord() .Move(oldIndex, newIndex) .ToCommand() .DoAndRecord(CommandRecorder.Default); diff --git a/src/Beutl/Views/Tools/SourceOperatorView.axaml.cs b/src/Beutl/Views/Tools/SourceOperatorView.axaml.cs index 41910f54a..f1a7122bc 100644 --- a/src/Beutl/Views/Tools/SourceOperatorView.axaml.cs +++ b/src/Beutl/Views/Tools/SourceOperatorView.axaml.cs @@ -36,7 +36,7 @@ public void Remove_Click(object? sender, RoutedEventArgs e) { SourceOperator operation = viewModel2.Model; Layer layer = operation.FindRequiredHierarchicalParent(); - layer.RemoveChild(operation) + layer.Operation.RemoveChild(operation) .DoAndRecord(CommandRecorder.Default); } } @@ -51,16 +51,16 @@ private void Drop(object? sender, DragEventArgs e) Rect bounds = Bounds; Point position = e.GetPosition(this); double half = bounds.Height / 2; - int index = layer.Operators.IndexOf(operation); + int index = layer.Operation.Children.IndexOf(operation); if (half < position.Y) { - layer.InsertChild(index + 1, (SourceOperator)Activator.CreateInstance(item2.Type)!) + layer.Operation.InsertChild(index + 1, (SourceOperator)Activator.CreateInstance(item2.Type)!) .DoAndRecord(CommandRecorder.Default); } else { - layer.InsertChild(index, (SourceOperator)Activator.CreateInstance(item2.Type)!) + layer.Operation.InsertChild(index, (SourceOperator)Activator.CreateInstance(item2.Type)!) .DoAndRecord(CommandRecorder.Default); } @@ -100,7 +100,7 @@ private sealed class _DragBehavior : GenericDragBehavior { protected override void OnMoveDraggedItem(ItemsControl? itemsControl, int oldIndex, int newIndex) { - if (itemsControl?.DataContext is SourceOperatorsTabViewModel { Layer.Value.Operators: { } list }) + if (itemsControl?.DataContext is SourceOperatorsTabViewModel { Layer.Value.Operation.Children: { } list }) { list.BeginRecord() .Move(oldIndex, newIndex) diff --git a/src/Beutl/Views/Tools/SourceOperatorsTab.axaml.cs b/src/Beutl/Views/Tools/SourceOperatorsTab.axaml.cs index 25958a351..6aa57edea 100644 --- a/src/Beutl/Views/Tools/SourceOperatorsTab.axaml.cs +++ b/src/Beutl/Views/Tools/SourceOperatorsTab.axaml.cs @@ -57,7 +57,7 @@ private void Drop(object? sender, DragEventArgs e) && DataContext is SourceOperatorsTabViewModel vm && vm.Layer.Value is Layer layer) { - layer.AddChild((SourceOperator)Activator.CreateInstance(item.Type)!) + layer.Operation.AddChild((SourceOperator)Activator.CreateInstance(item.Type)!) .DoAndRecord(CommandRecorder.Default); e.Handled = true; From f6890489a5fbf5e210623b1017a0aaaf24de8954 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Wed, 22 Mar 2023 14:52:50 +0900 Subject: [PATCH 04/84] =?UTF-8?q?ProjectItem.FileName=E3=81=AE=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E3=81=99=E3=82=8B=E3=82=BF=E3=82=A4=E3=83=9F=E3=83=B3?= =?UTF-8?q?=E3=82=B0=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Core/ProjectItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Beutl.Core/ProjectItem.cs b/src/Beutl.Core/ProjectItem.cs index 33bbb166d..de0691424 100644 --- a/src/Beutl.Core/ProjectItem.cs +++ b/src/Beutl.Core/ProjectItem.cs @@ -26,8 +26,8 @@ public string FileName public virtual void Restore(string filename) { - RestoreCore(filename); FileName = filename; + RestoreCore(filename); LastSavedTime = File.GetLastWriteTimeUtc(filename); Restored?.Invoke(this, EventArgs.Empty); From 750484dceca4f1f6937216736e3a85dc991c1024 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Wed, 22 Mar 2023 15:09:26 +0900 Subject: [PATCH 05/84] =?UTF-8?q?SourceOperation=E3=81=AE=E3=82=B3?= =?UTF-8?q?=E3=83=BC=E3=83=89=E3=82=92=E5=8D=98=E7=B4=94=E3=81=AB=E3=81=97?= =?UTF-8?q?=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Graphics/Audio/Sound.cs | 2 +- .../Graphics/Drawables/VideoFrame.cs | 2 +- .../BitmapEffect/BitmapEffectOperator.cs | 18 +--- .../Configure/ConfigureOperator.cs | 94 ++----------------- .../ImageFilter/ImageFilterOperator.cs | 17 +--- .../SoundEffect/SoundEffectOperator.cs | 25 +---- .../Configure/Transform/TransformOperator.cs | 17 +--- .../Handler/DefaultSourceHandler.cs | 72 ++------------ .../Source/DrawablePublishOperator.cs | 46 +++++++++ .../Source/EllipseOperation.cs | 12 +-- .../Source/ImageFileOperator.cs | 9 +- src/Beutl.Operators/Source/RectOperator.cs | 9 +- .../Source/RoundedRectOperator.cs | 9 +- .../Source/SourceImageOperator.cs | 9 +- .../Source/TextBlockOperator.cs | 9 +- .../Source/VideoFrameOperator.cs | 9 +- src/Beutl.ProjectSystem/EvaluationContext.cs | 2 +- .../NodeTree/Nodes/RectNode.cs | 6 ++ .../Operation/OperatorEvaluationContext.cs | 8 +- .../Operation/SourceOperation.cs | 56 ++++++----- .../Operation/SourceOperator.cs | 22 +---- .../Operation/SourceStyler.cs | 7 +- src/Beutl.ProjectSystem/SceneRenderer.cs | 37 +++++--- 23 files changed, 150 insertions(+), 347 deletions(-) create mode 100644 src/Beutl.Operators/Source/DrawablePublishOperator.cs diff --git a/src/Beutl.Graphics/Audio/Sound.cs b/src/Beutl.Graphics/Audio/Sound.cs index 05998a4a3..6bff85d25 100644 --- a/src/Beutl.Graphics/Audio/Sound.cs +++ b/src/Beutl.Graphics/Audio/Sound.cs @@ -128,7 +128,7 @@ public void Time(TimeSpan available) protected override void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs args) { base.OnAttachedToHierarchy(args); - _layerSpan = args.Parent as RenderLayerSpan; + _layerSpan = this.FindHierarchicalParent(); } protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) diff --git a/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs b/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs index 877b194ee..588641695 100644 --- a/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs +++ b/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs @@ -141,7 +141,7 @@ public override void ApplyAnimations(IClock clock) protected override void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs args) { base.OnAttachedToHierarchy(args); - _layerNode = args.Parent?.FindHierarchicalParent(true); + _layerNode = this.FindHierarchicalParent(); } protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) diff --git a/src/Beutl.Operators/Configure/BitmapEffect/BitmapEffectOperator.cs b/src/Beutl.Operators/Configure/BitmapEffect/BitmapEffectOperator.cs index 6456c39a1..b6759d3ce 100644 --- a/src/Beutl.Operators/Configure/BitmapEffect/BitmapEffectOperator.cs +++ b/src/Beutl.Operators/Configure/BitmapEffect/BitmapEffectOperator.cs @@ -1,7 +1,6 @@ using Beutl.Graphics; using Beutl.Graphics.Effects; using Beutl.Operation; -using Beutl.Styling; namespace Beutl.Operators.Configure.BitmapEffect; @@ -16,21 +15,8 @@ protected override void PreProcess(Drawable target, T value) value.IsEnabled = IsEnabled; } - protected override void OnAttached(Drawable target, T value) + protected override void Process(Drawable target, T value) { - if (target.Effect is not BitmapEffectGroup group) - { - target.Effect = group = new BitmapEffectGroup(); - } - - group.Children.Add(value); - } - - protected override void OnDetached(Drawable target, T value) - { - if (target.Effect is BitmapEffectGroup group) - { - group.Children.Remove(value); - } + (target.Effect as BitmapEffectGroup)?.Children.Add(value); } } diff --git a/src/Beutl.Operators/Configure/ConfigureOperator.cs b/src/Beutl.Operators/Configure/ConfigureOperator.cs index a594922ac..53d4925fe 100644 --- a/src/Beutl.Operators/Configure/ConfigureOperator.cs +++ b/src/Beutl.Operators/Configure/ConfigureOperator.cs @@ -3,9 +3,8 @@ using Beutl.Animation; using Beutl.Framework; using Beutl.Media; -using Beutl.Rendering; using Beutl.Operation; -using System.Buffers; +using Beutl.Rendering; namespace Beutl.Operators.Configure; @@ -14,8 +13,6 @@ public abstract class ConfigureOperator : SourceOperator, ISour where TValue : CoreObject, IAffectsRender, new() { private bool _transforming; - private Renderable[]? _snapshot; - private int _snapshotCount; public ConfigureOperator() { @@ -51,77 +48,16 @@ public void Transform(IList value, IClock clock) { _transforming = true; - if (_snapshot != null) - { - var set = new HashSet(value); - - foreach (Renderable item in _snapshot.AsSpan().Slice(_snapshotCount)) - { - if (set.Add(item) && item is TTarget target) - { - // valueにない、つまり削除された。 - PreProcess(target, Value); - OnDetached(target, Value); - PostProcess(target, Value); - } - } - - set.IntersectWith(_snapshot.Take(_snapshotCount)); - foreach (Renderable item in value) - { - if (set.Add(item) && item is TTarget target) - { - // _snapshotになくてvalueにある。(追加された) - PreProcess(target, Value); - OnAttached(target, Value); - PostProcess(target, Value); - } - } - } - else + foreach (TTarget item in value.OfType()) { - foreach (TTarget item in value.OfType()) - { - PreProcess(item, Value); - OnAttached(item, Value); - PostProcess(item, Value); - } + PreProcess(item, Value); + Process(item, Value); + PostProcess(item, Value); } } finally { _transforming = false; - - if (_snapshot != null) - { - if (_snapshot.Length < value.Count) - { - ArrayPool.Shared.Return(_snapshot, true); - _snapshot = ArrayPool.Shared.Rent(value.Count); - } - } - else - { - _snapshot = ArrayPool.Shared.Rent(value.Count); - } - - _snapshotCount = value.Count; - value.CopyTo(_snapshot, 0); - } - } - - public override void UninitializeForContext(OperatorEvaluationContext context) - { - base.UninitializeForContext(context); - if (_snapshot != null) - { - foreach (TTarget item in _snapshot.Take(_snapshotCount).OfType()) - { - OnDetached(item, Value); - } - - ArrayPool.Shared.Return(_snapshot, true); - _snapshot = null; } } @@ -147,22 +83,6 @@ public override void WriteToJson(ref JsonNode json) } } - public override void Exit() - { - base.Exit(); - - if (_snapshot != null) - { - foreach (TTarget item in _snapshot.Take(_snapshotCount).OfType()) - { - OnDetached(item, Value); - } - - ArrayPool.Shared.Return(_snapshot, true); - _snapshot = null; - } - } - protected virtual void PreProcess(TTarget target, TValue value) { } @@ -171,9 +91,7 @@ protected virtual void PostProcess(TTarget target, TValue value) { } - protected abstract void OnAttached(TTarget target, TValue value); - - protected abstract void OnDetached(TTarget target, TValue value); + protected abstract void Process(TTarget target, TValue value); protected virtual IEnumerable GetProperties() { diff --git a/src/Beutl.Operators/Configure/ImageFilter/ImageFilterOperator.cs b/src/Beutl.Operators/Configure/ImageFilter/ImageFilterOperator.cs index 3cfd886a6..d0a7c0f17 100644 --- a/src/Beutl.Operators/Configure/ImageFilter/ImageFilterOperator.cs +++ b/src/Beutl.Operators/Configure/ImageFilter/ImageFilterOperator.cs @@ -14,21 +14,8 @@ protected override void PreProcess(Drawable target, T value) value.IsEnabled = IsEnabled; } - protected override void OnAttached(Drawable target, T value) + protected override void Process(Drawable target, T value) { - if (target.Filter is not ImageFilterGroup group) - { - target.Filter = group = new ImageFilterGroup(); - } - - group.Children.Add(value); - } - - protected override void OnDetached(Drawable target, T value) - { - if (target.Filter is ImageFilterGroup group) - { - group.Children.Remove(value); - } + (target.Filter as ImageFilterGroup)?.Children.Add(value); } } diff --git a/src/Beutl.Operators/Configure/SoundEffect/SoundEffectOperator.cs b/src/Beutl.Operators/Configure/SoundEffect/SoundEffectOperator.cs index 5bb00f1b5..baadde3cb 100644 --- a/src/Beutl.Operators/Configure/SoundEffect/SoundEffectOperator.cs +++ b/src/Beutl.Operators/Configure/SoundEffect/SoundEffectOperator.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Beutl.Audio; +using Beutl.Audio; using Beutl.Audio.Effects; using Beutl.Operation; @@ -21,21 +15,8 @@ protected override void PreProcess(Sound target, T value) value.IsEnabled = IsEnabled; } - protected override void OnAttached(Sound target, T value) - { - if (target.Effect is not SoundEffectGroup group) - { - target.Effect = group = new SoundEffectGroup(); - } - - group.Children.Add(value); - } - - protected override void OnDetached(Sound target, T value) + protected override void Process(Sound target, T value) { - if (target.Effect is SoundEffectGroup group) - { - group.Children.Remove(value); - } + (target.Effect as SoundEffectGroup)?.Children.Add(value); } } diff --git a/src/Beutl.Operators/Configure/Transform/TransformOperator.cs b/src/Beutl.Operators/Configure/Transform/TransformOperator.cs index 747313fa9..a08f6d059 100644 --- a/src/Beutl.Operators/Configure/Transform/TransformOperator.cs +++ b/src/Beutl.Operators/Configure/Transform/TransformOperator.cs @@ -14,21 +14,8 @@ protected override void PreProcess(Drawable target, T value) value.IsEnabled = IsEnabled; } - protected override void OnAttached(Drawable target, T value) + protected override void Process(Drawable target, T value) { - if (target.Transform is not TransformGroup group) - { - target.Transform = group = new TransformGroup(); - } - - group.Children.Add(value); - } - - protected override void OnDetached(Drawable target, T value) - { - if (target.Transform is TransformGroup group) - { - group.Children.Remove(value); - } + (target.Transform as TransformGroup)?.Children.Add(value); } } diff --git a/src/Beutl.Operators/Handler/DefaultSourceHandler.cs b/src/Beutl.Operators/Handler/DefaultSourceHandler.cs index d389562c9..9aac77a23 100644 --- a/src/Beutl.Operators/Handler/DefaultSourceHandler.cs +++ b/src/Beutl.Operators/Handler/DefaultSourceHandler.cs @@ -1,75 +1,19 @@ -using Beutl.Animation; -using Beutl.Operation; -using Beutl.ProjectSystem; +using Beutl.Operation; using Beutl.Rendering; namespace Beutl.Operators.Handler; -public sealed class DefaultSourceHandler : SourceOperator, ISourceHandler +public sealed class DefaultSourceHandler : SourceOperator { - private Layer? _layer; - - private static void Detach(Layer layer, IList renderables) - { - foreach (Renderable item in renderables) - { - if ((item as IHierarchical).HierarchicalParent is RenderLayerSpan span - && layer.Span != span) - { - span.Value.Remove(item); - } - } - } - - public void Handle(IList renderables, IClock clock) + public override void Evaluate(OperatorEvaluationContext context) { - if (_layer != null) + for (int i = 0; i < context.FlowRenderables.Count; i++) { - RenderLayerSpan span = _layer.Span; - Detach(_layer, renderables); + Renderable item = context.FlowRenderables[i]; - span.Value.Replace(renderables); - - foreach (Renderable item in span.Value.GetMarshal().Value) - { - item.ApplyStyling(clock); - item.ApplyAnimations(clock); - item.IsVisible = _layer.IsEnabled; - while (!item.EndBatchUpdate()) - { - } - } - - renderables.Clear(); - } - } - - public override void Exit() - { - base.Exit(); - - if (_layer != null) - { - RenderLayerSpan span = _layer.Span; - span.Value.Clear(); - } - } - - protected override void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs args) - { - base.OnAttachedToHierarchy(args); - _layer = args.Parent as Layer; - } - - protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) - { - base.OnDetachedFromHierarchy(args); - - if (_layer != null) - { - RenderLayerSpan span = _layer.Span; - span.Value.Clear(); - _layer = null; + context.FlowRenderables.RemoveAt(i); + context.AddRenderable(item); + i--; } } } diff --git a/src/Beutl.Operators/Source/DrawablePublishOperator.cs b/src/Beutl.Operators/Source/DrawablePublishOperator.cs new file mode 100644 index 000000000..e03338920 --- /dev/null +++ b/src/Beutl.Operators/Source/DrawablePublishOperator.cs @@ -0,0 +1,46 @@ +using Beutl.Graphics; +using Beutl.Graphics.Effects; +using Beutl.Graphics.Filters; +using Beutl.Graphics.Transformation; +using Beutl.Media; +using Beutl.Operation; +using Beutl.Styling; + +namespace Beutl.Operators.Source; + +public abstract class DrawablePublishOperator : StyledSourcePublisher + where T : Drawable +{ + protected override Style OnInitializeStyle(Func> setters) + { + var style = new Style(); + style.Setters.AddRange(setters()); + return style; + } + + protected override void OnPostPublish() + { + base.OnPostPublish(); + if (Instance?.Target is T drawable) + { + drawable.BlendMode = BlendMode.SrcOver; + drawable.AlignmentX = AlignmentX.Left; + drawable.AlignmentY = AlignmentY.Top; + drawable.TransformOrigin = RelativePoint.TopLeft; + if (drawable.Transform is TransformGroup transformGroup) + transformGroup.Children.Clear(); + else + drawable.Transform = new TransformGroup(); + + if (drawable.Filter is ImageFilterGroup filterGroup) + filterGroup.Children.Clear(); + else + drawable.Filter = new ImageFilterGroup(); + + if (drawable.Effect is BitmapEffectGroup effectGroup) + effectGroup.Children.Clear(); + else + drawable.Effect = new BitmapEffectGroup(); + } + } +} diff --git a/src/Beutl.Operators/Source/EllipseOperation.cs b/src/Beutl.Operators/Source/EllipseOperation.cs index 9cc8b3786..a215d2bc4 100644 --- a/src/Beutl.Operators/Source/EllipseOperation.cs +++ b/src/Beutl.Operators/Source/EllipseOperation.cs @@ -1,20 +1,16 @@ using Beutl.Graphics; +using Beutl.Graphics.Effects; +using Beutl.Graphics.Filters; using Beutl.Graphics.Shapes; +using Beutl.Graphics.Transformation; using Beutl.Media; using Beutl.Operation; using Beutl.Styling; namespace Beutl.Operators.Source; -public sealed class EllipseOperator : StyledSourcePublisher +public sealed class EllipseOperator : DrawablePublishOperator { - protected override Style OnInitializeStyle(Func> setters) - { - var style = new Style(); - style.Setters.AddRange(setters()); - return style; - } - protected override void OnInitializeSetters(IList initializing) { initializing.Add(new Setter(Drawable.WidthProperty, 100)); diff --git a/src/Beutl.Operators/Source/ImageFileOperator.cs b/src/Beutl.Operators/Source/ImageFileOperator.cs index 0ec310d86..abd64ba8d 100644 --- a/src/Beutl.Operators/Source/ImageFileOperator.cs +++ b/src/Beutl.Operators/Source/ImageFileOperator.cs @@ -4,15 +4,8 @@ namespace Beutl.Operators.Source; -public sealed class ImageFileOperator : StyledSourcePublisher +public sealed class ImageFileOperator : DrawablePublishOperator { - protected override Style OnInitializeStyle(Func> setters) - { - var style = new Style(); - style.Setters.AddRange(setters()); - return style; - } - protected override void OnInitializeSetters(IList initializing) { initializing.Add(new Setter(ImageFile.SourceFileProperty, null)); diff --git a/src/Beutl.Operators/Source/RectOperator.cs b/src/Beutl.Operators/Source/RectOperator.cs index e51d337a2..71094276a 100644 --- a/src/Beutl.Operators/Source/RectOperator.cs +++ b/src/Beutl.Operators/Source/RectOperator.cs @@ -6,15 +6,8 @@ namespace Beutl.Operators.Source; -public sealed class RectOperator : StyledSourcePublisher +public sealed class RectOperator : DrawablePublishOperator { - protected override Style OnInitializeStyle(Func> setters) - { - var style = new Style(); - style.Setters.AddRange(setters()); - return style; - } - protected override void OnInitializeSetters(IList initializing) { initializing.Add(new Setter(Drawable.WidthProperty, 100)); diff --git a/src/Beutl.Operators/Source/RoundedRectOperator.cs b/src/Beutl.Operators/Source/RoundedRectOperator.cs index fece27ead..6936dc3ca 100644 --- a/src/Beutl.Operators/Source/RoundedRectOperator.cs +++ b/src/Beutl.Operators/Source/RoundedRectOperator.cs @@ -6,15 +6,8 @@ namespace Beutl.Operators.Source; -public sealed class RoundedRectOperator : StyledSourcePublisher +public sealed class RoundedRectOperator : DrawablePublishOperator { - protected override Style OnInitializeStyle(Func> setters) - { - var style = new Style(); - style.Setters.AddRange(setters()); - return style; - } - protected override void OnInitializeSetters(IList initializing) { initializing.Add(new Setter(Drawable.WidthProperty, 100)); diff --git a/src/Beutl.Operators/Source/SourceImageOperator.cs b/src/Beutl.Operators/Source/SourceImageOperator.cs index 74d0c974a..2bbe914fa 100644 --- a/src/Beutl.Operators/Source/SourceImageOperator.cs +++ b/src/Beutl.Operators/Source/SourceImageOperator.cs @@ -5,15 +5,8 @@ namespace Beutl.Operators.Source; -public sealed class SourceImageOperator : StyledSourcePublisher +public sealed class SourceImageOperator : DrawablePublishOperator { - protected override Style OnInitializeStyle(Func> setters) - { - var style = new Style(); - style.Setters.AddRange(setters()); - return style; - } - protected override void OnInitializeSetters(IList initializing) { initializing.Add(new Setter(SourceImage.SourceProperty, null)); diff --git a/src/Beutl.Operators/Source/TextBlockOperator.cs b/src/Beutl.Operators/Source/TextBlockOperator.cs index db2a13100..9cc7b553f 100644 --- a/src/Beutl.Operators/Source/TextBlockOperator.cs +++ b/src/Beutl.Operators/Source/TextBlockOperator.cs @@ -6,15 +6,8 @@ namespace Beutl.Operators.Source; -public sealed class TextBlockOperator : StyledSourcePublisher +public sealed class TextBlockOperator : DrawablePublishOperator { - protected override Style OnInitializeStyle(Func> setters) - { - var style = new Style(); - style.Setters.AddRange(setters()); - return style; - } - protected override void OnInitializeSetters(IList initializing) { initializing.Add(new Setter(TextBlock.SizeProperty, 24)); diff --git a/src/Beutl.Operators/Source/VideoFrameOperator.cs b/src/Beutl.Operators/Source/VideoFrameOperator.cs index db3067ee1..92c08189c 100644 --- a/src/Beutl.Operators/Source/VideoFrameOperator.cs +++ b/src/Beutl.Operators/Source/VideoFrameOperator.cs @@ -4,15 +4,8 @@ namespace Beutl.Operators.Source; -public sealed class VideoFrameOperator : StyledSourcePublisher +public sealed class VideoFrameOperator : DrawablePublishOperator { - protected override Style OnInitializeStyle(Func> setters) - { - var style = new Style(); - style.Setters.AddRange(setters()); - return style; - } - protected override void OnInitializeSetters(IList initializing) { initializing.Add(new Setter(VideoFrame.OffsetPositionProperty, TimeSpan.Zero)); diff --git a/src/Beutl.ProjectSystem/EvaluationContext.cs b/src/Beutl.ProjectSystem/EvaluationContext.cs index 30066b3d6..a9e94ef9f 100644 --- a/src/Beutl.ProjectSystem/EvaluationContext.cs +++ b/src/Beutl.ProjectSystem/EvaluationContext.cs @@ -30,6 +30,6 @@ public EvaluationContext() public void AddRenderable(Renderable renderable) { - _renderables.Add(renderable); + _renderables?.Add(renderable); } } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/RectNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/RectNode.cs index 5312fdde8..1dcfc2459 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/RectNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/RectNode.cs @@ -1,4 +1,5 @@ using Beutl.Graphics; +using Beutl.Graphics.Effects; using Beutl.Graphics.Filters; using Beutl.Graphics.Shapes; using Beutl.Graphics.Transformation; @@ -59,6 +60,11 @@ public override void Evaluate(NodeEvaluationContext context) else rectangle.Filter = new ImageFilterGroup(); + if (rectangle.Effect is BitmapEffectGroup effectGroup) + effectGroup.Children.Clear(); + else + rectangle.Effect = new BitmapEffectGroup(); + _outputSocket.Value = rectangle; } } diff --git a/src/Beutl.ProjectSystem/Operation/OperatorEvaluationContext.cs b/src/Beutl.ProjectSystem/Operation/OperatorEvaluationContext.cs index 0b7da2b5f..1c656315a 100644 --- a/src/Beutl.ProjectSystem/Operation/OperatorEvaluationContext.cs +++ b/src/Beutl.ProjectSystem/Operation/OperatorEvaluationContext.cs @@ -15,14 +15,12 @@ public OperatorEvaluationContext(SourceOperator sourceOperator) Operator = sourceOperator; } - public IList GlobalRenderables { get; internal set; } = null!; + public IList FlowRenderables { get; internal set; } = null!; public SourceOperator Operator { get; } - // AllowOutflowがfalseでもOutflowができる - public void AddGlobalRenderable(Renderable renderable) + public void AddFlowRenderable(Renderable renderable) { - AddRenderable(renderable); - GlobalRenderables.Add(renderable); + FlowRenderables?.Add(renderable); } } diff --git a/src/Beutl.ProjectSystem/Operation/SourceOperation.cs b/src/Beutl.ProjectSystem/Operation/SourceOperation.cs index f4274fba3..e84a740c2 100644 --- a/src/Beutl.ProjectSystem/Operation/SourceOperation.cs +++ b/src/Beutl.ProjectSystem/Operation/SourceOperation.cs @@ -91,13 +91,6 @@ public override void WriteToJson(ref JsonNode json) } } - // NodeTreeとSourceOperationの違い - - // NodeTreeは評価コンテキストにAddRenderableしたらすべて、レイヤーに追加される。 - // SourceOperationは編集者がハンドラ(自動ハンドラもある)を指定して、それが受け取ったRenderableをレイヤーに追加する。 - - // NodeTreeは流れるオブジェクトが何でもあり - // SourceOperationはRenderableのみ public void Evaluate(IRenderer renderer, Layer layer, IList unhandled) { void Detach(IList renderables) @@ -112,35 +105,46 @@ void Detach(IList renderables) } } + layer.Span.Value.Clear(); + Initialize(renderer); if (_contexts != null) { - PooledList? pooled = null; - IList list = layer.AllowOutflow ? unhandled : (pooled = new PooledList()); + if (!layer.AllowOutflow) + { + using var flow = new PooledList(); + foreach (OperatorEvaluationContext? item in _contexts.AsSpan().Slice(0, _contextsLength)) + { + item.FlowRenderables = flow; + item.Operator.Evaluate(item); + } - foreach (OperatorEvaluationContext? item in _contexts.AsSpan().Slice(0, _contextsLength)) + Detach(flow); + layer.Span.Value.AddRange(flow); + } + else { - item._renderables = list; - item.GlobalRenderables = unhandled; - item.Operator.Evaluate(item); + using var pooled = new PooledList(); + foreach (OperatorEvaluationContext? item in _contexts.AsSpan().Slice(0, _contextsLength)) + { + item._renderables = pooled; + item.FlowRenderables = unhandled; + item.Operator.Evaluate(item); + } + + Detach(pooled); + layer.Span.Value.AddRange(pooled); } - if (!layer.AllowOutflow) + foreach (Renderable item in layer.Span.Value.GetMarshal().Value) { - Detach(list); - layer.Span.Value.Replace(list); - - foreach (Renderable item in layer.Span.Value.GetMarshal().Value) + item.ApplyStyling(renderer.Clock); + item.ApplyAnimations(renderer.Clock); + item.IsVisible = layer.IsEnabled; + while (item.BatchUpdate) { - item.ApplyStyling(renderer.Clock); - item.ApplyAnimations(renderer.Clock); - item.IsVisible = layer.IsEnabled; - while (!item.EndBatchUpdate()) - { - } + item.EndBatchUpdate(); } - - pooled?.Dispose(); } } else diff --git a/src/Beutl.ProjectSystem/Operation/SourceOperator.cs b/src/Beutl.ProjectSystem/Operation/SourceOperator.cs index 67a97779b..d39e9c077 100644 --- a/src/Beutl.ProjectSystem/Operation/SourceOperator.cs +++ b/src/Beutl.ProjectSystem/Operation/SourceOperator.cs @@ -3,8 +3,6 @@ using Beutl.Media; using Beutl.Rendering; -using DynamicData; - namespace Beutl.Operation; public interface ISourceOperator : IAffectsRender @@ -58,28 +56,16 @@ public virtual void Evaluate(OperatorEvaluationContext context) switch (this) { case ISourceTransformer selector: - selector.Transform(context._renderables, context.Clock); + selector.Transform(context.FlowRenderables, context.Clock); break; case ISourcePublisher source: if (source.Publish(context.Clock) is Renderable renderable) { - context.AddRenderable(renderable); - } - break; - case ISourceFilter filter: - if (filter.Scope == SourceFilterScope.Local) - { - context._renderables = filter.Filter((IReadOnlyList)context._renderables, context.Clock); - } - else - { - context._renderables = filter.Filter((IReadOnlyList)context._renderables, context.Clock); - context.GlobalRenderables.Clear(); - context.GlobalRenderables.AddRange(context._renderables); + context.AddFlowRenderable(renderable); } break; case ISourceHandler handler: - handler.Handle(context._renderables, context.Clock); + handler.Handle(context.FlowRenderables, context.Clock); break; default: break; @@ -89,7 +75,7 @@ public virtual void Evaluate(OperatorEvaluationContext context) public virtual void Enter() { } - + public virtual void Exit() { } diff --git a/src/Beutl.ProjectSystem/Operation/SourceStyler.cs b/src/Beutl.ProjectSystem/Operation/SourceStyler.cs index 923fb2a02..63f49fb78 100644 --- a/src/Beutl.ProjectSystem/Operation/SourceStyler.cs +++ b/src/Beutl.ProjectSystem/Operation/SourceStyler.cs @@ -27,11 +27,6 @@ public virtual void Transform(IList value, IClock clock) } } - public override void Exit() - { - base.Exit(); - } - protected virtual void OnPreSelect(IRenderable? value) { } @@ -51,7 +46,7 @@ protected virtual void OnPostSelect(IRenderable? value) { if (type.IsAssignableTo(Style.TargetType) && value is IStyleable styleable) { - var instance = Style.Instance(styleable); + IStyleInstance instance = Style.Instance(styleable); _table.AddOrUpdate(value, instance); return instance; } diff --git a/src/Beutl.ProjectSystem/SceneRenderer.cs b/src/Beutl.ProjectSystem/SceneRenderer.cs index 849343db8..495b319dd 100644 --- a/src/Beutl.ProjectSystem/SceneRenderer.cs +++ b/src/Beutl.ProjectSystem/SceneRenderer.cs @@ -1,5 +1,6 @@ using System.Runtime.InteropServices; +using Beutl.Media; using Beutl.Operation; using Beutl.ProjectSystem; using Beutl.Rendering; @@ -11,8 +12,8 @@ internal sealed class SceneRenderer : //DeferredRenderer { private readonly Scene _scene; - private readonly List _begin = new(); - private readonly List _end = new(); + private readonly List _entered = new(); + private readonly List _exited = new(); private readonly List _layers = new(); private readonly List _unhandleds = new(); private TimeSpan _recentTime = TimeSpan.MinValue; @@ -31,18 +32,18 @@ protected override void RenderGraphicsCore() { var timeSpan = Clock.CurrentTime; CurrentTime = timeSpan; - SortLayers(timeSpan); + SortLayers(timeSpan, out _); Span layers = CollectionsMarshal.AsSpan(_layers); - Span begin = CollectionsMarshal.AsSpan(_begin); - Span end = CollectionsMarshal.AsSpan(_end); + Span entered = CollectionsMarshal.AsSpan(_entered); + Span exited = CollectionsMarshal.AsSpan(_exited); _unhandleds.Clear(); - foreach (Layer item in end) + foreach (Layer item in exited) { ExitSourceOperators(item); } - foreach (Layer item in begin) + foreach (Layer item in entered) { EnterSourceOperators(item); } @@ -80,12 +81,14 @@ private static void ExitSourceOperators(Layer layer) } // Layersを振り分ける - private void SortLayers(TimeSpan timeSpan) + private void SortLayers(TimeSpan timeSpan, out TimeRange enterAffectsRange) { - _begin.Clear(); - _end.Clear(); + _entered.Clear(); + _exited.Clear(); _layers.Clear(); - // Todo: 'public Layers Children'はソート済みにしたい + TimeSpan enterStart = TimeSpan.MaxValue; + TimeSpan enterEnd = TimeSpan.Zero; + foreach (Layer? item in _scene.Children) { bool recent = InRange(item, _recentTime); @@ -99,14 +102,22 @@ private void SortLayers(TimeSpan timeSpan) if (!recent && current) { // _recentTimeの範囲外でcurrntTimeの範囲内 - _begin.OrderedAdd(item, x => x.ZIndex); + _entered.OrderedAdd(item, x => x.ZIndex); + if (item.Start < enterStart) + enterStart = item.Start; + + TimeSpan end = item.Range.End; + if (enterEnd < end) + enterEnd = end; } else if (recent && !current) { // _recentTimeの範囲内でcurrntTimeの範囲外 - _end.OrderedAdd(item, x => x.ZIndex); + _exited.OrderedAdd(item, x => x.ZIndex); } } + + enterAffectsRange = TimeRange.FromRange(enterStart, enterEnd); } From 4cd4d5085b0dfe5a7c333d552e300aae6d153f30 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Wed, 22 Mar 2023 15:14:30 +0900 Subject: [PATCH 06/84] =?UTF-8?q?StyleSetter=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Graphics/Styling/StyleSetter.cs | 94 ------------------- .../Styling/StyleSetterInstance.cs | 92 ------------------ 2 files changed, 186 deletions(-) delete mode 100644 src/Beutl.Graphics/Styling/StyleSetter.cs delete mode 100644 src/Beutl.Graphics/Styling/StyleSetterInstance.cs diff --git a/src/Beutl.Graphics/Styling/StyleSetter.cs b/src/Beutl.Graphics/Styling/StyleSetter.cs deleted file mode 100644 index 23cf56e3f..000000000 --- a/src/Beutl.Graphics/Styling/StyleSetter.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System.Reactive; -using System.Reactive.Linq; - -using Beutl.Animation; -using Beutl.Reactive; - -namespace Beutl.Styling; - -public class StyleSetter : LightweightObservableBase, ISetter -{ - private CoreProperty? _property; - private Style? _value; - - public StyleSetter() - { - } - - public StyleSetter(CoreProperty property, Style? value) - { - _property = property; - Value = value; - } - - public CoreProperty Property - { - get => _property ?? throw new InvalidOperationException(); - set => _property = value; - } - - public Style? Value - { - get => _value; - set - { - if (_value != value) - { - if (_value != null) - { - _value.Invalidated -= OnInvalidated; - } - - _value = value; - PublishNext(value); - - Invalidated?.Invoke(this, EventArgs.Empty); - - if (value != null) - { - value.Invalidated += OnInvalidated; - } - } - } - } - - CoreProperty ISetter.Property => Property; - - object? ISetter.Value => Value; - - IAnimation? ISetter.Animation => throw new InvalidOperationException(); - - public event EventHandler? Invalidated; - - public ISetterInstance Instance(IStyleable target) - { - if (Value?.TargetType?.IsAssignableTo(typeof(T)) == false) - { - throw new InvalidCastException($"Unable to cast object of type {Value?.TargetType} to type {typeof(T)}."); - } - return new StyleSetterInstance(this, target); - } - - public IObservable GetObservable() - { - return this.Select(i => Unit.Default); - } - - protected override void Subscribed(IObserver observer, bool first) - { - observer.OnNext(_value); - } - - protected override void Initialize() - { - } - - protected override void Deinitialize() - { - } - - private void OnInvalidated(object? sender, EventArgs e) - { - Invalidated?.Invoke(this, EventArgs.Empty); - } -} diff --git a/src/Beutl.Graphics/Styling/StyleSetterInstance.cs b/src/Beutl.Graphics/Styling/StyleSetterInstance.cs deleted file mode 100644 index b01c49917..000000000 --- a/src/Beutl.Graphics/Styling/StyleSetterInstance.cs +++ /dev/null @@ -1,92 +0,0 @@ -using Beutl.Animation; - -namespace Beutl.Styling; -#pragma warning disable CA1816 - -public class StyleSetterInstance : ISetterInstance -{ - private IStyleable? _target; - private StyleSetter? _setter; - private IStyleInstance? _inner; - private IStyleable? _targetValue; - private IDisposable? _disposable; - - public StyleSetterInstance(StyleSetter setter, IStyleable target) - { - _setter = setter; - _target = target; - _disposable = setter.Subscribe(Setter_ValueChanged); - } - - public CoreProperty Property => Setter.Property; - - public StyleSetter Setter => _setter ?? throw new InvalidOperationException(); - - public IStyleable Target => _target ?? throw new InvalidOperationException(); - - CoreProperty ISetterInstance.Property => Property; - - ISetter ISetterInstance.Setter => Setter; - - public void Apply(IClock clock) - { - if (_inner != null && _target != null) - { - _target.SetValue(Property, _targetValue); - _inner.Apply(clock); - } - } - - public void Begin() - { - _inner?.Begin(); - } - - public void Dispose() - { - _target?.ClearValue(Property); - _targetValue?.InvalidateStyles(); - _disposable?.Dispose(); - - _disposable = null; - _setter = null; - _target = null; - } - - public void End() - { - _inner?.End(); - } - - private IStyleable CreateOrGetTargetValue(Type type) - { - object? value = Target.GetValue(Setter.Property); - if (value?.GetType().IsAssignableTo(type) == true) - { - return (IStyleable)value; - } - - return (IStyleable)Activator.CreateInstance(type)!; - } - - private void Setter_ValueChanged(Style? value) - { - if (value?.TargetType?.IsAssignableTo(typeof(T)) == false) - { - throw new InvalidCastException($"Unable to cast object of type {value?.TargetType} to type {typeof(T)}."); - } - - if (_targetValue != null && _inner != null) - { - _targetValue.InvalidateStyles(); - } - - if (value != null) - { - _targetValue = CreateOrGetTargetValue(value.TargetType)!; - _inner = value.Instance(_targetValue); - _targetValue.StyleApplied(_inner); - Target.SetValue(Property, _targetValue); - } - } -} From 919efdaf49babc836daadcf4bedf51e41b052ff2 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Wed, 22 Mar 2023 17:20:19 +0900 Subject: [PATCH 07/84] =?UTF-8?q?ConfigureOperator=E3=81=A7StylingOperator?= =?UTF-8?q?=E3=82=92=E7=B6=99=E6=89=BF=E3=81=99=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Core/Beutl.Core.csproj | 1 - src/Beutl.Core/Collections/PooledArray.cs | 86 ++++++++++++++----- src/Beutl.Core/Hierarchy/Hierarchical.cs | 22 ++--- src/Beutl.Graphics/Styling/ISetter.cs | 2 +- src/Beutl.Graphics/Styling/ISetterInstance.cs | 2 +- src/Beutl.Graphics/Styling/IStyle.cs | 2 +- src/Beutl.Graphics/Styling/IStyleInstance.cs | 4 +- src/Beutl.Graphics/Styling/Setter.cs | 2 +- src/Beutl.Graphics/Styling/SetterInstance.cs | 6 +- src/Beutl.Graphics/Styling/Style.cs | 4 +- src/Beutl.Graphics/Styling/StyleInstance.cs | 8 +- src/Beutl.Graphics/Styling/Styles.cs | 2 +- .../Configure/ConfigureOperator.cs | 81 ++++++++--------- .../Operation/SourceOperation.cs | 13 ++- .../Operation/SourceStyler.cs | 41 +++++---- .../Operation/StyledSourcePublisher.cs | 4 +- .../ViewModels/Tools/StyleEditorViewModel.cs | 4 +- 17 files changed, 162 insertions(+), 122 deletions(-) diff --git a/src/Beutl.Core/Beutl.Core.csproj b/src/Beutl.Core/Beutl.Core.csproj index 9d6d6250a..79ba7f38d 100644 --- a/src/Beutl.Core/Beutl.Core.csproj +++ b/src/Beutl.Core/Beutl.Core.csproj @@ -3,7 +3,6 @@ Beutl - diff --git a/src/Beutl.Core/Collections/PooledArray.cs b/src/Beutl.Core/Collections/PooledArray.cs index b449edf65..fdf16e93e 100644 --- a/src/Beutl.Core/Collections/PooledArray.cs +++ b/src/Beutl.Core/Collections/PooledArray.cs @@ -1,11 +1,55 @@ using System.Buffers; using System.Collections; -namespace BeUtl.Collections; +namespace Beutl.Collections; -public struct PooledArray : IDisposable, IEnumerable, ICloneable +public static class PooledArrayExtensions { - private readonly T[] _array; + public static PooledArray ToPooledArray(this IEnumerable source) + { + if (source is ICollection collectionoft) + { + var array = new PooledArray(collectionoft.Count); + collectionoft.CopyTo(array._array, 0); + return array; + } + else if (source.TryGetNonEnumeratedCount(out int count)) + { + var array = new PooledArray(count); + int index = 0; + foreach (T item in source) + { + array[index++] = item; + } + + return array; + } + else + { + T[] array = ArrayPool.Shared.Rent(4); + int index = 0; + foreach (T item in source) + { + if (index >= array.Length) + { + T[] tmp = ArrayPool.Shared.Rent(array.Length * 2); + Array.Copy(array, tmp, array.Length); + ArrayPool.Shared.Return(array, true); + array = tmp; + } + + array[index] = item; + index++; + } + + return new PooledArray(array, index); + } + } +} + +public struct PooledArray : IDisposable, IEnumerable +{ + internal readonly T[] _array; public PooledArray(int length) { @@ -14,6 +58,13 @@ public PooledArray(int length) IsDisposed = false; } + internal PooledArray(T[] array, int length) + { + _array = array; + Length = length; + IsDisposed = false; + } + public int Length { get; } public bool IsDisposed { get; private set; } @@ -30,12 +81,6 @@ public ref T this[int index] } } - public object Clone() - { - ThrowIfDisposed(); - throw new NotImplementedException(); - } - public void Dispose() { if (!IsDisposed) @@ -45,12 +90,17 @@ public void Dispose() } } - public IEnumerator GetEnumerator() + public ArrayEnumerator GetEnumerator() { ThrowIfDisposed(); return new ArrayEnumerator(this); } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + private void ThrowIfDisposed() { if (IsDisposed) @@ -65,11 +115,10 @@ IEnumerator IEnumerable.GetEnumerator() return _array.GetEnumerator(); } - private sealed class ArrayEnumerator : IEnumerator, ICloneable + public struct ArrayEnumerator : IEnumerator { private PooledArray _array; private int _index; - private T? _currentElement; public ArrayEnumerator(PooledArray array) { @@ -77,7 +126,7 @@ public ArrayEnumerator(PooledArray array) _index = -1; } - public T Current + public ref T Current { get { @@ -85,23 +134,19 @@ public T Current throw new InvalidOperationException(); if (_index >= _array!.Length) throw new InvalidOperationException(); - return _currentElement!; + return ref _array[_index]; } } - object? IEnumerator.Current => Current; + T IEnumerator.Current => Current; - public object Clone() - { - return MemberwiseClone(); - } + object? IEnumerator.Current => Current; public bool MoveNext() { if (_index < (_array!.Length - 1)) { _index++; - _currentElement = _array[_index]; return true; } else @@ -120,7 +165,6 @@ public void Dispose() public void Reset() { - _currentElement = default; _index = -1; } } diff --git a/src/Beutl.Core/Hierarchy/Hierarchical.cs b/src/Beutl.Core/Hierarchy/Hierarchical.cs index d65d80fa0..8f96c7c4e 100644 --- a/src/Beutl.Core/Hierarchy/Hierarchical.cs +++ b/src/Beutl.Core/Hierarchy/Hierarchical.cs @@ -112,7 +112,7 @@ private void OnAttachedToHierarchyCore(in HierarchyAttachmentEventArgs e) if (_parent == null && this is not IHierarchicalRoot) { throw new InvalidOperationException( - $"AttachedToLogicalTreeCore called for '{GetType().Name}' but element has no logical parent."); + $"OnAttachedToHierarchyCore called for '{GetType().Name}' but element has no logical parent."); } if (_root == null) @@ -122,10 +122,9 @@ private void OnAttachedToHierarchyCore(in HierarchyAttachmentEventArgs e) AttachedToHierarchy?.Invoke(this, e); } - _ = HierarchicalChildren; - foreach (IHierarchical item in _hierarchicalChildren!.GetMarshal().Value) + foreach (IHierarchical item in _hierarchicalChildren.GetMarshal().Value) { - (item as IModifiableHierarchical)?.NotifyAttachedToHierarchy(e); + (item as IModifiableHierarchical)?.NotifyAttachedToHierarchy(new(e.Root, this)); } } @@ -137,10 +136,9 @@ private void OnDetachedFromHierarchyCore(in HierarchyAttachmentEventArgs e) OnDetachedFromHierarchy(e); DetachedFromHierarchy?.Invoke(this, e); - _ = HierarchicalChildren; - foreach (IHierarchical item in _hierarchicalChildren!.GetMarshal().Value) + foreach (IHierarchical item in _hierarchicalChildren.GetMarshal().Value) { - (item as IModifiableHierarchical)?.NotifyDetachedFromHierarchy(e); + (item as IModifiableHierarchical)?.NotifyDetachedFromHierarchy(new(e.Root, this)); } } } @@ -166,24 +164,20 @@ void IModifiableHierarchical.SetParent(IHierarchical? parent) throw new InvalidOperationException("This logical element already has a parent."); } + IHierarchicalRoot? newRoot = parent?.FindHierarchicalRoot() ?? (this as IHierarchicalRoot); + HierarchicalParent = parent; + if (_root != null) { var e = new HierarchyAttachmentEventArgs(_root, old); OnDetachedFromHierarchyCore(e); } - _parent = parent; - IHierarchicalRoot? newRoot = this.FindHierarchicalRoot(); - if (newRoot != null) { var e = new HierarchyAttachmentEventArgs(newRoot, parent); OnAttachedToHierarchyCore(e); } - - // Raise PropertyChanged - _parent = old; - HierarchicalParent = parent; } } diff --git a/src/Beutl.Graphics/Styling/ISetter.cs b/src/Beutl.Graphics/Styling/ISetter.cs index f1e0583f3..c2e7d98ad 100644 --- a/src/Beutl.Graphics/Styling/ISetter.cs +++ b/src/Beutl.Graphics/Styling/ISetter.cs @@ -15,7 +15,7 @@ public interface ISetter event EventHandler? Invalidated; - ISetterInstance Instance(IStyleable target); + ISetterInstance Instance(ICoreObject target); IObservable GetObservable(); } diff --git a/src/Beutl.Graphics/Styling/ISetterInstance.cs b/src/Beutl.Graphics/Styling/ISetterInstance.cs index c62fef9c2..230b6c102 100644 --- a/src/Beutl.Graphics/Styling/ISetterInstance.cs +++ b/src/Beutl.Graphics/Styling/ISetterInstance.cs @@ -9,7 +9,7 @@ public interface ISetterInstance : IDisposable ISetter Setter { get; } - IStyleable Target { get; } + ICoreObject Target { get; } void Apply(IClock clock); diff --git a/src/Beutl.Graphics/Styling/IStyle.cs b/src/Beutl.Graphics/Styling/IStyle.cs index a9711cd44..69194903b 100644 --- a/src/Beutl.Graphics/Styling/IStyle.cs +++ b/src/Beutl.Graphics/Styling/IStyle.cs @@ -10,5 +10,5 @@ public interface IStyle event EventHandler? Invalidated; - IStyleInstance Instance(IStyleable target, IStyleInstance? baseStyle = null); + IStyleInstance Instance(ICoreObject target, IStyleInstance? baseStyle = null); } diff --git a/src/Beutl.Graphics/Styling/IStyleInstance.cs b/src/Beutl.Graphics/Styling/IStyleInstance.cs index 1b3861d92..199179f80 100644 --- a/src/Beutl.Graphics/Styling/IStyleInstance.cs +++ b/src/Beutl.Graphics/Styling/IStyleInstance.cs @@ -5,11 +5,9 @@ namespace Beutl.Styling; public interface IStyleInstance : IDisposable { - bool IsEnabled { get; set; } - IStyleInstance? BaseStyle { get; } - IStyleable Target { get; } + ICoreObject Target { get; } IStyle Source { get; } diff --git a/src/Beutl.Graphics/Styling/Setter.cs b/src/Beutl.Graphics/Styling/Setter.cs index 1f6c01ae2..ef39619e4 100644 --- a/src/Beutl.Graphics/Styling/Setter.cs +++ b/src/Beutl.Graphics/Styling/Setter.cs @@ -112,7 +112,7 @@ public IAnimation? Animation public event EventHandler? Invalidated; - public ISetterInstance Instance(IStyleable target) + public ISetterInstance Instance(ICoreObject target) { return new SetterInstance(this, target); } diff --git a/src/Beutl.Graphics/Styling/SetterInstance.cs b/src/Beutl.Graphics/Styling/SetterInstance.cs index 343024650..24837e262 100644 --- a/src/Beutl.Graphics/Styling/SetterInstance.cs +++ b/src/Beutl.Graphics/Styling/SetterInstance.cs @@ -6,10 +6,10 @@ namespace Beutl.Styling; public class SetterInstance : ISetterInstance { - private IStyleable? _target; + private ICoreObject? _target; private Setter? _setter; - public SetterInstance(Setter setter, IStyleable target) + public SetterInstance(Setter setter, ICoreObject target) { _setter = setter; _target = target; @@ -19,7 +19,7 @@ public SetterInstance(Setter setter, IStyleable target) public Setter Setter => _setter ?? throw new InvalidOperationException(); - public IStyleable Target => _target ?? throw new InvalidOperationException(); + public ICoreObject Target => _target ?? throw new InvalidOperationException(); CoreProperty ISetterInstance.Property => Property; diff --git a/src/Beutl.Graphics/Styling/Style.cs b/src/Beutl.Graphics/Styling/Style.cs index 511f32830..9b21c4adb 100644 --- a/src/Beutl.Graphics/Styling/Style.cs +++ b/src/Beutl.Graphics/Styling/Style.cs @@ -32,7 +32,7 @@ public virtual Type TargetType public event EventHandler? Invalidated; - public IStyleInstance Instance(IStyleable target, IStyleInstance? baseStyle = null) + public IStyleInstance Instance(ICoreObject target, IStyleInstance? baseStyle = null) { var array = new ISetterInstance[_setters.Count]; int index = 0; @@ -46,7 +46,7 @@ public IStyleInstance Instance(IStyleable target, IStyleInstance? baseStyle = nu } public sealed class Style : Style - where T : Styleable + where T : ICoreObject { public override Type TargetType { diff --git a/src/Beutl.Graphics/Styling/StyleInstance.cs b/src/Beutl.Graphics/Styling/StyleInstance.cs index 19dcd8d76..6f161e749 100644 --- a/src/Beutl.Graphics/Styling/StyleInstance.cs +++ b/src/Beutl.Graphics/Styling/StyleInstance.cs @@ -8,12 +8,12 @@ namespace Beutl.Styling; public class StyleInstance : IStyleInstance { - private IStyleable? _target; + private ICoreObject? _target; private IStyle? _source; private ISetterInstance[] _setters; private ISetterInstance[][]? _cache; - public StyleInstance(IStyleable target, IStyle source, ISetterInstance[] setters, IStyleInstance? baseStyle) + public StyleInstance(ICoreObject target, IStyle source, ISetterInstance[] setters, IStyleInstance? baseStyle) { _target = target; _source = source; @@ -21,11 +21,9 @@ public StyleInstance(IStyleable target, IStyle source, ISetterInstance[] setters BaseStyle = baseStyle; } - public bool IsEnabled { get; set; } - public IStyleInstance? BaseStyle { get; private set; } - public IStyleable Target => _target ?? throw new InvalidOperationException(); + public ICoreObject Target => _target ?? throw new InvalidOperationException(); public IStyle Source => _source ?? throw new InvalidOperationException(); diff --git a/src/Beutl.Graphics/Styling/Styles.cs b/src/Beutl.Graphics/Styling/Styles.cs index ab1e15592..4d4018dab 100644 --- a/src/Beutl.Graphics/Styling/Styles.cs +++ b/src/Beutl.Graphics/Styling/Styles.cs @@ -4,7 +4,7 @@ namespace Beutl.Styling; public sealed class Styles : CoreList { - public IStyleInstance? Instance(IStyleable target) + public IStyleInstance? Instance(ICoreObject target) { IStyleInstance? baseStyle = null; diff --git a/src/Beutl.Operators/Configure/ConfigureOperator.cs b/src/Beutl.Operators/Configure/ConfigureOperator.cs index 53d4925fe..5f1997063 100644 --- a/src/Beutl.Operators/Configure/ConfigureOperator.cs +++ b/src/Beutl.Operators/Configure/ConfigureOperator.cs @@ -1,17 +1,16 @@ -using System.Text.Json.Nodes; - -using Beutl.Animation; -using Beutl.Framework; +using Beutl.Animation; using Beutl.Media; using Beutl.Operation; using Beutl.Rendering; +using Beutl.Styling; namespace Beutl.Operators.Configure; -public abstract class ConfigureOperator : SourceOperator, ISourceTransformer +public abstract class ConfigureOperator : StylingOperator, ISourceTransformer where TTarget : IRenderable where TValue : CoreObject, IAffectsRender, new() { + private IStyleInstance? _instance; private bool _transforming; public ConfigureOperator() @@ -24,29 +23,53 @@ public ConfigureOperator() RaiseInvalidated(e); } }; + } - Type anmPropType = typeof(AnimatableCorePropertyImpl<>); - Type propType = typeof(CorePropertyImpl<>); - Type ownerType = typeof(TTarget); - bool isAnimatable = Value is IAnimatable; - - Properties.AddRange(GetProperties().Select(x => - { - Type propTypeFact = (isAnimatable && x.GetMetadata(ownerType).PropertyFlags.HasFlag(PropertyFlags.Animatable) - ? anmPropType - : propType).MakeGenericType(x.PropertyType); + protected TValue Value { get; } - return (IAbstractProperty)Activator.CreateInstance(propTypeFact, x, Value)!; - })); + protected override Style OnInitializeStyle(Func> setters) + { + var style = new Style(); + style.Setters.AddRange(setters()); + return style; } - protected TValue Value { get; } + protected override void OnInitializeSetters(IList initializing) + { + base.OnInitializeSetters(initializing); + foreach (CoreProperty item in GetProperties()) + { + Type setterType = typeof(Setter<>).MakeGenericType(item.PropertyType); + ICorePropertyMetadata metadata = item.GetMetadata(); + object? defaultValue = metadata.GetDefaultValue(); + object? setter = defaultValue != null + ? Activator.CreateInstance(setterType, item, defaultValue) + : Activator.CreateInstance(setterType, item); + + if (setter is ISetter setter1) + { + initializing.Add(setter1); + } + } + } public void Transform(IList value, IClock clock) { try { _transforming = true; + if (!ReferenceEquals(Style, _instance?.Source) || _instance == null) + { + _instance?.Dispose(); + _instance = Style.Instance(Value); + } + + if (_instance != null && IsEnabled) + { + _instance.Begin(); + _instance.Apply(clock); + _instance.End(); + } foreach (TTarget item in value.OfType()) { @@ -61,28 +84,6 @@ public void Transform(IList value, IClock clock) } } - public override void ReadFromJson(JsonNode json) - { - base.ReadFromJson(json); - if (json is JsonObject jobj - && jobj.TryGetPropertyValue("value", out JsonNode? node) - && node != null) - { - Value.ReadFromJson(node); - } - } - - public override void WriteToJson(ref JsonNode json) - { - base.WriteToJson(ref json); - if (json is JsonObject jobj) - { - JsonNode node = new JsonObject(); - Value.WriteToJson(ref node); - jobj["value"] = node; - } - } - protected virtual void PreProcess(TTarget target, TValue value) { } diff --git a/src/Beutl.ProjectSystem/Operation/SourceOperation.cs b/src/Beutl.ProjectSystem/Operation/SourceOperation.cs index e84a740c2..6b4e12c0b 100644 --- a/src/Beutl.ProjectSystem/Operation/SourceOperation.cs +++ b/src/Beutl.ProjectSystem/Operation/SourceOperation.cs @@ -198,15 +198,12 @@ private void Initialize(IRenderer renderer) int index = 0; foreach (SourceOperator item in Children.GetMarshal().Value) { - if (item.IsEnabled) + contexts[index++] = new OperatorEvaluationContext(item) { - contexts[index++] = new OperatorEvaluationContext(item) - { - Clock = renderer.Clock, - Renderer = renderer, - List = _contexts - }; - } + Clock = renderer.Clock, + Renderer = renderer, + List = _contexts + }; } foreach (OperatorEvaluationContext item in contexts) diff --git a/src/Beutl.ProjectSystem/Operation/SourceStyler.cs b/src/Beutl.ProjectSystem/Operation/SourceStyler.cs index 63f49fb78..e50520caa 100644 --- a/src/Beutl.ProjectSystem/Operation/SourceStyler.cs +++ b/src/Beutl.ProjectSystem/Operation/SourceStyler.cs @@ -1,6 +1,7 @@ using System.Runtime.CompilerServices; using Beutl.Animation; +using Beutl.Collections; using Beutl.Rendering; using Beutl.Styling; @@ -12,18 +13,21 @@ public abstract class SourceStyler : StylingOperator, ISourceTransformer public virtual void Transform(IList value, IClock clock) { - for (int i = 0; i < value.Count; i++) + if (IsEnabled) { - Renderable renderable = value[i]; - OnPreSelect(renderable); - IStyleInstance? instance = GetInstance(value[i]); - - if (instance != null) + for (int i = 0; i < value.Count; i++) { - ApplyStyle(instance, renderable, clock); - } + Renderable renderable = value[i]; + OnPreSelect(renderable); + IStyleInstance? instance = GetInstance(value[i]); + + if (instance != null) + { + ApplyStyle(instance, renderable, clock); + } - OnPostSelect(renderable); + OnPostSelect(renderable); + } } } @@ -44,9 +48,9 @@ protected virtual void OnPostSelect(IRenderable? value) } else { - if (type.IsAssignableTo(Style.TargetType) && value is IStyleable styleable) + if (type.IsAssignableTo(Style.TargetType) && value is ICoreObject coreObj) { - IStyleInstance instance = Style.Instance(styleable); + IStyleInstance instance = Style.Instance(coreObj); _table.AddOrUpdate(value, instance); return instance; } @@ -59,20 +63,25 @@ protected virtual void OnPostSelect(IRenderable? value) protected virtual void ApplyStyle(IStyleInstance instance, IRenderable value, IClock clock) { - instance.IsEnabled = IsEnabled; instance.Begin(); instance.Apply(clock); instance.End(); } + protected override void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs args) + { + base.OnAttachedToHierarchy(args); + } + protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) { base.OnDetachedFromHierarchy(args); - foreach (KeyValuePair item in _table) + using var instances = _table.Select(x => x.Value).ToPooledArray(); + _table.Clear(); + + foreach (IStyleInstance item in instances) { - item.Value.Dispose(); + item.Dispose(); } - - _table.Clear(); } } diff --git a/src/Beutl.ProjectSystem/Operation/StyledSourcePublisher.cs b/src/Beutl.ProjectSystem/Operation/StyledSourcePublisher.cs index 391d128b3..0f4601238 100644 --- a/src/Beutl.ProjectSystem/Operation/StyledSourcePublisher.cs +++ b/src/Beutl.ProjectSystem/Operation/StyledSourcePublisher.cs @@ -16,10 +16,10 @@ public abstract class StyledSourcePublisher : StylingOperator, ISourcePublisher if (!ReferenceEquals(Style, Instance?.Source) || Instance?.Target == null) { renderable = Activator.CreateInstance(Style.TargetType) as IRenderable; - if (renderable is IStyleable styleable) + if (renderable is ICoreObject coreObj) { Instance?.Dispose(); - Instance = Style.Instance(styleable); + Instance = Style.Instance(coreObj); } else { diff --git a/src/Beutl/ViewModels/Tools/StyleEditorViewModel.cs b/src/Beutl/ViewModels/Tools/StyleEditorViewModel.cs index 3791bc33c..4028f18e1 100644 --- a/src/Beutl/ViewModels/Tools/StyleEditorViewModel.cs +++ b/src/Beutl/ViewModels/Tools/StyleEditorViewModel.cs @@ -32,7 +32,7 @@ public StyleEditorViewModel(EditViewModel editViewModel) { StyleableTypes.Value = s_cache = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(x => x.GetTypes()) - .Where(x => x.IsPublic && x.IsAssignableTo(typeof(IStyleable))) + .Where(x => x.IsPublic && x.IsAssignableTo(typeof(ICoreObject))) .ToArray(); }); } @@ -43,7 +43,7 @@ public StyleEditorViewModel(EditViewModel editViewModel) { if (Style.Value != null) { - Style.Value.TargetType = v ?? typeof(Styleable); + Style.Value.TargetType = v ?? typeof(ICoreObject); } }); From 943ca6c40b76697f7d06992e7ab3699a0da618a5 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Wed, 22 Mar 2023 23:12:06 +0900 Subject: [PATCH 08/84] =?UTF-8?q?CorePropertyMetadata=E3=81=A7=E3=81=AE?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E3=82=92=E6=9C=80=E5=B0=8F=E9=99=90=E3=81=AB?= =?UTF-8?q?=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Configuration/BackupConfig.cs | 2 - src/Beutl.Configuration/ViewConfig.cs | 8 +- src/Beutl.Core/Beutl.Core.csproj | 11 - src/Beutl.Core/CoreObject.cs | 18 +- src/Beutl.Core/CorePropertyBuilder.cs | 303 +----------------- src/Beutl.Core/CorePropertyMetadata.cs | 152 +++------ src/Beutl.Core/CorePropertyMetadata`1.cs | 112 +++++++ src/Beutl.Core/ICorePropertyMetadata.cs | 20 ++ src/Beutl.Core/JsonHelper.cs | 18 +- src/Beutl.Core/NotifiableAttribute.cs | 12 + src/Beutl.Core/ProjectItem.cs | 2 +- src/Beutl.Core/ShouldSerializeAttribute.cs | 12 + .../Validation/FileInfoExtensionValidator.cs | 53 --- .../Validation/FilePathExtensionValidator.cs | 53 --- src/Beutl.Core/Validation/FuncValidator.cs | 29 -- .../Validation/NumberRangeValidator.cs | 31 -- .../RangeDataAnnotationValidater.cs | 68 ++++ .../Validation/RangeValidatableAttribute.cs | 25 -- .../Validation/RangeValidationService.cs | 77 ----- .../Validation/RangeValidatorGen.cs | 107 ------- .../Validation/RangeValidatorGen.tt | 138 -------- .../Validation/TimeSpanRangeValidator.cs | 28 -- src/Beutl.Core/Validation/TuppleValidator.cs | 42 --- .../Animation/AnimationSerializer.cs | 6 +- src/Beutl.Graphics/Animation/KeyFrame.cs | 5 +- src/Beutl.Graphics/Animation/KeyFrame{T}.cs | 2 - src/Beutl.Graphics/Audio/Effects/Delay.cs | 19 +- .../Audio/Effects/SoundEffect.cs | 1 - .../Audio/Effects/SoundEffectGroup.cs | 2 +- src/Beutl.Graphics/Audio/Sound.cs | 4 - src/Beutl.Graphics/Audio/SourceSound.cs | 3 - src/Beutl.Graphics/Beutl.Graphics.csproj | 11 - src/Beutl.Graphics/Graphics/Drawable.cs | 52 +-- .../Graphics/Drawables/VideoFrame.cs | 17 +- .../Graphics/Effects/BitmapEffect.cs | 1 - .../Graphics/Effects/BitmapEffectGroup.cs | 2 +- src/Beutl.Graphics/Graphics/Effects/Border.cs | 27 +- .../Graphics/Effects/InnerShadow.cs | 18 +- .../Graphics/Effects/OpenCv/Blur.cs | 16 +- .../Graphics/Effects/OpenCv/GaussianBlur.cs | 16 +- .../Graphics/Effects/OpenCv/MedianBlur.cs | 12 +- src/Beutl.Graphics/Graphics/Filters/Blur.cs | 9 +- .../Graphics/Filters/DropShadow.cs | 22 +- .../Graphics/Filters/ImageFilter.cs | 1 - .../Graphics/Filters/ImageFilterGroup.cs | 6 +- src/Beutl.Graphics/Graphics/ImageFile.cs | 8 +- src/Beutl.Graphics/Graphics/Point.cs | 1 - src/Beutl.Graphics/Graphics/Rect.cs | 1 - src/Beutl.Graphics/Graphics/Shapes/Ellipse.cs | 10 +- .../Graphics/Shapes/Rectangle.cs | 10 +- .../Graphics/Shapes/RoundedRect.cs | 16 +- .../Graphics/Shapes/TextBlock.cs | 45 +-- .../Graphics/Shapes/TextElement.cs | 35 +- src/Beutl.Graphics/Graphics/Size.cs | 1 - src/Beutl.Graphics/Graphics/SourceImage.cs | 2 +- .../Transformation/MatrixTransform.cs | 2 - .../Transformation/Rotation3DTransform.cs | 14 - .../Transformation/RotationTransform.cs | 2 - .../Graphics/Transformation/ScaleTransform.cs | 10 +- .../Graphics/Transformation/SkewTransform.cs | 4 - .../Graphics/Transformation/Transform.cs | 1 - .../Graphics/Transformation/TransformGroup.cs | 6 +- .../Transformation/TranslateTransform.cs | 4 - src/Beutl.Graphics/Graphics/Vector.cs | 1 - src/Beutl.Graphics/Media/Brush.cs | 17 +- .../Media/ConicGradientBrush.cs | 12 +- src/Beutl.Graphics/Media/CornerRadius.cs | 1 - src/Beutl.Graphics/Media/DrawableBrush.cs | 2 - src/Beutl.Graphics/Media/GradientBrush.cs | 11 +- src/Beutl.Graphics/Media/GradientStop.cs | 4 - src/Beutl.Graphics/Media/ImageBrush.cs | 1 - .../Media/LinearGradientBrush.cs | 16 +- src/Beutl.Graphics/Media/PixelPoint.cs | 1 - src/Beutl.Graphics/Media/PixelRect.cs | 1 - src/Beutl.Graphics/Media/PixelSize.cs | 1 - .../Media/RadialGradientBrush.cs | 16 +- src/Beutl.Graphics/Media/SolidColorBrush.cs | 13 +- src/Beutl.Graphics/Media/TileBrush.cs | 32 +- .../Rendering/RenderLayerSpan.cs | 11 +- src/Beutl.Graphics/Rendering/Renderable.cs | 1 - src/Beutl.Graphics/Styling/StyleSerializer.cs | 28 +- .../Validation/RangeValidatorGen.cs | 255 --------------- .../Validation/RangeValidatorGen.tt | 140 -------- src/Beutl.ProjectSystem/NodeTree/Node.cs | 16 +- src/Beutl.ProjectSystem/NodeTree/NodeItem.cs | 6 +- .../NodeTree/NodeTreeSpace.cs | 11 +- .../NodeTree/Nodes/Group/GroupInput.cs | 24 +- .../NodeTree/Nodes/Group/GroupOutput.cs | 24 +- .../NodeTree/Nodes/LayerInputNode.cs | 9 +- .../NodeTree/Nodes/Utilities/MatrixNode.cs | 2 - .../Nodes/Utilities/RandomFloatNode.cs | 12 +- .../Nodes/Utilities/Struct/PixelPointNode.cs | 2 - .../Nodes/Utilities/Struct/PixelRectNode.cs | 10 +- .../Nodes/Utilities/Struct/PixelSizeNode.cs | 10 +- .../Nodes/Utilities/Struct/PointNode.cs | 2 - .../Nodes/Utilities/Struct/RectNode.cs | 10 +- .../Utilities/Struct/RelativePointNode.cs | 3 - .../Nodes/Utilities/Struct/SizeNode.cs | 10 +- .../NodeTree/Nodes/Utilities/SwitchNode.cs | 1 - .../NodeTree/SetterPropertyImpl.cs | 9 +- .../Operation/SourceOperator.cs | 2 - .../ProjectSystem/Layer.cs | 30 +- .../ProjectSystem/Scene.cs | 36 +-- .../ViewModels/Editors/BaseEditorViewModel.cs | 2 +- .../Editors/BrushEditorViewModel.cs | 2 +- .../Editors/PropertiesEditorViewModel.cs | 2 +- src/Beutl/Views/Tools/StyleEditor.axaml.cs | 8 +- 107 files changed, 585 insertions(+), 1967 deletions(-) create mode 100644 src/Beutl.Core/CorePropertyMetadata`1.cs create mode 100644 src/Beutl.Core/ICorePropertyMetadata.cs create mode 100644 src/Beutl.Core/NotifiableAttribute.cs create mode 100644 src/Beutl.Core/ShouldSerializeAttribute.cs delete mode 100644 src/Beutl.Core/Validation/FileInfoExtensionValidator.cs delete mode 100644 src/Beutl.Core/Validation/FilePathExtensionValidator.cs delete mode 100644 src/Beutl.Core/Validation/FuncValidator.cs delete mode 100644 src/Beutl.Core/Validation/NumberRangeValidator.cs create mode 100644 src/Beutl.Core/Validation/RangeDataAnnotationValidater.cs delete mode 100644 src/Beutl.Core/Validation/RangeValidatableAttribute.cs delete mode 100644 src/Beutl.Core/Validation/RangeValidationService.cs delete mode 100644 src/Beutl.Core/Validation/RangeValidatorGen.cs delete mode 100644 src/Beutl.Core/Validation/RangeValidatorGen.tt delete mode 100644 src/Beutl.Core/Validation/TimeSpanRangeValidator.cs delete mode 100644 src/Beutl.Core/Validation/TuppleValidator.cs delete mode 100644 src/Beutl.Graphics/Validation/RangeValidatorGen.cs delete mode 100644 src/Beutl.Graphics/Validation/RangeValidatorGen.tt diff --git a/src/Beutl.Configuration/BackupConfig.cs b/src/Beutl.Configuration/BackupConfig.cs index ccfabc9f9..07d5488b4 100644 --- a/src/Beutl.Configuration/BackupConfig.cs +++ b/src/Beutl.Configuration/BackupConfig.cs @@ -9,9 +9,7 @@ public sealed class BackupConfig : ConfigurationBase static BackupConfig() { BackupSettingsProperty = ConfigureProperty(nameof(BackupSettings)) - .SerializeName("backup-settings") .DefaultValue(true) - .PropertyFlags(PropertyFlags.NotifyChanged) .Register(); } diff --git a/src/Beutl.Configuration/ViewConfig.cs b/src/Beutl.Configuration/ViewConfig.cs index 5a7cd0d49..1c9a7430d 100644 --- a/src/Beutl.Configuration/ViewConfig.cs +++ b/src/Beutl.Configuration/ViewConfig.cs @@ -20,21 +20,15 @@ public sealed class ViewConfig : ConfigurationBase static ViewConfig() { ThemeProperty = ConfigureProperty("Theme") - .SerializeName("theme") .DefaultValue(ViewTheme.Dark) - .PropertyFlags(PropertyFlags.NotifyChanged) .Register(); UICultureProperty = ConfigureProperty("UICulture") - .SerializeName("ui-culture") .DefaultValue(CultureInfo.InstalledUICulture) - .PropertyFlags(PropertyFlags.NotifyChanged) .Register(); IsMicaEffectEnabledProperty = ConfigureProperty("IsMicaEffectEnabled") - .SerializeName("is-mica-enabled") .DefaultValue(false) - .PropertyFlags(PropertyFlags.NotifyChanged) .Register(); RecentFilesProperty = ConfigureProperty, ViewConfig>("RecentFiles") @@ -70,12 +64,14 @@ public bool IsMicaEffectEnabled set => SetValue(IsMicaEffectEnabledProperty, value); } + [ShouldSerialize(false)] public CoreList RecentFiles { get => _recentFiles; set => _recentFiles.Replace(value); } + [ShouldSerialize(false)] public CoreList RecentProjects { get => _recentProjects; diff --git a/src/Beutl.Core/Beutl.Core.csproj b/src/Beutl.Core/Beutl.Core.csproj index 79ba7f38d..d8af828f7 100644 --- a/src/Beutl.Core/Beutl.Core.csproj +++ b/src/Beutl.Core/Beutl.Core.csproj @@ -9,17 +9,6 @@ - - - TextTemplatingFileGenerator - RangeValidatorGen.cs - - - True - True - RangeValidatorGen.tt - - diff --git a/src/Beutl.Core/CoreObject.cs b/src/Beutl.Core/CoreObject.cs index 690f58a67..2db5d3601 100644 --- a/src/Beutl.Core/CoreObject.cs +++ b/src/Beutl.Core/CoreObject.cs @@ -70,12 +70,10 @@ public void ApplyTo(CoreObject obj, CoreProperty property) static CoreObject() { IdProperty = ConfigureProperty(nameof(Id)) - .PropertyFlags(PropertyFlags.NotifyChanged) .DefaultValue(Guid.Empty) .Register(); NameProperty = ConfigureProperty(nameof(Name)) - .PropertyFlags(PropertyFlags.NotifyChanged) .DefaultValue(string.Empty) .Register(); } @@ -360,13 +358,12 @@ public virtual void WriteToJson(ref JsonNode json) { CoreProperty item = list[i]; CorePropertyMetadata metadata = item.GetMetadata(ownerType); - string? jsonName = metadata.SerializeName; - if (jsonName != null) + if (metadata.ShouldSerialize) { - JsonNode? valueNode = item.RouteWriteToJson(metadata, GetValue(item), out var isDefault); + JsonNode? valueNode = item.RouteWriteToJson(metadata, GetValue(item), out bool isDefault); if (!isDefault) { - jsonObject[jsonName] = valueNode; + jsonObject[item.Name] = valueNode; } } } @@ -384,10 +381,8 @@ public virtual void ReadFromJson(JsonNode json) { CoreProperty item = list[i]; CorePropertyMetadata metadata = item.GetMetadata(ownerType); - string? jsonName = metadata.SerializeName; - - if (jsonName != null - && obj.TryGetPropertyValue(jsonName, out JsonNode? jsonNode) + if (metadata.ShouldSerialize + && obj.TryGetPropertyValue(item.Name, out JsonNode? jsonNode) && jsonNode != null) { if (item.RouteReadFromJson(metadata, jsonNode) is { } value) @@ -403,13 +398,12 @@ protected virtual void OnPropertyChanged(PropertyChangedEventArgs args) { if (args is CorePropertyChangedEventArgs coreArgs) { - bool hasChangedFlag = coreArgs.PropertyMetadata.PropertyFlags.HasFlag(PropertyFlags.NotifyChanged); if (coreArgs.Property.HasObservers) { coreArgs.Property.NotifyChanged(coreArgs); } - if (hasChangedFlag) + if (coreArgs.PropertyMetadata.Notifiable) { PropertyChanged?.Invoke(this, args); } diff --git a/src/Beutl.Core/CorePropertyBuilder.cs b/src/Beutl.Core/CorePropertyBuilder.cs index fdf118068..8dc4200e5 100644 --- a/src/Beutl.Core/CorePropertyBuilder.cs +++ b/src/Beutl.Core/CorePropertyBuilder.cs @@ -1,48 +1,35 @@ -using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reflection; -using System.Text.Json.Serialization; - -using Beutl.Validation; namespace Beutl; -public sealed class CorePropertyBuilder +public sealed class CorePropertyBuilder<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] T, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TOwner> { private readonly string _name; private Func? _getter; private Action? _setter; - private string? _serializeName; - private Optional _propertyFlags; private Optional _defaultValue; - private IValidator? _validator; - private JsonConverter? _jsonConverter; private readonly PropertyInfo? _propertyInfo; - private DisplayAttribute? _displayAttribute; - // Whether _displayAttribute was set from the attribute - private bool _displayAttByAtt; + private Attribute[] _attributes; - public CorePropertyBuilder(string name) + public CorePropertyBuilder(string name, bool isAttached = false) { _name = name; - Raw = new RawBuilder(this); - - _propertyInfo = typeof(TOwner).GetProperty(name) ?? throw new InvalidOperationException(); - _displayAttribute = _propertyInfo.GetCustomAttribute(); - _displayAttByAtt = _displayAttribute != null; - DataAnnotationValidater[] validations - = _propertyInfo.GetCustomAttributes() - .Select(att => new DataAnnotationValidater(att)) - .ToArray(); - - _validator = new MultipleValidator(validations); + if (!isAttached) + { + _propertyInfo = typeof(TOwner).GetProperty(name) ?? throw new InvalidOperationException(); + _attributes = _propertyInfo.GetCustomAttributes().ToArray(); + } + else + { + _attributes = Array.Empty(); + } } public CorePropertyBuilder(Expression> exp) { - Raw = new RawBuilder(this); - _getter = exp.Compile(); if (exp.Body is MemberExpression memberExp && memberExp.Member is PropertyInfo propInfo && @@ -58,15 +45,7 @@ memberExp.Member is PropertyInfo propInfo && Expression> lambda1 = Expression.Lambda>(assign, new[] { ownerParam, valueParam }); _setter = lambda1.Compile(); - _displayAttribute = propInfo.GetCustomAttribute(); - _displayAttByAtt = _displayAttribute != null; - - DataAnnotationValidater[] validations - = propInfo.GetCustomAttributes() - .Select(att => new DataAnnotationValidater(att)) - .ToArray(); - - _validator = new MultipleValidator(validations); + _attributes = _propertyInfo.GetCustomAttributes().ToArray(); } else { @@ -74,19 +53,11 @@ DataAnnotationValidater[] validations } } - public RawBuilder Raw { get; } - public CoreProperty Register() { CoreProperty? property = null; - var metadata = new CorePropertyMetadata( - serializeName: _serializeName, - propertyFlags: _propertyFlags, - defaultValue: _defaultValue, - validator: _validator, - jsonConverter: _jsonConverter, - displayAttribute: _displayAttribute); + var metadata = new CorePropertyMetadata(_defaultValue, _getter == null || _setter != null, _attributes); if (_getter != null) { property = new StaticProperty(_name, _getter, _setter, metadata); @@ -119,249 +90,9 @@ public CorePropertyBuilder DefaultValue(T value) return this; } - public CorePropertyBuilder SerializeName(string? value) - { - _serializeName = value; - return this; - } - - public CorePropertyBuilder Display( - string? name = null, - string? shortName = null, - string? description = null, - string? prompt = null, - string? groupName = null, - int? order = null) - { - Display(name, shortName, description, prompt, groupName, order, typeof(TResource)); - return this; - } - - public CorePropertyBuilder Display( - string? name = null, - string? shortName = null, - string? description = null, - string? prompt = null, - string? groupName = null, - int? order = null, - Type? resourceType = null) - { - _displayAttribute = _displayAttByAtt || _displayAttribute == null ? new DisplayAttribute() : _displayAttribute; - _displayAttByAtt = false; - - _displayAttribute.ResourceType = resourceType; - _displayAttribute.Name = name; - _displayAttribute.ShortName = shortName; - _displayAttribute.Description = description; - _displayAttribute.Prompt = prompt; - _displayAttribute.GroupName = groupName; - if (order.HasValue) - _displayAttribute.Order = order.Value; - - return this; - } - - private static IValidator MergeValidator(IValidator oldValidator, IValidator newValidator) - { - if (oldValidator is TuppleValidator tupple) - { - newValidator = new MultipleValidator(new IValidator[] - { - tupple.First, - tupple.Second, - newValidator - }); - } - else if (oldValidator is MultipleValidator multiple) - { - int length = multiple.Items.Length; - var array = new IValidator[length + 1]; - multiple.Items.AsSpan().CopyTo(array.AsSpan().Slice(0, length)); - - array[^1] = newValidator; - - newValidator = new MultipleValidator(array); - } - else - { - newValidator = new TuppleValidator(oldValidator, newValidator); - } - - return newValidator; - } - - public CorePropertyBuilder Range(T min, T max, bool merge = false) - where TValidator : RangeValidator, new() - { - IValidator validator = new TValidator - { - Maximum = max, - Minimum = min - }; - - if (merge && _validator != null) - { - validator = MergeValidator(_validator, validator); - } - - _validator = validator; - return this; - } - - public CorePropertyBuilder Range(T min, T max, bool merge = false) - { - if (Activator.CreateInstance(RangeValidationService.Instance.Get()) is RangeValidator validator1) - { - validator1.Minimum = min; - validator1.Maximum = max; - - IValidator validator2 = validator1; - if (merge && _validator != null) - { - validator2 = MergeValidator(_validator, validator1); - } - - _validator = validator2; - } - - return this; - } - - public CorePropertyBuilder Minimum(T min, bool merge = false) - where TValidator : RangeValidator, new() - { - IValidator validator = new TValidator - { - Minimum = min - }; - - if (merge && _validator != null) - { - validator = MergeValidator(_validator, validator); - } - _validator = validator; - return this; - } - - public CorePropertyBuilder Minimum(T min, bool merge = false) - { - if (Activator.CreateInstance(RangeValidationService.Instance.Get()) is RangeValidator validator1) - { - validator1.Minimum = min; - - IValidator validator2 = validator1; - if (merge && _validator != null) - { - validator2 = MergeValidator(_validator, validator1); - } - _validator = validator2; - } - - return this; - } - - public CorePropertyBuilder Maximum(T max, bool merge = false) - where TValidator : RangeValidator, new() - { - IValidator validator = new TValidator - { - Maximum = max - }; - - if (merge && _validator != null) - { - validator = MergeValidator(_validator, validator); - } - _validator = validator; - return this; - } - - public CorePropertyBuilder Maximum(T max, bool merge = false) - { - if (Activator.CreateInstance(RangeValidationService.Instance.Get()) is RangeValidator validator1) - { - validator1.Maximum = max; - - IValidator validator2 = validator1; - if (merge && _validator != null) - { - validator2 = MergeValidator(_validator, validator1); - } - _validator = validator2; - } - - return this; - } - - public CorePropertyBuilder Validator(IValidator validator, bool merge = false) + public CorePropertyBuilder SetAttribute(params Attribute[] attributes) { - if (merge && _validator != null) - { - validator = MergeValidator(_validator, validator); - } - _validator = validator; - + _attributes = attributes; return this; } - - public CorePropertyBuilder Validator( - ValueValidator? validate = null, - ValueCoercer? coerce = null, - bool merge = false) - { - IValidator validator1 = new FuncValidator - { - ValidateFunc = validate, - CoerceFunc = coerce, - }; - if (merge && _validator != null) - { - validator1 = MergeValidator(_validator, validator1); - } - _validator = validator1; - - return this; - } - - public CorePropertyBuilder JsonConverter(JsonConverter jsonConverter) - { - _jsonConverter = jsonConverter; - return this; - } - - public CorePropertyBuilder PropertyFlags(PropertyFlags value, bool merge = false) - { - if (merge) - { - _propertyFlags = _propertyFlags.GetValueOrDefault() | value; - } - else - { - _propertyFlags = value; - } - - return this; - } - - public sealed class RawBuilder - { - private readonly CorePropertyBuilder _builder; - - internal RawBuilder(CorePropertyBuilder builder) - { - _builder = builder; - } - - public CorePropertyBuilder DefaultValue(Optional value) - { - _builder._defaultValue = value; - return _builder; - } - - public CorePropertyBuilder PropertyFlags(Optional value) - { - _builder._propertyFlags = value; - return _builder; - } - } } diff --git a/src/Beutl.Core/CorePropertyMetadata.cs b/src/Beutl.Core/CorePropertyMetadata.cs index 58f97497f..8d0210154 100644 --- a/src/Beutl.Core/CorePropertyMetadata.cs +++ b/src/Beutl.Core/CorePropertyMetadata.cs @@ -1,149 +1,69 @@ -using System.ComponentModel.DataAnnotations; -using System.Reflection; -using System.Text.Json.Serialization; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; using Beutl.Validation; namespace Beutl; -public class CorePropertyMetadata : CorePropertyMetadata +public abstract class CorePropertyMetadata : ICorePropertyMetadata { - private Optional _defaultValue; - - public CorePropertyMetadata( - string? serializeName = null, - Optional propertyFlags = default, - Optional defaultValue = default, - IValidator? validator = null, - JsonConverter? jsonConverter = null, - DisplayAttribute? displayAttribute = null) - : base(serializeName, propertyFlags, displayAttribute) + protected CorePropertyMetadata(bool shouldSerialize = true, params Attribute[] attributes) { - _defaultValue = defaultValue; - Validator = validator; - JsonConverter = jsonConverter; + ShouldSerialize = shouldSerialize; + Attributes = attributes; + UpdatedAttributes(); } - public T DefaultValue => _defaultValue.GetValueOrDefault()!; + public bool ShouldSerialize { get; private set; } = true; - public bool HasDefaultValue => _defaultValue.HasValue; + public bool Notifiable { get; private set; } = true; - public IValidator? Validator { get; private set; } + public bool Browsable { get; private set; } = true; - public JsonConverter? JsonConverter { get; private set; } + public DisplayAttribute? DisplayAttribute { get; private set; } - public override Type PropertyType => typeof(T); + public abstract Type PropertyType { get; } - public override void Merge(ICorePropertyMetadata baseMetadata, CoreProperty? property) - { - base.Merge(baseMetadata, property); + public Attribute[] Attributes { get; private set; } - if (baseMetadata is CorePropertyMetadata baseT) + private void UpdatedAttributes() + { + if (Attributes != null) { - if (!HasDefaultValue) + foreach (Attribute item in Attributes) { - _defaultValue = baseT.DefaultValue; - } + switch (item) + { + case DisplayAttribute display: + DisplayAttribute = display; + break; - Validator ??= baseT.Validator; + case NotifiableAttribute notifiable: + Notifiable = notifiable.Notifiable; + break; - JsonConverter ??= baseT.JsonConverter; - } - } + case ShouldSerializeAttribute shouldSerialize: + ShouldSerialize = shouldSerialize.ShouldSerialize; + break; - public TValidator? FindValidator() - where TValidator : IValidator - { - if (Validator is TValidator validator1) - { - return validator1; - } - else if (Validator is TuppleValidator validator2) - { - if (validator2.First is TValidator validator3) - { - return validator3; - } - else if (validator2.Second is TValidator validator4) - { - return validator4; - } - } - else if (Validator is MultipleValidator validator5) - { - foreach (IValidator item in validator5.Items) - { - if (item is TValidator validator6) - { - return validator6; + case BrowsableAttribute browsableAttribute: + Browsable = browsableAttribute.Browsable; + break; } } } - - return default; - } - - protected internal override object? GetDefaultValue() - { - return HasDefaultValue ? _defaultValue.Value : null; } - protected internal override IValidator? GetValidator() - { - return Validator; - } -} - -public interface ICorePropertyMetadata -{ - Type PropertyType { get; } - - DisplayAttribute? DisplayAttribute { get; } - - void Merge(ICorePropertyMetadata baseMetadata, CoreProperty? property); - - object? GetDefaultValue(); - - IValidator? GetValidator(); -} - -public abstract class CorePropertyMetadata : ICorePropertyMetadata -{ - private Optional _propertyFlags; - - protected CorePropertyMetadata(string? serializeName, Optional propertyFlags, DisplayAttribute? displayAttribute = null) - { - SerializeName = serializeName; - _propertyFlags = propertyFlags; - DisplayAttribute = displayAttribute; - } - - public string? SerializeName { get; private set; } - - public PropertyFlags PropertyFlags => _propertyFlags.GetValueOrDefault(); - - public abstract Type PropertyType { get; } - - public DisplayAttribute? DisplayAttribute { get; private set; } - public virtual void Merge(ICorePropertyMetadata baseMetadata, CoreProperty? property) { if (baseMetadata is CorePropertyMetadata metadata1) { - if (string.IsNullOrEmpty(SerializeName)) - { - SerializeName = metadata1.SerializeName; - } - - if (!_propertyFlags.HasValue) - { - _propertyFlags = metadata1._propertyFlags; - } + var att = new Attribute[Attributes.Length + metadata1.Attributes.Length]; + Attributes.CopyTo(att, 0); + metadata1.Attributes.CopyTo(att, Attributes.Length); + Attributes = att; - if (DisplayAttribute == null) - { - DisplayAttribute = metadata1.DisplayAttribute; - } + UpdatedAttributes(); } } diff --git a/src/Beutl.Core/CorePropertyMetadata`1.cs b/src/Beutl.Core/CorePropertyMetadata`1.cs new file mode 100644 index 000000000..38be34628 --- /dev/null +++ b/src/Beutl.Core/CorePropertyMetadata`1.cs @@ -0,0 +1,112 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Numerics; +using System.Reflection; +using System.Text.Json.Serialization; + +using Beutl.Validation; + +namespace Beutl; + +public class CorePropertyMetadata : CorePropertyMetadata +{ + private Optional _defaultValue; + + public CorePropertyMetadata( + Optional defaultValue = default, + bool shouldSerialize = true, + params Attribute[] attributes) + : base(shouldSerialize, attributes) + { + _defaultValue = defaultValue; + UpdatedAttributes(); + } + + public T DefaultValue => _defaultValue.GetValueOrDefault()!; + + public bool HasDefaultValue => _defaultValue.HasValue; + + public IValidator? Validator { get; private set; } + + public JsonConverter? JsonConverter { get; private set; } + + public override Type PropertyType => typeof(T); + + private static IValidator ConvertValidator(ValidationAttribute att) + { + switch (att) + { + case RangeAttribute rangeAttribute: + Type propType = typeof(T); + Type[] interfaces = propType.GetInterfaces(); + if (propType.IsValueType + && interfaces.Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(INumber<>)) + && interfaces.Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IMinMaxValue<>))) + { + Type validatorType = typeof(RangeDataAnnotationValidater<>); + if (Activator.CreateInstance(validatorType, rangeAttribute) is IValidator validator) + { + return validator; + } + else + { + goto default; + } + } + else + { + goto default; + } + + default: + return new DataAnnotationValidater(att); + } + } + + private void UpdatedAttributes() + { + if (Attributes != null) + { + IValidator[] validations = Attributes.OfType() + .Select(ConvertValidator) + .ToArray(); + + Validator = new MultipleValidator(validations); + + JsonConverterAttribute? jsonConverter = Attributes.OfType().FirstOrDefault(); + if (jsonConverter is { ConverterType: { } }) + { + JsonConverter = JsonHelper.GetOrCreateConverterInstance(jsonConverter.ConverterType) as JsonConverter; + } + else + { + JsonConverter = null; + } + } + } + + public override void Merge(ICorePropertyMetadata baseMetadata, CoreProperty? property) + { + base.Merge(baseMetadata, property); + + if (baseMetadata is CorePropertyMetadata baseT) + { + if (!HasDefaultValue) + { + _defaultValue = baseT.DefaultValue; + } + + UpdatedAttributes(); + } + } + + protected internal override object? GetDefaultValue() + { + return HasDefaultValue ? _defaultValue.Value : null; + } + + protected internal override IValidator? GetValidator() + { + return Validator; + } +} diff --git a/src/Beutl.Core/ICorePropertyMetadata.cs b/src/Beutl.Core/ICorePropertyMetadata.cs new file mode 100644 index 000000000..01df80fb1 --- /dev/null +++ b/src/Beutl.Core/ICorePropertyMetadata.cs @@ -0,0 +1,20 @@ +using System.ComponentModel.DataAnnotations; + +using Beutl.Validation; + +namespace Beutl; + +public interface ICorePropertyMetadata +{ + Type PropertyType { get; } + + DisplayAttribute? DisplayAttribute { get; } + + Attribute[]? Attributes { get; } + + void Merge(ICorePropertyMetadata baseMetadata, CoreProperty? property); + + object? GetDefaultValue(); + + IValidator? GetValidator(); +} diff --git a/src/Beutl.Core/JsonHelper.cs b/src/Beutl.Core/JsonHelper.cs index 70818334e..e69bf754c 100644 --- a/src/Beutl.Core/JsonHelper.cs +++ b/src/Beutl.Core/JsonHelper.cs @@ -1,6 +1,7 @@ using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Nodes; +using System.Text.Json.Serialization; using System.Text.Unicode; using Beutl.JsonConverters; @@ -9,6 +10,8 @@ namespace Beutl; public static class JsonHelper { + private static readonly Dictionary s_converters = new(); + public static JsonWriterOptions WriterOptions { get; } = new() { Indented = true, @@ -26,6 +29,20 @@ public static class JsonHelper } }; + public static JsonConverter GetOrCreateConverterInstance(Type converterType) + { + if (s_converters.TryGetValue(converterType, out var converter)) + { + return converter; + } + else + { + converter = (JsonConverter)Activator.CreateInstance(converterType)!; + s_converters.Add(converterType, converter); + return converter; + } + } + public static void JsonSave(this IJsonSerializable serializable, string filename) { using var stream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.Write); @@ -62,7 +79,6 @@ public static void JsonSave(this JsonNode node, string filename) return JsonNode.Parse(stream); } - private static Dictionary ParseJson(string json) { Dictionary dic = JsonSerializer.Deserialize>(json)!; diff --git a/src/Beutl.Core/NotifiableAttribute.cs b/src/Beutl.Core/NotifiableAttribute.cs new file mode 100644 index 000000000..0ad128c26 --- /dev/null +++ b/src/Beutl.Core/NotifiableAttribute.cs @@ -0,0 +1,12 @@ +namespace Beutl; + +[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)] +public sealed class NotifiableAttribute : Attribute +{ + public NotifiableAttribute(bool notifiable) + { + Notifiable = notifiable; + } + + public bool Notifiable { get; } +} diff --git a/src/Beutl.Core/ProjectItem.cs b/src/Beutl.Core/ProjectItem.cs index de0691424..784c7c626 100644 --- a/src/Beutl.Core/ProjectItem.cs +++ b/src/Beutl.Core/ProjectItem.cs @@ -9,10 +9,10 @@ static ProjectItem() { FileNameProperty = ConfigureProperty(nameof(FileName)) .Accessor(o => o.FileName, (o, v) => o.FileName = v) - .PropertyFlags(PropertyFlags.NotifyChanged) .Register(); } + [ShouldSerialize(false)] public string FileName { get => _fileName!; diff --git a/src/Beutl.Core/ShouldSerializeAttribute.cs b/src/Beutl.Core/ShouldSerializeAttribute.cs new file mode 100644 index 000000000..c31d12a17 --- /dev/null +++ b/src/Beutl.Core/ShouldSerializeAttribute.cs @@ -0,0 +1,12 @@ +namespace Beutl; + +[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)] +public sealed class ShouldSerializeAttribute : Attribute +{ + public ShouldSerializeAttribute(bool shouldSerialize) + { + ShouldSerialize = shouldSerialize; + } + + public bool ShouldSerialize { get; } +} diff --git a/src/Beutl.Core/Validation/FileInfoExtensionValidator.cs b/src/Beutl.Core/Validation/FileInfoExtensionValidator.cs deleted file mode 100644 index eaa53787d..000000000 --- a/src/Beutl.Core/Validation/FileInfoExtensionValidator.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace Beutl.Validation; - -public sealed class FileInfoExtensionValidator : IValidator -{ - private string[] _fileExtensions = Array.Empty(); - private string? _display; - - public string[] FileExtensions - { - get => _fileExtensions; - set - { - if (_fileExtensions != value) - { - _display = null; - _fileExtensions = value; - } - } - } - - public bool TryCoerce(ValidationContext context, ref FileInfo? value) - { - return false; - } - - public string? Validate(ValidationContext context, FileInfo? value) - { - if (value != null) - { - string extension = value.Extension; - foreach (string item in FileExtensions) - { - if (extension.EndsWith(item)) - { - return null; - } - } - } - - return GetDisplay(); - } - - private string GetDisplay() - { - if (_display == null) - { - string ext = string.Join(';', _fileExtensions); - _display = $"Please specify a file with the following extension.\n{ext}"; - } - - return _display; - } -} diff --git a/src/Beutl.Core/Validation/FilePathExtensionValidator.cs b/src/Beutl.Core/Validation/FilePathExtensionValidator.cs deleted file mode 100644 index 16a2688a9..000000000 --- a/src/Beutl.Core/Validation/FilePathExtensionValidator.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace Beutl.Validation; - -public sealed class FilePathExtensionValidator : IValidator -{ - private string[] _fileExtensions = Array.Empty(); - private string? _display; - - public string[] FileExtensions - { - get => _fileExtensions; - set - { - if (_fileExtensions != value) - { - _display = null; - _fileExtensions = value; - } - } - } - - public bool TryCoerce(ValidationContext context, ref string? value) - { - return false; - } - - public string? Validate(ValidationContext context, string? value) - { - if (value != null) - { - string extension = Path.GetExtension(value); - foreach (string item in FileExtensions) - { - if (extension.EndsWith(item)) - { - return null; - } - } - } - - return GetDisplay(); - } - - private string GetDisplay() - { - if (_display == null) - { - string ext = string.Join(';', _fileExtensions); - _display = $"Please specify a file with the following extension.\n{ext}"; - } - - return _display; - } -} diff --git a/src/Beutl.Core/Validation/FuncValidator.cs b/src/Beutl.Core/Validation/FuncValidator.cs deleted file mode 100644 index 811e9615b..000000000 --- a/src/Beutl.Core/Validation/FuncValidator.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Beutl.Validation; - -public delegate bool ValueCoercer(ValidationContext context, ref T? value); - -public delegate string? ValueValidator(ValidationContext context, T? value); - -public sealed class FuncValidator : IValidator -{ - public ValueCoercer? CoerceFunc { get; set; } - - public ValueValidator? ValidateFunc { get; set; } - - public bool TryCoerce(ValidationContext context, ref T? value) - { - if (CoerceFunc != null) - { - return CoerceFunc(context, ref value); - } - else - { - return false; - } - } - - public string? Validate(ValidationContext context, T? value) - { - return ValidateFunc?.Invoke(context, value) ?? null; - } -} diff --git a/src/Beutl.Core/Validation/NumberRangeValidator.cs b/src/Beutl.Core/Validation/NumberRangeValidator.cs deleted file mode 100644 index 9d383a0b9..000000000 --- a/src/Beutl.Core/Validation/NumberRangeValidator.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Numerics; - -namespace Beutl.Validation; - -internal sealed class NumberRangeValidator : RangeValidator - where TNumber : struct, INumber, IMinMaxValue -{ - public NumberRangeValidator() - { - Maximum = TNumber.MaxValue; - Minimum = TNumber.MinValue; - } - - public override bool TryCoerce(ValidationContext context, ref TNumber value) - { - value = TNumber.Clamp(value, Minimum, Maximum); - return true; - } - - public override string? Validate(ValidationContext context, TNumber value) - { - if (value >= Minimum && value <= Maximum) - { - return $"The value must be between {Minimum} and {Maximum}."; - } - else - { - return null; - } - } -} diff --git a/src/Beutl.Core/Validation/RangeDataAnnotationValidater.cs b/src/Beutl.Core/Validation/RangeDataAnnotationValidater.cs new file mode 100644 index 000000000..37b339b20 --- /dev/null +++ b/src/Beutl.Core/Validation/RangeDataAnnotationValidater.cs @@ -0,0 +1,68 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Numerics; + +namespace Beutl.Validation; + +public sealed class RangeDataAnnotationValidater : RangeValidator + where TNumber : struct, INumber, IMinMaxValue +{ + public RangeDataAnnotationValidater() + { + } + + public RangeDataAnnotationValidater(RangeAttribute attribute) + { + Attribute = attribute; + if (Attribute.OperandType == typeof(double)) + { + Maximum = TNumber.CreateTruncating((double)attribute.Maximum); + Minimum = TNumber.CreateTruncating((double)attribute.Minimum); + } + else if (Attribute.OperandType == typeof(int)) + { + Maximum = TNumber.CreateTruncating((int)attribute.Maximum); + Minimum = TNumber.CreateTruncating((int)attribute.Minimum); + } + else if (Attribute.OperandType == typeof(TNumber) + && Attribute.Maximum is string maximumStr + && Attribute.Minimum is string minimumStr) + { + TypeConverter converter = TypeDescriptor.GetConverter(Attribute.OperandType); + Maximum = (TNumber)converter.ConvertFromInvariantString(maximumStr)!; + Minimum = (TNumber)converter.ConvertFromInvariantString(minimumStr)!; + } + } + + public RangeAttribute? Attribute { get; set; } + + public override bool TryCoerce(ValidationContext context, ref TNumber value) + { + value = TNumber.Clamp(value, Minimum, Maximum); + return true; + } + + public override string? Validate(ValidationContext context, TNumber value) + { + if (Attribute == null) + { + return null; + } + + if (!Attribute.RequiresValidationContext) + { + if (!Attribute.IsValid(value)) + { + return Attribute.FormatErrorMessage(context.Property?.Name ?? typeof(TNumber).Name); + } + else + { + return null; + } + } + else + { + throw new InvalidOperationException("System.ComponentModel.DataAnnotations.ValidationContext required validation is not yet supported."); + } + } +} diff --git a/src/Beutl.Core/Validation/RangeValidatableAttribute.cs b/src/Beutl.Core/Validation/RangeValidatableAttribute.cs deleted file mode 100644 index cb0c4db93..000000000 --- a/src/Beutl.Core/Validation/RangeValidatableAttribute.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Beutl.Validation; - -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)] -public sealed class RangeValidatableAttribute : Attribute -{ - public RangeValidatableAttribute(Type validatorType) - { - if (validatorType.BaseType is { } baseType - && baseType.GetGenericArguments().FirstOrDefault() is Type targetType - && typeof(RangeValidator<>).MakeGenericType(targetType).IsAssignableFrom(validatorType) - && !validatorType.IsAbstract) - { - ValidatorType = validatorType; - TargetType = targetType; - } - else - { - throw new InvalidOperationException(); - } - } - - public Type ValidatorType { get; } - - public Type TargetType { get; } -} diff --git a/src/Beutl.Core/Validation/RangeValidationService.cs b/src/Beutl.Core/Validation/RangeValidationService.cs deleted file mode 100644 index bf58d1a10..000000000 --- a/src/Beutl.Core/Validation/RangeValidationService.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.Numerics; - -namespace Beutl.Validation; - -public sealed class RangeValidationService -{ - public static readonly RangeValidationService Instance = new(); - private readonly Dictionary _services = new() - { - { typeof(Vector2), typeof(Vector2RangeValidator) }, - { typeof(Vector3), typeof(Vector3RangeValidator) }, - { typeof(Vector4), typeof(Vector4RangeValidator) }, - { typeof(TimeSpan), typeof(TimeSpanRangeValidator) }, - }; - - public RangeValidationService() - { - RegisterNumber(); - RegisterNumber(); - RegisterNumber(); - RegisterNumber(); - RegisterNumber(); - RegisterNumber(); - RegisterNumber(); - RegisterNumber(); - RegisterNumber(); - RegisterNumber(); - RegisterNumber(); - RegisterNumber(); - } - - public void RegisterNumber() - where TNumber : struct, INumber, IMinMaxValue - { - _services.Add(typeof(TNumber), typeof(NumberRangeValidator)); - } - - public void Register() - where TValidator : RangeValidator - { - _services.Add(typeof(T), typeof(TValidator)); - } - - public void Register(Type type, Type validatorType) - { - if (typeof(RangeValidator<>).MakeGenericType(type).IsAssignableFrom(validatorType) && !validatorType.IsAbstract) - { - _services.Add(type, validatorType); - } - else - { - throw new InvalidOperationException(); - } - } - - public Type Get() - { - return Get(typeof(T)); - } - - public Type Get(Type type) - { - if (_services.TryGetValue(type, out Type? value)) - { - return value; - } - else if (Attribute.GetCustomAttribute(type, typeof(RangeValidatableAttribute)) is RangeValidatableAttribute att) - { - _services.Add(type, att.ValidatorType); - return att.ValidatorType; - } - else - { - throw new Exception(); - } - } -} diff --git a/src/Beutl.Core/Validation/RangeValidatorGen.cs b/src/Beutl.Core/Validation/RangeValidatorGen.cs deleted file mode 100644 index 1063d33de..000000000 --- a/src/Beutl.Core/Validation/RangeValidatorGen.cs +++ /dev/null @@ -1,107 +0,0 @@ - -#pragma warning disable IDE0001, IDE0049 - -namespace Beutl.Validation -{ - // Vector2 - internal sealed class Vector2RangeValidator : RangeValidator - { - public Vector2RangeValidator() - { - Maximum = new(System.Single.MaxValue, System.Single.MaxValue); - Minimum = new(System.Single.MinValue, System.Single.MinValue); - } - - public override bool TryCoerce(ValidationContext context, ref System.Numerics.Vector2 value) - { - value = new System.Numerics.Vector2( - Math.Clamp(value.X, Minimum.X, Maximum.X), - Math.Clamp(value.Y, Minimum.Y, Maximum.Y)); - - return true; - } - - public override string? Validate(ValidationContext context, System.Numerics.Vector2 value) - { - if (value.X >= Minimum.X && value.X <= Maximum.X - && value.Y >= Minimum.Y && value.Y <= Maximum.Y) - { - return $"The value must be between {Minimum} and {Maximum}."; - } - else - { - return null; - } - } - } - - // Vector3 - internal sealed class Vector3RangeValidator : RangeValidator - { - public Vector3RangeValidator() - { - Maximum = new(System.Single.MaxValue, System.Single.MaxValue, System.Single.MaxValue); - Minimum = new(System.Single.MinValue, System.Single.MinValue, System.Single.MinValue); - } - - public override bool TryCoerce(ValidationContext context, ref System.Numerics.Vector3 value) - { - value = new System.Numerics.Vector3( - Math.Clamp(value.X, Minimum.X, Maximum.X), - Math.Clamp(value.Y, Minimum.Y, Maximum.Y), - Math.Clamp(value.Z, Minimum.Z, Maximum.Z)); - - return true; - } - - public override string? Validate(ValidationContext context, System.Numerics.Vector3 value) - { - if (value.X >= Minimum.X && value.X <= Maximum.X - && value.Y >= Minimum.Y && value.Y <= Maximum.Y - && value.Z >= Minimum.Z && value.Z <= Maximum.Z) - { - return $"The value must be between {Minimum} and {Maximum}."; - } - else - { - return null; - } - } - } - - // Vector4 - internal sealed class Vector4RangeValidator : RangeValidator - { - public Vector4RangeValidator() - { - Maximum = new(System.Single.MaxValue, System.Single.MaxValue, System.Single.MaxValue, System.Single.MaxValue); - Minimum = new(System.Single.MinValue, System.Single.MinValue, System.Single.MinValue, System.Single.MinValue); - } - - public override bool TryCoerce(ValidationContext context, ref System.Numerics.Vector4 value) - { - value = new System.Numerics.Vector4( - Math.Clamp(value.X, Minimum.X, Maximum.X), - Math.Clamp(value.Y, Minimum.Y, Maximum.Y), - Math.Clamp(value.Z, Minimum.Z, Maximum.Z), - Math.Clamp(value.W, Minimum.W, Maximum.W)); - - return true; - } - - public override string? Validate(ValidationContext context, System.Numerics.Vector4 value) - { - if (value.X >= Minimum.X && value.X <= Maximum.X - && value.Y >= Minimum.Y && value.Y <= Maximum.Y - && value.Z >= Minimum.Z && value.Z <= Maximum.Z - && value.W >= Minimum.W && value.W <= Maximum.W) - { - return $"The value must be between {Minimum} and {Maximum}."; - } - else - { - return null; - } - } - } -} diff --git a/src/Beutl.Core/Validation/RangeValidatorGen.tt b/src/Beutl.Core/Validation/RangeValidatorGen.tt deleted file mode 100644 index 6b14f4ffc..000000000 --- a/src/Beutl.Core/Validation/RangeValidatorGen.tt +++ /dev/null @@ -1,138 +0,0 @@ -<#@ template debug="false" hostspecific="false" language="C#" #> -<#@ assembly name="System.Core" #> -<#@ import namespace="System.Linq" #> -<#@ import namespace="System.Text" #> -<#@ import namespace="System.Collections.Generic" #> -<#@ output extension=".cs" #> -<# - var vec2Types = new (string Namespace, string Name, string Element, string FieldX, string FieldY)[] - { - ("System.Numerics", "Vector2", "System.Single", "X", "Y"), - }; - - var vec3Types = new (string Namespace, string Name, string Element, string FieldX, string FieldY, string FieldZ)[] - { - ("System.Numerics", "Vector3", "System.Single", "X", "Y", "Z"), - }; - - var vec4Types = new (string Namespace, string Name, string Element, string FieldX, string FieldY, string FieldZ, string FieldW)[] - { - ("System.Numerics", "Vector4", "System.Single", "X", "Y", "Z", "W"), - }; -#> - -#pragma warning disable IDE0001, IDE0049 - -namespace Beutl.Validation -{ - // Vector2 -<# foreach(var t in vec2Types) { #> -<# var fullName = $"{t.Namespace}.{t.Name}"; #> - internal sealed class <#= t.Name #>RangeValidator : RangeValidator<<#= fullName #>> - { - public <#= t.Name #>RangeValidator() - { - Maximum = new(<#= t.Element #>.MaxValue, <#= t.Element #>.MaxValue); - Minimum = new(<#= t.Element #>.MinValue, <#= t.Element #>.MinValue); - } - - public override bool TryCoerce(ValidationContext context, ref <#= fullName #> value) - { - value = new <#= fullName #>( - Math.Clamp(value.<#= t.FieldX #>, Minimum.<#= t.FieldX #>, Maximum.<#= t.FieldX #>), - Math.Clamp(value.<#= t.FieldY #>, Minimum.<#= t.FieldY #>, Maximum.<#= t.FieldY #>)); - - return true; - } - - public override string? Validate(ValidationContext context, <#= fullName #> value) - { - if (value.<#= t.FieldX #> >= Minimum.<#= t.FieldX #> && value.<#= t.FieldX #> <= Maximum.<#= t.FieldX #> - && value.<#= t.FieldY #> >= Minimum.<#= t.FieldY #> && value.<#= t.FieldY #> <= Maximum.<#= t.FieldY #>) - { - return $"The value must be between {Minimum} and {Maximum}."; - } - else - { - return null; - } - } - } -<# } #> - - // Vector3 -<# foreach(var t in vec3Types) { #> -<# var fullName = $"{t.Namespace}.{t.Name}"; #> - internal sealed class <#= t.Name #>RangeValidator : RangeValidator<<#= fullName #>> - { - public <#= t.Name #>RangeValidator() - { - Maximum = new(<#= t.Element #>.MaxValue, <#= t.Element #>.MaxValue, <#= t.Element #>.MaxValue); - Minimum = new(<#= t.Element #>.MinValue, <#= t.Element #>.MinValue, <#= t.Element #>.MinValue); - } - - public override bool TryCoerce(ValidationContext context, ref <#= fullName #> value) - { - value = new <#= fullName #>( - Math.Clamp(value.<#= t.FieldX #>, Minimum.<#= t.FieldX #>, Maximum.<#= t.FieldX #>), - Math.Clamp(value.<#= t.FieldY #>, Minimum.<#= t.FieldY #>, Maximum.<#= t.FieldY #>), - Math.Clamp(value.<#= t.FieldZ #>, Minimum.<#= t.FieldZ #>, Maximum.<#= t.FieldZ #>)); - - return true; - } - - public override string? Validate(ValidationContext context, <#= fullName #> value) - { - if (value.<#= t.FieldX #> >= Minimum.<#= t.FieldX #> && value.<#= t.FieldX #> <= Maximum.<#= t.FieldX #> - && value.<#= t.FieldY #> >= Minimum.<#= t.FieldY #> && value.<#= t.FieldY #> <= Maximum.<#= t.FieldY #> - && value.<#= t.FieldZ #> >= Minimum.<#= t.FieldZ #> && value.<#= t.FieldZ #> <= Maximum.<#= t.FieldZ #>) - { - return $"The value must be between {Minimum} and {Maximum}."; - } - else - { - return null; - } - } - } -<# } #> - - // Vector4 -<# foreach(var t in vec4Types) { #> -<# var fullName = $"{t.Namespace}.{t.Name}"; #> - internal sealed class <#= t.Name #>RangeValidator : RangeValidator<<#= fullName #>> - { - public <#= t.Name #>RangeValidator() - { - Maximum = new(<#= t.Element #>.MaxValue, <#= t.Element #>.MaxValue, <#= t.Element #>.MaxValue, <#= t.Element #>.MaxValue); - Minimum = new(<#= t.Element #>.MinValue, <#= t.Element #>.MinValue, <#= t.Element #>.MinValue, <#= t.Element #>.MinValue); - } - - public override bool TryCoerce(ValidationContext context, ref <#= fullName #> value) - { - value = new <#= fullName #>( - Math.Clamp(value.<#= t.FieldX #>, Minimum.<#= t.FieldX #>, Maximum.<#= t.FieldX #>), - Math.Clamp(value.<#= t.FieldY #>, Minimum.<#= t.FieldY #>, Maximum.<#= t.FieldY #>), - Math.Clamp(value.<#= t.FieldZ #>, Minimum.<#= t.FieldZ #>, Maximum.<#= t.FieldZ #>), - Math.Clamp(value.<#= t.FieldW #>, Minimum.<#= t.FieldW #>, Maximum.<#= t.FieldW #>)); - - return true; - } - - public override string? Validate(ValidationContext context, <#= fullName #> value) - { - if (value.<#= t.FieldX #> >= Minimum.<#= t.FieldX #> && value.<#= t.FieldX #> <= Maximum.<#= t.FieldX #> - && value.<#= t.FieldY #> >= Minimum.<#= t.FieldY #> && value.<#= t.FieldY #> <= Maximum.<#= t.FieldY #> - && value.<#= t.FieldZ #> >= Minimum.<#= t.FieldZ #> && value.<#= t.FieldZ #> <= Maximum.<#= t.FieldZ #> - && value.<#= t.FieldW #> >= Minimum.<#= t.FieldW #> && value.<#= t.FieldW #> <= Maximum.<#= t.FieldW #>) - { - return $"The value must be between {Minimum} and {Maximum}."; - } - else - { - return null; - } - } - } -<# } #> -} diff --git a/src/Beutl.Core/Validation/TimeSpanRangeValidator.cs b/src/Beutl.Core/Validation/TimeSpanRangeValidator.cs deleted file mode 100644 index d7e122978..000000000 --- a/src/Beutl.Core/Validation/TimeSpanRangeValidator.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Beutl.Validation; - -internal sealed class TimeSpanRangeValidator : RangeValidator -{ - public TimeSpanRangeValidator() - { - Maximum = TimeSpan.MaxValue; - Minimum = TimeSpan.MinValue; - } - - public override bool TryCoerce(ValidationContext context, ref TimeSpan value) - { - value = TimeSpan.FromTicks(Math.Clamp(value.Ticks, Minimum.Ticks, Maximum.Ticks)); - return true; - } - - public override string? Validate(ValidationContext context, TimeSpan value) - { - if (value >= Minimum && value <= Maximum) - { - return $"The value must be between {Minimum} and {Maximum}."; - } - else - { - return null; - } - } -} diff --git a/src/Beutl.Core/Validation/TuppleValidator.cs b/src/Beutl.Core/Validation/TuppleValidator.cs deleted file mode 100644 index f6719a1d5..000000000 --- a/src/Beutl.Core/Validation/TuppleValidator.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace Beutl.Validation; - -public sealed class TuppleValidator : IValidator -{ - public TuppleValidator(IValidator first, IValidator second) - { - First = first; - Second = second; - } - - public IValidator First { get; } - - public IValidator Second { get; } - - public bool TryCoerce(ValidationContext context, ref T? value) - { - T? tmp = value; - if (First.TryCoerce(context, ref tmp) && Second.TryCoerce(context, ref tmp)) - { - value = tmp; - return true; - } - else - { - return false; - } - } - - public string? Validate(ValidationContext context, T? value) - { - string? ms1 = First.Validate(context, value); - string? ms2 = Second.Validate(context, value); - if (ms1 != null && ms2 != null) - { - return $"{ms1}\n{ms2}"; - } - else - { - return ms1 ?? ms2; - } - } -} diff --git a/src/Beutl.Graphics/Animation/AnimationSerializer.cs b/src/Beutl.Graphics/Animation/AnimationSerializer.cs index b7f63c70f..3e34a3e4f 100644 --- a/src/Beutl.Graphics/Animation/AnimationSerializer.cs +++ b/src/Beutl.Graphics/Animation/AnimationSerializer.cs @@ -7,7 +7,7 @@ internal static class AnimationSerializer public static (string?, JsonNode?) ToJson(this IAnimation animation, Type targetType) { JsonNode? node = animation.ToJson(); - string name = animation.Property.GetMetadata(targetType).SerializeName ?? animation.Property.Name; + string name = animation.Property.Name; if (node != null) { return (name, node); @@ -44,9 +44,7 @@ public static (string?, JsonNode?) ToJson(this IAnimation animation, Type target public static IAnimation? ToAnimation(this JsonNode json, string name, Type targetType) { - CoreProperty? property - = PropertyRegistry.GetRegistered(targetType).FirstOrDefault( - x => x.GetMetadata(targetType).SerializeName == name || x.Name == name); + CoreProperty? property = PropertyRegistry.GetRegistered(targetType).FirstOrDefault(x => x.Name == name); if (property == null) return null; diff --git a/src/Beutl.Graphics/Animation/KeyFrame.cs b/src/Beutl.Graphics/Animation/KeyFrame.cs index 3c5826b0a..4bfee8683 100644 --- a/src/Beutl.Graphics/Animation/KeyFrame.cs +++ b/src/Beutl.Graphics/Animation/KeyFrame.cs @@ -23,15 +23,11 @@ static KeyFrame() { EasingProperty = ConfigureProperty(nameof(Easing)) .Accessor(o => o.Easing, (o, v) => o.Easing = v) - .Display(Strings.Easing) - .PropertyFlags(PropertyFlags.NotifyChanged) .DefaultValue(new LinearEasing()) .Register(); KeyTimeProperty = ConfigureProperty(nameof(KeyTime)) .Accessor(o => o.KeyTime, (o, v) => o.KeyTime = v) - .PropertyFlags(PropertyFlags.NotifyChanged) - .SerializeName("key-time") .Register(); //DurationProperty = ConfigureProperty(nameof(Duration)) @@ -40,6 +36,7 @@ static KeyFrame() // .Register(); } + [ShouldSerialize(false)] public Easing Easing { get => _easing; diff --git a/src/Beutl.Graphics/Animation/KeyFrame{T}.cs b/src/Beutl.Graphics/Animation/KeyFrame{T}.cs index c714a6006..21da517c3 100644 --- a/src/Beutl.Graphics/Animation/KeyFrame{T}.cs +++ b/src/Beutl.Graphics/Animation/KeyFrame{T}.cs @@ -24,8 +24,6 @@ static KeyFrame() ValueProperty = ConfigureProperty>(nameof(Value)) .Accessor(o => o.Value, (o, v) => o.Value = v) - .PropertyFlags(PropertyFlags.NotifyChanged) - .SerializeName("value") .Register(); } diff --git a/src/Beutl.Graphics/Audio/Effects/Delay.cs b/src/Beutl.Graphics/Audio/Effects/Delay.cs index fe7d6031a..2dc935b6a 100644 --- a/src/Beutl.Graphics/Audio/Effects/Delay.cs +++ b/src/Beutl.Graphics/Audio/Effects/Delay.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -91,31 +92,19 @@ public sealed class Delay : SoundEffect static Delay() { DelayTimeProperty = ConfigureProperty(o => o.DelayTime) - .PropertyFlags(PropertyFlags.All & ~PropertyFlags.Animatable) - .SerializeName("delay-time") .DefaultValue(0.2f) - .Maximum(MaxDelayTime) .Register(); FeedbackProperty = ConfigureProperty(o => o.Feedback) - .PropertyFlags(PropertyFlags.All & ~PropertyFlags.Animatable) - .SerializeName("feedback") .DefaultValue(0.5f) - .Minimum(0) .Register(); DryMixProperty = ConfigureProperty(o => o.DryMix) - .PropertyFlags(PropertyFlags.All & ~PropertyFlags.Animatable) - .SerializeName("dry-mix") .DefaultValue(0.6f) - .Minimum(0) .Register(); WetMixProperty = ConfigureProperty(o => o.WetMix) - .PropertyFlags(PropertyFlags.All & ~PropertyFlags.Animatable) - .SerializeName("wet-mix") .DefaultValue(0.4f) - .Minimum(0) .Register(); AffectsRender( @@ -123,24 +112,28 @@ static Delay() DryMixProperty, WetMixProperty); } + [Range(0, MaxDelayTime)] public float DelayTime { get => _delayTime; set => SetAndRaise(DelayTimeProperty, ref _delayTime, value); } + [Range(0, float.MaxValue)] public float Feedback { get => _feedback; set => SetAndRaise(FeedbackProperty, ref _feedback, value); } + [Range(0, float.MaxValue)] public float DryMix { get => _dryMix; set => SetAndRaise(DryMixProperty, ref _dryMix, value); } + [Range(0, float.MaxValue)] public float WetMix { get => _wetMix; diff --git a/src/Beutl.Graphics/Audio/Effects/SoundEffect.cs b/src/Beutl.Graphics/Audio/Effects/SoundEffect.cs index a44fa151c..643fdc8d5 100644 --- a/src/Beutl.Graphics/Audio/Effects/SoundEffect.cs +++ b/src/Beutl.Graphics/Audio/Effects/SoundEffect.cs @@ -13,7 +13,6 @@ static SoundEffect() IsEnabledProperty = ConfigureProperty(nameof(IsEnabled)) .Accessor(o => o.IsEnabled, (o, v) => o.IsEnabled = v) .DefaultValue(true) - .SerializeName("is-enabled") .Register(); AffectsRender(IsEnabledProperty); diff --git a/src/Beutl.Graphics/Audio/Effects/SoundEffectGroup.cs b/src/Beutl.Graphics/Audio/Effects/SoundEffectGroup.cs index bcaa90beb..6a9697795 100644 --- a/src/Beutl.Graphics/Audio/Effects/SoundEffectGroup.cs +++ b/src/Beutl.Graphics/Audio/Effects/SoundEffectGroup.cs @@ -11,7 +11,6 @@ static SoundEffectGroup() { ChildrenProperty = ConfigureProperty(nameof(Children)) .Accessor(o => o.Children, (o, v) => o.Children = v) - .PropertyFlags(PropertyFlags.Styleable | PropertyFlags.Designable) .Register(); AffectsRender(ChildrenProperty); } @@ -22,6 +21,7 @@ public SoundEffectGroup() _children.Invalidated += (_, e) => RaiseInvalidated(e); } + [ShouldSerialize(false)] public SoundEffects Children { get => _children; diff --git a/src/Beutl.Graphics/Audio/Sound.cs b/src/Beutl.Graphics/Audio/Sound.cs index 6bff85d25..a16b6638c 100644 --- a/src/Beutl.Graphics/Audio/Sound.cs +++ b/src/Beutl.Graphics/Audio/Sound.cs @@ -23,16 +23,12 @@ static Sound() { GainProperty = ConfigureProperty(nameof(Gain)) .Accessor(o => o.Gain, (o, v) => o.Gain = v) - .PropertyFlags(PropertyFlags.All & ~PropertyFlags.Animatable) .DefaultValue(1) - .SerializeName("gain") .Register(); EffectProperty = ConfigureProperty(nameof(Effect)) .Accessor(o => o.Effect, (o, v) => o.Effect = v) - .PropertyFlags(PropertyFlags.All & ~PropertyFlags.Animatable) .DefaultValue(null) - .SerializeName("effect") .Register(); AffectsRender(GainProperty, EffectProperty); diff --git a/src/Beutl.Graphics/Audio/SourceSound.cs b/src/Beutl.Graphics/Audio/SourceSound.cs index 8d067126d..27889dde1 100644 --- a/src/Beutl.Graphics/Audio/SourceSound.cs +++ b/src/Beutl.Graphics/Audio/SourceSound.cs @@ -16,10 +16,7 @@ static SourceSound() { SourceProperty = ConfigureProperty(nameof(Source)) .Accessor(o => o.Source, (o, v) => o.Source = v) - .PropertyFlags(PropertyFlags.All & ~PropertyFlags.Animatable) .DefaultValue(null) - .SerializeName("source") - .JsonConverter(new SoundSourceJsonConverter()) .Register(); AffectsRender(SourceProperty); diff --git a/src/Beutl.Graphics/Beutl.Graphics.csproj b/src/Beutl.Graphics/Beutl.Graphics.csproj index cff5787ff..4ea9303bb 100644 --- a/src/Beutl.Graphics/Beutl.Graphics.csproj +++ b/src/Beutl.Graphics/Beutl.Graphics.csproj @@ -17,17 +17,6 @@ - - - TextTemplatingFileGenerator - RangeValidatorGen.cs - - - True - True - RangeValidatorGen.tt - - diff --git a/src/Beutl.Graphics/Graphics/Drawable.cs b/src/Beutl.Graphics/Graphics/Drawable.cs index dee6b540e..0fc0dd02c 100644 --- a/src/Beutl.Graphics/Graphics/Drawable.cs +++ b/src/Beutl.Graphics/Graphics/Drawable.cs @@ -1,4 +1,6 @@ -using Beutl.Animation; +using System.ComponentModel.DataAnnotations; + +using Beutl.Animation; using Beutl.Graphics.Effects; using Beutl.Graphics.Filters; using Beutl.Graphics.Transformation; @@ -38,90 +40,55 @@ static Drawable() { WidthProperty = ConfigureProperty(nameof(Width)) .Accessor(o => o.Width, (o, v) => o.Width = v) - .Display(Strings.Width) - .PropertyFlags(PropertyFlags.All) .DefaultValue(0) - .Minimum(0) - .SerializeName("width") .Register(); HeightProperty = ConfigureProperty(nameof(Height)) .Accessor(o => o.Height, (o, v) => o.Height = v) - .Display(Strings.Height) - .PropertyFlags(PropertyFlags.All) .DefaultValue(0) - .Minimum(0) - .SerializeName("height") .Register(); TransformProperty = ConfigureProperty(nameof(Transform)) .Accessor(o => o.Transform, (o, v) => o.Transform = v) - .Display(Strings.Transform) - .PropertyFlags(PropertyFlags.All & ~PropertyFlags.Animatable) .DefaultValue(null) - .SerializeName("transform") .Register(); FilterProperty = ConfigureProperty(nameof(Filter)) .Accessor(o => o.Filter, (o, v) => o.Filter = v) - .Display(Strings.ImageFilter) - .PropertyFlags(PropertyFlags.All & ~PropertyFlags.Animatable) .DefaultValue(null) - .SerializeName("filter") .Register(); EffectProperty = ConfigureProperty(nameof(Effect)) .Accessor(o => o.Effect, (o, v) => o.Effect = v) - .Display(Strings.BitmapEffect) - .PropertyFlags(PropertyFlags.All & ~PropertyFlags.Animatable) .DefaultValue(null) - .SerializeName("effect") .Register(); AlignmentXProperty = ConfigureProperty(nameof(AlignmentX)) .Accessor(o => o.AlignmentX, (o, v) => o.AlignmentX = v) - .Display(Strings.AlignmentX) - .PropertyFlags(PropertyFlags.All) .DefaultValue(AlignmentX.Left) - .SerializeName("align-x") .Register(); AlignmentYProperty = ConfigureProperty(nameof(AlignmentY)) .Accessor(o => o.AlignmentY, (o, v) => o.AlignmentY = v) - .Display(Strings.AlignmentY) - .PropertyFlags(PropertyFlags.All) .DefaultValue(AlignmentY.Top) - .SerializeName("align-y") .Register(); TransformOriginProperty = ConfigureProperty(nameof(TransformOrigin)) .Accessor(o => o.TransformOrigin, (o, v) => o.TransformOrigin = v) - .Display(Strings.TransformOrigin) - .PropertyFlags(PropertyFlags.All) - .SerializeName("transform-origin") .Register(); ForegroundProperty = ConfigureProperty(nameof(Foreground)) .Accessor(o => o.Foreground, (o, v) => o.Foreground = v) - .Display(Strings.Foreground) - .PropertyFlags(PropertyFlags.All & ~PropertyFlags.Animatable) - .SerializeName("foreground") .Register(); OpacityMaskProperty = ConfigureProperty(nameof(OpacityMask)) .Accessor(o => o.OpacityMask, (o, v) => o.OpacityMask = v) - .Display(Strings.OpacityMask) - .PropertyFlags(PropertyFlags.All & ~PropertyFlags.Animatable) .DefaultValue(null) - .SerializeName("opacity-mask") .Register(); BlendModeProperty = ConfigureProperty(nameof(BlendMode)) .Accessor(o => o.BlendMode, (o, v) => o.BlendMode = v) - .Display(Strings.BlendMode) - .PropertyFlags(PropertyFlags.All) .DefaultValue(BlendMode.SrcOver) - .SerializeName("blend-mode") .Register(); AffectsRender( @@ -133,12 +100,16 @@ static Drawable() BlendModeProperty); } + [Display(Name = nameof(Strings.Width), ResourceType = typeof(Strings))] + [Range(0, float.MaxValue)] public float Width { get => _width; set => SetAndRaise(WidthProperty, ref _width, value); } + [Display(Name = nameof(Strings.Height), ResourceType = typeof(Strings))] + [Range(0, float.MaxValue)] public float Height { get => _height; @@ -147,54 +118,63 @@ public float Height public Rect Bounds { get; private set; } + [Display(Name = nameof(Strings.Transform), ResourceType = typeof(Strings))] public ITransform? Transform { get => _transform; set => SetAndRaise(TransformProperty, ref _transform, value); } + [Display(Name = nameof(Strings.ImageFilter), ResourceType = typeof(Strings))] public IImageFilter? Filter { get => _filter; set => SetAndRaise(FilterProperty, ref _filter, value); } + [Display(Name = nameof(Strings.BitmapEffect), ResourceType = typeof(Strings))] public IBitmapEffect? Effect { get => _effect; set => SetAndRaise(EffectProperty, ref _effect, value); } + [Display(Name = nameof(Strings.AlignmentX), ResourceType = typeof(Strings))] public AlignmentX AlignmentX { get => _alignX; set => SetAndRaise(AlignmentXProperty, ref _alignX, value); } + [Display(Name = nameof(Strings.AlignmentY), ResourceType = typeof(Strings))] public AlignmentY AlignmentY { get => _alignY; set => SetAndRaise(AlignmentYProperty, ref _alignY, value); } + [Display(Name = nameof(Strings.TransformOrigin), ResourceType = typeof(Strings))] public RelativePoint TransformOrigin { get => _transformOrigin; set => SetAndRaise(TransformOriginProperty, ref _transformOrigin, value); } + [Display(Name = nameof(Strings.Foreground), ResourceType = typeof(Strings))] public IBrush? Foreground { get => _foreground; set => SetAndRaise(ForegroundProperty, ref _foreground, value); } + [Display(Name = nameof(Strings.OpacityMask), ResourceType = typeof(Strings))] public IBrush? OpacityMask { get => _opacityMask; set => SetAndRaise(OpacityMaskProperty, ref _opacityMask, value); } + [Display(Name = nameof(Strings.BlendMode), ResourceType = typeof(Strings))] public BlendMode BlendMode { get => _blendMode; diff --git a/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs b/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs index 588641695..9bc0366b9 100644 --- a/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs +++ b/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs @@ -37,34 +37,26 @@ static VideoFrame() { OffsetPositionProperty = ConfigureProperty(nameof(OffsetPosition)) .Accessor(o => o.OffsetPosition, (o, v) => o.OffsetPosition = v) - .PropertyFlags(PropertyFlags.All) .DefaultValue(TimeSpan.Zero) - .SerializeName("offset-position") .Register(); PlaybackPositionProperty = ConfigureProperty(nameof(PlaybackPosition)) .Accessor(o => o.PlaybackPosition, (o, v) => o.PlaybackPosition = v) - .PropertyFlags(PropertyFlags.All) .DefaultValue(TimeSpan.Zero) - .Minimum(TimeSpan.Zero) - .SerializeName("playback-position") .Register(); PositionModeProperty = ConfigureProperty(nameof(PositionMode)) .Accessor(o => o.PositionMode, (o, v) => o.PositionMode = v) - .PropertyFlags(PropertyFlags.All) .DefaultValue(VideoPositionMode.Automatic) - .SerializeName("position-mode") .Register(); SourceFileProperty = ConfigureProperty(nameof(SourceFile)) .Accessor(o => o.SourceFile, (o, v) => o.SourceFile = v) - .PropertyFlags(PropertyFlags.All & ~PropertyFlags.Animatable) #if DEBUG - .Validator(new FileInfoExtensionValidator() - { - FileExtensions = new[] { "mp4" } - }) + //.Validator(new FileInfoExtensionValidator() + //{ + // FileExtensions = new[] { "mp4" } + //}) #else #warning Todo: DecoderRegistryからファイル拡張子を取得してセットする。 #endif @@ -95,6 +87,7 @@ public VideoPositionMode PositionMode set => SetAndRaise(PositionModeProperty, ref _positionMode, value); } + [ShouldSerialize(false)] public FileInfo? SourceFile { get => _sourceFile; diff --git a/src/Beutl.Graphics/Graphics/Effects/BitmapEffect.cs b/src/Beutl.Graphics/Graphics/Effects/BitmapEffect.cs index b8ad3eee9..52e03f32f 100644 --- a/src/Beutl.Graphics/Graphics/Effects/BitmapEffect.cs +++ b/src/Beutl.Graphics/Graphics/Effects/BitmapEffect.cs @@ -13,7 +13,6 @@ static BitmapEffect() IsEnabledProperty = ConfigureProperty(nameof(IsEnabled)) .Accessor(o => o.IsEnabled, (o, v) => o.IsEnabled = v) .DefaultValue(true) - .SerializeName("is-enabled") .Register(); AffectsRender(IsEnabledProperty); diff --git a/src/Beutl.Graphics/Graphics/Effects/BitmapEffectGroup.cs b/src/Beutl.Graphics/Graphics/Effects/BitmapEffectGroup.cs index a0bd5a3f2..a6021f9ef 100644 --- a/src/Beutl.Graphics/Graphics/Effects/BitmapEffectGroup.cs +++ b/src/Beutl.Graphics/Graphics/Effects/BitmapEffectGroup.cs @@ -14,7 +14,6 @@ static BitmapEffectGroup() { ChildrenProperty = ConfigureProperty(nameof(Children)) .Accessor(o => o.Children, (o, v) => o.Children = v) - .PropertyFlags(PropertyFlags.Styleable | PropertyFlags.Designable) .Register(); AffectsRender(ChildrenProperty); } @@ -39,6 +38,7 @@ public BitmapEffectGroup() }; } + [ShouldSerialize(false)] public BitmapEffects Children { get => _children; diff --git a/src/Beutl.Graphics/Graphics/Effects/Border.cs b/src/Beutl.Graphics/Graphics/Effects/Border.cs index 1f599b3ec..a6a014e33 100644 --- a/src/Beutl.Graphics/Graphics/Effects/Border.cs +++ b/src/Beutl.Graphics/Graphics/Effects/Border.cs @@ -1,4 +1,6 @@ -using Beutl.Language; +using System.ComponentModel.DataAnnotations; + +using Beutl.Language; using Beutl.Media; using Beutl.Media.Immutable; using Beutl.Media.Pixel; @@ -56,43 +58,27 @@ static Border() { OffsetProperty = ConfigureProperty(nameof(Offset)) .Accessor(o => o.Offset, (o, v) => o.Offset = v) - .Display(Strings.Offset) .DefaultValue(default) - .PropertyFlags(PropertyFlags.All) - .SerializeName("offset") .Register(); ThicknessProperty = ConfigureProperty(nameof(Thickness)) .Accessor(o => o.Thickness, (o, v) => o.Thickness = v) - .Display(Strings.Thickness) .DefaultValue(0) - .Minimum(0) - .PropertyFlags(PropertyFlags.All) - .SerializeName("thickness") .Register(); ColorProperty = ConfigureProperty(nameof(Color)) .Accessor(o => o.Color, (o, v) => o.Color = v) - .Display(Strings.Color) .DefaultValue(Colors.White) - .PropertyFlags(PropertyFlags.All) - .SerializeName("color") .Register(); MaskTypeProperty = ConfigureProperty(nameof(MaskType)) .Accessor(o => o.MaskType, (o, v) => o.MaskType = v) - .Display(Strings.MaskType) .DefaultValue(MaskTypes.None) - .PropertyFlags(PropertyFlags.All) - .SerializeName("mask-type") .Register(); StyleProperty = ConfigureProperty(nameof(Style)) .Accessor(o => o.Style, (o, v) => o.Style = v) - .Display(Strings.BorderStyle) .DefaultValue(BorderStyles.Background) - .PropertyFlags(PropertyFlags.All) - .SerializeName("border-style") .Register(); AffectsRender( @@ -108,30 +94,37 @@ public Border() Processor = new _(this); } + + [Display(Name = nameof(Strings.Offset), ResourceType = typeof(Strings))] public Point Offset { get => _offset; set => SetAndRaise(OffsetProperty, ref _offset, value); } + [Display(Name = nameof(Strings.Thickness), ResourceType = typeof(Strings))] + [Range(0, int.MaxValue)] public int Thickness { get => _thickness; set => SetAndRaise(ThicknessProperty, ref _thickness, value); } + [Display(Name = nameof(Strings.Color), ResourceType = typeof(Strings))] public Color Color { get => _color; set => SetAndRaise(ColorProperty, ref _color, value); } + [Display(Name = nameof(Strings.MaskType), ResourceType = typeof(Strings))] public MaskTypes MaskType { get => _maskType; set => SetAndRaise(MaskTypeProperty, ref _maskType, value); } + [Display(Name = nameof(Strings.BorderStyle), ResourceType = typeof(Strings))] public BorderStyles Style { get => _style; diff --git a/src/Beutl.Graphics/Graphics/Effects/InnerShadow.cs b/src/Beutl.Graphics/Graphics/Effects/InnerShadow.cs index 73167c556..782644f99 100644 --- a/src/Beutl.Graphics/Graphics/Effects/InnerShadow.cs +++ b/src/Beutl.Graphics/Graphics/Effects/InnerShadow.cs @@ -1,4 +1,6 @@ -using Beutl.Graphics.Transformation; +using System.ComponentModel.DataAnnotations; + +using Beutl.Graphics.Transformation; using Beutl.Language; using Beutl.Media; using Beutl.Media.Pixel; @@ -21,26 +23,17 @@ static InnerShadow() { PositionProperty = ConfigureProperty(nameof(Position)) .Accessor(o => o.Position, (o, v) => o.Position = v) - .Display(Strings.Position) .DefaultValue(new Point()) - .PropertyFlags(PropertyFlags.All) - .SerializeName("position") .Register(); KernelSizeProperty = ConfigureProperty(nameof(KernelSize)) .Accessor(o => o.KernelSize, (o, v) => o.KernelSize = v) - .Display(Strings.KernelSize) - .PropertyFlags(PropertyFlags.All) - .Minimum(new PixelSize(1, 1)) - .SerializeName("kernel-size") + .DefaultValue(new PixelSize()) .Register(); ColorProperty = ConfigureProperty(nameof(Color)) .Accessor(o => o.Color, (o, v) => o.Color = v) - .Display(Strings.Color) .DefaultValue(Colors.Transparent) - .PropertyFlags(PropertyFlags.All) - .SerializeName("color") .Register(); AffectsRender(PositionProperty, KernelSizeProperty, ColorProperty); @@ -51,18 +44,21 @@ public InnerShadow() Processor = new _(this); } + [Display(Name = nameof(Strings.Position), ResourceType = typeof(Strings))] public Point Position { get => _position; set => SetAndRaise(PositionProperty, ref _position, value); } + [Display(Name = nameof(Strings.KernelSize), ResourceType = typeof(Strings))] public PixelSize KernelSize { get => _kernelSize; set => SetAndRaise(KernelSizeProperty, ref _kernelSize, value); } + [Display(Name = nameof(Strings.Color), ResourceType = typeof(Strings))] public Color Color { get => _color; diff --git a/src/Beutl.Graphics/Graphics/Effects/OpenCv/Blur.cs b/src/Beutl.Graphics/Graphics/Effects/OpenCv/Blur.cs index 9ce7d1431..3eaf96330 100644 --- a/src/Beutl.Graphics/Graphics/Effects/OpenCv/Blur.cs +++ b/src/Beutl.Graphics/Graphics/Effects/OpenCv/Blur.cs @@ -1,4 +1,7 @@ -using Beutl.Language; +using System.ComponentModel.DataAnnotations; +using System.Numerics; + +using Beutl.Language; using Beutl.Media; using Beutl.Media.Pixel; @@ -17,19 +20,12 @@ static Blur() { KernelSizeProperty = ConfigureProperty(nameof(KernelSize)) .Accessor(o => o.KernelSize, (o, v) => o.KernelSize = v) - .Display(Strings.KernelSize) .DefaultValue(PixelSize.Empty) - .Minimum(new PixelSize(1, 1)) - .PropertyFlags(PropertyFlags.All) - .SerializeName("kernel-size") .Register(); FixImageSizeProperty = ConfigureProperty(nameof(FixImageSize)) .Accessor(o => o.FixImageSize, (o, v) => o.FixImageSize = v) - .Display(Strings.FixImageSize) .DefaultValue(false) - .PropertyFlags(PropertyFlags.All) - .SerializeName("fix-image-size") .Register(); AffectsRender(KernelSizeProperty, FixImageSizeProperty); @@ -40,12 +36,14 @@ public Blur() Processor = new _(this); } + [Display(Name = nameof(Strings.KernelSize), ResourceType = typeof(Strings))] public PixelSize KernelSize { get => _kernelSize; set => SetAndRaise(KernelSizeProperty, ref _kernelSize, value); } + [Display(Name = nameof(Strings.FixImageSize), ResourceType = typeof(Strings))] public bool FixImageSize { get => _fixImageSize; @@ -80,7 +78,7 @@ public void Process(in Bitmap src, out Bitmap dst) Bitmap? image; if (_blur.FixImageSize) { - image = (Bitmap)src.Clone(); + image = src.Clone(); } else { diff --git a/src/Beutl.Graphics/Graphics/Effects/OpenCv/GaussianBlur.cs b/src/Beutl.Graphics/Graphics/Effects/OpenCv/GaussianBlur.cs index e001d91ed..4c55778c6 100644 --- a/src/Beutl.Graphics/Graphics/Effects/OpenCv/GaussianBlur.cs +++ b/src/Beutl.Graphics/Graphics/Effects/OpenCv/GaussianBlur.cs @@ -1,4 +1,6 @@ -using Beutl.Language; +using System.ComponentModel.DataAnnotations; + +using Beutl.Language; using Beutl.Media; using Beutl.Media.Pixel; @@ -19,26 +21,17 @@ static GaussianBlur() { KernelSizeProperty = ConfigureProperty(nameof(KernelSize)) .Accessor(o => o.KernelSize, (o, v) => o.KernelSize = v) - .Display(Strings.KernelSize) .DefaultValue(PixelSize.Empty) - .PropertyFlags(PropertyFlags.All) - .SerializeName("kernel-size") .Register(); SigmaProperty = ConfigureProperty(nameof(Sigma)) .Accessor(o => o.Sigma, (o, v) => o.Sigma = v) - .Display(Strings.Sigma) .DefaultValue(Vector.Zero) - .PropertyFlags(PropertyFlags.All) - .SerializeName("sigma") .Register(); FixImageSizeProperty = ConfigureProperty(nameof(FixImageSize)) .Accessor(o => o.FixImageSize, (o, v) => o.FixImageSize = v) - .Display(Strings.FixImageSize) .DefaultValue(false) - .PropertyFlags(PropertyFlags.All) - .SerializeName("fix-image-size") .Register(); AffectsRender(KernelSizeProperty, SigmaProperty, FixImageSizeProperty); @@ -49,18 +42,21 @@ public GaussianBlur() Processor = new _(this); } + [Display(Name = nameof(Strings.KernelSize), ResourceType = typeof(Strings))] public PixelSize KernelSize { get => _kernelSize; set => SetAndRaise(KernelSizeProperty, ref _kernelSize, value); } + [Display(Name = nameof(Strings.Sigma), ResourceType = typeof(Strings))] public Vector Sigma { get => _sigma; set => SetAndRaise(SigmaProperty, ref _sigma, value); } + [Display(Name = nameof(Strings.FixImageSize), ResourceType = typeof(Strings))] public bool FixImageSize { get => _fixImageSize; diff --git a/src/Beutl.Graphics/Graphics/Effects/OpenCv/MedianBlur.cs b/src/Beutl.Graphics/Graphics/Effects/OpenCv/MedianBlur.cs index 8235a9604..0489c37ac 100644 --- a/src/Beutl.Graphics/Graphics/Effects/OpenCv/MedianBlur.cs +++ b/src/Beutl.Graphics/Graphics/Effects/OpenCv/MedianBlur.cs @@ -1,4 +1,6 @@ -using Beutl.Language; +using System.ComponentModel.DataAnnotations; + +using Beutl.Language; using Beutl.Media; using Beutl.Media.Pixel; @@ -17,18 +19,12 @@ static MedianBlur() { KernelSizeProperty = ConfigureProperty(nameof(KernelSize)) .Accessor(o => o.KernelSize, (o, v) => o.KernelSize = v) - .Display(Strings.KernelSize) .DefaultValue(0) - .PropertyFlags(PropertyFlags.All) - .SerializeName("kernel-size") .Register(); FixImageSizeProperty = ConfigureProperty(nameof(FixImageSize)) .Accessor(o => o.FixImageSize, (o, v) => o.FixImageSize = v) - .Display(Strings.FixImageSize) .DefaultValue(false) - .PropertyFlags(PropertyFlags.All) - .SerializeName("fix-image-size") .Register(); AffectsRender(KernelSizeProperty, FixImageSizeProperty); @@ -39,12 +35,14 @@ public MedianBlur() Processor = new _(this); } + [Display(Name = nameof(Strings.KernelSize), ResourceType = typeof(Strings))] public int KernelSize { get => _kernelSize; set => SetAndRaise(KernelSizeProperty, ref _kernelSize, value); } + [Display(Name = nameof(Strings.FixImageSize), ResourceType = typeof(Strings))] public bool FixImageSize { get => _fixImageSize; diff --git a/src/Beutl.Graphics/Graphics/Filters/Blur.cs b/src/Beutl.Graphics/Graphics/Filters/Blur.cs index 301c050bb..017e1e32b 100644 --- a/src/Beutl.Graphics/Graphics/Filters/Blur.cs +++ b/src/Beutl.Graphics/Graphics/Filters/Blur.cs @@ -1,4 +1,6 @@ -using Beutl.Language; +using System.ComponentModel.DataAnnotations; + +using Beutl.Language; using SkiaSharp; @@ -13,16 +15,13 @@ static Blur() { SigmaProperty = ConfigureProperty(nameof(Sigma)) .Accessor(o => o.Sigma, (o, v) => o.Sigma = v) - .Display(Strings.Sigma) .DefaultValue(Vector.Zero) - .Minimum(Vector.Zero) - .PropertyFlags(PropertyFlags.All) - .SerializeName("sigma") .Register(); AffectsRender(SigmaProperty); } + [Display(Name = nameof(Strings.Sigma), ResourceType = typeof(Strings))] public Vector Sigma { get => _sigma; diff --git a/src/Beutl.Graphics/Graphics/Filters/DropShadow.cs b/src/Beutl.Graphics/Graphics/Filters/DropShadow.cs index 5e70ba0ba..dffef7781 100644 --- a/src/Beutl.Graphics/Graphics/Filters/DropShadow.cs +++ b/src/Beutl.Graphics/Graphics/Filters/DropShadow.cs @@ -1,4 +1,6 @@ -using Beutl.Language; +using System.ComponentModel.DataAnnotations; + +using Beutl.Language; using Beutl.Media; using SkiaSharp; @@ -20,58 +22,50 @@ static DropShadow() { PositionProperty = ConfigureProperty(nameof(Position)) .Accessor(o => o.Position, (o, v) => o.Position = v) - .Display(Strings.Position) .DefaultValue(new Point()) - .PropertyFlags(PropertyFlags.All) - .SerializeName("position") .Register(); SigmaProperty = ConfigureProperty(nameof(Sigma)) .Accessor(o => o.Sigma, (o, v) => o.Sigma = v) - .Display(Strings.Sigma) .DefaultValue(Vector.Zero) - .Minimum(Vector.Zero) - .PropertyFlags(PropertyFlags.All) - .SerializeName("sigma") .Register(); ColorProperty = ConfigureProperty(nameof(Color)) .Accessor(o => o.Color, (o, v) => o.Color = v) - .Display(Strings.Color) .DefaultValue(Colors.Transparent) - .PropertyFlags(PropertyFlags.All) - .SerializeName("color") .Register(); ShadowOnlyProperty = ConfigureProperty(nameof(ShadowOnly)) .Accessor(o => o.ShadowOnly, (o, v) => o.ShadowOnly = v) - .Display(Strings.ShadowOnly) .DefaultValue(false) - .PropertyFlags(PropertyFlags.All) - .SerializeName("shadow-only") .Register(); AffectsRender(PositionProperty, SigmaProperty, ColorProperty, ShadowOnlyProperty); } + + [Display(Name = nameof(Strings.Position), ResourceType = typeof(Strings))] public Point Position { get => _position; set => SetAndRaise(PositionProperty, ref _position, value); } + [Display(Name = nameof(Strings.Sigma), ResourceType = typeof(Strings))] public Vector Sigma { get => _sigma; set => SetAndRaise(SigmaProperty, ref _sigma, value); } + [Display(Name = nameof(Strings.Color), ResourceType = typeof(Strings))] public Color Color { get => _color; set => SetAndRaise(ColorProperty, ref _color, value); } + [Display(Name = nameof(Strings.ShadowOnly), ResourceType = typeof(Strings))] public bool ShadowOnly { get => _shadowOnly; diff --git a/src/Beutl.Graphics/Graphics/Filters/ImageFilter.cs b/src/Beutl.Graphics/Graphics/Filters/ImageFilter.cs index 0fded28cd..7ac9eb6db 100644 --- a/src/Beutl.Graphics/Graphics/Filters/ImageFilter.cs +++ b/src/Beutl.Graphics/Graphics/Filters/ImageFilter.cs @@ -15,7 +15,6 @@ static ImageFilter() IsEnabledProperty = ConfigureProperty(nameof(IsEnabled)) .Accessor(o => o.IsEnabled, (o, v) => o.IsEnabled = v) .DefaultValue(true) - .SerializeName("is-enabled") .Register(); AffectsRender(IsEnabledProperty); diff --git a/src/Beutl.Graphics/Graphics/Filters/ImageFilterGroup.cs b/src/Beutl.Graphics/Graphics/Filters/ImageFilterGroup.cs index d443c7d87..91b5054da 100644 --- a/src/Beutl.Graphics/Graphics/Filters/ImageFilterGroup.cs +++ b/src/Beutl.Graphics/Graphics/Filters/ImageFilterGroup.cs @@ -15,7 +15,6 @@ static ImageFilterGroup() { ChildrenProperty = ConfigureProperty(nameof(Children)) .Accessor(o => o.Children, (o, v) => o.Children = v) - .PropertyFlags(PropertyFlags.Designable | PropertyFlags.Styleable) .Register(); } @@ -25,6 +24,7 @@ public ImageFilterGroup() _children.Invalidated += (_, e) => RaiseInvalidated(e); } + [ShouldSerialize(false)] public ImageFilters Children { get => _children; @@ -49,7 +49,7 @@ public override void ReadFromJson(JsonNode json) base.ReadFromJson(json); if (json is JsonObject jobject) { - if (jobject.TryGetPropertyValue("children", out JsonNode? childrenNode) + if (jobject.TryGetPropertyValue(nameof(Children), out JsonNode? childrenNode) && childrenNode is JsonArray childrenArray) { _children.Clear(); @@ -92,7 +92,7 @@ public override void WriteToJson(ref JsonNode json) } } - jobject["children"] = array; + jobject[nameof(Children)] = array; } } diff --git a/src/Beutl.Graphics/Graphics/ImageFile.cs b/src/Beutl.Graphics/Graphics/ImageFile.cs index 93a3c91f7..0e0744e92 100644 --- a/src/Beutl.Graphics/Graphics/ImageFile.cs +++ b/src/Beutl.Graphics/Graphics/ImageFile.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Nodes; @@ -19,17 +20,12 @@ static ImageFile() { SourceFileProperty = ConfigureProperty(nameof(SourceFile)) .Accessor(o => o.SourceFile, (o, v) => o.SourceFile = v) - .Display(Strings.SourceFile) - .PropertyFlags(PropertyFlags.All & ~PropertyFlags.Animatable) - .Validator(new FileInfoExtensionValidator() - { - FileExtensions = new[] { "bmp", "gif", "ico", "jpg", "jpeg", "png", "wbmp", "webp", "pkm", "ktx", "astc", "dng", "heif" } - }) .Register(); AffectsRender(SourceFileProperty); } + [Display(Name = nameof(Strings.SourceFile), ResourceType = typeof(Strings))] public FileInfo? SourceFile { get => _sourceFile; diff --git a/src/Beutl.Graphics/Graphics/Point.cs b/src/Beutl.Graphics/Graphics/Point.cs index 3d3c14b50..95b88ad82 100644 --- a/src/Beutl.Graphics/Graphics/Point.cs +++ b/src/Beutl.Graphics/Graphics/Point.cs @@ -13,7 +13,6 @@ namespace Beutl.Graphics; /// Defines a point. /// [JsonConverter(typeof(PointJsonConverter))] -[RangeValidatable(typeof(PointRangeValidator))] public readonly struct Point : IEquatable, IParsable, diff --git a/src/Beutl.Graphics/Graphics/Rect.cs b/src/Beutl.Graphics/Graphics/Rect.cs index 05a288be9..32edbe9f9 100644 --- a/src/Beutl.Graphics/Graphics/Rect.cs +++ b/src/Beutl.Graphics/Graphics/Rect.cs @@ -13,7 +13,6 @@ namespace Beutl.Graphics; /// Defines a rectangle. /// [JsonConverter(typeof(RectJsonConverter))] -[RangeValidatable(typeof(RectRangeValidator))] public readonly struct Rect : IEquatable, IParsable, diff --git a/src/Beutl.Graphics/Graphics/Shapes/Ellipse.cs b/src/Beutl.Graphics/Graphics/Shapes/Ellipse.cs index e171d157f..92957443d 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/Ellipse.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/Ellipse.cs @@ -1,4 +1,6 @@ -using Beutl.Language; +using System.ComponentModel.DataAnnotations; + +using Beutl.Language; namespace Beutl.Graphics.Shapes; @@ -11,16 +13,14 @@ static Ellipse() { StrokeWidthProperty = ConfigureProperty(nameof(StrokeWidth)) .Accessor(o => o.StrokeWidth, (o, v) => o.StrokeWidth = v) - .Display(Strings.StrokeWidth) - .PropertyFlags(PropertyFlags.All) .DefaultValue(4000) - .Minimum(0) - .SerializeName("stroke-width") .Register(); AffectsRender(StrokeWidthProperty); } + [Display(Name = nameof(Strings.StrokeWidth), ResourceType = typeof(Strings))] + [Range(0, float.MaxValue)] public float StrokeWidth { get => _strokeWidth; diff --git a/src/Beutl.Graphics/Graphics/Shapes/Rectangle.cs b/src/Beutl.Graphics/Graphics/Shapes/Rectangle.cs index 98ce04ac5..1f9333107 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/Rectangle.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/Rectangle.cs @@ -1,4 +1,6 @@ -using Beutl.Language; +using System.ComponentModel.DataAnnotations; + +using Beutl.Language; namespace Beutl.Graphics.Shapes; @@ -11,16 +13,14 @@ static Rectangle() { StrokeWidthProperty = ConfigureProperty(nameof(StrokeWidth)) .Accessor(o => o.StrokeWidth, (o, v) => o.StrokeWidth = v) - .Display(Strings.StrokeWidth) - .PropertyFlags(PropertyFlags.All) .DefaultValue(4000) - .Minimum(0) - .SerializeName("stroke-width") .Register(); AffectsRender(StrokeWidthProperty); } + [Display(Name = nameof(Strings.StrokeWidth), ResourceType = typeof(Strings))] + [Range(0, float.MaxValue)] public float StrokeWidth { get => _strokeWidth; diff --git a/src/Beutl.Graphics/Graphics/Shapes/RoundedRect.cs b/src/Beutl.Graphics/Graphics/Shapes/RoundedRect.cs index 3db98b2c6..93c415eb8 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/RoundedRect.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/RoundedRect.cs @@ -1,4 +1,7 @@ -using Beutl.Language; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +using Beutl.Language; using Beutl.Media; using Beutl.Media.Pixel; @@ -15,31 +18,26 @@ static RoundedRect() { StrokeWidthProperty = ConfigureProperty(nameof(StrokeWidth)) .Accessor(o => o.StrokeWidth, (o, v) => o.StrokeWidth = v) - .Display(Strings.StrokeWidth) - .PropertyFlags(PropertyFlags.All) .DefaultValue(4000) - .Minimum(0) - .SerializeName("stroke-width") .Register(); CornerRadiusProperty = ConfigureProperty(nameof(CornerRadius)) .Accessor(o => o.CornerRadius, (o, v) => o.CornerRadius = v) - .Display(Strings.CornerRadius) - .PropertyFlags(PropertyFlags.All) .DefaultValue(new CornerRadius()) - .Minimum(new CornerRadius()) - .SerializeName("corner-radius") .Register(); AffectsRender(StrokeWidthProperty, CornerRadiusProperty); } + [Display(Name = nameof(Strings.StrokeWidth), ResourceType = typeof(Strings))] + [Range(0, float.MaxValue)] public float StrokeWidth { get => _strokeWidth; set => SetAndRaise(StrokeWidthProperty, ref _strokeWidth, value); } + [Display(Name = nameof(Strings.CornerRadius), ResourceType = typeof(Strings))] public CornerRadius CornerRadius { get => _cornerRadius; diff --git a/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs b/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs index f874db02c..19d8befce 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.ComponentModel.DataAnnotations; using System.Runtime.InteropServices; using System.Text.Json.Nodes; @@ -33,64 +34,41 @@ static TextBlock() { FontWeightProperty = ConfigureProperty(nameof(FontWeight)) .Accessor(o => o.FontWeight, (o, v) => o.FontWeight = v) - .Display(Strings.FontWeight) - .PropertyFlags(PropertyFlags.All) .DefaultValue(FontWeight.Regular) - .SerializeName("font-weight") .Register(); FontStyleProperty = ConfigureProperty(nameof(FontStyle)) .Accessor(o => o.FontStyle, (o, v) => o.FontStyle = v) - .Display(Strings.FontStyle) - .PropertyFlags(PropertyFlags.All) .DefaultValue(FontStyle.Normal) - .SerializeName("font-style") .Register(); FontFamilyProperty = ConfigureProperty(nameof(FontFamily)) .Accessor(o => o.FontFamily, (o, v) => o.FontFamily = v) - .Display(Strings.FontFamily) - .PropertyFlags(PropertyFlags.All) .DefaultValue(FontFamily.Default) - .SerializeName("font-family") .Register(); SizeProperty = ConfigureProperty(nameof(Size)) .Accessor(o => o.Size, (o, v) => o.Size = v) - .Display(Strings.Size) - .PropertyFlags(PropertyFlags.All) .DefaultValue(0) - .Minimum(0) - .SerializeName("size") .Register(); SpacingProperty = ConfigureProperty(nameof(Spacing)) .Accessor(o => o.Spacing, (o, v) => o.Spacing = v) - .Display(Strings.CharactorSpacing) - .PropertyFlags(PropertyFlags.All) .DefaultValue(0) - .SerializeName("spacing") .Register(); TextProperty = ConfigureProperty(nameof(Text)) .Accessor(o => o.Text, (o, v) => o.Text = v) - .Display(Strings.Text) - .PropertyFlags(PropertyFlags.All) .DefaultValue(string.Empty) - .SerializeName("text") .Register(); MarginProperty = ConfigureProperty(nameof(Margin)) .Accessor(o => o.Margin, (o, v) => o.Margin = v) - .Display(Strings.Margin) - .PropertyFlags(PropertyFlags.All) .DefaultValue(new Thickness()) - .SerializeName("margin") .Register(); ElementsProperty = ConfigureProperty(nameof(Elements)) .Accessor(o => o.Elements, (o, v) => o.Elements = v) - .PropertyFlags(PropertyFlags.NotifyChanged | PropertyFlags.Styleable | PropertyFlags.Designable) .Register(); AffectsRender(ElementsProperty); @@ -100,48 +78,57 @@ public TextBlock() { } - public FontFamily FontFamily - { - get => _fontFamily; - set => SetAndRaise(FontFamilyProperty, ref _fontFamily, value); - } - + [Display(Name = nameof(Strings.FontWeight), ResourceType = typeof(Strings))] public FontWeight FontWeight { get => _fontWeight; set => SetAndRaise(FontWeightProperty, ref _fontWeight, value); } + [Display(Name = nameof(Strings.FontStyle), ResourceType = typeof(Strings))] public FontStyle FontStyle { get => _fontStyle; set => SetAndRaise(FontStyleProperty, ref _fontStyle, value); } + [Display(Name = nameof(Strings.FontFamily), ResourceType = typeof(Strings))] + public FontFamily FontFamily + { + get => _fontFamily; + set => SetAndRaise(FontFamilyProperty, ref _fontFamily, value); + } + + [Display(Name = nameof(Strings.Size), ResourceType = typeof(Strings))] + [Range(0, float.MaxValue)] public float Size { get => _size; set => SetAndRaise(SizeProperty, ref _size, value); } + [Display(Name = nameof(Strings.CharactorSpacing), ResourceType = typeof(Strings))] public float Spacing { get => _spacing; set => SetAndRaise(SpacingProperty, ref _spacing, value); } + [Display(Name = nameof(Strings.Text), ResourceType = typeof(Strings))] public string Text { get => _text; set => SetAndRaise(TextProperty, ref _text, value); } + [Display(Name = nameof(Strings.Margin), ResourceType = typeof(Strings))] public Thickness Margin { get => _margin; set => SetAndRaise(MarginProperty, ref _margin, value); } + [ShouldSerialize(false)] public TextElements? Elements { get => _elements; diff --git a/src/Beutl.Graphics/Graphics/Shapes/TextElement.cs b/src/Beutl.Graphics/Graphics/Shapes/TextElement.cs index 9b2167624..4549ab735 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/TextElement.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/TextElement.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics; using Beutl.Language; using Beutl.Media; @@ -31,66 +32,42 @@ static TextElement() { FontWeightProperty = ConfigureProperty(nameof(FontWeight)) .Accessor(o => o.FontWeight, (o, v) => o.FontWeight = v) - .Display(Strings.FontWeight) - .PropertyFlags(PropertyFlags.All) .DefaultValue(FontWeight.Regular) - .SerializeName("font-weight") .Register(); FontStyleProperty = ConfigureProperty(nameof(FontStyle)) .Accessor(o => o.FontStyle, (o, v) => o.FontStyle = v) - .Display(Strings.FontStyle) - .PropertyFlags(PropertyFlags.All) .DefaultValue(FontStyle.Normal) - .SerializeName("font-style") .Register(); FontFamilyProperty = ConfigureProperty(nameof(FontFamily)) .Accessor(o => o.FontFamily, (o, v) => o.FontFamily = v) - .Display(Strings.FontFamily) - .PropertyFlags(PropertyFlags.All) .DefaultValue(FontFamily.Default) - .SerializeName("font-family") .Register(); SizeProperty = ConfigureProperty(nameof(Size)) .Accessor(o => o.Size, (o, v) => o.Size = v) - .Display(Strings.Size) - .PropertyFlags(PropertyFlags.All) .DefaultValue(0) - .Minimum(0) - .SerializeName("size") .Register(); SpacingProperty = ConfigureProperty(nameof(Spacing)) .Accessor(o => o.Spacing, (o, v) => o.Spacing = v) - .Display(Strings.CharactorSpacing) - .PropertyFlags(PropertyFlags.All) .DefaultValue(0) - .SerializeName("spacing") .Register(); TextProperty = ConfigureProperty(nameof(Text)) .Accessor(o => o.Text, (o, v) => o.Text = v) - .Display(Strings.Text) - .PropertyFlags(PropertyFlags.All) .DefaultValue(string.Empty) - .SerializeName("text") .Register(); MarginProperty = ConfigureProperty(nameof(Margin)) .Accessor(o => o.Margin, (o, v) => o.Margin = v) - .Display(Strings.Margin) - .PropertyFlags(PropertyFlags.All) .DefaultValue(new Thickness()) - .SerializeName("margin") .Register(); IgnoreLineBreaksProperty = ConfigureProperty(nameof(IgnoreLineBreaks)) .Accessor(o => o.IgnoreLineBreaks, (o, v) => o.IgnoreLineBreaks = v) - .PropertyFlags(PropertyFlags.All) .DefaultValue(false) - .SerializeName("ignore-line-breaks") .Register(); AffectsRender( @@ -104,42 +81,50 @@ static TextElement() IgnoreLineBreaksProperty); } + [Display(Name = nameof(Strings.FontWeight), ResourceType = typeof(Strings))] public FontWeight FontWeight { get => _fontWeight; set => SetAndRaise(FontWeightProperty, ref _fontWeight, value); } + [Display(Name = nameof(Strings.FontStyle), ResourceType = typeof(Strings))] public FontStyle FontStyle { get => _fontStyle; set => SetAndRaise(FontStyleProperty, ref _fontStyle, value); } + [Display(Name = nameof(Strings.FontFamily), ResourceType = typeof(Strings))] public FontFamily FontFamily { get => _fontFamily; set => SetAndRaise(FontFamilyProperty, ref _fontFamily, value); } + [Display(Name = nameof(Strings.Size), ResourceType = typeof(Strings))] + [Range(0, float.MaxValue)] public float Size { get => _size; set => SetAndRaise(SizeProperty, ref _size, value); } + [Display(Name = nameof(Strings.CharactorSpacing), ResourceType = typeof(Strings))] public float Spacing { get => _spacing; set => SetAndRaise(SpacingProperty, ref _spacing, value); } + [Display(Name = nameof(Strings.Text), ResourceType = typeof(Strings))] public string Text { get => _text; set => SetAndRaise(TextProperty, ref _text, value); } + [Display(Name = nameof(Strings.Margin), ResourceType = typeof(Strings))] public Thickness Margin { get => _margin; diff --git a/src/Beutl.Graphics/Graphics/Size.cs b/src/Beutl.Graphics/Graphics/Size.cs index e7cdeaef3..8a63ed60e 100644 --- a/src/Beutl.Graphics/Graphics/Size.cs +++ b/src/Beutl.Graphics/Graphics/Size.cs @@ -13,7 +13,6 @@ namespace Beutl.Graphics; /// Defines a size. /// [JsonConverter(typeof(SizeJsonConverter))] -[RangeValidatable(typeof(SizeRangeValidator))] public readonly struct Size : IEquatable, IParsable, diff --git a/src/Beutl.Graphics/Graphics/SourceImage.cs b/src/Beutl.Graphics/Graphics/SourceImage.cs index 9add017e2..00827440d 100644 --- a/src/Beutl.Graphics/Graphics/SourceImage.cs +++ b/src/Beutl.Graphics/Graphics/SourceImage.cs @@ -19,12 +19,12 @@ static SourceImage() { SourceProperty = ConfigureProperty(nameof(Source)) .Accessor(o => o.Source, (o, v) => o.Source = v) - .PropertyFlags(PropertyFlags.All & ~PropertyFlags.Animatable) .Register(); AffectsRender(SourceProperty); } + [ShouldSerialize(false)] public IImageSource? Source { get => _source; diff --git a/src/Beutl.Graphics/Graphics/Transformation/MatrixTransform.cs b/src/Beutl.Graphics/Graphics/Transformation/MatrixTransform.cs index 1cdedd32d..09d2ddd29 100644 --- a/src/Beutl.Graphics/Graphics/Transformation/MatrixTransform.cs +++ b/src/Beutl.Graphics/Graphics/Transformation/MatrixTransform.cs @@ -10,8 +10,6 @@ static MatrixTransform() MatrixProperty = ConfigureProperty(nameof(Matrix)) .Accessor(o => o.Matrix, (o, v) => o.Matrix = v) .DefaultValue(Matrix.Identity) - .PropertyFlags(PropertyFlags.All) - .SerializeName("matrix") .Register(); AffectsRender(MatrixProperty); diff --git a/src/Beutl.Graphics/Graphics/Transformation/Rotation3DTransform.cs b/src/Beutl.Graphics/Graphics/Transformation/Rotation3DTransform.cs index e2112e456..b3cc7eafe 100644 --- a/src/Beutl.Graphics/Graphics/Transformation/Rotation3DTransform.cs +++ b/src/Beutl.Graphics/Graphics/Transformation/Rotation3DTransform.cs @@ -25,51 +25,37 @@ static Rotation3DTransform() { RotationXProperty = ConfigureProperty(nameof(RotationX)) .Accessor(o => o.RotationX, (o, v) => o.RotationX = v) - .PropertyFlags(PropertyFlags.All) .DefaultValue(0) - .SerializeName("rotation-x") .Register(); RotationYProperty = ConfigureProperty(nameof(RotationY)) .Accessor(o => o.RotationY, (o, v) => o.RotationY = v) - .PropertyFlags(PropertyFlags.All) .DefaultValue(0) - .SerializeName("rotation-y") .Register(); RotationZProperty = ConfigureProperty(nameof(RotationZ)) .Accessor(o => o.RotationZ, (o, v) => o.RotationZ = v) - .PropertyFlags(PropertyFlags.All) .DefaultValue(0) - .SerializeName("rotation-z") .Register(); CenterXProperty = ConfigureProperty(nameof(CenterX)) .Accessor(o => o.CenterX, (o, v) => o.CenterX = v) - .PropertyFlags(PropertyFlags.All) .DefaultValue(0) - .SerializeName("center-x") .Register(); CenterYProperty = ConfigureProperty(nameof(CenterY)) .Accessor(o => o.CenterY, (o, v) => o.CenterY = v) - .PropertyFlags(PropertyFlags.All) .DefaultValue(0) - .SerializeName("center-y") .Register(); CenterZProperty = ConfigureProperty(nameof(CenterZ)) .Accessor(o => o.CenterZ, (o, v) => o.CenterZ = v) - .PropertyFlags(PropertyFlags.All) .DefaultValue(0) - .SerializeName("center-z") .Register(); DepthProperty = ConfigureProperty(nameof(Depth)) .Accessor(o => o.Depth, (o, v) => o.Depth = v) - .PropertyFlags(PropertyFlags.All) .DefaultValue(0) - .SerializeName("depth") .Register(); AffectsRender( diff --git a/src/Beutl.Graphics/Graphics/Transformation/RotationTransform.cs b/src/Beutl.Graphics/Graphics/Transformation/RotationTransform.cs index 57f650263..aea3ce61b 100644 --- a/src/Beutl.Graphics/Graphics/Transformation/RotationTransform.cs +++ b/src/Beutl.Graphics/Graphics/Transformation/RotationTransform.cs @@ -12,8 +12,6 @@ static RotationTransform() RotationProperty = ConfigureProperty(nameof(Rotation)) .Accessor(o => o.Rotation, (o, v) => o.Rotation = v) .DefaultValue(0) - .PropertyFlags(PropertyFlags.All) - .SerializeName("rotation") .Register(); AffectsRender(RotationProperty); diff --git a/src/Beutl.Graphics/Graphics/Transformation/ScaleTransform.cs b/src/Beutl.Graphics/Graphics/Transformation/ScaleTransform.cs index ecf1c6b9b..5517b2c7d 100644 --- a/src/Beutl.Graphics/Graphics/Transformation/ScaleTransform.cs +++ b/src/Beutl.Graphics/Graphics/Transformation/ScaleTransform.cs @@ -1,6 +1,4 @@ -using Beutl.Utilities; - -namespace Beutl.Graphics.Transformation; +namespace Beutl.Graphics.Transformation; public sealed class ScaleTransform : Transform { @@ -16,22 +14,16 @@ static ScaleTransform() ScaleProperty = ConfigureProperty(nameof(Scale)) .Accessor(o => o.Scale, (o, v) => o.Scale = v) .DefaultValue(1) - .PropertyFlags(PropertyFlags.All) - .SerializeName("scale") .Register(); ScaleXProperty = ConfigureProperty(nameof(ScaleX)) .Accessor(o => o.ScaleX, (o, v) => o.ScaleX = v) .DefaultValue(1) - .PropertyFlags(PropertyFlags.All) - .SerializeName("scale-x") .Register(); ScaleYProperty = ConfigureProperty(nameof(ScaleY)) .Accessor(o => o.ScaleY, (o, v) => o.ScaleY = v) .DefaultValue(1) - .PropertyFlags(PropertyFlags.All) - .SerializeName("scale-y") .Register(); AffectsRender(ScaleProperty, ScaleXProperty, ScaleYProperty); diff --git a/src/Beutl.Graphics/Graphics/Transformation/SkewTransform.cs b/src/Beutl.Graphics/Graphics/Transformation/SkewTransform.cs index c15b18d4a..9806cf661 100644 --- a/src/Beutl.Graphics/Graphics/Transformation/SkewTransform.cs +++ b/src/Beutl.Graphics/Graphics/Transformation/SkewTransform.cs @@ -14,15 +14,11 @@ static SkewTransform() SkewXProperty = ConfigureProperty(nameof(SkewX)) .Accessor(o => o.SkewX, (o, v) => o.SkewX = v) .DefaultValue(0) - .PropertyFlags(PropertyFlags.All) - .SerializeName("skew-x") .Register(); SkewYProperty = ConfigureProperty(nameof(SkewY)) .Accessor(o => o.SkewY, (o, v) => o.SkewY = v) .DefaultValue(0) - .PropertyFlags(PropertyFlags.All) - .SerializeName("skew-y") .Register(); AffectsRender(SkewXProperty, SkewYProperty); diff --git a/src/Beutl.Graphics/Graphics/Transformation/Transform.cs b/src/Beutl.Graphics/Graphics/Transformation/Transform.cs index d6c441d4d..b057cfbe0 100644 --- a/src/Beutl.Graphics/Graphics/Transformation/Transform.cs +++ b/src/Beutl.Graphics/Graphics/Transformation/Transform.cs @@ -16,7 +16,6 @@ static Transform() IsEnabledProperty = ConfigureProperty(nameof(IsEnabled)) .Accessor(o => o.IsEnabled, (o, v) => o.IsEnabled = v) .DefaultValue(true) - .SerializeName("is-enabled") .Register(); AffectsRender(IsEnabledProperty); diff --git a/src/Beutl.Graphics/Graphics/Transformation/TransformGroup.cs b/src/Beutl.Graphics/Graphics/Transformation/TransformGroup.cs index f0fbfb307..b90b85034 100644 --- a/src/Beutl.Graphics/Graphics/Transformation/TransformGroup.cs +++ b/src/Beutl.Graphics/Graphics/Transformation/TransformGroup.cs @@ -13,7 +13,6 @@ static TransformGroup() { ChildrenProperty = ConfigureProperty(nameof(Children)) .Accessor(o => o.Children, (o, v) => o.Children = v) - .PropertyFlags(PropertyFlags.All) .Register(); } @@ -23,6 +22,7 @@ public TransformGroup() _children.Invalidated += (_, e) => RaiseInvalidated(e); } + [ShouldSerialize(false)] public Transforms Children { get => _children; @@ -50,7 +50,7 @@ public override void ReadFromJson(JsonNode json) base.ReadFromJson(json); if (json is JsonObject jobject) { - if (jobject.TryGetPropertyValue("children", out JsonNode? childrenNode) + if (jobject.TryGetPropertyValue(nameof(Children), out JsonNode? childrenNode) && childrenNode is JsonArray childrenArray) { _children.Clear(); @@ -93,7 +93,7 @@ public override void WriteToJson(ref JsonNode json) } } - jobject["children"] = array; + jobject[nameof(Children)] = array; } } diff --git a/src/Beutl.Graphics/Graphics/Transformation/TranslateTransform.cs b/src/Beutl.Graphics/Graphics/Transformation/TranslateTransform.cs index 9cdf6e6b8..d19c8a82c 100644 --- a/src/Beutl.Graphics/Graphics/Transformation/TranslateTransform.cs +++ b/src/Beutl.Graphics/Graphics/Transformation/TranslateTransform.cs @@ -12,15 +12,11 @@ static TranslateTransform() XProperty = ConfigureProperty(nameof(X)) .Accessor(o => o.X, (o, v) => o.X = v) .DefaultValue(0) - .PropertyFlags(PropertyFlags.All) - .SerializeName("x") .Register(); YProperty = ConfigureProperty(nameof(Y)) .Accessor(o => o.Y, (o, v) => o.Y = v) .DefaultValue(0) - .PropertyFlags(PropertyFlags.All) - .SerializeName("y") .Register(); AffectsRender(XProperty, YProperty); diff --git a/src/Beutl.Graphics/Graphics/Vector.cs b/src/Beutl.Graphics/Graphics/Vector.cs index ec3cfdb69..36298a02b 100644 --- a/src/Beutl.Graphics/Graphics/Vector.cs +++ b/src/Beutl.Graphics/Graphics/Vector.cs @@ -11,7 +11,6 @@ namespace Beutl.Graphics; /// Defines a vector. /// [JsonConverter(typeof(VectorJsonConverter))] -[RangeValidatable(typeof(VectorRangeValidator))] public readonly struct Vector : IEquatable { /// diff --git a/src/Beutl.Graphics/Media/Brush.cs b/src/Beutl.Graphics/Media/Brush.cs index 4e9d515ed..8ed97e3bc 100644 --- a/src/Beutl.Graphics/Media/Brush.cs +++ b/src/Beutl.Graphics/Media/Brush.cs @@ -1,8 +1,9 @@ -using Beutl.Animation; +using System.ComponentModel.DataAnnotations; + +using Beutl.Animation; using Beutl.Graphics; using Beutl.Graphics.Transformation; using Beutl.Language; -using Beutl.Styling; namespace Beutl.Media; @@ -22,24 +23,15 @@ static Brush() { OpacityProperty = ConfigureProperty(nameof(Opacity)) .Accessor(o => o.Opacity, (o, v) => o.Opacity = v) - .Display(Strings.Opacity) - .PropertyFlags(PropertyFlags.All) .DefaultValue(1f) - .SerializeName("opacity") .Register(); TransformProperty = ConfigureProperty(nameof(Transform)) .Accessor(o => o.Transform, (o, v) => o.Transform = v) - .Display(Strings.Transform) - .PropertyFlags(PropertyFlags.All & ~PropertyFlags.Animatable) - .SerializeName("transform") .Register(); TransformOriginProperty = ConfigureProperty(nameof(TransformOrigin)) .Accessor(o => o.TransformOrigin, (o, v) => o.TransformOrigin = v) - .Display(Strings.TransformOrigin) - .PropertyFlags(PropertyFlags.All) - .SerializeName("transform-origin") .Register(); AffectsRender(OpacityProperty, TransformProperty, TransformOriginProperty); @@ -55,6 +47,7 @@ protected Brush() /// /// Gets or sets the opacity of the brush. /// + [Display(Name = nameof(Strings.Opacity), ResourceType = typeof(Strings))] public float Opacity { get => _opacity; @@ -64,6 +57,7 @@ public float Opacity /// /// Gets or sets the transform of the brush. /// + [Display(Name = nameof(Strings.Transform), ResourceType = typeof(Strings))] public ITransform? Transform { get => _transform; @@ -73,6 +67,7 @@ public ITransform? Transform /// /// Gets or sets the origin of the brush /// + [Display(Name = nameof(Strings.TransformOrigin), ResourceType = typeof(Strings))] public RelativePoint TransformOrigin { get => _transformOrigin; diff --git a/src/Beutl.Graphics/Media/ConicGradientBrush.cs b/src/Beutl.Graphics/Media/ConicGradientBrush.cs index 70ef3b118..0943fe09a 100644 --- a/src/Beutl.Graphics/Media/ConicGradientBrush.cs +++ b/src/Beutl.Graphics/Media/ConicGradientBrush.cs @@ -1,4 +1,6 @@ -using Beutl.Graphics; +using System.ComponentModel.DataAnnotations; + +using Beutl.Graphics; using Beutl.Language; using Beutl.Media.Immutable; @@ -18,18 +20,12 @@ static ConicGradientBrush() { CenterProperty = ConfigureProperty(nameof(Center)) .Accessor(o => o.Center, (o, v) => o.Center = v) - .Display(Strings.Center) - .PropertyFlags(PropertyFlags.All) .DefaultValue(RelativePoint.Center) - .SerializeName("center") .Register(); AngleProperty = ConfigureProperty(nameof(Angle)) .Accessor(o => o.Angle, (o, v) => o.Angle = v) - .Display(Strings.Angle) - .PropertyFlags(PropertyFlags.All) .DefaultValue(0) - .SerializeName("angle") .Register(); AffectsRender(CenterProperty, AngleProperty); @@ -38,6 +34,7 @@ static ConicGradientBrush() /// /// Gets or sets the center point of the gradient. /// + [Display(Name = nameof(Strings.Center), ResourceType = typeof(Strings))] public RelativePoint Center { get => _center; @@ -47,6 +44,7 @@ public RelativePoint Center /// /// Gets or sets the angle of the start and end of the sweep, measured from above the center point. /// + [Display(Name = nameof(Strings.Angle), ResourceType = typeof(Strings))] public float Angle { get => _angle; diff --git a/src/Beutl.Graphics/Media/CornerRadius.cs b/src/Beutl.Graphics/Media/CornerRadius.cs index 3420d6c65..40247d725 100644 --- a/src/Beutl.Graphics/Media/CornerRadius.cs +++ b/src/Beutl.Graphics/Media/CornerRadius.cs @@ -11,7 +11,6 @@ namespace Beutl.Media; /// Represents the radii of a rectangle's corners. /// [JsonConverter(typeof(CornerRadiusJsonConverter))] -[RangeValidatable(typeof(CornerRadiusRangeValidator))] public readonly struct CornerRadius : IEquatable { public CornerRadius(float uniformRadius) diff --git a/src/Beutl.Graphics/Media/DrawableBrush.cs b/src/Beutl.Graphics/Media/DrawableBrush.cs index af8d17a91..713a665fb 100644 --- a/src/Beutl.Graphics/Media/DrawableBrush.cs +++ b/src/Beutl.Graphics/Media/DrawableBrush.cs @@ -15,8 +15,6 @@ static DrawableBrush() { DrawableProperty = ConfigureProperty(nameof(Drawable)) .Accessor(o => o.Drawable, (o, v) => o.Drawable = v) - .PropertyFlags(PropertyFlags.All & ~PropertyFlags.Animatable) - .SerializeName("drawable") .Register(); AffectsRender(DrawableProperty); diff --git a/src/Beutl.Graphics/Media/GradientBrush.cs b/src/Beutl.Graphics/Media/GradientBrush.cs index 145d43e78..2bd56d25a 100644 --- a/src/Beutl.Graphics/Media/GradientBrush.cs +++ b/src/Beutl.Graphics/Media/GradientBrush.cs @@ -1,4 +1,6 @@ -using Beutl.Language; +using System.ComponentModel.DataAnnotations; + +using Beutl.Language; namespace Beutl.Media; @@ -16,16 +18,11 @@ static GradientBrush() { SpreadMethodProperty = ConfigureProperty(nameof(SpreadMethod)) .Accessor(o => o.SpreadMethod, (o, v) => o.SpreadMethod = v) - .Display(Strings.SpreadMethod) - .PropertyFlags(PropertyFlags.All) .DefaultValue(GradientSpreadMethod.Pad) - .SerializeName("spread-method") .Register(); GradientStopsProperty = ConfigureProperty(nameof(GradientStops)) .Accessor(o => o.GradientStops, (o, v) => o.GradientStops = v) - .Display(Strings.GradientStops) - .PropertyFlags(PropertyFlags.All) .Register(); AffectsRender(SpreadMethodProperty, GradientStopsProperty); @@ -41,6 +38,7 @@ public GradientBrush() } /// + [Display(Name = nameof(Strings.SpreadMethod), ResourceType = typeof(Strings))] public GradientSpreadMethod SpreadMethod { get => _spreadMethod; @@ -48,6 +46,7 @@ public GradientSpreadMethod SpreadMethod } /// + [Display(Name = nameof(Strings.GradientStops), ResourceType = typeof(Strings))] public GradientStops GradientStops { get => _gradientStops; diff --git a/src/Beutl.Graphics/Media/GradientStop.cs b/src/Beutl.Graphics/Media/GradientStop.cs index c3b9c6b19..c0dac6f68 100644 --- a/src/Beutl.Graphics/Media/GradientStop.cs +++ b/src/Beutl.Graphics/Media/GradientStop.cs @@ -18,15 +18,11 @@ static GradientStop() OffsetProperty = ConfigureProperty(nameof(Offset)) .Accessor(o => o.Offset, (o, v) => o.Offset = v) .DefaultValue(0) - .PropertyFlags(PropertyFlags.All) - .SerializeName("offset") .Register(); ColorProperty = ConfigureProperty(nameof(Color)) .Accessor(o => o.Color, (o, v) => o.Color = v) .DefaultValue(Colors.Transparent) - .PropertyFlags(PropertyFlags.All) - .SerializeName("color") .Register(); static void OnChanged(CorePropertyChangedEventArgs obj) diff --git a/src/Beutl.Graphics/Media/ImageBrush.cs b/src/Beutl.Graphics/Media/ImageBrush.cs index 86967c69e..fa80aed5c 100644 --- a/src/Beutl.Graphics/Media/ImageBrush.cs +++ b/src/Beutl.Graphics/Media/ImageBrush.cs @@ -15,7 +15,6 @@ static ImageBrush() { SourceProperty = ConfigureProperty(nameof(Source)) .Accessor(o => o.Source, (o, v) => o.Source = v) - .PropertyFlags(PropertyFlags.All) .Register(); AffectsRender(SourceProperty); diff --git a/src/Beutl.Graphics/Media/LinearGradientBrush.cs b/src/Beutl.Graphics/Media/LinearGradientBrush.cs index f295f5390..54b459ba4 100644 --- a/src/Beutl.Graphics/Media/LinearGradientBrush.cs +++ b/src/Beutl.Graphics/Media/LinearGradientBrush.cs @@ -1,4 +1,6 @@ -using Beutl.Graphics; +using System.ComponentModel.DataAnnotations; + +using Beutl.Graphics; using Beutl.Language; using Beutl.Media.Immutable; @@ -17,19 +19,13 @@ public sealed class LinearGradientBrush : GradientBrush, ILinearGradientBrush static LinearGradientBrush() { StartPointProperty = ConfigureProperty(nameof(StartPoint)) - .DefaultValue(RelativePoint.TopLeft) - .Display(Strings.StartPoint) .Accessor(o => o.StartPoint, (o, v) => o.StartPoint = v) - .PropertyFlags(PropertyFlags.All) - .SerializeName("start-point") + .DefaultValue(RelativePoint.TopLeft) .Register(); EndPointProperty = ConfigureProperty(nameof(EndPoint)) - .DefaultValue(RelativePoint.BottomRight) - .Display(Strings.EndPoint) .Accessor(o => o.EndPoint, (o, v) => o.EndPoint = v) - .PropertyFlags(PropertyFlags.All) - .SerializeName("end-point") + .DefaultValue(RelativePoint.BottomRight) .Register(); AffectsRender(StartPointProperty, EndPointProperty); @@ -38,6 +34,7 @@ static LinearGradientBrush() /// /// Gets or sets the start point for the gradient. /// + [Display(Name = nameof(Strings.StartPoint), ResourceType = typeof(Strings))] public RelativePoint StartPoint { get => _startPoint; @@ -47,6 +44,7 @@ public RelativePoint StartPoint /// /// Gets or sets the end point for the gradient. /// + [Display(Name = nameof(Strings.EndPoint), ResourceType = typeof(Strings))] public RelativePoint EndPoint { get => _endPoint; diff --git a/src/Beutl.Graphics/Media/PixelPoint.cs b/src/Beutl.Graphics/Media/PixelPoint.cs index 30e287b21..4255c74c4 100644 --- a/src/Beutl.Graphics/Media/PixelPoint.cs +++ b/src/Beutl.Graphics/Media/PixelPoint.cs @@ -16,7 +16,6 @@ namespace Beutl.Media; /// Represents a point in device pixels. /// [JsonConverter(typeof(PixelPointJsonConverter))] -[RangeValidatable(typeof(PixelPointRangeValidator))] public readonly struct PixelPoint : IEquatable, IParsable, diff --git a/src/Beutl.Graphics/Media/PixelRect.cs b/src/Beutl.Graphics/Media/PixelRect.cs index 3ac923607..ac79ce708 100644 --- a/src/Beutl.Graphics/Media/PixelRect.cs +++ b/src/Beutl.Graphics/Media/PixelRect.cs @@ -16,7 +16,6 @@ namespace Beutl.Media; /// Represents a rectangle in device pixels. /// [JsonConverter(typeof(PixelRectJsonConverter))] -[RangeValidatable(typeof(PixelRectRangeValidator))] public readonly struct PixelRect : IEquatable, IParsable, diff --git a/src/Beutl.Graphics/Media/PixelSize.cs b/src/Beutl.Graphics/Media/PixelSize.cs index 030717f00..55d42d81f 100644 --- a/src/Beutl.Graphics/Media/PixelSize.cs +++ b/src/Beutl.Graphics/Media/PixelSize.cs @@ -16,7 +16,6 @@ namespace Beutl.Media; /// Represents a size in device pixels. /// [JsonConverter(typeof(PixelSizeJsonConverter))] -[RangeValidatable(typeof(PixelSizeRangeValidator))] public readonly struct PixelSize : IEquatable, IParsable, diff --git a/src/Beutl.Graphics/Media/RadialGradientBrush.cs b/src/Beutl.Graphics/Media/RadialGradientBrush.cs index c4ee75b33..ebe527b9d 100644 --- a/src/Beutl.Graphics/Media/RadialGradientBrush.cs +++ b/src/Beutl.Graphics/Media/RadialGradientBrush.cs @@ -1,4 +1,6 @@ -using Beutl.Graphics; +using System.ComponentModel.DataAnnotations; + +using Beutl.Graphics; using Beutl.Language; using Beutl.Media.Immutable; @@ -20,26 +22,17 @@ static RadialGradientBrush() { CenterProperty = ConfigureProperty(nameof(Center)) .Accessor(o => o.Center, (o, v) => o.Center = v) - .Display(Strings.Center) - .PropertyFlags(PropertyFlags.All) .DefaultValue(RelativePoint.Center) - .SerializeName("center") .Register(); GradientOriginProperty = ConfigureProperty(nameof(GradientOrigin)) .Accessor(o => o.GradientOrigin, (o, v) => o.GradientOrigin = v) - .Display(Strings.GradientOrigin) - .PropertyFlags(PropertyFlags.All) .DefaultValue(RelativePoint.Center) - .SerializeName("gradient-origin") .Register(); RadiusProperty = ConfigureProperty(nameof(Radius)) .Accessor(o => o.Radius, (o, v) => o.Radius = v) - .Display(Strings.Radius) - .PropertyFlags(PropertyFlags.All) .DefaultValue(0.5f) - .SerializeName("radius") .Register(); AffectsRender(CenterProperty, GradientOriginProperty, RadiusProperty); @@ -48,6 +41,7 @@ static RadialGradientBrush() /// /// Gets or sets the start point for the gradient. /// + [Display(Name = nameof(Strings.Center), ResourceType = typeof(Strings))] public RelativePoint Center { get => _center; @@ -58,6 +52,7 @@ public RelativePoint Center /// Gets or sets the location of the two-dimensional focal point that defines the beginning /// of the gradient. /// + [Display(Name = nameof(Strings.GradientOrigin), ResourceType = typeof(Strings))] public RelativePoint GradientOrigin { get => _gradientOrigin; @@ -68,6 +63,7 @@ public RelativePoint GradientOrigin /// Gets or sets the horizontal and vertical radius of the outermost circle of the radial /// gradient. /// + [Display(Name = nameof(Strings.Radius), ResourceType = typeof(Strings))] public float Radius { get => _radius; diff --git a/src/Beutl.Graphics/Media/SolidColorBrush.cs b/src/Beutl.Graphics/Media/SolidColorBrush.cs index b1122422e..f4ac57c16 100644 --- a/src/Beutl.Graphics/Media/SolidColorBrush.cs +++ b/src/Beutl.Graphics/Media/SolidColorBrush.cs @@ -1,4 +1,7 @@ -using Beutl.Graphics; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +using Beutl.Graphics; using Beutl.Graphics.Transformation; using Beutl.Language; using Beutl.Media.Immutable; @@ -17,16 +20,13 @@ static SolidColorBrush() { ColorProperty = ConfigureProperty(nameof(Color)) .Accessor(o => o.Color, (o, v) => o.Color = v) - .Display(Strings.Color) - .PropertyFlags(PropertyFlags.All) - .SerializeName("color") .Register(); TransformOriginProperty.OverrideMetadata( - new CorePropertyMetadata(propertyFlags: PropertyFlags.Styleable | PropertyFlags.NotifyChanged)); + new CorePropertyMetadata(attributes: new BrowsableAttribute(false))); TransformProperty.OverrideMetadata( - new CorePropertyMetadata(propertyFlags: PropertyFlags.Styleable | PropertyFlags.NotifyChanged)); + new CorePropertyMetadata(attributes: new BrowsableAttribute(false))); AffectsRender(ColorProperty); } @@ -61,6 +61,7 @@ public SolidColorBrush(uint color) /// /// Gets or sets the color of the brush. /// + [Display(Name = nameof(Strings.Color), ResourceType = typeof(Strings))] public Color Color { get => _color; diff --git a/src/Beutl.Graphics/Media/TileBrush.cs b/src/Beutl.Graphics/Media/TileBrush.cs index 71442d135..581f22b66 100644 --- a/src/Beutl.Graphics/Media/TileBrush.cs +++ b/src/Beutl.Graphics/Media/TileBrush.cs @@ -1,4 +1,6 @@ -using Beutl.Graphics; +using System.ComponentModel.DataAnnotations; + +using Beutl.Graphics; using Beutl.Language; namespace Beutl.Media; @@ -27,58 +29,37 @@ static TileBrush() { AlignmentXProperty = ConfigureProperty(nameof(AlignmentX)) .Accessor(o => o.AlignmentX, (o, v) => o.AlignmentX = v) - .Display(Strings.AlignmentX) - .PropertyFlags(PropertyFlags.All) .DefaultValue(AlignmentX.Center) - .SerializeName("alignment-x") .Register(); AlignmentYProperty = ConfigureProperty(nameof(AlignmentY)) .Accessor(o => o.AlignmentY, (o, v) => o.AlignmentY = v) - .Display(Strings.AlignmentY) - .PropertyFlags(PropertyFlags.All) .DefaultValue(AlignmentY.Center) - .SerializeName("alignment-y") .Register(); DestinationRectProperty = ConfigureProperty(nameof(DestinationRect)) .Accessor(o => o.DestinationRect, (o, v) => o.DestinationRect = v) - .Display(Strings.DestinationRect) - .PropertyFlags(PropertyFlags.All) .DefaultValue(RelativeRect.Fill) - .SerializeName("destination-rect") .Register(); SourceRectProperty = ConfigureProperty(nameof(SourceRect)) .Accessor(o => o.SourceRect, (o, v) => o.SourceRect = v) - .Display(Strings.SourceRect) - .PropertyFlags(PropertyFlags.All) .DefaultValue(RelativeRect.Fill) - .SerializeName("source-rect") .Register(); StretchProperty = ConfigureProperty(nameof(Stretch)) .Accessor(o => o.Stretch, (o, v) => o.Stretch = v) - .Display(Strings.Stretch) - .PropertyFlags(PropertyFlags.All) .DefaultValue(Stretch.Uniform) - .SerializeName("stretch") .Register(); TileModeProperty = ConfigureProperty(nameof(TileMode)) .Accessor(o => o.TileMode, (o, v) => o.TileMode = v) - .Display(Strings.TileMode) - .PropertyFlags(PropertyFlags.All) .DefaultValue(TileMode.None) - .SerializeName("tile-mode") .Register(); BitmapInterpolationModeProperty = ConfigureProperty(nameof(BitmapInterpolationMode)) .Accessor(o => o.BitmapInterpolationMode, (o, v) => o.BitmapInterpolationMode = v) - .Display(Strings.BitmapInterpolationMode) - .PropertyFlags(PropertyFlags.All) .DefaultValue(BitmapInterpolationMode.Default) - .SerializeName("interpolation-mode") .Register(); AffectsRender( @@ -92,6 +73,7 @@ static TileBrush() /// /// Gets or sets the horizontal alignment of a tile in the destination. /// + [Display(Name = nameof(Strings.AlignmentX), ResourceType = typeof(Strings))] public AlignmentX AlignmentX { get => _alignmentX; @@ -101,6 +83,7 @@ public AlignmentX AlignmentX /// /// Gets or sets the horizontal alignment of a tile in the destination. /// + [Display(Name = nameof(Strings.AlignmentY), ResourceType = typeof(Strings))] public AlignmentY AlignmentY { get => _alignmentY; @@ -110,6 +93,7 @@ public AlignmentY AlignmentY /// /// Gets or sets the rectangle on the destination in which to paint a tile. /// + [Display(Name = nameof(Strings.DestinationRect), ResourceType = typeof(Strings))] public RelativeRect DestinationRect { get => _destinationRect; @@ -119,6 +103,7 @@ public RelativeRect DestinationRect /// /// Gets or sets the rectangle of the source image that will be displayed. /// + [Display(Name = nameof(Strings.SourceRect), ResourceType = typeof(Strings))] public RelativeRect SourceRect { get => _sourceRect; @@ -129,6 +114,7 @@ public RelativeRect SourceRect /// Gets or sets a value controlling how the source rectangle will be stretched to fill /// the destination rect. /// + [Display(Name = nameof(Strings.Stretch), ResourceType = typeof(Strings))] public Stretch Stretch { get => _stretch; @@ -138,6 +124,7 @@ public Stretch Stretch /// /// Gets or sets the brush's tile mode. /// + [Display(Name = nameof(Strings.TileMode), ResourceType = typeof(Strings))] public TileMode TileMode { get => _tileMode; @@ -150,6 +137,7 @@ public TileMode TileMode /// /// The bitmap interpolation mode. /// + [Display(Name = nameof(Strings.BitmapInterpolationMode), ResourceType = typeof(Strings))] public BitmapInterpolationMode BitmapInterpolationMode { get => _bitmapInterpolationMode; diff --git a/src/Beutl.Graphics/Rendering/RenderLayerSpan.cs b/src/Beutl.Graphics/Rendering/RenderLayerSpan.cs index 308b98133..e103301dd 100644 --- a/src/Beutl.Graphics/Rendering/RenderLayerSpan.cs +++ b/src/Beutl.Graphics/Rendering/RenderLayerSpan.cs @@ -1,4 +1,6 @@ -using Beutl.Language; +using System.ComponentModel.DataAnnotations; + +using Beutl.Language; using Beutl.Media; namespace Beutl.Rendering; @@ -18,14 +20,10 @@ static RenderLayerSpan() { StartProperty = ConfigureProperty(nameof(Start)) .Accessor(o => o.Start, (o, v) => o.Start = v) - .Display(Strings.StartTime) - .PropertyFlags(PropertyFlags.NotifyChanged) .Register(); DurationProperty = ConfigureProperty(nameof(Duration)) .Accessor(o => o.Duration, (o, v) => o.Duration = v) - .Display(Strings.DurationTime) - .PropertyFlags(PropertyFlags.NotifyChanged) .Register(); ValueProperty = ConfigureProperty(nameof(Value)) @@ -34,7 +32,6 @@ static RenderLayerSpan() RenderLayerProperty = ConfigureProperty(nameof(RenderLayer)) .Accessor(o => o.RenderLayer) - .PropertyFlags(PropertyFlags.NotifyChanged) .Register(); } @@ -44,12 +41,14 @@ public RenderLayerSpan() _value.Invalidated += OnValueInvalidated; } + [Display(Name = nameof(Strings.StartTime), ResourceType = typeof(Strings))] public TimeSpan Start { get => _start; set => SetAndRaise(StartProperty, ref _start, value); } + [Display(Name = nameof(Strings.DurationTime), ResourceType = typeof(Strings))] public TimeSpan Duration { get => _duration; diff --git a/src/Beutl.Graphics/Rendering/Renderable.cs b/src/Beutl.Graphics/Rendering/Renderable.cs index b150eab5d..9586e5517 100644 --- a/src/Beutl.Graphics/Rendering/Renderable.cs +++ b/src/Beutl.Graphics/Rendering/Renderable.cs @@ -14,7 +14,6 @@ static Renderable() { IsVisibleProperty = ConfigureProperty(nameof(IsVisible)) .Accessor(o => o.IsVisible, (o, v) => o.IsVisible = v) - .PropertyFlags(PropertyFlags.NotifyChanged) .DefaultValue(true) .Register(); diff --git a/src/Beutl.Graphics/Styling/StyleSerializer.cs b/src/Beutl.Graphics/Styling/StyleSerializer.cs index d0c32ff0d..541bb0644 100644 --- a/src/Beutl.Graphics/Styling/StyleSerializer.cs +++ b/src/Beutl.Graphics/Styling/StyleSerializer.cs @@ -10,7 +10,7 @@ public static JsonNode ToJson(this IStyle style) { var styleJson = new JsonObject { - ["target"] = TypeFormat.ToString(style.TargetType) + ["Target"] = TypeFormat.ToString(style.TargetType) }; var setters = new JsonObject(); @@ -19,7 +19,7 @@ public static JsonNode ToJson(this IStyle style) (string name, JsonNode? node) = item.ToJson(style.TargetType); setters[name] = node; } - styleJson["setters"] = setters; + styleJson["Setters"] = setters; return styleJson; } @@ -29,14 +29,14 @@ public static (string, JsonNode?) ToJson(this ISetter setter, Type targetType) string? owner = null; JsonNode? animationNode = null; CorePropertyMetadata? metadata = setter.Property.GetMetadata(targetType); - string? name = metadata.SerializeName ?? setter.Property.Name; + string? name = setter.Property.Name; if (!targetType.IsAssignableTo(setter.Property.OwnerType)) { owner = TypeFormat.ToString(setter.Property.OwnerType); } - JsonNode? value = setter.Property.RouteWriteToJson(metadata, setter.Value, out bool isDefault); + JsonNode? value = setter.Property.RouteWriteToJson(metadata, setter.Value, out _); if (setter.Animation is { } animation) { @@ -62,11 +62,11 @@ public static (string, JsonNode?) ToJson(this ISetter setter, Type targetType) { var json = new JsonObject(); if (value != null) - json["value"] = value; + json["Value"] = value; if (owner != null) - json["owner"] = owner; + json["Owner"] = owner; if (animationNode != null) - json["animation"] = animationNode; + json["Animation"] = animationNode; return (name, json); } @@ -74,7 +74,7 @@ public static (string, JsonNode?) ToJson(this ISetter setter, Type targetType) public static Style? ToStyle(this JsonObject json) { - if (json.TryGetPropertyValue("target", out JsonNode? targetNode) + if (json.TryGetPropertyValue("Target", out JsonNode? targetNode) && targetNode is JsonValue targetValue && targetValue.TryGetValue(out string? targetStr) && TypeFormat.ToType(targetStr) is Type targetType) @@ -84,7 +84,7 @@ public static (string, JsonNode?) ToJson(this ISetter setter, Type targetType) TargetType = targetType }; - if (json.TryGetPropertyValue("setters", out JsonNode? settersNode) + if (json.TryGetPropertyValue("Setters", out JsonNode? settersNode) && settersNode is JsonObject settersObj) { foreach (KeyValuePair item in settersObj) @@ -115,7 +115,7 @@ public static (string, JsonNode?) ToJson(this ISetter setter, Type targetType) } else if (json is JsonObject jobj) { - if (jobj.TryGetPropertyValue("owner", out JsonNode? ownerNode) + if (jobj.TryGetPropertyValue("Owner", out JsonNode? ownerNode) && ownerNode is JsonValue ownerValue && ownerValue.TryGetValue(out string? ownerStr)) { @@ -129,14 +129,12 @@ public static (string, JsonNode?) ToJson(this ISetter setter, Type targetType) } } - valueNode = jobj["value"]; + valueNode = jobj["Value"]; - animationNode = jobj["animation"]; + animationNode = jobj["Animation"]; } - CoreProperty? property - = PropertyRegistry.GetRegistered(ownerType).FirstOrDefault( - x => x.GetMetadata(ownerType).SerializeName == name || x.Name == name); + CoreProperty? property = PropertyRegistry.GetRegistered(ownerType).FirstOrDefault(x => x.Name == name); if (property == null) return null; diff --git a/src/Beutl.Graphics/Validation/RangeValidatorGen.cs b/src/Beutl.Graphics/Validation/RangeValidatorGen.cs deleted file mode 100644 index 1637016f2..000000000 --- a/src/Beutl.Graphics/Validation/RangeValidatorGen.cs +++ /dev/null @@ -1,255 +0,0 @@ - -#pragma warning disable IDE0001, IDE0049 - -namespace Beutl.Validation -{ - // Vector2 - public sealed class PixelPointRangeValidator : RangeValidator - { - public PixelPointRangeValidator() - { - Maximum = new(System.Int32.MaxValue, System.Int32.MaxValue); - Minimum = new(System.Int32.MinValue, System.Int32.MinValue); - } - - public override bool TryCoerce(ValidationContext context, ref Beutl.Media.PixelPoint value) - { - value = new Beutl.Media.PixelPoint( - Math.Clamp(value.X, Minimum.X, Maximum.X), - Math.Clamp(value.Y, Minimum.Y, Maximum.Y)); - return true; - } - - public override string? Validate(ValidationContext context, Beutl.Media.PixelPoint value) - { - if (value.X >= Minimum.X && value.X <= Maximum.X - && value.Y >= Minimum.Y && value.Y <= Maximum.Y) - { - return $"The value must be between {Minimum} and {Maximum}."; - } - else - { - return null; - } - } - } - public sealed class PixelSizeRangeValidator : RangeValidator - { - public PixelSizeRangeValidator() - { - Maximum = new(System.Int32.MaxValue, System.Int32.MaxValue); - Minimum = new(System.Int32.MinValue, System.Int32.MinValue); - } - - public override bool TryCoerce(ValidationContext context, ref Beutl.Media.PixelSize value) - { - value = new Beutl.Media.PixelSize( - Math.Clamp(value.Width, Minimum.Width, Maximum.Width), - Math.Clamp(value.Height, Minimum.Height, Maximum.Height)); - return true; - } - - public override string? Validate(ValidationContext context, Beutl.Media.PixelSize value) - { - if (value.Width >= Minimum.Width && value.Width <= Maximum.Width - && value.Height >= Minimum.Height && value.Height <= Maximum.Height) - { - return $"The value must be between {Minimum} and {Maximum}."; - } - else - { - return null; - } - } - } - public sealed class PointRangeValidator : RangeValidator - { - public PointRangeValidator() - { - Maximum = new(System.Single.MaxValue, System.Single.MaxValue); - Minimum = new(System.Single.MinValue, System.Single.MinValue); - } - - public override bool TryCoerce(ValidationContext context, ref Beutl.Graphics.Point value) - { - value = new Beutl.Graphics.Point( - Math.Clamp(value.X, Minimum.X, Maximum.X), - Math.Clamp(value.Y, Minimum.Y, Maximum.Y)); - return true; - } - - public override string? Validate(ValidationContext context, Beutl.Graphics.Point value) - { - if (value.X >= Minimum.X && value.X <= Maximum.X - && value.Y >= Minimum.Y && value.Y <= Maximum.Y) - { - return $"The value must be between {Minimum} and {Maximum}."; - } - else - { - return null; - } - } - } - public sealed class SizeRangeValidator : RangeValidator - { - public SizeRangeValidator() - { - Maximum = new(System.Single.MaxValue, System.Single.MaxValue); - Minimum = new(System.Single.MinValue, System.Single.MinValue); - } - - public override bool TryCoerce(ValidationContext context, ref Beutl.Graphics.Size value) - { - value = new Beutl.Graphics.Size( - Math.Clamp(value.Width, Minimum.Width, Maximum.Width), - Math.Clamp(value.Height, Minimum.Height, Maximum.Height)); - return true; - } - - public override string? Validate(ValidationContext context, Beutl.Graphics.Size value) - { - if (value.Width >= Minimum.Width && value.Width <= Maximum.Width - && value.Height >= Minimum.Height && value.Height <= Maximum.Height) - { - return $"The value must be between {Minimum} and {Maximum}."; - } - else - { - return null; - } - } - } - public sealed class VectorRangeValidator : RangeValidator - { - public VectorRangeValidator() - { - Maximum = new(System.Single.MaxValue, System.Single.MaxValue); - Minimum = new(System.Single.MinValue, System.Single.MinValue); - } - - public override bool TryCoerce(ValidationContext context, ref Beutl.Graphics.Vector value) - { - value = new Beutl.Graphics.Vector( - Math.Clamp(value.X, Minimum.X, Maximum.X), - Math.Clamp(value.Y, Minimum.Y, Maximum.Y)); - return true; - } - - public override string? Validate(ValidationContext context, Beutl.Graphics.Vector value) - { - if (value.X >= Minimum.X && value.X <= Maximum.X - && value.Y >= Minimum.Y && value.Y <= Maximum.Y) - { - return $"The value must be between {Minimum} and {Maximum}."; - } - else - { - return null; - } - } - } - - // Vector3 - - // Vector4 - public sealed class PixelRectRangeValidator : RangeValidator - { - public PixelRectRangeValidator() - { - Maximum = new(System.Int32.MaxValue, System.Int32.MaxValue, System.Int32.MaxValue, System.Int32.MaxValue); - Minimum = new(System.Int32.MinValue, System.Int32.MinValue, System.Int32.MinValue, System.Int32.MinValue); - } - - public override bool TryCoerce(ValidationContext context, ref Beutl.Media.PixelRect value) - { - value = new Beutl.Media.PixelRect( - Math.Clamp(value.X, Minimum.X, Maximum.X), - Math.Clamp(value.Y, Minimum.Y, Maximum.Y), - Math.Clamp(value.Width, Minimum.Width, Maximum.Width), - Math.Clamp(value.Height, Minimum.Height, Maximum.Height)); - return true; - } - - public override string? Validate(ValidationContext context, Beutl.Media.PixelRect value) - { - if (value.X >= Minimum.X && value.X <= Maximum.X - && value.Y >= Minimum.Y && value.Y <= Maximum.Y - && value.Width >= Minimum.Width && value.Width <= Maximum.Width - && value.Height >= Minimum.Height && value.Height <= Maximum.Height) - { - return $"The value must be between {Minimum} and {Maximum}."; - } - else - { - return null; - } - } - } - public sealed class CornerRadiusRangeValidator : RangeValidator - { - public CornerRadiusRangeValidator() - { - Maximum = new(System.Single.MaxValue, System.Single.MaxValue, System.Single.MaxValue, System.Single.MaxValue); - Minimum = new(System.Single.MinValue, System.Single.MinValue, System.Single.MinValue, System.Single.MinValue); - } - - public override bool TryCoerce(ValidationContext context, ref Beutl.Media.CornerRadius value) - { - value = new Beutl.Media.CornerRadius( - Math.Clamp(value.TopLeft, Minimum.TopLeft, Maximum.TopLeft), - Math.Clamp(value.TopRight, Minimum.TopRight, Maximum.TopRight), - Math.Clamp(value.BottomRight, Minimum.BottomRight, Maximum.BottomRight), - Math.Clamp(value.BottomLeft, Minimum.BottomLeft, Maximum.BottomLeft)); - return true; - } - - public override string? Validate(ValidationContext context, Beutl.Media.CornerRadius value) - { - if (value.TopLeft >= Minimum.TopLeft && value.TopLeft <= Maximum.TopLeft - && value.TopRight >= Minimum.TopRight && value.TopRight <= Maximum.TopRight - && value.BottomRight >= Minimum.BottomRight && value.BottomRight <= Maximum.BottomRight - && value.BottomLeft >= Minimum.BottomLeft && value.BottomLeft <= Maximum.BottomLeft) - { - return $"The value must be between {Minimum} and {Maximum}."; - } - else - { - return null; - } - } - } - public sealed class RectRangeValidator : RangeValidator - { - public RectRangeValidator() - { - Maximum = new(System.Single.MaxValue, System.Single.MaxValue, System.Single.MaxValue, System.Single.MaxValue); - Minimum = new(System.Single.MinValue, System.Single.MinValue, System.Single.MinValue, System.Single.MinValue); - } - - public override bool TryCoerce(ValidationContext context, ref Beutl.Graphics.Rect value) - { - value = new Beutl.Graphics.Rect( - Math.Clamp(value.X, Minimum.X, Maximum.X), - Math.Clamp(value.Y, Minimum.Y, Maximum.Y), - Math.Clamp(value.Width, Minimum.Width, Maximum.Width), - Math.Clamp(value.Height, Minimum.Height, Maximum.Height)); - return true; - } - - public override string? Validate(ValidationContext context, Beutl.Graphics.Rect value) - { - if (value.X >= Minimum.X && value.X <= Maximum.X - && value.Y >= Minimum.Y && value.Y <= Maximum.Y - && value.Width >= Minimum.Width && value.Width <= Maximum.Width - && value.Height >= Minimum.Height && value.Height <= Maximum.Height) - { - return $"The value must be between {Minimum} and {Maximum}."; - } - else - { - return null; - } - } - } -} diff --git a/src/Beutl.Graphics/Validation/RangeValidatorGen.tt b/src/Beutl.Graphics/Validation/RangeValidatorGen.tt deleted file mode 100644 index 32da0518a..000000000 --- a/src/Beutl.Graphics/Validation/RangeValidatorGen.tt +++ /dev/null @@ -1,140 +0,0 @@ -<#@ template debug="false" hostspecific="false" language="C#" #> -<#@ assembly name="System.Core" #> -<#@ import namespace="System.Linq" #> -<#@ import namespace="System.Text" #> -<#@ import namespace="System.Collections.Generic" #> -<#@ output extension=".cs" #> -<# - var vec2Types = new (string Namespace, string Name, string Element, string FieldX, string FieldY)[] - { - ("Beutl.Media", "PixelPoint", "System.Int32", "X", "Y"), - ("Beutl.Media", "PixelSize", "System.Int32", "Width", "Height"), - ("Beutl.Graphics", "Point", "System.Single", "X", "Y"), - ("Beutl.Graphics", "Size", "System.Single", "Width", "Height"), - ("Beutl.Graphics", "Vector", "System.Single", "X", "Y"), - }; - - var vec3Types = new (string Namespace, string Name, string Element, string FieldX, string FieldY, string FieldZ)[] - { - }; - - var vec4Types = new (string Namespace, string Name, string Element, string FieldX, string FieldY, string FieldZ, string FieldW)[] - { - ("Beutl.Media", "PixelRect", "System.Int32", "X", "Y", "Width", "Height"), - ("Beutl.Media", "CornerRadius", "System.Single", "TopLeft", "TopRight", "BottomRight", "BottomLeft"), - ("Beutl.Graphics", "Rect", "System.Single", "X", "Y", "Width", "Height"), - }; -#> - -#pragma warning disable IDE0001, IDE0049 - -namespace Beutl.Validation -{ - // Vector2 -<# foreach(var t in vec2Types) { #> -<# var fullName = $"{t.Namespace}.{t.Name}"; #> - public sealed class <#= t.Name #>RangeValidator : RangeValidator<<#= fullName #>> - { - public <#= t.Name #>RangeValidator() - { - Maximum = new(<#= t.Element #>.MaxValue, <#= t.Element #>.MaxValue); - Minimum = new(<#= t.Element #>.MinValue, <#= t.Element #>.MinValue); - } - - public override bool TryCoerce(ValidationContext context, ref <#= fullName #> value) - { - value = new <#= fullName #>( - Math.Clamp(value.<#= t.FieldX #>, Minimum.<#= t.FieldX #>, Maximum.<#= t.FieldX #>), - Math.Clamp(value.<#= t.FieldY #>, Minimum.<#= t.FieldY #>, Maximum.<#= t.FieldY #>)); - return true; - } - - public override string? Validate(ValidationContext context, <#= fullName #> value) - { - if (value.<#= t.FieldX #> >= Minimum.<#= t.FieldX #> && value.<#= t.FieldX #> <= Maximum.<#= t.FieldX #> - && value.<#= t.FieldY #> >= Minimum.<#= t.FieldY #> && value.<#= t.FieldY #> <= Maximum.<#= t.FieldY #>) - { - return $"The value must be between {Minimum} and {Maximum}."; - } - else - { - return null; - } - } - } -<# } #> - - // Vector3 -<# foreach(var t in vec3Types) { #> -<# var fullName = $"{t.Namespace}.{t.Name}"; #> - public sealed class <#= t.Name #>RangeValidator : RangeValidator<<#= fullName #>> - { - public <#= t.Name #>RangeValidator() - { - Maximum = new(<#= t.Element #>.MaxValue, <#= t.Element #>.MaxValue, <#= t.Element #>.MaxValue); - Minimum = new(<#= t.Element #>.MinValue, <#= t.Element #>.MinValue, <#= t.Element #>.MinValue); - } - - public override bool TryCoerce(ValidationContext context, ref <#= fullName #> value) - { - value = new <#= fullName #>( - Math.Clamp(value.<#= t.FieldX #>, Minimum.<#= t.FieldX #>, Maximum.<#= t.FieldX #>), - Math.Clamp(value.<#= t.FieldY #>, Minimum.<#= t.FieldY #>, Maximum.<#= t.FieldY #>), - Math.Clamp(value.<#= t.FieldZ #>, Minimum.<#= t.FieldZ #>, Maximum.<#= t.FieldZ #>)); - return true; - } - - public override string? Validate(ValidationContext context, <#= fullName #> value) - { - if (value.<#= t.FieldX #> >= Minimum.<#= t.FieldX #> && value.<#= t.FieldX #> <= Maximum.<#= t.FieldX #> - && value.<#= t.FieldY #> >= Minimum.<#= t.FieldY #> && value.<#= t.FieldY #> <= Maximum.<#= t.FieldY #> - && value.<#= t.FieldZ #> >= Minimum.<#= t.FieldZ #> && value.<#= t.FieldZ #> <= Maximum.<#= t.FieldZ #>) - { - return $"The value must be between {Minimum} and {Maximum}."; - } - else - { - return null; - } - } - } -<# } #> - - // Vector4 -<# foreach(var t in vec4Types) { #> -<# var fullName = $"{t.Namespace}.{t.Name}"; #> - public sealed class <#= t.Name #>RangeValidator : RangeValidator<<#= fullName #>> - { - public <#= t.Name #>RangeValidator() - { - Maximum = new(<#= t.Element #>.MaxValue, <#= t.Element #>.MaxValue, <#= t.Element #>.MaxValue, <#= t.Element #>.MaxValue); - Minimum = new(<#= t.Element #>.MinValue, <#= t.Element #>.MinValue, <#= t.Element #>.MinValue, <#= t.Element #>.MinValue); - } - - public override bool TryCoerce(ValidationContext context, ref <#= fullName #> value) - { - value = new <#= fullName #>( - Math.Clamp(value.<#= t.FieldX #>, Minimum.<#= t.FieldX #>, Maximum.<#= t.FieldX #>), - Math.Clamp(value.<#= t.FieldY #>, Minimum.<#= t.FieldY #>, Maximum.<#= t.FieldY #>), - Math.Clamp(value.<#= t.FieldZ #>, Minimum.<#= t.FieldZ #>, Maximum.<#= t.FieldZ #>), - Math.Clamp(value.<#= t.FieldW #>, Minimum.<#= t.FieldW #>, Maximum.<#= t.FieldW #>)); - return true; - } - - public override string? Validate(ValidationContext context, <#= fullName #> value) - { - if (value.<#= t.FieldX #> >= Minimum.<#= t.FieldX #> && value.<#= t.FieldX #> <= Maximum.<#= t.FieldX #> - && value.<#= t.FieldY #> >= Minimum.<#= t.FieldY #> && value.<#= t.FieldY #> <= Maximum.<#= t.FieldY #> - && value.<#= t.FieldZ #> >= Minimum.<#= t.FieldZ #> && value.<#= t.FieldZ #> <= Maximum.<#= t.FieldZ #> - && value.<#= t.FieldW #> >= Minimum.<#= t.FieldW #> && value.<#= t.FieldW #> <= Maximum.<#= t.FieldW #>) - { - return $"The value must be between {Minimum} and {Maximum}."; - } - else - { - return null; - } - } - } -<# } #> -} diff --git a/src/Beutl.ProjectSystem/NodeTree/Node.cs b/src/Beutl.ProjectSystem/NodeTree/Node.cs index 6b0c30865..d90e5c408 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Node.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Node.cs @@ -20,17 +20,12 @@ static Node() { IsExpandedProperty = ConfigureProperty(nameof(Position)) .DefaultValue(true) - .SerializeName("is-expanded") - .PropertyFlags(PropertyFlags.NotifyChanged) .Register(); PositionProperty = ConfigureProperty<(double X, double Y), Node>(nameof(Position)) .Accessor(o => o.Position, (o, v) => o.Position = v) .DefaultValue((0, 0)) - .PropertyFlags(PropertyFlags.NotifyChanged) .Register(); - - NameProperty.OverrideMetadata(new CorePropertyMetadata("name")); } public Node() @@ -74,6 +69,7 @@ public bool IsExpanded set => SetValue(IsExpandedProperty, value); } + [ShouldSerialize(false)] public (double X, double Y) Position { get => _position; @@ -412,7 +408,7 @@ public override void ReadFromJson(JsonNode json) base.ReadFromJson(json); if (json is JsonObject obj) { - if (obj.TryGetPropertyValue("position", out JsonNode? posNode) + if (obj.TryGetPropertyValue(nameof(Position), out JsonNode? posNode) && posNode is JsonValue posVal && posVal.TryGetValue(out string? posStr)) { @@ -424,7 +420,7 @@ public override void ReadFromJson(JsonNode json) } } - if (obj.TryGetPropertyValue("items", out var itemsNode) + if (obj.TryGetPropertyValue(nameof(Items), out var itemsNode) && itemsNode is JsonArray itemsArray) { int index = 0; @@ -433,7 +429,7 @@ public override void ReadFromJson(JsonNode json) if (item is JsonObject itemObj) { int localId; - if (itemObj.TryGetPropertyValue("local-id", out var localIdNode) + if (itemObj.TryGetPropertyValue("LocalId", out var localIdNode) && localIdNode is JsonValue localIdValue && localIdValue.TryGetValue(out int actualLId)) { @@ -461,7 +457,7 @@ public override void ReadFromJson(JsonNode json) public override void WriteToJson(ref JsonNode json) { base.WriteToJson(ref json); - json["position"] = $"{Position.X},{Position.Y}"; + json[nameof(Position)] = $"{Position.X},{Position.Y}"; var array = new JsonArray(); foreach (INodeItem item in Items) @@ -475,7 +471,7 @@ public override void WriteToJson(ref JsonNode json) array.Add(itemJson); } - json["items"] = array; + json[nameof(Items)] = array; } protected override void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs args) diff --git a/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs b/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs index f58c23dfb..76716ba52 100644 --- a/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs +++ b/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs @@ -14,16 +14,11 @@ public abstract class NodeItem : Hierarchical static NodeItem() { IsValidProperty = ConfigureProperty(o => o.IsValid) - .PropertyFlags(PropertyFlags.NotifyChanged) .Register(); LocalIdProperty = ConfigureProperty(o => o.LocalId) - .PropertyFlags(PropertyFlags.NotifyChanged) - .SerializeName("local-id") .DefaultValue(-1) .Register(); - - IdProperty.OverrideMetadata(new CorePropertyMetadata("id")); } public NodeItem() @@ -31,6 +26,7 @@ public NodeItem() Id = Guid.NewGuid(); } + [ShouldSerialize(false)] public bool? IsValid { get => _isValid; diff --git a/src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs b/src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs index 59b084786..184964a90 100644 --- a/src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs +++ b/src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs @@ -24,15 +24,10 @@ public class NodeGroup : NodeTreeSpace static NodeGroup() { InputProperty = ConfigureProperty(o => o.Input) - .PropertyFlags(PropertyFlags.NotifyChanged) .Register(); OutputProperty = ConfigureProperty(o => o.Output) - .PropertyFlags(PropertyFlags.NotifyChanged) .Register(); - - IdProperty.OverrideMetadata(new CorePropertyMetadata("id")); - NameProperty.OverrideMetadata(new CorePropertyMetadata("name")); } public NodeGroup() @@ -42,12 +37,14 @@ public NodeGroup() Nodes.Detached += OnNodeDetached; } + [ShouldSerialize(false)] public GroupInput? Input { get => _input; set => SetAndRaise(InputProperty, ref _input, value); } + [ShouldSerialize(false)] public GroupOutput? Output { get => _output; @@ -248,7 +245,7 @@ public override void ReadFromJson(JsonNode json) if (json is JsonObject jobject) { - if (jobject.TryGetPropertyValue("nodes", out JsonNode? nodesNode) + if (jobject.TryGetPropertyValue(nameof(Nodes), out JsonNode? nodesNode) && nodesNode is JsonArray nodesArray) { foreach (JsonObject nodeJson in nodesArray.OfType()) @@ -297,7 +294,7 @@ public override void WriteToJson(ref JsonNode json) array.Add(jsonNode); } - jobject["nodes"] = array; + jobject[nameof(Nodes)] = array; } } } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupInput.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupInput.cs index 68233dfa7..569f016cf 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupInput.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupInput.cs @@ -9,24 +9,19 @@ public class GroupInput : Node, ISocketsCanBeAdded public class GroupInputSocket : OutputSocket, IGroupSocket, IAutomaticallyGeneratedSocket { - static GroupInputSocket() - { - NameProperty.OverrideMetadata>(new CorePropertyMetadata("name")); - } - public CoreProperty? AssociatedProperty { get; set; } public override void ReadFromJson(JsonNode json) { base.ReadFromJson(json); - JsonNode propertyJson = json["associated-property"]!; - string name = (string)propertyJson["name"]!; - string owner = (string)propertyJson["owner"]!; + JsonNode propertyJson = json[nameof(AssociatedProperty)]!; + string name = (string)propertyJson["Name"]!; + string owner = (string)propertyJson["Owner"]!; Type ownerType = TypeFormat.ToType(owner)!; AssociatedProperty = PropertyRegistry.GetRegistered(ownerType) - .FirstOrDefault(x => x.GetMetadata(ownerType).SerializeName == name || x.Name == name); + .FirstOrDefault(x => x.Name == name); } public override void WriteToJson(ref JsonNode json) @@ -34,14 +29,13 @@ public override void WriteToJson(ref JsonNode json) base.WriteToJson(ref json); if (AssociatedProperty is { OwnerType: Type ownerType } property) { - CorePropertyMetadata? metadata = property.GetMetadata(ownerType); - string name = metadata.SerializeName ?? property.Name; + string name = property.Name; string owner = TypeFormat.ToString(ownerType); - json["associated-property"] = new JsonObject + json[nameof(AssociatedProperty)] = new JsonObject { - ["name"] = name, - ["owner"] = owner, + ["Name"] = name, + ["Owner"] = owner, }; } } @@ -84,7 +78,7 @@ public override void ReadFromJson(JsonNode json) base.ReadFromJson(json); if (json is JsonObject obj) { - if (obj.TryGetPropertyValue("items", out var itemsNode) + if (obj.TryGetPropertyValue("Items", out var itemsNode) && itemsNode is JsonArray itemsArray) { int index = 0; diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupOutput.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupOutput.cs index 0df982bb6..4cb51dd2b 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupOutput.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupOutput.cs @@ -9,24 +9,19 @@ public class GroupOutput : Node, ISocketsCanBeAdded public class GroupOutputSocket : InputSocket, IAutomaticallyGeneratedSocket, IGroupSocket { - static GroupOutputSocket() - { - NameProperty.OverrideMetadata>(new CorePropertyMetadata("name")); - } - public CoreProperty? AssociatedProperty { get; set; } public override void ReadFromJson(JsonNode json) { base.ReadFromJson(json); - JsonNode propertyJson = json["associated-property"]!; - string name = (string)propertyJson["name"]!; - string owner = (string)propertyJson["owner"]!; + JsonNode propertyJson = json[nameof(AssociatedProperty)]!; + string name = (string)propertyJson["Name"]!; + string owner = (string)propertyJson["Owner"]!; Type ownerType = TypeFormat.ToType(owner)!; AssociatedProperty = PropertyRegistry.GetRegistered(ownerType) - .FirstOrDefault(x => x.GetMetadata(ownerType).SerializeName == name || x.Name == name); + .FirstOrDefault(x => x.Name == name); } public override void WriteToJson(ref JsonNode json) @@ -34,14 +29,13 @@ public override void WriteToJson(ref JsonNode json) base.WriteToJson(ref json); if (AssociatedProperty is { OwnerType: Type ownerType } property) { - CorePropertyMetadata? metadata = property.GetMetadata(ownerType); - string name = metadata.SerializeName ?? property.Name; + string name = property.Name; string owner = TypeFormat.ToString(ownerType); - json["associated-property"] = new JsonObject + json["AssociatedProperty"] = new JsonObject { - ["name"] = name, - ["owner"] = owner, + ["Name"] = name, + ["Owner"] = owner, }; } } @@ -84,7 +78,7 @@ public override void ReadFromJson(JsonNode json) base.ReadFromJson(json); if (json is JsonObject obj) { - if (obj.TryGetPropertyValue("items", out var itemsNode) + if (obj.TryGetPropertyValue("Items", out var itemsNode) && itemsNode is JsonArray itemsArray) { int index = 0; diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/LayerInputNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/LayerInputNode.cs index 6e7db2bb5..b0a3f1b8c 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/LayerInputNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/LayerInputNode.cs @@ -28,7 +28,6 @@ public class LayerInputSocket : OutputSocket, ILayerInputSocket, IGroupSoc static LayerInputSocket() { - NameProperty.OverrideMetadata>(new CorePropertyMetadata("name")); } public CoreProperty? AssociatedProperty { get; set; } @@ -85,12 +84,12 @@ public override void PreEvaluate(EvaluationContext context) public override void ReadFromJson(JsonNode json) { base.ReadFromJson(json); - string name = (string)json["property"]!; - string owner = (string)json["target"]!; + string name = (string)json["Property"]!; + string owner = (string)json["Target"]!; Type ownerType = TypeFormat.ToType(owner)!; AssociatedProperty = PropertyRegistry.GetRegistered(ownerType) - .FirstOrDefault(x => x.GetMetadata(ownerType).SerializeName == name || x.Name == name); + .FirstOrDefault(x => x.Name == name); if (AssociatedProperty != null) { @@ -141,7 +140,7 @@ public override void ReadFromJson(JsonNode json) base.ReadFromJson(json); if (json is JsonObject obj) { - if (obj.TryGetPropertyValue("items", out JsonNode? itemsNode) + if (obj.TryGetPropertyValue("Items", out JsonNode? itemsNode) && itemsNode is JsonArray itemsArray) { int index = 0; diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/MatrixNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/MatrixNode.cs index 642025828..d253d9338 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/MatrixNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/MatrixNode.cs @@ -13,8 +13,6 @@ static MatrixNode() { OperatorProperty = ConfigureProperty(o => o.Operator) .DefaultValue(MultiplicationOperator.Prepend) - .PropertyFlags(PropertyFlags.All) - .SerializeName("operator") .Register(); } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/RandomFloatNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/RandomFloatNode.cs index 74b30364b..dca4f8465 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/RandomFloatNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/RandomFloatNode.cs @@ -5,12 +5,10 @@ public class RandomSingleNode : Node private static readonly CoreProperty MaximumProperty = ConfigureProperty(o => o.Maximum) .DefaultValue(1) - .SerializeName("maximum") .Register(); private static readonly CoreProperty MinimumProperty = ConfigureProperty(o => o.Minimum) .DefaultValue(0) - .SerializeName("minimum") .Register(); private readonly OutputSocket _valueSocket; private readonly InputSocket _maximumSocket; @@ -42,12 +40,10 @@ public class RandomDoubleNode : Node private static readonly CoreProperty MaximumProperty = ConfigureProperty(o => o.Maximum) .DefaultValue(1) - .SerializeName("maximum") .Register(); private static readonly CoreProperty MinimumProperty = ConfigureProperty(o => o.Minimum) .DefaultValue(0) - .SerializeName("minimum") .Register(); private readonly OutputSocket _valueSocket; private readonly InputSocket _maximumSocket; @@ -79,12 +75,10 @@ public class RandomInt32Node : Node private static readonly CoreProperty MaximumProperty = ConfigureProperty(o => o.Maximum) .DefaultValue(100) - .SerializeName("maximum") .Register(); private static readonly CoreProperty MinimumProperty = ConfigureProperty(o => o.Minimum) .DefaultValue(0) - .SerializeName("minimum") .Register(); private readonly OutputSocket _valueSocket; private readonly InputSocket _maximumSocket; @@ -97,7 +91,7 @@ public RandomInt32Node() _minimumSocket = AsInput(MinimumProperty).AcceptNumber(); } - private int Maximum { get; set; } = 1; + private int Maximum { get; set; } = 100; private int Minimum { get; set; } @@ -114,12 +108,10 @@ public class RandomInt64Node : Node private static readonly CoreProperty MaximumProperty = ConfigureProperty(o => o.Maximum) .DefaultValue(100) - .SerializeName("maximum") .Register(); private static readonly CoreProperty MinimumProperty = ConfigureProperty(o => o.Minimum) .DefaultValue(0) - .SerializeName("minimum") .Register(); private readonly OutputSocket _valueSocket; private readonly InputSocket _maximumSocket; @@ -132,7 +124,7 @@ public RandomInt64Node() _minimumSocket = AsInput(MinimumProperty).AcceptNumber(); } - private long Maximum { get; set; } = 1; + private long Maximum { get; set; } = 100; private long Minimum { get; set; } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/PixelPointNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/PixelPointNode.cs index 9328d7b62..041348f52 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/PixelPointNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/PixelPointNode.cs @@ -7,12 +7,10 @@ public class PixelPointNode : Node private static readonly CoreProperty XProperty = ConfigureProperty(o => o.X) .DefaultValue(0) - .SerializeName("x") .Register(); private static readonly CoreProperty YProperty = ConfigureProperty(o => o.Y) .DefaultValue(0) - .SerializeName("y") .Register(); private readonly OutputSocket _valueSocket; private readonly InputSocket _xSocket; diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/PixelRectNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/PixelRectNode.cs index 19253aae7..11d877ccd 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/PixelRectNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/PixelRectNode.cs @@ -1,4 +1,6 @@ -using Beutl.Language; +using System.ComponentModel.DataAnnotations; + +using Beutl.Language; using Beutl.Media; namespace Beutl.NodeTree.Nodes.Utilities.Struct; @@ -7,15 +9,11 @@ public class PixelRectNode : Node { private static readonly CoreProperty TopLeftProperty = ConfigureProperty(o => o.TopLeft) - .Display(Strings.Position) .DefaultValue(default) - .SerializeName("top-left") .Register(); private static readonly CoreProperty SizeProperty = ConfigureProperty(o => o.Size) - .Display(Strings.Size) .DefaultValue(default) - .SerializeName("size") .Register(); private readonly OutputSocket _valueSocket; private readonly InputSocket _positionSocket; @@ -28,12 +26,14 @@ public PixelRectNode() _sizeSocket = AsInput(SizeProperty).AcceptNumber(); } + [Display(Name = nameof(Strings.Position), ResourceType = typeof(Strings))] private PixelPoint TopLeft { get => default; set { } } + [Display(Name = nameof(Strings.Size), ResourceType = typeof(Strings))] private PixelSize Size { get => default; diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/PixelSizeNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/PixelSizeNode.cs index da43f644e..03f92f148 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/PixelSizeNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/PixelSizeNode.cs @@ -1,4 +1,6 @@ -using Beutl.Language; +using System.ComponentModel.DataAnnotations; + +using Beutl.Language; using Beutl.Media; namespace Beutl.NodeTree.Nodes.Utilities.Struct; @@ -7,15 +9,11 @@ public class PixelSizeNode : Node { private static readonly CoreProperty WidthProperty = ConfigureProperty(o => o.Width) - .Display(Strings.Width) .DefaultValue(0) - .SerializeName("width") .Register(); private static readonly CoreProperty HeightProperty = ConfigureProperty(o => o.Height) - .Display(Strings.Height) .DefaultValue(0) - .SerializeName("height") .Register(); private readonly OutputSocket _valueSocket; private readonly InputSocket _widthSocket; @@ -28,12 +26,14 @@ public PixelSizeNode() _heightSocket = AsInput(HeightProperty).AcceptNumber(); } + [Display(Name = nameof(Strings.Width), ResourceType = typeof(Strings))] private int Width { get => 0; set { } } + [Display(Name = nameof(Strings.Height), ResourceType = typeof(Strings))] private int Height { get => 0; diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/PointNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/PointNode.cs index 338dcb691..4c31a4198 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/PointNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/PointNode.cs @@ -7,12 +7,10 @@ public class PointNode : Node private static readonly CoreProperty XProperty = ConfigureProperty(o => o.X) .DefaultValue(0) - .SerializeName("x") .Register(); private static readonly CoreProperty YProperty = ConfigureProperty(o => o.Y) .DefaultValue(0) - .SerializeName("y") .Register(); private readonly OutputSocket _valueSocket; private readonly InputSocket _xSocket; diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/RectNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/RectNode.cs index d77e05fc9..24ecf0d73 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/RectNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/RectNode.cs @@ -1,4 +1,6 @@ -using Beutl.Graphics; +using System.ComponentModel.DataAnnotations; + +using Beutl.Graphics; using Beutl.Language; namespace Beutl.NodeTree.Nodes.Utilities.Struct; @@ -7,15 +9,11 @@ public class RectNode : Node { private static readonly CoreProperty TopLeftProperty = ConfigureProperty(o => o.TopLeft) - .Display(Strings.Position) .DefaultValue(default) - .SerializeName("top-left") .Register(); private static readonly CoreProperty SizeProperty = ConfigureProperty(o => o.Size) - .Display(Strings.Size) .DefaultValue(default) - .SerializeName("size") .Register(); private readonly OutputSocket _valueSocket; private readonly InputSocket _positionSocket; @@ -28,12 +26,14 @@ public RectNode() _sizeSocket = AsInput(SizeProperty).AcceptNumber(); } + [Display(Name = nameof(Strings.Position), ResourceType = typeof(Strings))] private Point TopLeft { get => default; set { } } + [Display(Name = nameof(Strings.Size), ResourceType = typeof(Strings))] private Size Size { get => default; diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/RelativePointNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/RelativePointNode.cs index 8bdd44785..ef306b9fc 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/RelativePointNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/RelativePointNode.cs @@ -7,17 +7,14 @@ public class RelativePointNode : Node private static readonly CoreProperty UnitProperty = ConfigureProperty(o => o.Unit) .DefaultValue(RelativeUnit.Relative) - .SerializeName("unit") .Register(); private static readonly CoreProperty XProperty = ConfigureProperty(o => o.X) .DefaultValue(0) - .SerializeName("x") .Register(); private static readonly CoreProperty YProperty = ConfigureProperty(o => o.Y) .DefaultValue(0) - .SerializeName("y") .Register(); private readonly OutputSocket _valueSocket; private readonly NodeItem _unitSocket; diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/SizeNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/SizeNode.cs index f8343c174..80bbea7d1 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/SizeNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/Struct/SizeNode.cs @@ -1,4 +1,6 @@ -using Beutl.Graphics; +using System.ComponentModel.DataAnnotations; + +using Beutl.Graphics; using Beutl.Language; namespace Beutl.NodeTree.Nodes.Utilities.Struct; @@ -7,15 +9,11 @@ public class SizeNode : Node { private static readonly CoreProperty WidthProperty = ConfigureProperty(o => o.Width) - .Display(Strings.Width) .DefaultValue(0) - .SerializeName("width") .Register(); private static readonly CoreProperty HeightProperty = ConfigureProperty(o => o.Height) - .Display(Strings.Height) .DefaultValue(0) - .SerializeName("height") .Register(); private readonly OutputSocket _valueSocket; private readonly InputSocket _widthSocket; @@ -28,12 +26,14 @@ public SizeNode() _heightSocket = AsInput(HeightProperty).AcceptNumber(); } + [Display(Name = nameof(Strings.Width), ResourceType = typeof(Strings))] private float Width { get => 0; set { } } + [Display(Name = nameof(Strings.Height), ResourceType = typeof(Strings))] private float Height { get => 0; diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/SwitchNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/SwitchNode.cs index 16f403c57..4c13132b3 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/SwitchNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/SwitchNode.cs @@ -4,7 +4,6 @@ public class SwitchNode : Node { private static readonly CoreProperty SwitchProperty = ConfigureProperty(o => o.Switch) .DefaultValue(false) - .SerializeName("switch") .Register(); private readonly OutputSocket _outputSocket; private readonly InputSocket _switchSocket; diff --git a/src/Beutl.ProjectSystem/NodeTree/SetterPropertyImpl.cs b/src/Beutl.ProjectSystem/NodeTree/SetterPropertyImpl.cs index 999e66f79..bcd71e498 100644 --- a/src/Beutl.ProjectSystem/NodeTree/SetterPropertyImpl.cs +++ b/src/Beutl.ProjectSystem/NodeTree/SetterPropertyImpl.cs @@ -84,18 +84,17 @@ public void SetValue(T? value) public void WriteToJson(ref JsonNode json) { - CorePropertyMetadata? metadata = Property.GetMetadata(ImplementedType); - json["property"] = metadata.SerializeName ?? Property.Name; - json["target"] = TypeFormat.ToString(ImplementedType); + json[nameof(Property)] = Property.Name; + json["Target"] = TypeFormat.ToString(ImplementedType); - json["setter"] = StyleSerializer.ToJson(Setter, ImplementedType).Item2; + json[nameof(Setter)] = StyleSerializer.ToJson(Setter, ImplementedType).Item2; } public void ReadFromJson(JsonNode json) { if (json is JsonObject obj) { - if (obj.TryGetPropertyValue("setter", out JsonNode? setterNode) + if (obj.TryGetPropertyValue(nameof(Setter), out JsonNode? setterNode) && setterNode != null) { if (StyleSerializer.ToSetter(setterNode, Property.Name, ImplementedType) is Setter setter) diff --git a/src/Beutl.ProjectSystem/Operation/SourceOperator.cs b/src/Beutl.ProjectSystem/Operation/SourceOperator.cs index d39e9c077..ced6a55f3 100644 --- a/src/Beutl.ProjectSystem/Operation/SourceOperator.cs +++ b/src/Beutl.ProjectSystem/Operation/SourceOperator.cs @@ -22,8 +22,6 @@ static SourceOperator() IsEnabledProperty = ConfigureProperty(nameof(IsEnabled)) .Accessor(o => o.IsEnabled, (o, v) => o.IsEnabled = v) .DefaultValue(true) - .PropertyFlags(PropertyFlags.NotifyChanged) - .SerializeName("is-enabled") .Register(); } diff --git a/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs b/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs index 5fb775c1c..09752ef18 100644 --- a/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs +++ b/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs @@ -1,4 +1,5 @@ using System.Collections.Specialized; +using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Reactive; using System.Reactive.Linq; @@ -37,47 +38,32 @@ static Layer() { StartProperty = ConfigureProperty(nameof(Start)) .Accessor(o => o.Start, (o, v) => o.Start = v) - .Display(Strings.StartTime) - .PropertyFlags(PropertyFlags.NotifyChanged) - .SerializeName("start") .Register(); LengthProperty = ConfigureProperty(nameof(Length)) .Accessor(o => o.Length, (o, v) => o.Length = v) - .Display(Strings.DurationTime) - .PropertyFlags(PropertyFlags.NotifyChanged) - .SerializeName("length") .Register(); ZIndexProperty = ConfigureProperty(nameof(ZIndex)) .Accessor(o => o.ZIndex, (o, v) => o.ZIndex = v) - .PropertyFlags(PropertyFlags.NotifyChanged) - .SerializeName("zIndex") .Register(); AccentColorProperty = ConfigureProperty(nameof(AccentColor)) .DefaultValue(Colors.Teal) - .PropertyFlags(PropertyFlags.NotifyChanged) - .SerializeName("accentColor") .Register(); IsEnabledProperty = ConfigureProperty(nameof(IsEnabled)) .Accessor(o => o.IsEnabled, (o, v) => o.IsEnabled = v) .DefaultValue(true) - .PropertyFlags(PropertyFlags.NotifyChanged) - .SerializeName("isEnabled") .Register(); AllowOutflowProperty = ConfigureProperty(nameof(AllowOutflow)) .Accessor(o => o.AllowOutflow, (o, v) => o.AllowOutflow = v) .DefaultValue(false) - .PropertyFlags(PropertyFlags.NotifyChanged) - .SerializeName("allowOutflow") .Register(); SpanProperty = ConfigureProperty(nameof(Span)) .Accessor(o => o.Span, null) - .PropertyFlags(PropertyFlags.NotifyChanged) .Register(); OperationProperty = ConfigureProperty(nameof(Operation)) @@ -91,12 +77,8 @@ static Layer() UseNodeProperty = ConfigureProperty(nameof(UseNode)) .Accessor(o => o.UseNode, (o, v) => o.UseNode = v) .DefaultValue(false) - .PropertyFlags(PropertyFlags.NotifyChanged) - .SerializeName("useNode") .Register(); - NameProperty.OverrideMetadata(new CorePropertyMetadata("name")); - ZIndexProperty.Changed.Subscribe(args => { if (args.Sender is Layer layer && layer.HierarchicalParent is Scene { Renderer: { IsDisposed: false } renderer }) @@ -194,12 +176,14 @@ private void UpdateName() } // 0以上 + [Display(Name = nameof(Strings.StartTime), ResourceType = typeof(Strings))] public TimeSpan Start { get => _start; set => SetAndRaise(StartProperty, ref _start, value); } + [Display(Name = nameof(Strings.DurationTime), ResourceType = typeof(Strings))] public TimeSpan Length { get => _length; @@ -260,13 +244,13 @@ public override void ReadFromJson(JsonNode json) if (json is JsonObject jobject) { - if (jobject.TryGetPropertyValue("operation", out JsonNode? operationNode) + if (jobject.TryGetPropertyValue(nameof(Operation), out JsonNode? operationNode) && operationNode != null) { Operation.ReadFromJson(operationNode); } - if (jobject.TryGetPropertyValue("nodeTree", out JsonNode? nodeTreeNode) + if (jobject.TryGetPropertyValue(nameof(NodeTree), out JsonNode? nodeTreeNode) && nodeTreeNode != null) { NodeTree.ReadFromJson(nodeTreeNode); @@ -282,11 +266,11 @@ public override void WriteToJson(ref JsonNode json) { JsonNode operationNode = new JsonObject(); Operation.WriteToJson(ref operationNode); - jobject["operation"] = operationNode; + jobject[nameof(Operation)] = operationNode; JsonNode nodeTreeNode = new JsonObject(); NodeTree.WriteToJson(ref nodeTreeNode); - jobject["nodeTree"] = nodeTreeNode; + jobject[nameof(NodeTree)] = nodeTreeNode; } } diff --git a/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs b/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs index 4433e9dc5..411bf9201 100644 --- a/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs +++ b/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs @@ -1,4 +1,5 @@ using System.Collections.Specialized; +using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Nodes; @@ -49,12 +50,10 @@ static Scene() { WidthProperty = ConfigureProperty(nameof(Width)) .Accessor(o => o.Width) - .PropertyFlags(PropertyFlags.NotifyChanged) .Register(); HeightProperty = ConfigureProperty(nameof(Height)) .Accessor(o => o.Height) - .PropertyFlags(PropertyFlags.NotifyChanged) .Register(); ChildrenProperty = ConfigureProperty(nameof(Children)) @@ -63,29 +62,20 @@ static Scene() DurationProperty = ConfigureProperty(nameof(Duration)) .Accessor(o => o.Duration, (o, v) => o.Duration = v) - .Display(Strings.DurationTime) - .PropertyFlags(PropertyFlags.NotifyChanged) - .SerializeName("duration") .Register(); CurrentFrameProperty = ConfigureProperty(nameof(CurrentFrame)) .Accessor(o => o.CurrentFrame, (o, v) => o.CurrentFrame = v) - .PropertyFlags(PropertyFlags.NotifyChanged) - .SerializeName("currentFrame") .Register(); PreviewOptionsProperty = ConfigureProperty(nameof(PreviewOptions)) .Accessor(o => o.PreviewOptions, (o, v) => o.PreviewOptions = v) - .PropertyFlags(PropertyFlags.NotifyChanged) .Register(); RendererProperty = ConfigureProperty(nameof(Renderer)) .Accessor(o => o.Renderer, (o, v) => o.Renderer = v) - .PropertyFlags(PropertyFlags.NotifyChanged) .Register(); - NameProperty.OverrideMetadata(new CorePropertyMetadata(serializeName: "name")); - CurrentFrameProperty.Changed.Subscribe(e => { if (e.Sender is Scene scene) @@ -99,6 +89,7 @@ static Scene() public int Height => Renderer.Graphics.Size.Height; + [Display(Name = nameof(Strings.DurationTime), ResourceType = typeof(Strings))] public TimeSpan Duration { get => _duration; @@ -125,18 +116,21 @@ public TimeSpan CurrentFrame } } + [ShouldSerialize(false)] public Layers Children { get => _children; set => _children.Replace(value); } + [ShouldSerialize(false)] public PreviewOptions? PreviewOptions { get => _previewOptions; set => SetAndRaise(PreviewOptionsProperty, ref _previewOptions, value); } + [ShouldSerialize(false)] public IRenderer Renderer { get => _renderer; @@ -246,8 +240,8 @@ static void Process(Func add, JsonNode node, List list) if (json is JsonObject jobject) { - if (jobject.TryGetPropertyValue("width", out JsonNode? widthNode) - && jobject.TryGetPropertyValue("height", out JsonNode? heightNode) + if (jobject.TryGetPropertyValue(nameof(Width), out JsonNode? widthNode) + && jobject.TryGetPropertyValue(nameof(Height), out JsonNode? heightNode) && widthNode != null && heightNode != null && widthNode.AsValue().TryGetValue(out int width) @@ -256,20 +250,20 @@ static void Process(Func add, JsonNode node, List list) Initialize(width, height); } - if (jobject.TryGetPropertyValue("layers", out JsonNode? layersNode) + if (jobject.TryGetPropertyValue(nameof(Layers), out JsonNode? layersNode) && layersNode is JsonObject layersJson) { var matcher = new Matcher(); var directory = new DirectoryInfoWrapper(new DirectoryInfo(Path.GetDirectoryName(FileName)!)); // 含めるクリップ - if (layersJson.TryGetPropertyValue("include", out JsonNode? includeNode)) + if (layersJson.TryGetPropertyValue("Include", out JsonNode? includeNode)) { Process(matcher.AddInclude, includeNode!, _includeLayers); } // 除外するクリップ - if (layersJson.TryGetPropertyValue("exclude", out JsonNode? excludeNode)) + if (layersJson.TryGetPropertyValue("Exclude", out JsonNode? excludeNode)) { Process(matcher.AddExclude, excludeNode!, _excludeLayers); } @@ -311,18 +305,18 @@ static void Process(JsonObject jobject, string jsonName, List list) base.WriteToJson(ref json); if (_renderer != null) { - json["width"] = _renderer.Graphics.Size.Width; - json["height"] = _renderer.Graphics.Size.Height; + json[nameof(Width)] = _renderer.Graphics.Size.Width; + json[nameof(Height)] = _renderer.Graphics.Size.Height; } var layersNode = new JsonObject(); UpdateInclude(); - Process(layersNode, "include", _includeLayers); - Process(layersNode, "exclude", _excludeLayers); + Process(layersNode, "Include", _includeLayers); + Process(layersNode, "Exclude", _excludeLayers); - json["layers"] = layersNode; + json["Layers"] = layersNode; } protected override void SaveCore(string filename) diff --git a/src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs b/src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs index 333ed7c75..2c2ceb96a 100644 --- a/src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs +++ b/src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs @@ -124,7 +124,7 @@ protected BaseEditorViewModel(IAbstractProperty property) public ReactivePropertySlim KeyFrameIndex { get; } = new(); - public bool IsAnimatable => WrappedProperty.Property.GetMetadata(WrappedProperty.ImplementedType).PropertyFlags.HasFlag(PropertyFlags.Animatable); + public bool IsAnimatable => WrappedProperty is IAbstractAnimatableProperty; public bool IsStylingSetter => WrappedProperty is IStylingSetterPropertyImpl; diff --git a/src/Beutl/ViewModels/Editors/BrushEditorViewModel.cs b/src/Beutl/ViewModels/Editors/BrushEditorViewModel.cs index c75cda01d..dd610b9e8 100644 --- a/src/Beutl/ViewModels/Editors/BrushEditorViewModel.cs +++ b/src/Beutl/ViewModels/Editors/BrushEditorViewModel.cs @@ -38,7 +38,7 @@ public BrushEditorViewModel(IAbstractProperty property) .DisposeWith(Disposables); ChildContext = Value.Select(v => v as ICoreObject) - .Select(x => x != null ? new PropertiesEditorViewModel(x, m => m.PropertyFlags.HasFlag(PropertyFlags.Designable)) : null) + .Select(x => x != null ? new PropertiesEditorViewModel(x, m => m.Browsable) : null) .Do(AcceptChildren) .ToReadOnlyReactivePropertySlim() .DisposeWith(Disposables); diff --git a/src/Beutl/ViewModels/Editors/PropertiesEditorViewModel.cs b/src/Beutl/ViewModels/Editors/PropertiesEditorViewModel.cs index 61468c778..b29c26f6a 100644 --- a/src/Beutl/ViewModels/Editors/PropertiesEditorViewModel.cs +++ b/src/Beutl/ViewModels/Editors/PropertiesEditorViewModel.cs @@ -50,7 +50,7 @@ private void InitializeCoreObject(ICoreObject obj, Predicate(objType); - Type wtype = isAnimatable && metadata.PropertyFlags.HasFlag(PropertyFlags.Animatable) ? animatableWrapperType : wrapperType; + Type wtype = isAnimatable ? animatableWrapperType : wrapperType; Type wrapperGType = wtype.MakeGenericType(item.PropertyType); tmp[i] = (IAbstractProperty)Activator.CreateInstance(wrapperGType, item, obj)!; } diff --git a/src/Beutl/Views/Tools/StyleEditor.axaml.cs b/src/Beutl/Views/Tools/StyleEditor.axaml.cs index 60661dc26..c7bb3cc19 100644 --- a/src/Beutl/Views/Tools/StyleEditor.axaml.cs +++ b/src/Beutl/Views/Tools/StyleEditor.axaml.cs @@ -41,9 +41,7 @@ protected override void OnDataContextChanged(EventArgs e) var mismatches = new List(style.Setters.Count); foreach (Styling.ISetter item in style.Setters) { - if (!item.Property.OwnerType.IsAssignableTo(type) - || (item.Property.TryGetMetadata(type, out CorePropertyMetadata? metadata) - && !metadata.PropertyFlags.HasFlag(PropertyFlags.Styleable))) + if (!item.Property.OwnerType.IsAssignableTo(type)) { mismatches.Add(item); } @@ -87,9 +85,7 @@ private async void Add_Click(object? sender, RoutedEventArgs e) { if (DataContext is StyleEditorViewModel viewModel && viewModel.Style.Value is Styling.Style style) { - CoreProperty[] props = PropertyRegistry.GetRegistered(style.TargetType) - .Where(x => x.GetMetadata(style.TargetType).PropertyFlags.HasFlag(PropertyFlags.Styleable)) - .ToArray(); + CoreProperty[] props = PropertyRegistry.GetRegistered(style.TargetType).ToArray(); var selectedItem = new ReactivePropertySlim(); var listBox = new ListBox From e1856b18f933f0fe0392c873ca05af9ebabdafe9 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Tue, 28 Mar 2023 22:52:56 +0900 Subject: [PATCH 09/84] =?UTF-8?q?=E6=95=B0=E5=80=A4=E5=9E=8B=E4=BB=A5?= =?UTF-8?q?=E5=A4=96=E3=81=AESize,=20Point=E3=81=AA=E3=81=A9=E3=81=AE?= =?UTF-8?q?=E7=AF=84=E5=9B=B2=E3=83=90=E3=83=AA=E3=83=87=E3=83=BC=E3=82=B7?= =?UTF-8?q?=E3=83=A7=E3=83=B3=E3=82=92=E4=BD=BF=E3=81=88=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Core/CoreObject.cs | 2 + src/Beutl.Core/CorePropertyMetadata`1.cs | 29 +++-- src/Beutl.Core/Hierarchy/Hierarchical.cs | 1 + src/Beutl.Core/IVectorConvertible.cs | 14 ++ .../TupleRangeDataAnnotationValidater.cs | 51 ++++++++ .../Converters/ColorConverter.cs | 123 ++++++++++++++++++ .../Converters/CornerRadiusConverter.cs | 85 ++++++++++++ .../Converters/FontFamilyConverter.cs | 24 ++++ .../Converters/MatrixConverter.cs | 69 ++++++++++ .../Converters/PixelPointConverter.cs | 110 ++++++++++++++++ .../Converters/PixelRectConverter.cs | 110 ++++++++++++++++ .../Converters/PixelSizeConverter.cs | 120 +++++++++++++++++ .../Converters/PointConverter.cs | 110 ++++++++++++++++ .../Converters/RectConverter.cs | 110 ++++++++++++++++ .../Converters/RelativePointConverter.cs | 25 ++++ .../Converters/SizeConverter.cs | 120 +++++++++++++++++ .../Converters/SizeJsonConverter.cs | 5 +- .../Converters/ThicknessConverter.cs | 85 ++++++++++++ .../Converters/ThicknessJsonConverter.cs | 5 +- .../Converters/VectorConverter.cs | 100 ++++++++++++++ src/Beutl.Graphics/Graphics/Drawable.cs | 17 ++- .../Graphics/Effects/InnerShadow.cs | 1 + .../Graphics/Effects/OpenCv/Blur.cs | 1 + src/Beutl.Graphics/Graphics/Filters/Blur.cs | 1 + .../Graphics/Filters/DropShadow.cs | 1 + src/Beutl.Graphics/Graphics/Matrix.cs | 32 ++++- src/Beutl.Graphics/Graphics/Point.cs | 20 ++- src/Beutl.Graphics/Graphics/Rect.cs | 22 +++- src/Beutl.Graphics/Graphics/RelativePoint.cs | 5 +- .../Graphics/Shapes/RoundedRect.cs | 1 + src/Beutl.Graphics/Graphics/Size.cs | 21 ++- src/Beutl.Graphics/Graphics/Thickness.cs | 26 +++- src/Beutl.Graphics/Graphics/Vector.cs | 22 +++- src/Beutl.Graphics/Media/Color.cs | 4 +- src/Beutl.Graphics/Media/CornerRadius.cs | 21 ++- src/Beutl.Graphics/Media/FontFamily.cs | 4 +- src/Beutl.Graphics/Media/PixelPoint.cs | 20 ++- src/Beutl.Graphics/Media/PixelRect.cs | 22 +++- src/Beutl.Graphics/Media/PixelSize.cs | 20 ++- .../Rendering/RenderLayerSpan.cs | 1 + src/Beutl.Graphics/Styling/Styleable.cs | 2 + src/Beutl.Utilities/RefStringTokenizer.cs | 72 +++++++--- ...erter.cs => AvaloniaThicknessConverter.cs} | 4 +- src/Beutl/Views/InlineAnimationLayer.axaml | 2 +- 44 files changed, 1570 insertions(+), 70 deletions(-) create mode 100644 src/Beutl.Core/IVectorConvertible.cs create mode 100644 src/Beutl.Core/Validation/TupleRangeDataAnnotationValidater.cs create mode 100644 src/Beutl.Graphics/Converters/ColorConverter.cs create mode 100644 src/Beutl.Graphics/Converters/CornerRadiusConverter.cs create mode 100644 src/Beutl.Graphics/Converters/FontFamilyConverter.cs create mode 100644 src/Beutl.Graphics/Converters/MatrixConverter.cs create mode 100644 src/Beutl.Graphics/Converters/PixelPointConverter.cs create mode 100644 src/Beutl.Graphics/Converters/PixelRectConverter.cs create mode 100644 src/Beutl.Graphics/Converters/PixelSizeConverter.cs create mode 100644 src/Beutl.Graphics/Converters/PointConverter.cs create mode 100644 src/Beutl.Graphics/Converters/RectConverter.cs create mode 100644 src/Beutl.Graphics/Converters/RelativePointConverter.cs create mode 100644 src/Beutl.Graphics/Converters/SizeConverter.cs create mode 100644 src/Beutl.Graphics/Converters/ThicknessConverter.cs create mode 100644 src/Beutl.Graphics/Converters/VectorConverter.cs rename src/Beutl/Converters/{ThicknessConverter.cs => AvaloniaThicknessConverter.cs} (90%) diff --git a/src/Beutl.Core/CoreObject.cs b/src/Beutl.Core/CoreObject.cs index 2db5d3601..1d0938f02 100644 --- a/src/Beutl.Core/CoreObject.cs +++ b/src/Beutl.Core/CoreObject.cs @@ -78,12 +78,14 @@ static CoreObject() .Register(); } + [Browsable(false)] public Guid Id { get => GetValue(IdProperty); set => SetValue(IdProperty, value); } + [Browsable(false)] public string Name { get => GetValue(NameProperty); diff --git a/src/Beutl.Core/CorePropertyMetadata`1.cs b/src/Beutl.Core/CorePropertyMetadata`1.cs index 38be34628..12ef9029c 100644 --- a/src/Beutl.Core/CorePropertyMetadata`1.cs +++ b/src/Beutl.Core/CorePropertyMetadata`1.cs @@ -39,24 +39,29 @@ private static IValidator ConvertValidator(ValidationAttribute att) case RangeAttribute rangeAttribute: Type propType = typeof(T); Type[] interfaces = propType.GetInterfaces(); - if (propType.IsValueType - && interfaces.Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(INumber<>)) - && interfaces.Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IMinMaxValue<>))) + if (propType.IsValueType) { - Type validatorType = typeof(RangeDataAnnotationValidater<>); - if (Activator.CreateInstance(validatorType, rangeAttribute) is IValidator validator) + if (interfaces.Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(INumber<>)) + && interfaces.Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IMinMaxValue<>))) { - return validator; + Type validatorType = typeof(RangeDataAnnotationValidater<>).MakeGenericType(propType); + if (Activator.CreateInstance(validatorType, rangeAttribute) is IValidator validator) + { + return validator; + } } - else + else if (interfaces.FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ITupleConvertible<,>)) is { } interfaceType) { - goto default; + Type validatorType = typeof(TupleRangeDataAnnotationValidater<,>); + validatorType = validatorType.MakeGenericType(interfaceType.GetGenericArguments()); + if (Activator.CreateInstance(validatorType, rangeAttribute) is IValidator validator) + { + return validator; + } } } - else - { - goto default; - } + + goto default; default: return new DataAnnotationValidater(att); diff --git a/src/Beutl.Core/Hierarchy/Hierarchical.cs b/src/Beutl.Core/Hierarchy/Hierarchical.cs index 8f96c7c4e..b2b2004a6 100644 --- a/src/Beutl.Core/Hierarchy/Hierarchical.cs +++ b/src/Beutl.Core/Hierarchy/Hierarchical.cs @@ -29,6 +29,7 @@ public Hierarchical() _hierarchicalChildren.CollectionChanged += HierarchicalChildrenCollectionChanged; } + [ShouldSerialize(false)] public IHierarchical? HierarchicalParent { get => _parent; diff --git a/src/Beutl.Core/IVectorConvertible.cs b/src/Beutl.Core/IVectorConvertible.cs new file mode 100644 index 000000000..6553182ae --- /dev/null +++ b/src/Beutl.Core/IVectorConvertible.cs @@ -0,0 +1,14 @@ +using System.Numerics; + +namespace Beutl; + +public interface ITupleConvertible + where TSelf : struct + where T : unmanaged, INumber +{ + static abstract int TupleLength { get; } + + static abstract void ConvertTo(TSelf self, Span tuple); + + static abstract void ConvertFrom(Span tuple, out TSelf self); +} diff --git a/src/Beutl.Core/Validation/TupleRangeDataAnnotationValidater.cs b/src/Beutl.Core/Validation/TupleRangeDataAnnotationValidater.cs new file mode 100644 index 000000000..b1c852b24 --- /dev/null +++ b/src/Beutl.Core/Validation/TupleRangeDataAnnotationValidater.cs @@ -0,0 +1,51 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Numerics; + +namespace Beutl.Validation; + +public sealed class TupleRangeDataAnnotationValidater : RangeValidator + where TTuple : struct, ITupleConvertible + where TNumber : unmanaged, INumber +{ + public TupleRangeDataAnnotationValidater(RangeAttribute attribute) + { + Attribute = attribute; + if (Attribute.OperandType == typeof(TTuple) + && Attribute.Maximum is string maximumStr + && Attribute.Minimum is string minimumStr) + { + TypeConverter converter = TypeDescriptor.GetConverter(Attribute.OperandType); + Maximum = (TTuple)converter.ConvertFromInvariantString(maximumStr)!; + Minimum = (TTuple)converter.ConvertFromInvariantString(minimumStr)!; + } + } + + public RangeAttribute? Attribute { get; set; } + + public override bool TryCoerce(ValidationContext context, ref TTuple value) + { + int length = TTuple.TupleLength; + Span max = stackalloc TNumber[length]; + Span min = stackalloc TNumber[length]; + Span tuple = stackalloc TNumber[length]; + + TTuple.ConvertTo(Maximum, max); + TTuple.ConvertTo(Minimum, min); + TTuple.ConvertTo(value, tuple); + + for (int i = 0; i < length; i++) + { + tuple[i] = TNumber.Clamp(tuple[i], min[i], max[i]); + } + + TTuple.ConvertFrom(tuple, out value); + + return true; + } + + public override string? Validate(ValidationContext context, TTuple value) + { + return null; + } +} diff --git a/src/Beutl.Graphics/Converters/ColorConverter.cs b/src/Beutl.Graphics/Converters/ColorConverter.cs new file mode 100644 index 000000000..22f1bcaf1 --- /dev/null +++ b/src/Beutl.Graphics/Converters/ColorConverter.cs @@ -0,0 +1,123 @@ +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + +using Beutl.Media; + +namespace Beutl.Converters; + +public sealed class ColorConverter : TypeConverter +{ + public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType) + { + return destinationType == typeof(float[]) + || destinationType == typeof(byte[]) + || destinationType == typeof(Tuple) + || destinationType == typeof(Tuple) + || destinationType == typeof(int) + || destinationType == typeof(uint) + || destinationType == typeof(IBrush) + || destinationType == typeof(ISolidColorBrush) + || destinationType == typeof(SolidColorBrush) + || base.CanConvertTo(context, destinationType); + } + + public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) + { + if (value is Color color) + { + if (destinationType == typeof(float[])) + { + return new float[] + { + color.A / 255f, color.R / 255f, color.G / 255f, color.B / 255f, + }; + } + else if (destinationType == typeof(byte[])) + { + return new byte[] + { + color.A, color.R, color.G, color.B, + }; + } + else if (destinationType == typeof(Tuple)) + { + return new Tuple(color.A / 255f, color.R / 255f, color.G / 255f, color.B / 255f); + } + else if (destinationType == typeof(Tuple)) + { + return new Tuple(color.A, color.R, color.G, color.B); + } + else if (destinationType == typeof(int)) + { + return color.ToInt32(); + } + else if (destinationType == typeof(uint)) + { + return color.ToUint32(); + } + else if (destinationType == typeof(IBrush) + || destinationType == typeof(ISolidColorBrush)) + { + return color.ToImmutableBrush(); + } + else if (destinationType == typeof(SolidColorBrush)) + { + return color.ToBrush(); + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) + { + return sourceType == typeof(float[]) + || sourceType == typeof(byte[]) + || sourceType == typeof(Tuple) + || sourceType == typeof(Tuple) + || sourceType == typeof(int) + || sourceType == typeof(uint) + || sourceType == typeof(ISolidColorBrush) + || sourceType == typeof(SolidColorBrush) + || sourceType == typeof(string); + } + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + if (value is float[] { Length: >= 4 } array) + { + return Color.FromArgb((byte)(array[0] * 255), (byte)(array[1] * 255), (byte)(array[2] * 255), (byte)(array[3] * 255)); + } + else if (value is byte[] { Length: >= 4 } array2) + { + return Color.FromArgb(array2[0], array2[1], array2[2], array2[3]); + } + else if (value is Tuple tuple1) + { + return Color.FromArgb((byte)(tuple1.Item1 * 255), (byte)(tuple1.Item2 * 255), (byte)(tuple1.Item3 * 255), (byte)(tuple1.Item4 * 255)); + } + else if (value is Tuple tuple2) + { + return Color.FromArgb(tuple2.Item1, tuple2.Item2, tuple2.Item3, tuple2.Item4); + } + else if (value is int @int) + { + return Color.FromInt32(@int); + } + else if (value is uint @uint) + { + return Color.FromUInt32(@uint); + } + else if (value is ISolidColorBrush solidColorBrush) + { + return solidColorBrush.Color; + } + else if (value is string str) + { + return Color.Parse(str); + } + + return base.ConvertFrom(context, culture, value); + } +} diff --git a/src/Beutl.Graphics/Converters/CornerRadiusConverter.cs b/src/Beutl.Graphics/Converters/CornerRadiusConverter.cs new file mode 100644 index 000000000..510865e1d --- /dev/null +++ b/src/Beutl.Graphics/Converters/CornerRadiusConverter.cs @@ -0,0 +1,85 @@ +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + +using Beutl.Media; + +namespace Beutl.Converters; + +public sealed class CornerRadiusConverter : TypeConverter +{ + public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType) + { + return destinationType == typeof(float[]) + || destinationType == typeof(Tuple) + || destinationType == typeof(Tuple) + || base.CanConvertTo(context, destinationType); + } + + public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) + { + if (value is CornerRadius cr) + { + if (destinationType == typeof(float[])) + { + return new float[] { cr.TopLeft, cr.TopRight, cr.BottomRight, cr.BottomLeft }; + } + else if (destinationType == typeof(Tuple)) + { + return new Tuple(cr.TopLeft, cr.TopRight, cr.BottomRight, cr.BottomLeft); + } + else if (destinationType == typeof(Tuple)) + { + return new Tuple(cr.TopLeft, cr.BottomLeft); + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + + public override bool CanConvertFrom(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? sourceType) + { + return sourceType == typeof(float[]) + || sourceType == typeof(float) + || sourceType == typeof(Tuple) + || sourceType == typeof(Tuple) + || sourceType == typeof(string); + } + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + if (value is float[] array) + { + if (array.Length == 1) + { + return new CornerRadius(array[0]); + } + else if (array.Length == 2) + { + return new CornerRadius(array[0], array[1]); + } + else if (array.Length == 4) + { + return new CornerRadius(array[0], array[1], array[2], array[3]); + } + } + else if (value is float f) + { + return new CornerRadius(f); + } + else if (value is Tuple t1) + { + return new CornerRadius(t1.Item1, t1.Item2); + } + else if (value is Tuple t2) + { + return new CornerRadius(t2.Item1, t2.Item2, t2.Item3, t2.Item4); + } + else if (value is string str) + { + return CornerRadius.Parse(str); + } + + return base.ConvertFrom(context, culture, value); + } +} diff --git a/src/Beutl.Graphics/Converters/FontFamilyConverter.cs b/src/Beutl.Graphics/Converters/FontFamilyConverter.cs new file mode 100644 index 000000000..41ba27e75 --- /dev/null +++ b/src/Beutl.Graphics/Converters/FontFamilyConverter.cs @@ -0,0 +1,24 @@ +using System.ComponentModel; +using System.Globalization; + +using Beutl.Media; + +namespace Beutl.Converters; + +public sealed class FontFamilyConverter : TypeConverter +{ + public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) + { + return sourceType == typeof(string); + } + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + if (value is string str) + { + return new FontFamily(str); + } + + return base.ConvertFrom(context, culture, value); + } +} diff --git a/src/Beutl.Graphics/Converters/MatrixConverter.cs b/src/Beutl.Graphics/Converters/MatrixConverter.cs new file mode 100644 index 000000000..fe6408dd5 --- /dev/null +++ b/src/Beutl.Graphics/Converters/MatrixConverter.cs @@ -0,0 +1,69 @@ +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + +using Beutl.Graphics; + +namespace Beutl.Converters; + +public sealed class MatrixConverter : TypeConverter +{ + public override bool CanConvertFrom(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? sourceType) + { + return sourceType == typeof(float[]) + || sourceType == typeof(string); + } + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + if (value is float[] array) + { + if (array.Length == 6) + { + return new Matrix + ( + array[0], array[1], + array[2], array[3], + array[4], array[5] + ); + } + else if (array.Length >= 9) + { + return new Matrix + ( + array[0], array[1], array[2], + array[3], array[4], array[5], + array[6], array[7], array[8] + ); + } + } + else if (value is string str) + { + return Matrix.Parse(str); + } + + return base.ConvertFrom(context, culture, value); + } + + public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType) + { + return destinationType == typeof(float[]) + || base.CanConvertTo(context, destinationType); + } + + public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) + { + if (value is Matrix matrix + && destinationType == typeof(float[])) + { + return new float[] + { + matrix.M11, matrix.M12, matrix.M13, + matrix.M21, matrix.M22, matrix.M23, + matrix.M31, matrix.M32, matrix.M33, + }; + } + + return base.ConvertTo(context, culture, value, destinationType); + } +} diff --git a/src/Beutl.Graphics/Converters/PixelPointConverter.cs b/src/Beutl.Graphics/Converters/PixelPointConverter.cs new file mode 100644 index 000000000..f5af5d92f --- /dev/null +++ b/src/Beutl.Graphics/Converters/PixelPointConverter.cs @@ -0,0 +1,110 @@ +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + +using Beutl.Graphics; +using Beutl.Media; + +namespace Beutl.Converters; + +public sealed class PixelPointConverter : TypeConverter +{ + public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType) + { + return destinationType == typeof(int[]) + || destinationType == typeof(Tuple) + || destinationType == typeof(Point) + || destinationType == typeof(Rect) + || destinationType == typeof(Size) + || destinationType == typeof(PixelRect) + || destinationType == typeof(PixelSize) + || base.CanConvertTo(context, destinationType); + } + + public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) + { + if (value is PixelPoint pxpoint) + { + if (destinationType == typeof(int[])) + { + return new int[] { pxpoint.X, pxpoint.Y }; + } + else if (destinationType == typeof(Tuple)) + { + return new Tuple(pxpoint.X, pxpoint.Y); + } + else if (destinationType == typeof(Point)) + { + return new Point(pxpoint.X, pxpoint.Y); + } + else if (destinationType == typeof(Rect)) + { + return new Rect(pxpoint.X, pxpoint.Y, 0, 0); + } + else if (destinationType == typeof(Size)) + { + return new Size(pxpoint.X, pxpoint.Y); + } + else if (destinationType == typeof(PixelRect)) + { + return new PixelRect(pxpoint.X, pxpoint.Y, 0, 0); + } + else if (destinationType == typeof(PixelSize)) + { + return new PixelSize(pxpoint.X, pxpoint.Y); + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + + public override bool CanConvertFrom(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? sourceType) + { + return sourceType == typeof(int[]) + || sourceType == typeof(Tuple) + || sourceType == typeof(Point) + || sourceType == typeof(Rect) + || sourceType == typeof(Size) + || sourceType == typeof(PixelRect) + || sourceType == typeof(PixelSize) + || sourceType == typeof(string); + } + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + if (value is int[] { Length: >= 2 } array) + { + return new PixelPoint(array[0], array[1]); + } + else if (value is Tuple tuple) + { + return new PixelPoint(tuple.Item1, tuple.Item2); + } + else if (value is Point point) + { + return new PixelPoint((int)point.X, (int)point.Y); + } + else if (value is Rect rect) + { + return new PixelPoint((int)rect.X, (int)rect.Y); + } + else if (value is Size size) + { + return new PixelPoint((int)size.Width, (int)size.Height); + } + else if (value is PixelRect pxrect) + { + return new PixelPoint(pxrect.X, pxrect.Y); + } + else if (value is PixelSize pxsize) + { + return new PixelPoint(pxsize.Width, pxsize.Height); + } + else if (value is string str) + { + return PixelPoint.Parse(str); + } + + return base.ConvertFrom(context, culture, value); + } +} diff --git a/src/Beutl.Graphics/Converters/PixelRectConverter.cs b/src/Beutl.Graphics/Converters/PixelRectConverter.cs new file mode 100644 index 000000000..9827a4587 --- /dev/null +++ b/src/Beutl.Graphics/Converters/PixelRectConverter.cs @@ -0,0 +1,110 @@ +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + +using Beutl.Graphics; +using Beutl.Media; + +namespace Beutl.Converters; + +public sealed class PixelRectConverter : TypeConverter +{ + public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType) + { + return destinationType == typeof(int[]) + || destinationType == typeof(Tuple) + || destinationType == typeof(Point) + || destinationType == typeof(Rect) + || destinationType == typeof(Size) + || destinationType == typeof(PixelPoint) + || destinationType == typeof(PixelSize) + || base.CanConvertTo(context, destinationType); + } + + public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) + { + if (value is PixelRect pxrect) + { + if (destinationType == typeof(int[])) + { + return new int[] { pxrect.X, pxrect.Y, pxrect.Width, pxrect.Height }; + } + else if (destinationType == typeof(Tuple)) + { + return new Tuple(pxrect.X, pxrect.Y, pxrect.Width, pxrect.Width); + } + else if (destinationType == typeof(Point)) + { + return new Point(pxrect.X, pxrect.Y); + } + else if (destinationType == typeof(Rect)) + { + return new Rect(pxrect.X, pxrect.Y, pxrect.Width, pxrect.Height); + } + else if (destinationType == typeof(Size)) + { + return new Size(pxrect.Width, pxrect.Height); + } + else if (destinationType == typeof(PixelPoint)) + { + return new PixelPoint(pxrect.X, pxrect.Y); + } + else if (destinationType == typeof(PixelSize)) + { + return new PixelSize(pxrect.Width, pxrect.Height); + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + + public override bool CanConvertFrom(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? sourceType) + { + return sourceType == typeof(float[]) + || sourceType == typeof(Tuple) + || sourceType == typeof(Point) + || sourceType == typeof(Rect) + || sourceType == typeof(Size) + || sourceType == typeof(PixelPoint) + || sourceType == typeof(PixelSize) + || sourceType == typeof(string); + } + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + if (value is int[] { Length: >= 4 } array) + { + return new PixelRect(array[0], array[1], array[2], array[3]); + } + else if (value is Tuple tuple) + { + return new PixelRect(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4); + } + else if (value is Point point) + { + return new PixelRect((int)point.X, (int)point.Y, 0, 0); + } + else if (value is Rect rect) + { + return new PixelRect((int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height); + } + else if (value is Size size) + { + return new PixelRect(0,0, (int)size.Width, (int)size.Height); + } + else if (value is PixelPoint pxpoint) + { + return new PixelRect(pxpoint.X, pxpoint.Y, 0, 0); + } + else if (value is PixelSize pxsize) + { + return new PixelRect(0, 0, pxsize.Width, pxsize.Height); + } + else if (value is string str) + { + return PixelRect.Parse(str); + } + + return base.ConvertFrom(context, culture, value); + } +} diff --git a/src/Beutl.Graphics/Converters/PixelSizeConverter.cs b/src/Beutl.Graphics/Converters/PixelSizeConverter.cs new file mode 100644 index 000000000..8614ae984 --- /dev/null +++ b/src/Beutl.Graphics/Converters/PixelSizeConverter.cs @@ -0,0 +1,120 @@ +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + +using Beutl.Graphics; +using Beutl.Media; + +namespace Beutl.Converters; + +public sealed class PixelSizeConverter : TypeConverter +{ + public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType) + { + return destinationType == typeof(int[]) + || destinationType == typeof(Tuple) + || destinationType == typeof(Point) + || destinationType == typeof(Rect) + || destinationType == typeof(Vector) + || destinationType == typeof(PixelPoint) + || destinationType == typeof(PixelRect) + || destinationType == typeof(Size) + || base.CanConvertTo(context, destinationType); + } + + public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) + { + if (value is PixelSize pxsize) + { + if (destinationType == typeof(int[])) + { + return new int[] { pxsize.Width, pxsize.Height }; + } + else if (destinationType == typeof(Tuple)) + { + return new Tuple(pxsize.Width, pxsize.Width); + } + else if (destinationType == typeof(Point)) + { + return new Point(pxsize.Width, pxsize.Height); + } + else if (destinationType == typeof(Rect)) + { + return new Rect(0, 0, pxsize.Width, pxsize.Height); + } + else if (destinationType == typeof(Size)) + { + return new Size(pxsize.Width, pxsize.Height); + } + else if (destinationType == typeof(Vector)) + { + return new Vector(pxsize.Width, pxsize.Height); + } + else if (destinationType == typeof(PixelPoint)) + { + return new PixelPoint(pxsize.Width, pxsize.Height); + } + else if (destinationType == typeof(PixelRect)) + { + return new PixelRect(0, 0, pxsize.Width, pxsize.Height); + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + + public override bool CanConvertFrom(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? sourceType) + { + return sourceType == typeof(int[]) + || sourceType == typeof(Tuple) + || sourceType == typeof(Point) + || sourceType == typeof(Rect) + || sourceType == typeof(Size) + || sourceType == typeof(Vector) + || sourceType == typeof(PixelPoint) + || sourceType == typeof(PixelRect) + || sourceType == typeof(string); + } + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + if (value is int[] { Length: >= 2 } array) + { + return new PixelSize(array[0], array[1]); + } + else if (value is Tuple tuple) + { + return new PixelSize(tuple.Item1, tuple.Item2); + } + else if (value is Point point) + { + return new PixelSize((int)point.X, (int)point.Y); + } + else if (value is Rect rect) + { + return new PixelSize((int)rect.Width, (int)rect.Height); + } + else if (value is Size pxsize) + { + return new PixelSize((int)pxsize.Width, (int)pxsize.Height); + } + else if (value is Vector vector) + { + return new PixelSize((int)vector.X, (int)vector.Y); + } + else if (value is PixelPoint pxpoint) + { + return new PixelSize(pxpoint.X, pxpoint.Y); + } + else if (value is PixelRect pxrect) + { + return new PixelSize(pxrect.X, pxrect.Y); + } + else if (value is string str) + { + return PixelSize.Parse(str); + } + + return base.ConvertFrom(context, culture, value); + } +} diff --git a/src/Beutl.Graphics/Converters/PointConverter.cs b/src/Beutl.Graphics/Converters/PointConverter.cs new file mode 100644 index 000000000..4f9af300e --- /dev/null +++ b/src/Beutl.Graphics/Converters/PointConverter.cs @@ -0,0 +1,110 @@ +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + +using Beutl.Graphics; +using Beutl.Media; + +namespace Beutl.Converters; + +public sealed class PointConverter : TypeConverter +{ + public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType) + { + return destinationType == typeof(float[]) + || destinationType == typeof(Tuple) + || destinationType == typeof(Rect) + || destinationType == typeof(Size) + || destinationType == typeof(PixelPoint) + || destinationType == typeof(PixelRect) + || destinationType == typeof(PixelSize) + || base.CanConvertTo(context, destinationType); + } + + public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) + { + if (value is Point point) + { + if (destinationType == typeof(float[])) + { + return new float[] { point.X, point.Y }; + } + else if (destinationType == typeof(Tuple)) + { + return new Tuple(point.X, point.Y); + } + else if (destinationType == typeof(Rect)) + { + return new Rect(point.X, point.Y, 0, 0); + } + else if (destinationType == typeof(Size)) + { + return new Size(point.X, point.Y); + } + else if (destinationType == typeof(PixelPoint)) + { + return new PixelPoint((int)point.X, (int)point.Y); + } + else if (destinationType == typeof(PixelRect)) + { + return new PixelRect((int)point.X, (int)point.Y, 0, 0); + } + else if (destinationType == typeof(PixelSize)) + { + return new PixelSize((int)point.X, (int)point.Y); + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + + public override bool CanConvertFrom(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? sourceType) + { + return sourceType == typeof(float[]) + || sourceType == typeof(Tuple) + || sourceType == typeof(Rect) + || sourceType == typeof(Size) + || sourceType == typeof(PixelPoint) + || sourceType == typeof(PixelRect) + || sourceType == typeof(PixelSize) + || sourceType == typeof(string); + } + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + if (value is float[] { Length: >= 2 } array) + { + return new Point(array[0], array[1]); + } + else if (value is Tuple tuple) + { + return new Point(tuple.Item1, tuple.Item2); + } + else if (value is Rect rect) + { + return new Point(rect.X, rect.Y); + } + else if (value is Size size) + { + return new Point(size.Width, size.Height); + } + else if (value is PixelPoint pxpoint) + { + return new Point(pxpoint.X, pxpoint.Y); + } + else if (value is PixelRect pxrect) + { + return new Point(pxrect.X, pxrect.Y); + } + else if (value is PixelSize pxsize) + { + return new Point(pxsize.Width, pxsize.Height); + } + else if (value is string str) + { + return Point.Parse(str); + } + + return base.ConvertFrom(context, culture, value); + } +} diff --git a/src/Beutl.Graphics/Converters/RectConverter.cs b/src/Beutl.Graphics/Converters/RectConverter.cs new file mode 100644 index 000000000..eb2620bd2 --- /dev/null +++ b/src/Beutl.Graphics/Converters/RectConverter.cs @@ -0,0 +1,110 @@ +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + +using Beutl.Graphics; +using Beutl.Media; + +namespace Beutl.Converters; + +public sealed class RectConverter : TypeConverter +{ + public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType) + { + return destinationType == typeof(float[]) + || destinationType == typeof(Tuple) + || destinationType == typeof(Point) + || destinationType == typeof(Size) + || destinationType == typeof(PixelPoint) + || destinationType == typeof(PixelRect) + || destinationType == typeof(PixelSize) + || base.CanConvertTo(context, destinationType); + } + + public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) + { + if (value is Rect rect) + { + if (destinationType == typeof(float[])) + { + return new float[] { rect.X, rect.Y, rect.Width, rect.Height }; + } + else if (destinationType == typeof(Tuple)) + { + return new Tuple(rect.X, rect.Y, rect.Width, rect.Width); + } + else if (destinationType == typeof(Point)) + { + return new Point(rect.X, rect.Y); + } + else if (destinationType == typeof(Size)) + { + return new Size(rect.Width, rect.Height); + } + else if (destinationType == typeof(PixelPoint)) + { + return new PixelPoint((int)rect.X, (int)rect.Y); + } + else if (destinationType == typeof(PixelRect)) + { + return new PixelRect((int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height); + } + else if (destinationType == typeof(PixelSize)) + { + return new PixelSize((int)rect.Width, (int)rect.Height); + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + + public override bool CanConvertFrom(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? sourceType) + { + return sourceType == typeof(float[]) + || sourceType == typeof(Tuple) + || sourceType == typeof(Point) + || sourceType == typeof(Size) + || sourceType == typeof(PixelPoint) + || sourceType == typeof(PixelRect) + || sourceType == typeof(PixelSize) + || sourceType == typeof(string); + } + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + if (value is float[] { Length: >= 4 } array) + { + return new Rect(array[0], array[1], array[2], array[3]); + } + else if (value is Tuple tuple) + { + return new Rect(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4); + } + else if (value is Point point) + { + return new Rect(point.X, point.Y, 0, 0); + } + else if (value is Size size) + { + return new Rect(size); + } + else if (value is PixelPoint pxpoint) + { + return new Rect(pxpoint.X, pxpoint.Y, 0, 0); + } + else if (value is PixelRect pxrect) + { + return new Rect(pxrect.X, pxrect.Y, pxrect.Width, pxrect.Height); + } + else if (value is PixelSize pxsize) + { + return new Rect(0, 0, pxsize.Width, pxsize.Height); + } + else if (value is string str) + { + return Rect.Parse(str); + } + + return base.ConvertFrom(context, culture, value); + } +} diff --git a/src/Beutl.Graphics/Converters/RelativePointConverter.cs b/src/Beutl.Graphics/Converters/RelativePointConverter.cs new file mode 100644 index 000000000..14a4189e5 --- /dev/null +++ b/src/Beutl.Graphics/Converters/RelativePointConverter.cs @@ -0,0 +1,25 @@ +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + +using Beutl.Graphics; + +namespace Beutl.Converters; + +public sealed class RelativePointConverter : TypeConverter +{ + public override bool CanConvertFrom(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? sourceType) + { + return sourceType == typeof(string); + } + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + if (value is string str) + { + return RelativePoint.Parse(str); + } + + return base.ConvertFrom(context, culture, value); + } +} diff --git a/src/Beutl.Graphics/Converters/SizeConverter.cs b/src/Beutl.Graphics/Converters/SizeConverter.cs new file mode 100644 index 000000000..19a6b2701 --- /dev/null +++ b/src/Beutl.Graphics/Converters/SizeConverter.cs @@ -0,0 +1,120 @@ +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + +using Beutl.Graphics; +using Beutl.Media; + +namespace Beutl.Converters; + +public sealed class SizeConverter : TypeConverter +{ + public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType) + { + return destinationType == typeof(float[]) + || destinationType == typeof(Tuple) + || destinationType == typeof(Point) + || destinationType == typeof(Rect) + || destinationType == typeof(Vector) + || destinationType == typeof(PixelPoint) + || destinationType == typeof(PixelRect) + || destinationType == typeof(PixelSize) + || base.CanConvertTo(context, destinationType); + } + + public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) + { + if (value is Size size) + { + if (destinationType == typeof(float[])) + { + return new float[] { size.Width, size.Height }; + } + else if (destinationType == typeof(Tuple)) + { + return new Tuple(size.Width, size.Width); + } + else if (destinationType == typeof(Point)) + { + return new Point(size.Width, size.Height); + } + else if (destinationType == typeof(Rect)) + { + return new Rect(size); + } + else if (destinationType == typeof(Vector)) + { + return new Vector(size.Width, size.Height); + } + else if (destinationType == typeof(PixelPoint)) + { + return new PixelPoint((int)size.Width, (int)size.Height); + } + else if (destinationType == typeof(PixelRect)) + { + return new PixelRect(0, 0, (int)size.Width, (int)size.Height); + } + else if (destinationType == typeof(PixelSize)) + { + return new PixelSize((int)size.Width, (int)size.Height); + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + + public override bool CanConvertFrom(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? sourceType) + { + return sourceType == typeof(float[]) + || sourceType == typeof(Tuple) + || sourceType == typeof(Point) + || sourceType == typeof(Rect) + || sourceType == typeof(Vector) + || sourceType == typeof(PixelPoint) + || sourceType == typeof(PixelRect) + || sourceType == typeof(PixelSize) + || sourceType == typeof(string); + } + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + if (value is float[] { Length: >= 2 } array) + { + return new Size(array[0], array[1]); + } + else if (value is Tuple tuple) + { + return new Size(tuple.Item1, tuple.Item2); + } + else if (value is Point point) + { + return new Size(point.X, point.Y); + } + else if (value is Rect rect) + { + return new Size(rect.Width, rect.Height); + } + else if (value is Vector vector) + { + return new Size(vector.X, vector.Y); + } + else if (value is PixelPoint pxpoint) + { + return new Size(pxpoint.X, pxpoint.Y); + } + else if (value is PixelRect pxrect) + { + return new Size(pxrect.X, pxrect.Y); + } + else if (value is PixelSize pxsize) + { + return new Size(pxsize.Width, pxsize.Height); + } + else if (value is string str) + { + return Size.Parse(str); + } + + return base.ConvertFrom(context, culture, value); + } +} diff --git a/src/Beutl.Graphics/Converters/SizeJsonConverter.cs b/src/Beutl.Graphics/Converters/SizeJsonConverter.cs index 51b7d074a..54526c8b6 100644 --- a/src/Beutl.Graphics/Converters/SizeJsonConverter.cs +++ b/src/Beutl.Graphics/Converters/SizeJsonConverter.cs @@ -10,10 +10,7 @@ internal sealed class SizeJsonConverter : JsonConverter public override Size Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { string? s = reader.GetString(); - if (s == null) - throw new Exception("Invalid Size."); - - return Size.Parse(s); + return s == null ? throw new Exception("Invalid Size.") : Size.Parse(s); } public override void Write(Utf8JsonWriter writer, Size value, JsonSerializerOptions options) diff --git a/src/Beutl.Graphics/Converters/ThicknessConverter.cs b/src/Beutl.Graphics/Converters/ThicknessConverter.cs new file mode 100644 index 000000000..c2a104226 --- /dev/null +++ b/src/Beutl.Graphics/Converters/ThicknessConverter.cs @@ -0,0 +1,85 @@ +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + +using Beutl.Graphics; + +namespace Beutl.Converters; + +public sealed class ThicknessConverter : TypeConverter +{ + public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType) + { + return destinationType == typeof(float[]) + || destinationType == typeof(Tuple) + || destinationType == typeof(Tuple) + || base.CanConvertTo(context, destinationType); + } + + public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) + { + if (value is Thickness thickness) + { + if (destinationType == typeof(float[])) + { + return new float[] { thickness.Left, thickness.Top, thickness.Right, thickness.Bottom }; + } + else if (destinationType == typeof(Tuple)) + { + return new Tuple(thickness.Left, thickness.Top, thickness.Right, thickness.Bottom); + } + else if (destinationType == typeof(Tuple)) + { + return new Tuple(thickness.Left, thickness.Top); + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + + public override bool CanConvertFrom(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? sourceType) + { + return sourceType == typeof(float[]) + || sourceType == typeof(float) + || sourceType == typeof(Tuple) + || sourceType == typeof(Tuple) + || sourceType == typeof(string); + } + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + if (value is float[] array) + { + if (array.Length == 1) + { + return new Thickness(array[0]); + } + else if (array.Length == 2) + { + return new Thickness(array[0], array[1]); + } + else if (array.Length == 4) + { + return new Thickness(array[0], array[1], array[2], array[3]); + } + } + else if (value is float f) + { + return new Thickness(f); + } + else if (value is Tuple t1) + { + return new Thickness(t1.Item1, t1.Item2); + } + else if (value is Tuple t2) + { + return new Thickness(t2.Item1, t2.Item2, t2.Item3, t2.Item4); + } + else if (value is string str) + { + return Thickness.Parse(str); + } + + return base.ConvertFrom(context, culture, value); + } +} diff --git a/src/Beutl.Graphics/Converters/ThicknessJsonConverter.cs b/src/Beutl.Graphics/Converters/ThicknessJsonConverter.cs index 6ee5e5508..58becfdca 100644 --- a/src/Beutl.Graphics/Converters/ThicknessJsonConverter.cs +++ b/src/Beutl.Graphics/Converters/ThicknessJsonConverter.cs @@ -10,10 +10,7 @@ internal sealed class ThicknessJsonConverter : JsonConverter public override Thickness Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { string? s = reader.GetString(); - if (s == null) - throw new Exception("Invalid Thickness."); - - return Thickness.Parse(s); + return s == null ? throw new Exception("Invalid Thickness.") : Thickness.Parse(s); } public override void Write(Utf8JsonWriter writer, Thickness value, JsonSerializerOptions options) diff --git a/src/Beutl.Graphics/Converters/VectorConverter.cs b/src/Beutl.Graphics/Converters/VectorConverter.cs new file mode 100644 index 000000000..4d476b5ea --- /dev/null +++ b/src/Beutl.Graphics/Converters/VectorConverter.cs @@ -0,0 +1,100 @@ +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + +using Beutl.Graphics; +using Beutl.Media; + +namespace Beutl.Converters; + +public sealed class VectorConverter : TypeConverter +{ + public override bool CanConvertTo(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? destinationType) + { + return destinationType == typeof(float[]) + || destinationType == typeof(Tuple) + || destinationType == typeof(Point) + || destinationType == typeof(Size) + || destinationType == typeof(PixelPoint) + || destinationType == typeof(PixelSize) + || base.CanConvertTo(context, destinationType); + } + + public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) + { + if (value is Vector vector) + { + if (destinationType == typeof(float[])) + { + return new float[] { vector.X, vector.Y }; + } + else if (destinationType == typeof(Tuple)) + { + return new Tuple(vector.X, vector.Y); + } + else if (destinationType == typeof(Point)) + { + return new Point(vector.X, vector.Y); + } + else if (destinationType == typeof(Size)) + { + return new Size(vector.X, vector.Y); + } + else if (destinationType == typeof(PixelPoint)) + { + return new PixelPoint((int)vector.X, (int)vector.Y); + } + else if (destinationType == typeof(PixelSize)) + { + return new PixelSize((int)vector.X, (int)vector.Y); + } + } + + return base.ConvertTo(context, culture, value, destinationType); + } + + public override bool CanConvertFrom(ITypeDescriptorContext? context, [NotNullWhen(true)] Type? sourceType) + { + return sourceType == typeof(float[]) + || sourceType == typeof(Tuple) + || sourceType == typeof(Point) + || sourceType == typeof(Size) + || sourceType == typeof(PixelPoint) + || sourceType == typeof(PixelSize) + || sourceType == typeof(string); + } + + public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) + { + if (value is float[] { Length: >= 2 } array) + { + return new Vector(array[0], array[1]); + } + else if (value is Tuple tuple) + { + return new Vector(tuple.Item1, tuple.Item2); + } + else if (value is Point point) + { + return new Vector(point.X, point.Y); + } + else if (value is Size size) + { + return new Vector(size.Width, size.Height); + } + else if (value is PixelPoint pxpoint) + { + return new Vector(pxpoint.X, pxpoint.Y); + } + else if (value is PixelSize pxsize) + { + return new Vector(pxsize.Width, pxsize.Height); + } + else if (value is string str) + { + return Vector.Parse(str); + } + + return base.ConvertFrom(context, culture, value); + } +} diff --git a/src/Beutl.Graphics/Graphics/Drawable.cs b/src/Beutl.Graphics/Graphics/Drawable.cs index 0fc0dd02c..d5a7b3696 100644 --- a/src/Beutl.Graphics/Graphics/Drawable.cs +++ b/src/Beutl.Graphics/Graphics/Drawable.cs @@ -184,11 +184,20 @@ public BlendMode BlendMode public IBitmap ToBitmap() { Size size = MeasureCore(Size.Infinity); - using (var canvas = new Canvas((int)size.Width, (int)size.Height)) - using (Foreground == null ? new() : canvas.PushForeground(Foreground)) + int width = (int)size.Width; + int height = (int)size.Height; + if (width > 0 && height > 0) + { + using (var canvas = new Canvas(width, height)) + using (Foreground == null ? new() : canvas.PushForeground(Foreground)) + { + OnDraw(canvas); + return canvas.GetBitmap(); + } + } + else { - OnDraw(canvas); - return canvas.GetBitmap(); + return new Bitmap(0, 0); } } diff --git a/src/Beutl.Graphics/Graphics/Effects/InnerShadow.cs b/src/Beutl.Graphics/Graphics/Effects/InnerShadow.cs index 782644f99..475723a7e 100644 --- a/src/Beutl.Graphics/Graphics/Effects/InnerShadow.cs +++ b/src/Beutl.Graphics/Graphics/Effects/InnerShadow.cs @@ -52,6 +52,7 @@ public Point Position } [Display(Name = nameof(Strings.KernelSize), ResourceType = typeof(Strings))] + [Range(typeof(PixelSize), "1,1", "max,max")] public PixelSize KernelSize { get => _kernelSize; diff --git a/src/Beutl.Graphics/Graphics/Effects/OpenCv/Blur.cs b/src/Beutl.Graphics/Graphics/Effects/OpenCv/Blur.cs index 3eaf96330..1619c3404 100644 --- a/src/Beutl.Graphics/Graphics/Effects/OpenCv/Blur.cs +++ b/src/Beutl.Graphics/Graphics/Effects/OpenCv/Blur.cs @@ -37,6 +37,7 @@ public Blur() } [Display(Name = nameof(Strings.KernelSize), ResourceType = typeof(Strings))] + [Range(typeof(PixelSize), "1,1", "max,max")] public PixelSize KernelSize { get => _kernelSize; diff --git a/src/Beutl.Graphics/Graphics/Filters/Blur.cs b/src/Beutl.Graphics/Graphics/Filters/Blur.cs index 017e1e32b..8093904c6 100644 --- a/src/Beutl.Graphics/Graphics/Filters/Blur.cs +++ b/src/Beutl.Graphics/Graphics/Filters/Blur.cs @@ -22,6 +22,7 @@ static Blur() } [Display(Name = nameof(Strings.Sigma), ResourceType = typeof(Strings))] + [Range(typeof(Vector), "0,0", "max,max")] public Vector Sigma { get => _sigma; diff --git a/src/Beutl.Graphics/Graphics/Filters/DropShadow.cs b/src/Beutl.Graphics/Graphics/Filters/DropShadow.cs index dffef7781..9e72fa1c5 100644 --- a/src/Beutl.Graphics/Graphics/Filters/DropShadow.cs +++ b/src/Beutl.Graphics/Graphics/Filters/DropShadow.cs @@ -52,6 +52,7 @@ public Point Position } [Display(Name = nameof(Strings.Sigma), ResourceType = typeof(Strings))] + [Range(typeof(Vector), "0,0", "max,max")] public Vector Sigma { get => _sigma; diff --git a/src/Beutl.Graphics/Graphics/Matrix.cs b/src/Beutl.Graphics/Graphics/Matrix.cs index abb2bc73f..4dcb0c829 100644 --- a/src/Beutl.Graphics/Graphics/Matrix.cs +++ b/src/Beutl.Graphics/Graphics/Matrix.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.Design; +using System.ComponentModel; +using System.ComponentModel.Design; using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Text.Json.Serialization; @@ -21,12 +22,14 @@ namespace Beutl.Graphics; /// Note: Skia.SkMatrix uses a transposed layout (where for example skewX/skewY and perspp0/tranX are swapped). /// [JsonConverter(typeof(MatrixJsonConverter))] +[TypeConverter(typeof(MatrixConverter))] public readonly struct Matrix : IEquatable, IParsable, ISpanParsable, IMultiplyOperators, - IUnaryNegationOperators + IUnaryNegationOperators, + ITupleConvertible { /// /// Initializes a new instance of the struct (equivalent to a 2x3 Matrix without perspective). @@ -144,6 +147,8 @@ public Matrix( /// public float M33 { get; } + static int ITupleConvertible.TupleLength => 9; + /// /// Multiplies two matrices together and returns the resulting matrix. /// @@ -605,4 +610,27 @@ public static Matrix ComposeTransform(Vector translate, Vector scale, Vector ske .Prepend(CreateSkew(skew.X, skew.Y)) .Prepend(CreateScale(scale)); } + + static void ITupleConvertible.ConvertTo(Matrix self, Span tuple) + { + tuple[0] = self.M11; + tuple[1] = self.M12; + tuple[2] = self.M13; + tuple[3] = self.M21; + tuple[4] = self.M22; + tuple[5] = self.M23; + tuple[6] = self.M31; + tuple[7] = self.M32; + tuple[8] = self.M33; + } + + static void ITupleConvertible.ConvertFrom(Span tuple, out Matrix self) + { + self = new Matrix + ( + tuple[0], tuple[1], tuple[2], + tuple[3], tuple[4], tuple[5], + tuple[6], tuple[7], tuple[8] + ); + } } diff --git a/src/Beutl.Graphics/Graphics/Point.cs b/src/Beutl.Graphics/Graphics/Point.cs index 95b88ad82..59fa98701 100644 --- a/src/Beutl.Graphics/Graphics/Point.cs +++ b/src/Beutl.Graphics/Graphics/Point.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Numerics; using System.Text.Json.Serialization; @@ -13,6 +14,7 @@ namespace Beutl.Graphics; /// Defines a point. /// [JsonConverter(typeof(PointJsonConverter))] +[TypeConverter(typeof(PointConverter))] public readonly struct Point : IEquatable, IParsable, @@ -25,7 +27,8 @@ public readonly struct Point ISubtractionOperators, IMultiplyOperators, IDivisionOperators, - IMultiplyOperators + IMultiplyOperators, + ITupleConvertible { /// /// Initializes a new instance of the structure. @@ -53,6 +56,8 @@ public Point(float x, float y) /// public bool IsDefault => (X == 0) && (Y == 0); + static int ITupleConvertible.TupleLength => 2; + /// /// Converts the to a . /// @@ -336,4 +341,15 @@ static bool ISpanParsable.TryParse([NotNullWhen(true)] ReadOnlySpan { return TryParse(s, out result); } + + static void ITupleConvertible.ConvertTo(Point self, Span tuple) + { + tuple[0] = self.X; + tuple[1] = self.Y; + } + + static void ITupleConvertible.ConvertFrom(Span tuple, out Point self) + { + self = new Point(tuple[0], tuple[1]); + } } diff --git a/src/Beutl.Graphics/Graphics/Rect.cs b/src/Beutl.Graphics/Graphics/Rect.cs index 32edbe9f9..3c57d234d 100644 --- a/src/Beutl.Graphics/Graphics/Rect.cs +++ b/src/Beutl.Graphics/Graphics/Rect.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Numerics; using System.Text.Json.Serialization; @@ -13,6 +14,7 @@ namespace Beutl.Graphics; /// Defines a rectangle. /// [JsonConverter(typeof(RectJsonConverter))] +[TypeConverter(typeof(RectConverter))] public readonly struct Rect : IEquatable, IParsable, @@ -20,7 +22,8 @@ public readonly struct Rect IEqualityOperators, IMultiplyOperators, IMultiplyOperators, - IDivisionOperators + IDivisionOperators, + ITupleConvertible { /// /// An empty rectangle. @@ -160,6 +163,8 @@ public Rect(Point topLeft, Point bottomRight) /// public bool IsEmpty => Width == 0 && Height == 0; + static int ITupleConvertible.TupleLength => 4; + /// /// Checks for equality between two s. /// @@ -603,4 +608,17 @@ static bool ISpanParsable.TryParse([NotNullWhen(true)] ReadOnlySpan { return TryParse(s, out result); } + + static void ITupleConvertible.ConvertTo(Rect self, Span tuple) + { + tuple[0] = self.X; + tuple[1] = self.Y; + tuple[2] = self.Width; + tuple[3] = self.Height; + } + + static void ITupleConvertible.ConvertFrom(Span tuple, out Rect self) + { + self = new Rect(tuple[0], tuple[1], tuple[2], tuple[3]); + } } diff --git a/src/Beutl.Graphics/Graphics/RelativePoint.cs b/src/Beutl.Graphics/Graphics/RelativePoint.cs index ba8aeea4e..c9e6b63b3 100644 --- a/src/Beutl.Graphics/Graphics/RelativePoint.cs +++ b/src/Beutl.Graphics/Graphics/RelativePoint.cs @@ -1,11 +1,11 @@ -using System.Diagnostics.CodeAnalysis; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Numerics; using System.Text.Json.Serialization; using Beutl.Converters; using Beutl.Utilities; -using Beutl.Validation; namespace Beutl.Graphics; @@ -13,6 +13,7 @@ namespace Beutl.Graphics; /// Defines a point that may be defined relative to a containing element. /// [JsonConverter(typeof(RelativePointJsonConverter))] +[TypeConverter(typeof(RelativePointConverter))] public readonly struct RelativePoint : IEquatable, IParsable, diff --git a/src/Beutl.Graphics/Graphics/Shapes/RoundedRect.cs b/src/Beutl.Graphics/Graphics/Shapes/RoundedRect.cs index 93c415eb8..ac8e09ef8 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/RoundedRect.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/RoundedRect.cs @@ -38,6 +38,7 @@ public float StrokeWidth } [Display(Name = nameof(Strings.CornerRadius), ResourceType = typeof(Strings))] + [Range(typeof(CornerRadius), "0", "max")] public CornerRadius CornerRadius { get => _cornerRadius; diff --git a/src/Beutl.Graphics/Graphics/Size.cs b/src/Beutl.Graphics/Graphics/Size.cs index 8a63ed60e..a76a0636c 100644 --- a/src/Beutl.Graphics/Graphics/Size.cs +++ b/src/Beutl.Graphics/Graphics/Size.cs @@ -1,11 +1,11 @@ -using System.Diagnostics.CodeAnalysis; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Numerics; using System.Text.Json.Serialization; using Beutl.Converters; using Beutl.Utilities; -using Beutl.Validation; namespace Beutl.Graphics; @@ -13,6 +13,7 @@ namespace Beutl.Graphics; /// Defines a size. /// [JsonConverter(typeof(SizeJsonConverter))] +[TypeConverter(typeof(SizeConverter))] public readonly struct Size : IEquatable, IParsable, @@ -26,7 +27,8 @@ public readonly struct Size IAdditionOperators, ISubtractionOperators, IAdditionOperators, - ISubtractionOperators + ISubtractionOperators, + ITupleConvertible { /// /// A size representing infinity. @@ -69,6 +71,8 @@ public Size(float width, float height) /// public bool IsDefault => (Width == 0) && (Height == 0); + static int ITupleConvertible.TupleLength => 2; + /// /// Checks for equality between two s. /// @@ -376,4 +380,15 @@ static bool ISpanParsable.TryParse([NotNullWhen(true)] ReadOnlySpan { return TryParse(s, out result); } + + static void ITupleConvertible.ConvertTo(Size self, Span tuple) + { + tuple[0] = self.Width; + tuple[1] = self.Height; + } + + static void ITupleConvertible.ConvertFrom(Span tuple, out Size self) + { + self = new Size(tuple[0], tuple[1]); + } } diff --git a/src/Beutl.Graphics/Graphics/Thickness.cs b/src/Beutl.Graphics/Graphics/Thickness.cs index d8ab219d3..274ea2075 100644 --- a/src/Beutl.Graphics/Graphics/Thickness.cs +++ b/src/Beutl.Graphics/Graphics/Thickness.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Numerics; using System.Text.Json.Serialization; @@ -12,6 +13,7 @@ namespace Beutl.Graphics; /// Describes the thickness of a frame around a rectangle. /// [JsonConverter(typeof(ThicknessJsonConverter))] +[TypeConverter(typeof(ThicknessConverter))] public readonly struct Thickness : IEquatable, IParsable, @@ -19,7 +21,8 @@ public readonly struct Thickness IEqualityOperators, IAdditionOperators, ISubtractionOperators, - IMultiplyOperators + IMultiplyOperators, + ITupleConvertible { /// /// Initializes a new instance of the structure. @@ -91,6 +94,8 @@ public Thickness(float left, float top, float right, float bottom) /// public bool IsDefault => (Left == 0) && (Top == 0) && (Right == 0) && (Bottom == 0); + static int ITupleConvertible.TupleLength => 4; + /// /// Compares two Thicknesses. /// @@ -168,7 +173,7 @@ public static bool TryParse(string s, out Thickness thickness) { return TryParse(s.AsSpan(), out thickness); } - + /// /// Parses a string. /// @@ -198,7 +203,7 @@ public static Thickness Parse(string s) { return Parse(s.AsSpan()); } - + /// /// Parses a string. /// @@ -355,4 +360,17 @@ static bool ISpanParsable.TryParse([NotNullWhen(true)] ReadOnlySpan.ConvertTo(Thickness self, Span tuple) + { + tuple[0] = self.Left; + tuple[1] = self.Top; + tuple[2] = self.Right; + tuple[3] = self.Bottom; + } + + static void ITupleConvertible.ConvertFrom(Span tuple, out Thickness self) + { + self = new Thickness(tuple[0], tuple[1], tuple[2], tuple[3]); + } } diff --git a/src/Beutl.Graphics/Graphics/Vector.cs b/src/Beutl.Graphics/Graphics/Vector.cs index 36298a02b..722959e7d 100644 --- a/src/Beutl.Graphics/Graphics/Vector.cs +++ b/src/Beutl.Graphics/Graphics/Vector.cs @@ -1,4 +1,6 @@ -using System.Globalization; +using System.ComponentModel; +using System.Globalization; +using System.Numerics; using System.Text.Json.Serialization; using Beutl.Converters; @@ -11,7 +13,8 @@ namespace Beutl.Graphics; /// Defines a vector. /// [JsonConverter(typeof(VectorJsonConverter))] -public readonly struct Vector : IEquatable +[TypeConverter(typeof(VectorConverter))] +public readonly struct Vector : IEquatable, ITupleConvertible { /// /// Initializes a new instance of the structure. @@ -152,7 +155,7 @@ public static Vector Parse(string s) { return Parse(s.AsSpan()); } - + /// /// Parses a string. /// @@ -179,6 +182,8 @@ public static Vector Parse(ReadOnlySpan s) /// public float SquaredLength => X * X + Y * Y; + static int ITupleConvertible.TupleLength => 2; + /// /// Negates a vector. /// @@ -420,4 +425,15 @@ public void Deconstruct(out float x, out float y) x = X; y = Y; } + + static void ITupleConvertible.ConvertTo(Vector self, Span tuple) + { + tuple[0] = self.X; + tuple[1] = self.Y; + } + + static void ITupleConvertible.ConvertFrom(Span tuple, out Vector self) + { + self = new Vector(tuple[0], tuple[1]); + } } diff --git a/src/Beutl.Graphics/Media/Color.cs b/src/Beutl.Graphics/Media/Color.cs index b437c908f..91a7c3282 100644 --- a/src/Beutl.Graphics/Media/Color.cs +++ b/src/Beutl.Graphics/Media/Color.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Numerics; using System.Text.Json.Serialization; @@ -11,6 +12,7 @@ namespace Beutl.Media; /// An ARGB color. /// [JsonConverter(typeof(ColorJsonConverter))] +[TypeConverter(typeof(ColorConverter))] public readonly struct Color : IEquatable, IParsable, diff --git a/src/Beutl.Graphics/Media/CornerRadius.cs b/src/Beutl.Graphics/Media/CornerRadius.cs index 40247d725..fe2920b28 100644 --- a/src/Beutl.Graphics/Media/CornerRadius.cs +++ b/src/Beutl.Graphics/Media/CornerRadius.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using System.ComponentModel; +using System.Globalization; using System.Text.Json.Serialization; using Beutl.Converters; @@ -11,7 +12,8 @@ namespace Beutl.Media; /// Represents the radii of a rectangle's corners. /// [JsonConverter(typeof(CornerRadiusJsonConverter))] -public readonly struct CornerRadius : IEquatable +[TypeConverter(typeof(CornerRadiusConverter))] +public readonly struct CornerRadius : IEquatable, ITupleConvertible { public CornerRadius(float uniformRadius) { @@ -63,6 +65,8 @@ public CornerRadius(float topLeft, float topRight, float bottomRight, float bott /// public bool IsUniform => TopLeft.Equals(TopRight) && BottomLeft.Equals(BottomRight) && TopRight.Equals(BottomRight); + static int ITupleConvertible.TupleLength => 4; + /// /// Returns a boolean indicating whether the corner radius is equal to the other given corner radius. /// @@ -175,6 +179,19 @@ public CornerRadius WithBottom(float bottom) return new CornerRadius(TopLeft, TopRight, bottom, bottom); } + static void ITupleConvertible.ConvertTo(CornerRadius self, Span tuple) + { + tuple[0] = self.TopLeft; + tuple[1] = self.TopRight; + tuple[2] = self.BottomRight; + tuple[3] = self.BottomLeft; + } + + static void ITupleConvertible.ConvertFrom(Span tuple, out CornerRadius self) + { + self = new CornerRadius(tuple[0], tuple[1], tuple[2], tuple[3]); + } + public static bool operator ==(CornerRadius left, CornerRadius right) { return left.Equals(right); diff --git a/src/Beutl.Graphics/Media/FontFamily.cs b/src/Beutl.Graphics/Media/FontFamily.cs index 071923e74..5c8ee6b7a 100644 --- a/src/Beutl.Graphics/Media/FontFamily.cs +++ b/src/Beutl.Graphics/Media/FontFamily.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System.ComponentModel; +using System.Diagnostics; using System.Text.Json.Serialization; using Beutl.Converters; @@ -8,6 +9,7 @@ namespace Beutl.Media; [JsonConverter(typeof(FontFamilyJsonConverter))] +[TypeConverter(typeof(FontFamilyConverter))] public class FontFamily : IEquatable { public static readonly FontFamily Default = new(GetDefaultFontFamily()); diff --git a/src/Beutl.Graphics/Media/PixelPoint.cs b/src/Beutl.Graphics/Media/PixelPoint.cs index 4255c74c4..ee1208e52 100644 --- a/src/Beutl.Graphics/Media/PixelPoint.cs +++ b/src/Beutl.Graphics/Media/PixelPoint.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Numerics; using System.Text.Json.Serialization; @@ -16,13 +17,15 @@ namespace Beutl.Media; /// Represents a point in device pixels. /// [JsonConverter(typeof(PixelPointJsonConverter))] +[TypeConverter(typeof(PixelPointConverter))] public readonly struct PixelPoint : IEquatable, IParsable, ISpanParsable, IEqualityOperators, IAdditionOperators, - ISubtractionOperators + ISubtractionOperators, + ITupleConvertible { /// /// A point representing 0,0. @@ -50,6 +53,8 @@ public PixelPoint(int x, int y) /// public int Y { get; } + static int ITupleConvertible.TupleLength => 2; + /// /// Checks for equality between two s. /// @@ -276,4 +281,15 @@ static bool ISpanParsable.TryParse([NotNullWhen(true)] ReadOnlySpan< { return TryParse(s, out result); } + + static void ITupleConvertible.ConvertTo(PixelPoint self, Span tuple) + { + tuple[0] = self.X; + tuple[1] = self.Y; + } + + static void ITupleConvertible.ConvertFrom(Span tuple, out PixelPoint self) + { + self = new PixelPoint(tuple[0], tuple[1]); + } } diff --git a/src/Beutl.Graphics/Media/PixelRect.cs b/src/Beutl.Graphics/Media/PixelRect.cs index ac79ce708..ffc4fcc92 100644 --- a/src/Beutl.Graphics/Media/PixelRect.cs +++ b/src/Beutl.Graphics/Media/PixelRect.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Numerics; using System.Text.Json.Serialization; @@ -16,11 +17,13 @@ namespace Beutl.Media; /// Represents a rectangle in device pixels. /// [JsonConverter(typeof(PixelRectJsonConverter))] +[TypeConverter(typeof(PixelRectConverter))] public readonly struct PixelRect : IEquatable, IParsable, ISpanParsable, - IEqualityOperators + IEqualityOperators, + ITupleConvertible { /// /// An empty rectangle. @@ -150,6 +153,8 @@ public PixelRect(PixelPoint topLeft, PixelPoint bottomRight) /// public bool IsEmpty => Width == 0 && Height == 0; + static int ITupleConvertible.TupleLength => 4; + /// /// Checks for equality between two s. /// @@ -486,4 +491,17 @@ private static PixelPoint FromPointCeiling(Point point, Vector scale) (int)Math.Ceiling(point.X * scale.X), (int)Math.Ceiling(point.Y * scale.Y)); } + + static void ITupleConvertible.ConvertTo(PixelRect self, Span tuple) + { + tuple[0] = self.X; + tuple[1] = self.Y; + tuple[2] = self.Width; + tuple[3] = self.Height; + } + + static void ITupleConvertible.ConvertFrom(Span tuple, out PixelRect self) + { + self = new PixelRect(tuple[0], tuple[1], tuple[2], tuple[3]); + } } diff --git a/src/Beutl.Graphics/Media/PixelSize.cs b/src/Beutl.Graphics/Media/PixelSize.cs index 55d42d81f..0ff7eb25f 100644 --- a/src/Beutl.Graphics/Media/PixelSize.cs +++ b/src/Beutl.Graphics/Media/PixelSize.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Numerics; using System.Text.Json.Serialization; @@ -16,11 +17,13 @@ namespace Beutl.Media; /// Represents a size in device pixels. /// [JsonConverter(typeof(PixelSizeJsonConverter))] +[TypeConverter(typeof(PixelSizeConverter))] public readonly struct PixelSize : IEquatable, IParsable, ISpanParsable, - IEqualityOperators + IEqualityOperators, + ITupleConvertible { /// /// A size representing zero @@ -53,6 +56,8 @@ public PixelSize(int width, int height) /// public int Height { get; } + static int ITupleConvertible.TupleLength => 2; + /// /// Checks for equality between two s. /// @@ -258,4 +263,15 @@ public override string ToString() { return FormattableString.Invariant($"{Width}, {Height}"); } + + static void ITupleConvertible.ConvertTo(PixelSize self, Span tuple) + { + tuple[0] = self.Width; + tuple[1] = self.Height; + } + + static void ITupleConvertible.ConvertFrom(Span tuple, out PixelSize self) + { + self = new PixelSize(tuple[0], tuple[1]); + } } diff --git a/src/Beutl.Graphics/Rendering/RenderLayerSpan.cs b/src/Beutl.Graphics/Rendering/RenderLayerSpan.cs index e103301dd..cda3e21dd 100644 --- a/src/Beutl.Graphics/Rendering/RenderLayerSpan.cs +++ b/src/Beutl.Graphics/Rendering/RenderLayerSpan.cs @@ -59,6 +59,7 @@ public TimeSpan Duration public Renderables Value => _value; + [ShouldSerialize(false)] public RenderLayer? RenderLayer { get => _renderLayer; diff --git a/src/Beutl.Graphics/Styling/Styleable.cs b/src/Beutl.Graphics/Styling/Styleable.cs index 23ab5ae21..0b6307fb6 100644 --- a/src/Beutl.Graphics/Styling/Styleable.cs +++ b/src/Beutl.Graphics/Styling/Styleable.cs @@ -50,6 +50,7 @@ private void Style_Invalidated(object? sender, EventArgs e) _styleInstance = null; } + [ShouldSerialize(false)] public Styles Styles { get => _styles; @@ -155,6 +156,7 @@ public override void WriteToJson(ref JsonNode json) private IHierarchical? _parent; private IHierarchicalRoot? _root; + [ShouldSerialize(false)] public IHierarchical? HierarchicalParent { get => _parent; diff --git a/src/Beutl.Utilities/RefStringTokenizer.cs b/src/Beutl.Utilities/RefStringTokenizer.cs index 87409c4de..364075c8e 100644 --- a/src/Beutl.Utilities/RefStringTokenizer.cs +++ b/src/Beutl.Utilities/RefStringTokenizer.cs @@ -50,16 +50,30 @@ public void Dispose() public bool TryReadInt32(out int result, char? separator = null) { - if (TryReadString(out ReadOnlySpan stringResult, separator) && - int.TryParse(stringResult, NumberStyles.Integer, _formatProvider, out result)) + if (!TryReadString(out ReadOnlySpan stringResult, separator)) { - return true; + result = default; + return false; } else { - result = default; - return false; + if (stringResult.Equals("max", StringComparison.OrdinalIgnoreCase)) + { + result = int.MaxValue; + return true; + } + else if (stringResult.Equals("min", StringComparison.OrdinalIgnoreCase)) + { + result = int.MinValue; + return true; + } + else if (int.TryParse(stringResult, NumberStyles.Integer, _formatProvider, out result)) + { + return true; + } } + + return false; } public int ReadInt32(char? separator = null) @@ -74,16 +88,30 @@ public int ReadInt32(char? separator = null) public bool TryReadDouble(out double result, char? separator = null) { - if (TryReadString(out ReadOnlySpan stringResult, separator) && - double.TryParse(stringResult, NumberStyles.Float, _formatProvider, out result)) + if (!TryReadString(out ReadOnlySpan stringResult, separator)) { - return true; + result = default; + return false; } else { - result = default; - return false; + if (stringResult.Equals("max", StringComparison.OrdinalIgnoreCase)) + { + result = double.MaxValue; + return true; + } + else if (stringResult.Equals("min", StringComparison.OrdinalIgnoreCase)) + { + result = double.MinValue; + return true; + } + else if (double.TryParse(stringResult, NumberStyles.Float, _formatProvider, out result)) + { + return true; + } } + + return false; } public double ReadDouble(char? separator = null) @@ -98,16 +126,30 @@ public double ReadDouble(char? separator = null) public bool TryReadSingle(out float result, char? separator = null) { - if (TryReadString(out ReadOnlySpan stringResult, separator) && - float.TryParse(stringResult, NumberStyles.Float, _formatProvider, out result)) + if (!TryReadString(out ReadOnlySpan stringResult, separator)) { - return true; + result = default; + return false; } else { - result = default; - return false; + if (stringResult.Equals("max", StringComparison.OrdinalIgnoreCase)) + { + result = float.MaxValue; + return true; + } + else if (stringResult.Equals("min", StringComparison.OrdinalIgnoreCase)) + { + result = float.MinValue; + return true; + } + else if (float.TryParse(stringResult, NumberStyles.Float, _formatProvider, out result)) + { + return true; + } } + + return false; } public float ReadSingle(char? separator = null) diff --git a/src/Beutl/Converters/ThicknessConverter.cs b/src/Beutl/Converters/AvaloniaThicknessConverter.cs similarity index 90% rename from src/Beutl/Converters/ThicknessConverter.cs rename to src/Beutl/Converters/AvaloniaThicknessConverter.cs index c41c43e2f..8fc0911fd 100644 --- a/src/Beutl/Converters/ThicknessConverter.cs +++ b/src/Beutl/Converters/AvaloniaThicknessConverter.cs @@ -6,9 +6,9 @@ namespace Beutl.Converters; -public sealed class ThicknessConverter : IValueConverter +public sealed class AvaloniaThicknessConverter : IValueConverter { - public static readonly ThicknessConverter Instance = new(); + public static readonly AvaloniaThicknessConverter Instance = new(); public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { diff --git a/src/Beutl/Views/InlineAnimationLayer.axaml b/src/Beutl/Views/InlineAnimationLayer.axaml index e916bb17a..e381a102a 100644 --- a/src/Beutl/Views/InlineAnimationLayer.axaml +++ b/src/Beutl/Views/InlineAnimationLayer.axaml @@ -34,7 +34,7 @@ - From 963b4bae63dd40b4645a325fa8019d2408997845 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Tue, 28 Mar 2023 23:17:16 +0900 Subject: [PATCH 10/84] =?UTF-8?q?Setter=E3=81=A7=E3=83=97=E3=83=AD?= =?UTF-8?q?=E3=83=91=E3=83=86=E3=82=A3=E3=81=AE=E5=80=A4=E3=82=92=E5=BC=B7?= =?UTF-8?q?=E5=88=B6=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=97?= =?UTF-8?q?=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Graphics/Animation/KeyFrame{T}.cs | 8 ++++++ src/Beutl.Graphics/Styling/ISetter.cs | 2 ++ src/Beutl.Graphics/Styling/Setter.cs | 27 +++++++++++++++++---- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/Beutl.Graphics/Animation/KeyFrame{T}.cs b/src/Beutl.Graphics/Animation/KeyFrame{T}.cs index 21da517c3..b6ef6632d 100644 --- a/src/Beutl.Graphics/Animation/KeyFrame{T}.cs +++ b/src/Beutl.Graphics/Animation/KeyFrame{T}.cs @@ -64,6 +64,14 @@ internal override CoreProperty? Property { _validator = t.GetMetadata>(t.OwnerType).Validator; base.Property = t; + if (_validator != null) + { + T? coerced = Value; + if (_validator.TryCoerce(new ValidationContext(null, Property), ref coerced)) + { + Value = coerced!; + } + } } else { diff --git a/src/Beutl.Graphics/Styling/ISetter.cs b/src/Beutl.Graphics/Styling/ISetter.cs index c2e7d98ad..bc6b6b620 100644 --- a/src/Beutl.Graphics/Styling/ISetter.cs +++ b/src/Beutl.Graphics/Styling/ISetter.cs @@ -8,6 +8,8 @@ namespace Beutl.Styling; public interface ISetter { CoreProperty Property { get; } + + CorePropertyMetadata Metadata { get; } object? Value { get; } diff --git a/src/Beutl.Graphics/Styling/Setter.cs b/src/Beutl.Graphics/Styling/Setter.cs index ef39619e4..116beee57 100644 --- a/src/Beutl.Graphics/Styling/Setter.cs +++ b/src/Beutl.Graphics/Styling/Setter.cs @@ -4,12 +4,14 @@ using Beutl.Animation; using Beutl.Media; using Beutl.Reactive; +using Beutl.Validation; namespace Beutl.Styling; public class Setter : LightweightObservableBase, ISetter { private CoreProperty? _property; + private CorePropertyMetadata? _metadata; private T? _value; private IAnimation? _animation; private bool _isDefault; @@ -22,9 +24,10 @@ public Setter() public Setter(CoreProperty property) { _property = property; - if (property.GetMetadata>(property.OwnerType) is { HasDefaultValue: true } metadata) + _metadata = property.GetMetadata>(property.OwnerType); + if (_metadata.HasDefaultValue) { - Value = metadata.DefaultValue; + Value = _metadata.DefaultValue; } _isDefault = true; } @@ -32,21 +35,29 @@ public Setter(CoreProperty property) public Setter(CoreProperty property, T? value) { _property = property; + _metadata = property.GetMetadata>(property.OwnerType); Value = value; _isDefault = false; } + public CorePropertyMetadata Metadata + { + get => _metadata ?? throw new InvalidOperationException(); + set => _metadata = value; + } + public CoreProperty Property { get => _property ?? throw new InvalidOperationException(); set { _property = value; + _metadata = value.GetMetadata>(value.OwnerType); if (_isDefault) { - if (_property.GetMetadata>(_property.OwnerType) is { HasDefaultValue: true } metadata) + if (_metadata.HasDefaultValue) { - Value = metadata.DefaultValue; + Value = _metadata.DefaultValue; } _isDefault = true; @@ -54,12 +65,16 @@ public CoreProperty Property } } - // Todo: Validation public T? Value { get => _value; set { + if (_metadata?.Validator is IValidator validator) + { + validator.TryCoerce(new ValidationContext(null, _property), ref value); + } + if (!EqualityComparer.Default.Equals(_value, value)) { _isDefault = false; @@ -104,6 +119,8 @@ public IAnimation? Animation } } + CorePropertyMetadata ISetter.Metadata => Metadata; + CoreProperty ISetter.Property => Property; object? ISetter.Value => Value; From 0b41d3fb6c47e7b61e9dc20bccf70e9e730bf4f4 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Tue, 28 Mar 2023 23:25:10 +0900 Subject: [PATCH 11/84] =?UTF-8?q?IRenderer.Invalidate=E3=83=A1=E3=82=BD?= =?UTF-8?q?=E3=83=83=E3=83=89=E3=82=92RaiseInvalidated=E3=81=AB=E3=81=97?= =?UTF-8?q?=E3=81=A6=E3=80=81=E3=82=A4=E3=83=99=E3=83=B3=E3=83=88=E3=82=92?= =?UTF-8?q?=E7=99=BA=E7=94=9F=E3=81=95=E3=81=9B=E3=82=8B=E3=81=A0=E3=81=91?= =?UTF-8?q?=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Graphics/Rendering/IRenderer.cs | 6 ++---- src/Beutl.Graphics/Rendering/ImmediateRenderer.cs | 11 ++++------- src/Beutl.ProjectSystem/ProjectSystem/Layer.cs | 2 +- src/Beutl.ProjectSystem/ProjectSystem/Scene.cs | 2 +- src/Beutl/ViewModels/PlayerViewModel.cs | 15 +++++++++++---- 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/Beutl.Graphics/Rendering/IRenderer.cs b/src/Beutl.Graphics/Rendering/IRenderer.cs index ad923a0ac..582e4a8ed 100644 --- a/src/Beutl.Graphics/Rendering/IRenderer.cs +++ b/src/Beutl.Graphics/Rendering/IRenderer.cs @@ -9,8 +9,6 @@ namespace Beutl.Rendering; -// RenderingのApiで時間を考慮する -// Renderable内で持続時間と開始時間のプロパティを追加 public interface IRenderer : IDisposable { IRenderLayer? this[int index] { get; set; } @@ -31,7 +29,7 @@ public interface IRenderer : IDisposable bool IsAudioRendering { get; } - event EventHandler RenderInvalidated; + event EventHandler RenderInvalidated; RenderResult RenderGraphics(TimeSpan timeSpan); @@ -39,7 +37,7 @@ public interface IRenderer : IDisposable RenderResult Render(TimeSpan timeSpan); - void Invalidate(TimeSpan timeSpan); + void RaiseInvalidated(TimeSpan timeSpan); //void AddDirty(IRenderable renderable); diff --git a/src/Beutl.Graphics/Rendering/ImmediateRenderer.cs b/src/Beutl.Graphics/Rendering/ImmediateRenderer.cs index 524cf8e21..92efc7f44 100644 --- a/src/Beutl.Graphics/Rendering/ImmediateRenderer.cs +++ b/src/Beutl.Graphics/Rendering/ImmediateRenderer.cs @@ -59,7 +59,7 @@ public IRenderLayer? this[int index] } } - public event EventHandler? RenderInvalidated; + public event EventHandler? RenderInvalidated; public virtual void Dispose() { @@ -71,14 +71,11 @@ public virtual void Dispose() IsDisposed = true; } - public async void Invalidate(TimeSpan timeSpan) + public void RaiseInvalidated(TimeSpan timeSpan) { - if (RenderInvalidated != null && !IsGraphicsRendering) + if (!IsGraphicsRendering) { - IRenderer.RenderResult result = await Dispatcher.InvokeAsync(() => RenderGraphics(timeSpan)); - RenderInvalidated?.Invoke(this, result); - result.Bitmap?.Dispose(); - result.Audio?.Dispose(); + RenderInvalidated?.Invoke(this, timeSpan); } } diff --git a/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs b/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs index 09752ef18..8061fd08c 100644 --- a/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs +++ b/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs @@ -311,7 +311,7 @@ private void ForceRender() && scene.CurrentFrame < Start + Length && scene.Renderer is { IsDisposed: false, IsGraphicsRendering: false }) { - scene.Renderer.Invalidate(scene.CurrentFrame); + scene.Renderer.RaiseInvalidated(scene.CurrentFrame); } } diff --git a/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs b/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs index 411bf9201..4cde5e872 100644 --- a/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs +++ b/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs @@ -80,7 +80,7 @@ static Scene() { if (e.Sender is Scene scene) { - scene._renderer.Invalidate(e.NewValue); + scene._renderer.RaiseInvalidated(e.NewValue); } }); } diff --git a/src/Beutl/ViewModels/PlayerViewModel.cs b/src/Beutl/ViewModels/PlayerViewModel.cs index ae5236a69..d72a0d6c6 100644 --- a/src/Beutl/ViewModels/PlayerViewModel.cs +++ b/src/Beutl/ViewModels/PlayerViewModel.cs @@ -2,7 +2,6 @@ using Avalonia.Media.Imaging; using Avalonia.Platform; -using Beutl.Audio; using Beutl.Audio.Platforms.OpenAL; using Beutl.Audio.Platforms.XAudio2; using Beutl.Media; @@ -348,11 +347,19 @@ private unsafe void UpdateImage(Bitmap source) PreviewInvalidated?.Invoke(this, EventArgs.Empty); } - private unsafe void Renderer_RenderInvalidated(object? sender, IRenderer.RenderResult e) + private async void Renderer_RenderInvalidated(object? sender, TimeSpan e) { - if (e.Bitmap is { } bitmap) + if (sender is IRenderer renderer) { - UpdateImage(bitmap); + await renderer.Dispatcher.InvokeAsync(() => + { + IRenderer.RenderResult result = renderer.RenderGraphics(e); + if (result.Bitmap is { } bitmap) + { + UpdateImage(bitmap); + bitmap.Dispose(); + } + }); } } From 9d95cd300c8bc716d788f7745012da11c211ac09 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Tue, 28 Mar 2023 23:55:47 +0900 Subject: [PATCH 12/84] =?UTF-8?q?System.Type=E3=81=AEJson=E3=81=AB?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E6=99=82=E3=81=AE=E3=83=95=E3=82=A9=E3=83=BC?= =?UTF-8?q?=E3=83=9E=E3=83=83=E3=83=88=E3=82=92=E5=A4=89=E6=9B=B4=E3=80=81?= =?UTF-8?q?=E3=82=B8=E3=82=A7=E3=83=8D=E3=83=AA=E3=83=83=E3=82=AF=E5=BC=95?= =?UTF-8?q?=E6=95=B0=E3=81=8C=E8=A6=8B=E3=81=A5=E3=82=89=E3=81=8F=E3=81=AA?= =?UTF-8?q?=E3=82=8B=E3=81=AE=E3=81=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Core/JsonHelper.cs | 3 +- src/Beutl.Core/TypeFormat.cs | 506 +++++++++++++++++++++++++++++++++-- 2 files changed, 489 insertions(+), 20 deletions(-) diff --git a/src/Beutl.Core/JsonHelper.cs b/src/Beutl.Core/JsonHelper.cs index e69bf754c..68ef3788a 100644 --- a/src/Beutl.Core/JsonHelper.cs +++ b/src/Beutl.Core/JsonHelper.cs @@ -14,13 +14,14 @@ public static class JsonHelper public static JsonWriterOptions WriterOptions { get; } = new() { + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, Indented = true, }; public static JsonSerializerOptions SerializerOptions { get; } = new() { WriteIndented = true, - Encoder = JavaScriptEncoder.Create(UnicodeRanges.All), + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, Converters = { new CultureInfoConverter(), diff --git a/src/Beutl.Core/TypeFormat.cs b/src/Beutl.Core/TypeFormat.cs index 4105de54e..f9e0844de 100644 --- a/src/Beutl.Core/TypeFormat.cs +++ b/src/Beutl.Core/TypeFormat.cs @@ -1,37 +1,505 @@ -namespace Beutl; +using System.Reflection; +using System.Runtime.Loader; +using System.Text; -internal static class TypeFormat +using Beutl.JsonDiscriminator; + +namespace Beutl { - public static Type? ToType(string fullName) + internal static class TypeFormat { - string[] arr = fullName.Split(':'); + public static Type? ToType(string fullName) + { + List tokens = new TypeNameTokenizer(fullName).Tokenize(); + return new TypeNameParser(tokens).Parse(); + } - if (arr.Length == 1) + public static string ToString(Type type) { - return Type.GetType(arr[0]); + return new TypeNameFormatter(type).Format(); } - else if (arr.Length == 2) + } + + namespace JsonDiscriminator + { + internal class Token { - return Type.GetType($"{arr[0]}, {arr[1]}"); + public Token(TokenType type, string? text = null) + { + Type = type; + Text = text; + + if (text == null && type != TokenType.Part) + { + Text = type switch + { + TokenType.BeginAssembly => "[", + TokenType.EndAssembly => "]", + TokenType.Colon => ":", + TokenType.Period => ".", + TokenType.BeginGenericArguments => "<", + TokenType.EndGenericArguments => ">", + _ => null, + }; + } + } + + public TokenType Type { get; } + + public string? Text { get; } } - else + + /* + * 名前空間とアセンブリ名が同じ場合 + * [Beutl.Graphics]:Point + * + * 型がグローバル空間にある場合 + * [Beutl.Graphics]global::Point + * + * 名前空間とアセンブリ名が途中まで同じ場合 + * [Beutl.Graphics].Shapes:Ellipse + * + * 名前空間とアセンブリ名が一致しない場合 + * [Beutl.Graphics]Beutl.Audio:Sound + * + * ジェネリック引数がある場合 + * [System.Collections].Generic:List<[System.Runtime]System:Int32> + * + * 入れ子になったクラス + * [System.Net.Mail]System.Net.Mime:MediaTypeNames:Application + */ + internal enum TokenType { - return null; + BeginAssembly, + + EndAssembly, + + // Colon + Colon, + + Period, + + Comma, + + BeginGenericArguments, + + EndGenericArguments, + + Part, } - } - public static string ToString(Type type) - { - string? asm = type.Assembly.GetName().Name; - string tname = type.FullName!; + internal class TypeNameParser + { + private readonly List _tokens; + private readonly Func _assemblyResolver; + private string? _assemblyName; + private Assembly? _assembly; + private string? _namespace; + + public TypeNameParser(List tokens, Func? assemblyResolver = null) + { + _tokens = tokens; + _assemblyResolver = assemblyResolver ?? (s => AssemblyLoadContext.Default.Assemblies.First(x => x.GetName().Name == s)); + } + + public Type Parse() + { + Token[] asmTokens = TakeAssemblyTokens(_tokens).ToArray(); + _assemblyName = string.Concat(asmTokens + .Where(x => x.Type is not (TokenType.BeginAssembly or TokenType.EndAssembly)) + .Select(x => x.Text)); + _assembly = _assemblyResolver(_assemblyName); + + Token[] nsTokens = TakeNamespaceTokens(_tokens.Skip(asmTokens.Length)).ToArray(); + _namespace = ParseNamespace(nsTokens); + + Token[] typeTokens = _tokens.Skip(asmTokens.Length + nsTokens.Length).ToArray(); + return ParseNestedType(typeTokens); + } + + private static string ConcatTokens(ReadOnlySpan tokens) + { + var sb = new StringBuilder(); + foreach (Token item in tokens) + { + sb.Append(item.Text); + } + + return sb.ToString(); + } + + private static IEnumerable TakeAssemblyTokens(IEnumerable tokens) + { + foreach (Token item in tokens) + { + if (item.Type is TokenType.BeginAssembly or TokenType.EndAssembly or TokenType.Part or TokenType.Period) + { + yield return item; + if (item.Type is TokenType.EndAssembly) + { + yield break; + } + } + } + } + + private static IEnumerable TakeNamespaceTokens(IEnumerable tokens) + { + int colonCount = 0; + foreach (Token item in tokens) + { + if (item.Type is TokenType.Period or TokenType.Part or TokenType.Colon) + { + if (item.Type is TokenType.Colon) + { + colonCount++; + if (colonCount == 1) + { + continue; + } + else if (colonCount == 2) + { + // コロンが二連続 -> "global::XXX" + yield return item; + yield break; + } + } + + if (colonCount == 1) + { + yield break; + } + + yield return item; + } + } + } + + private string? ParseNamespace(Token[] tokens) + { + if (tokens.Length == 0) + { + return _assemblyName; + } + + if (tokens.Length >= 2) + { + if (tokens[^1].Type == TokenType.Colon + && tokens[^2] is { Text: "global" }) + { + if (tokens.Length > 2) + throw new InvalidOperationException($"Invalid Tokens: {ConcatTokens(tokens)}"); + + return null; + } + else if (tokens[0].Type == TokenType.Period) + { + return $"{_assemblyName}{ConcatTokens(tokens)}"; + } + } + + return ConcatTokens(tokens); + } + + private static void TakeGenericArguments(Span tokens, out Span genericArgs) + { + // リストパターンにするとNestedTypeの親にGeneric引数がある場合、壊れる + if (tokens[^1].Type == TokenType.EndGenericArguments) + { + // 解析中の型はジェネリック型 + var genericRange = new Range(Index.End, tokens.Length); + int innerGenericCount = 0; - if (asm == null) + for (int i = tokens.Length - 2; i >= 0; i--) + { + TokenType type = tokens[i].Type; + if (type == TokenType.EndGenericArguments) + { + innerGenericCount++; + } + else if (type == TokenType.BeginGenericArguments) + { + if (innerGenericCount == 0) + { + // 内側にジェネリック引数が無くて、'<'に到達した + genericRange = new Range(i, genericRange.End); + break; + } + else + { + innerGenericCount--; + } + } + } + + if (genericRange.Start.IsFromEnd) + throw new InvalidOperationException($"Invalid Tokens: {ConcatTokens(tokens)}"); + + genericArgs = tokens[genericRange]; + } + else + { + genericArgs = default; + } + } + + private static Type[] ParseGenericTypes(Span tokens) + { + if (tokens.Length == 0) + return Array.Empty(); + + var list = new List(); + + if (tokens is [{ Type: TokenType.BeginGenericArguments }, .. var generics, { Type: TokenType.EndGenericArguments }]) + { + int start = 0; + int innerGenericCount = 0; + for (int i = 0; i < generics.Length;) + { + Token item = generics[i]; + TokenType type = item.Type; + if (type == TokenType.BeginGenericArguments) + { + innerGenericCount++; + } + else if (type == TokenType.EndGenericArguments) + { + innerGenericCount--; + } + + if (++i == generics.Length || (innerGenericCount == 0 && item.Type is TokenType.Comma)) + { + Span genericType = generics[start..i]; + var parser = new TypeNameParser(new List(genericType.ToArray())); + list.Add(parser.Parse()); + } + } + } + + if (list.Any(x => x == null)) + throw new InvalidOperationException($"Invalid Tokens: {ConcatTokens(tokens)}"); + + return list.ToArray(); + } + + // ":List<[System.Runtime]:Int32>"を解析 + private static string TakeTypeNameTokens(Span tokens, out Span genericTokens, out Span parents) + { + TakeGenericArguments(tokens, out genericTokens); + + Span nokoriToken = tokens.Slice(0, tokens.Length - genericTokens.Length); + + if (nokoriToken is [.. var parentTokens, { Type: TokenType.Colon }, { Type: TokenType.Part, Text: string typeName }]) + { + parents = parentTokens; + return typeName; + } + else + { + throw new InvalidOperationException($"Invalid Tokens: {ConcatTokens(tokens)}"); + } + } + + private Type ParseNestedType(Span tokens) + { + string typeName = TakeTypeNameTokens(tokens, out Span genericTokens, out Span parents); + Type[] genericArgs = ParseGenericTypes(genericTokens); + string suffix = genericArgs.Length > 0 ? $"`{genericArgs.Length}" : ""; + + Type type; + if (parents.Length != 0) + { + Type parent = ParseNestedType(parents); + const BindingFlags flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; + type = parent.GetNestedType($"{typeName}{suffix}", flags)!; + } + else + { + type = _assembly!.GetType($"{_namespace ?? ""}.{typeName}{suffix}")!; + } + + if (genericArgs.Length > 0) + { + type = type.MakeGenericType(genericArgs); + } + + return type; + } + } + + internal class TypeNameFormatter { - return tname; + private readonly Type _type; + + public TypeNameFormatter(Type type) + { + _type = type; + } + + private void WriteNamespace(StringBuilder sb) + { + string? asmName = _type.Assembly.GetName().Name; + string? ns = _type.Namespace; + if (ns != null) + { + if (asmName != null) + { + if (ns.StartsWith(asmName)) + { + ns = ns.Substring(asmName.Length); + } + } + } + else + { + ns = "global:"; + } + + sb.Append(ns); + } + + public string Format() + { + var sb = new StringBuilder(); + string? asmName = _type.Assembly.GetName().Name; + if (asmName != null) + { + sb.Append('['); + sb.Append(asmName); + sb.Append(']'); + } + + WriteNamespace(sb); + + WriteTypeName(sb, _type); + + return sb.ToString(); + } + + private static void WriteGenericArguments(StringBuilder sb, Type type) + { + sb.Append('<'); + Type[] array = type.GetGenericArguments(); + for (int i = 0; i < array.Length;) + { + Type? item = array[i]; + var formatter = new TypeNameFormatter(item); + sb.Append(formatter.Format()); + if (++i < array.Length) + { + sb.Append(", "); + } + } + sb.Append('>'); + } + + private static void WriteTypeName(StringBuilder sb, Type type) + { + Type? declaringType = type.DeclaringType; + if (type.IsNested && declaringType != null) + { + WriteTypeName(sb, declaringType); + } + + sb.Append(':'); + sb.Append(TrimTypeName(type.Name)); + if (type.IsGenericType) + { + WriteGenericArguments(sb, type); + } + } + + private static string TrimTypeName(string typeName) + { + int idx = typeName.IndexOf('`'); + if (idx < 0) + { + return typeName; + } + else + { + return typeName[..idx]; + } + } } - else + + internal class TypeNameTokenizer { - return $"{tname}:{asm}"; + private readonly string _s; + + public TypeNameTokenizer(string s) + { + _s = s; + } + + public List Tokenize() + { + var list = new List(); + for (int i = 0; i < _s.Length; i++) + { + char c = _s[i]; + if (IsKigou(c)) + { + switch (c) + { + case '[': + list.Add(new(TokenType.BeginAssembly)); + break; + case ']': + list.Add(new(TokenType.EndAssembly)); + break; + case ':': + list.Add(new(TokenType.Colon)); + break; + case '<': + list.Add(new(TokenType.BeginGenericArguments)); + break; + case '>': + list.Add(new(TokenType.EndGenericArguments)); + break; + case '.': + list.Add(new(TokenType.Period)); + break; + case ',': + list.Add(new(TokenType.Comma)); + break; + } + } + else + { + int start = i; + while (true) + { + c = _s[i]; + + if (IsKigou(c)) + { + list.Add(new(TokenType.Part, _s.Substring(start, i - start))); + i--; + break; + } + + i++; + if (i >= _s.Length) + { + list.Add(new(TokenType.Part, _s.Substring(start, i - start))); + break; + } + } + } + } + + return list; + } + + private static bool IsKigou(char c) + { + return c switch + { + '[' or ']' or ':' or '<' or '>' or '.' or ',' => true, + _ => false, + }; + } } } } From a625708d53b79a3df07f390163182cf6b9b69620 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Wed, 29 Mar 2023 00:18:34 +0900 Subject: [PATCH 13/84] =?UTF-8?q?Json=E3=81=8B=E3=82=89=E5=9E=8B=E5=90=8D?= =?UTF-8?q?=E3=82=92=E5=8F=96=E5=BE=97=E3=81=97=E3=81=A6=E5=BE=A9=E5=85=83?= =?UTF-8?q?=E3=81=99=E3=82=8B=E5=87=A6=E7=90=86=E3=82=92=E9=96=A2=E6=95=B0?= =?UTF-8?q?=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Core/CoreProperty.cs | 12 ++----- src/Beutl.Core/JsonHelper.cs | 33 ++++++++++++++++++- .../Animation/AnimationSerializer.cs | 10 ++---- src/Beutl.Graphics/Animation/KeyFrame.cs | 22 ++++++------- .../Animation/KeyFrameAnimation{T}.cs | 4 +-- .../Audio/Effects/SoundEffectGroup.cs | 11 +++---- .../Converters/BrushJsonConverter.cs | 5 ++- .../Graphics/Effects/BitmapEffectGroup.cs | 11 +++---- .../Graphics/Filters/ImageFilterGroup.cs | 7 ++-- .../Graphics/Transformation/TransformGroup.cs | 7 ++-- src/Beutl.ProjectSystem/NodeTree/Node.cs | 2 +- .../NodeTree/NodeTreeSpace.cs | 22 +++---------- .../NodeTree/Nodes/Group/GroupInput.cs | 24 ++++---------- .../NodeTree/Nodes/Group/GroupOutput.cs | 22 +++---------- .../NodeTree/Nodes/LayerInputNode.cs | 22 +++---------- .../Operation/SourceOperation.cs | 28 +++++++--------- .../Operation/StylingOperator.cs | 4 +-- src/Beutl/ViewModels/EditViewModel.cs | 10 ++---- 18 files changed, 103 insertions(+), 153 deletions(-) diff --git a/src/Beutl.Core/CoreProperty.cs b/src/Beutl.Core/CoreProperty.cs index 9b33fb238..54b6421b4 100644 --- a/src/Beutl.Core/CoreProperty.cs +++ b/src/Beutl.Core/CoreProperty.cs @@ -247,7 +247,7 @@ internal override void RouteSetValue(ICoreObject o, object? value) Type objType = value.GetType(); if (objType != PropertyType && jsonNode is JsonObject) { - jsonNode["@type"] = TypeFormat.ToString(objType); + jsonNode.WriteDiscriminator(objType); } return jsonNode; @@ -272,10 +272,7 @@ internal override void RouteSetValue(ICoreObject o, object? value) return JsonSerializer.Deserialize(node, type, options); } else if (node is JsonObject jsonObject - && jsonObject.TryGetPropertyValue("@type", out JsonNode? atTypeNode) - && atTypeNode is JsonValue atTypeValue - && atTypeValue.TryGetValue(out string? atTypeStr) - && TypeFormat.ToType(atTypeStr) is Type realType + && jsonObject.TryGetDiscriminator(out Type? realType) && realType.IsAssignableTo(typeof(IJsonSerializable))) { var sobj = (IJsonSerializable?)Activator.CreateInstance(realType); @@ -286,10 +283,7 @@ internal override void RouteSetValue(ICoreObject o, object? value) else if (type.IsAssignableTo(typeof(IJsonSerializable))) { var sobj = (IJsonSerializable?)Activator.CreateInstance(type); - if (sobj != null) - { - sobj.ReadFromJson(node!); - } + sobj?.ReadFromJson(node!); return sobj; } diff --git a/src/Beutl.Core/JsonHelper.cs b/src/Beutl.Core/JsonHelper.cs index 68ef3788a..044e5d6ce 100644 --- a/src/Beutl.Core/JsonHelper.cs +++ b/src/Beutl.Core/JsonHelper.cs @@ -1,4 +1,5 @@ -using System.Text.Encodings.Web; +using System.Diagnostics.CodeAnalysis; +using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; @@ -80,6 +81,36 @@ public static void JsonSave(this JsonNode node, string filename) return JsonNode.Parse(stream); } + public static Type? GetDiscriminator(this JsonNode node) + { + return node.TryGetDiscriminator(out Type? type) ? type : null; + } + + public static bool TryGetDiscriminator(this JsonNode node, [NotNullWhen(true)] out Type? type) + { + type = null; + if (node is JsonObject obj) + { + JsonNode? typeNode = obj.TryGetPropertyValue("$type", out JsonNode? typeNode1) ? typeNode1 + : obj.TryGetPropertyValue("@type", out JsonNode? typeNode2) ? typeNode2 + : null; + + if (typeNode is JsonValue typeValue + && typeValue.TryGetValue(out string? typeStr) + && !string.IsNullOrWhiteSpace(typeStr)) + { + type = TypeFormat.ToType(typeStr); + } + } + + return type != null; + } + + public static void WriteDiscriminator(this JsonNode obj, Type type) + { + obj["$type"] = TypeFormat.ToString(type); + } + private static Dictionary ParseJson(string json) { Dictionary dic = JsonSerializer.Deserialize>(json)!; diff --git a/src/Beutl.Graphics/Animation/AnimationSerializer.cs b/src/Beutl.Graphics/Animation/AnimationSerializer.cs index 3e34a3e4f..a20d9863d 100644 --- a/src/Beutl.Graphics/Animation/AnimationSerializer.cs +++ b/src/Beutl.Graphics/Animation/AnimationSerializer.cs @@ -26,7 +26,7 @@ public static (string?, JsonNode?) ToJson(this IAnimation animation, Type target if (keyFrameAnimation.KeyFrames.Count > 0) { keyFrameAnimation.WriteToJson(ref node); - node["@type"] = TypeFormat.ToString(animation.GetType()); + node.WriteDiscriminator(animation.GetType()); return node; } else @@ -37,7 +37,7 @@ public static (string?, JsonNode?) ToJson(this IAnimation animation, Type target else { animation.WriteToJson(ref node); - node["@type"] = TypeFormat.ToString(animation.GetType()); + node.WriteDiscriminator(animation.GetType()); return node; } } @@ -56,11 +56,7 @@ public static (string?, JsonNode?) ToJson(this IAnimation animation, Type target { if (json is JsonObject obj) { - if (obj.TryGetPropertyValue("@type", out JsonNode? atTypeNode) - && atTypeNode is JsonValue atTypeValue - && atTypeValue.TryGetValue(out string? atType) - && atType != null - && TypeFormat.ToType(atType) is Type type + if (obj.TryGetDiscriminator(out Type? type) && Activator.CreateInstance(type, property) is IAnimation animation) { animation.ReadFromJson(json); diff --git a/src/Beutl.Graphics/Animation/KeyFrame.cs b/src/Beutl.Graphics/Animation/KeyFrame.cs index 4bfee8683..810e1ccaf 100644 --- a/src/Beutl.Graphics/Animation/KeyFrame.cs +++ b/src/Beutl.Graphics/Animation/KeyFrame.cs @@ -65,17 +65,17 @@ public override void WriteToJson(ref JsonNode json) { if (Easing is SplineEasing splineEasing) { - jsonObject["easing"] = new JsonObject + jsonObject[nameof(Easing)] = new JsonObject { - ["x1"] = splineEasing.X1, - ["y1"] = splineEasing.Y1, - ["x2"] = splineEasing.X2, - ["y2"] = splineEasing.Y2, + ["X1"] = splineEasing.X1, + ["Y1"] = splineEasing.Y1, + ["X2"] = splineEasing.X2, + ["Y2"] = splineEasing.Y2, }; } else { - jsonObject["easing"] = JsonValue.Create(TypeFormat.ToString(Easing.GetType())); + jsonObject[nameof(Easing)] = TypeFormat.ToString(Easing.GetType()); } } } @@ -86,7 +86,7 @@ public override void ReadFromJson(JsonNode json) if (json is JsonObject jsonObject) { - if (jsonObject.TryGetPropertyValue("easing", out JsonNode? easingNode)) + if (jsonObject.TryGetPropertyValue(nameof(Easing), out JsonNode? easingNode)) { if (easingNode is JsonValue easingTypeValue && easingTypeValue.TryGetValue(out string? easingType)) @@ -100,10 +100,10 @@ public override void ReadFromJson(JsonNode json) } else if (easingNode is JsonObject easingObject) { - float x1 = (float?)easingObject["x1"] ?? 0; - float y1 = (float?)easingObject["y1"] ?? 0; - float x2 = (float?)easingObject["x2"] ?? 1; - float y2 = (float?)easingObject["y2"] ?? 1; + float x1 = (float?)easingObject["X1"] ?? 0; + float y1 = (float?)easingObject["Y1"] ?? 0; + float x2 = (float?)easingObject["X2"] ?? 1; + float y2 = (float?)easingObject["Y2"] ?? 1; Easing = new SplineEasing(x1, y1, x2, y2); } diff --git a/src/Beutl.Graphics/Animation/KeyFrameAnimation{T}.cs b/src/Beutl.Graphics/Animation/KeyFrameAnimation{T}.cs index 61533eb51..21d203628 100644 --- a/src/Beutl.Graphics/Animation/KeyFrameAnimation{T}.cs +++ b/src/Beutl.Graphics/Animation/KeyFrameAnimation{T}.cs @@ -53,7 +53,7 @@ public override void ReadFromJson(JsonNode json) base.ReadFromJson(json); if (json is JsonObject obj) { - if (obj.TryGetPropertyValue("keyframes", out JsonNode? keyframesNode) + if (obj.TryGetPropertyValue(nameof(KeyFrames), out JsonNode? keyframesNode) && keyframesNode is JsonArray keyframesArray) { KeyFrames.Clear(); @@ -83,6 +83,6 @@ public override void WriteToJson(ref JsonNode json) array.Add(node); } - json["keyframes"] = array; + json[nameof(KeyFrames)] = array; } } diff --git a/src/Beutl.Graphics/Audio/Effects/SoundEffectGroup.cs b/src/Beutl.Graphics/Audio/Effects/SoundEffectGroup.cs index 6a9697795..0aacd2cca 100644 --- a/src/Beutl.Graphics/Audio/Effects/SoundEffectGroup.cs +++ b/src/Beutl.Graphics/Audio/Effects/SoundEffectGroup.cs @@ -46,7 +46,7 @@ public override void ReadFromJson(JsonNode json) base.ReadFromJson(json); if (json is JsonObject jobject) { - if (jobject.TryGetPropertyValue("children", out JsonNode? childrenNode) + if (jobject.TryGetPropertyValue(nameof(Children), out JsonNode? childrenNode) && childrenNode is JsonArray childrenArray) { _children.Clear(); @@ -54,10 +54,7 @@ public override void ReadFromJson(JsonNode json) foreach (JsonObject childJson in childrenArray.OfType()) { - if (childJson.TryGetPropertyValue("@type", out JsonNode? atTypeNode) - && atTypeNode is JsonValue atTypeValue - && atTypeValue.TryGetValue(out string? atType) - && TypeFormat.ToType(atType) is Type type + if (childJson.TryGetDiscriminator(out Type? type) && type.IsAssignableTo(typeof(SoundEffect)) && Activator.CreateInstance(type) is IMutableSoundEffect soundEffect) { @@ -83,13 +80,13 @@ public override void WriteToJson(ref JsonNode json) { JsonNode node = new JsonObject(); obj.WriteToJson(ref node); - node["@type"] = TypeFormat.ToString(item.GetType()); + node.WriteDiscriminator(item.GetType()); array.Add(node); } } - jobject["children"] = array; + jobject[nameof(Children)] = array; } } diff --git a/src/Beutl.Graphics/Converters/BrushJsonConverter.cs b/src/Beutl.Graphics/Converters/BrushJsonConverter.cs index 826534a77..8cc3032ed 100644 --- a/src/Beutl.Graphics/Converters/BrushJsonConverter.cs +++ b/src/Beutl.Graphics/Converters/BrushJsonConverter.cs @@ -17,8 +17,7 @@ public override IBrush Read(ref Utf8JsonReader reader, Type typeToConvert, JsonS { return new SolidColorBrush(Color.Parse(color)); } - else if ((string?)node["@type"] is string typeStr - && TypeFormat.ToType(typeStr) is Type type + else if (node.TryGetDiscriminator(out Type? type) && Activator.CreateInstance(type) is IJsonSerializable jsonSerializable && jsonSerializable is IBrush brush) { @@ -40,7 +39,7 @@ public override void Write(Utf8JsonWriter writer, IBrush value, JsonSerializerOp { JsonNode json = new JsonObject(); jsonSerializable.WriteToJson(ref json); - json["@type"] = TypeFormat.ToString(value.GetType()); + json.WriteDiscriminator(value.GetType()); json.WriteTo(writer); } diff --git a/src/Beutl.Graphics/Graphics/Effects/BitmapEffectGroup.cs b/src/Beutl.Graphics/Graphics/Effects/BitmapEffectGroup.cs index a6021f9ef..29f26889a 100644 --- a/src/Beutl.Graphics/Graphics/Effects/BitmapEffectGroup.cs +++ b/src/Beutl.Graphics/Graphics/Effects/BitmapEffectGroup.cs @@ -84,7 +84,7 @@ public override void ReadFromJson(JsonNode json) base.ReadFromJson(json); if (json is JsonObject jobject) { - if (jobject.TryGetPropertyValue("children", out JsonNode? childrenNode) + if (jobject.TryGetPropertyValue(nameof(Children), out JsonNode? childrenNode) && childrenNode is JsonArray childrenArray) { _children.Clear(); @@ -92,10 +92,7 @@ public override void ReadFromJson(JsonNode json) foreach (JsonObject childJson in childrenArray.OfType()) { - if (childJson.TryGetPropertyValue("@type", out JsonNode? atTypeNode) - && atTypeNode is JsonValue atTypeValue - && atTypeValue.TryGetValue(out string? atType) - && TypeFormat.ToType(atType) is Type type + if (childJson.TryGetDiscriminator(out Type? type) && type.IsAssignableTo(typeof(BitmapEffect)) && Activator.CreateInstance(type) is IMutableBitmapEffect bitmapEffect) { @@ -121,13 +118,13 @@ public override void WriteToJson(ref JsonNode json) { JsonNode node = new JsonObject(); obj.WriteToJson(ref node); - node["@type"] = TypeFormat.ToString(item.GetType()); + node.WriteDiscriminator(item.GetType()); array.Add(node); } } - jobject["children"] = array; + jobject[nameof(Children)] = array; } } } diff --git a/src/Beutl.Graphics/Graphics/Filters/ImageFilterGroup.cs b/src/Beutl.Graphics/Graphics/Filters/ImageFilterGroup.cs index 91b5054da..8a7ed1ea1 100644 --- a/src/Beutl.Graphics/Graphics/Filters/ImageFilterGroup.cs +++ b/src/Beutl.Graphics/Graphics/Filters/ImageFilterGroup.cs @@ -57,10 +57,7 @@ public override void ReadFromJson(JsonNode json) foreach (JsonObject childJson in childrenArray.OfType()) { - if (childJson.TryGetPropertyValue("@type", out JsonNode? atTypeNode) - && atTypeNode is JsonValue atTypeValue - && atTypeValue.TryGetValue(out string? atType) - && TypeFormat.ToType(atType) is Type type + if (childJson.TryGetDiscriminator(out Type? type) && type.IsAssignableTo(typeof(ImageFilter)) && Activator.CreateInstance(type) is IMutableImageFilter imageFilter) { @@ -86,7 +83,7 @@ public override void WriteToJson(ref JsonNode json) { JsonNode node = new JsonObject(); obj.WriteToJson(ref node); - node["@type"] = TypeFormat.ToString(item.GetType()); + node.WriteDiscriminator(item.GetType()); array.Add(node); } diff --git a/src/Beutl.Graphics/Graphics/Transformation/TransformGroup.cs b/src/Beutl.Graphics/Graphics/Transformation/TransformGroup.cs index b90b85034..43413c043 100644 --- a/src/Beutl.Graphics/Graphics/Transformation/TransformGroup.cs +++ b/src/Beutl.Graphics/Graphics/Transformation/TransformGroup.cs @@ -58,10 +58,7 @@ public override void ReadFromJson(JsonNode json) foreach (JsonObject childJson in childrenArray.OfType()) { - if (childJson.TryGetPropertyValue("@type", out JsonNode? atTypeNode) - && atTypeNode is JsonValue atTypeValue - && atTypeValue.TryGetValue(out string? atType) - && TypeFormat.ToType(atType) is Type type + if (childJson.TryGetDiscriminator(out Type? type) && type.IsAssignableTo(typeof(Transform)) && Activator.CreateInstance(type) is Transform transform) { @@ -87,7 +84,7 @@ public override void WriteToJson(ref JsonNode json) JsonNode node = new JsonObject(); transform.WriteToJson(ref node); - node["@type"] = TypeFormat.ToString(item.GetType()); + node.WriteDiscriminator(item.GetType()); array.Add(node); } diff --git a/src/Beutl.ProjectSystem/NodeTree/Node.cs b/src/Beutl.ProjectSystem/NodeTree/Node.cs index d90e5c408..ab487a0e8 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Node.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Node.cs @@ -466,7 +466,7 @@ public override void WriteToJson(ref JsonNode json) if (item is IJsonSerializable serializable) { serializable.WriteToJson(ref itemJson); - itemJson["@type"] = TypeFormat.ToString(item.GetType()); + itemJson.WriteDiscriminator(item.GetType()); } array.Add(itemJson); } diff --git a/src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs b/src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs index 184964a90..112e242fc 100644 --- a/src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs +++ b/src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs @@ -250,24 +250,12 @@ public override void ReadFromJson(JsonNode json) { foreach (JsonObject nodeJson in nodesArray.OfType()) { - if (nodeJson.TryGetPropertyValue("@type", out JsonNode? atTypeNode) - && atTypeNode is JsonValue atTypeValue - && atTypeValue.TryGetValue(out string? atType)) + if (nodeJson.TryGetDiscriminator(out Type? type) + && Activator.CreateInstance(type) is Node node) { - var type = TypeFormat.ToType(atType); - Node? node = null; - - if (type?.IsAssignableTo(typeof(Node)) ?? false) - { - node = Activator.CreateInstance(type) as Node; - } - // Todo: 型が見つからない場合、SourceOperatorと同じようにする - if (node != null) - { - node.ReadFromJson(nodeJson); - Nodes.Add(node); - } + node.ReadFromJson(nodeJson); + Nodes.Add(node); } } } @@ -289,7 +277,7 @@ public override void WriteToJson(ref JsonNode json) { JsonNode jsonNode = new JsonObject(); item.WriteToJson(ref jsonNode); - jsonNode["@type"] = TypeFormat.ToString(item.GetType()); + jsonNode.WriteDiscriminator(item.GetType()); array.Add(jsonNode); } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupInput.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupInput.cs index 569f016cf..6efc2fd41 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupInput.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupInput.cs @@ -52,7 +52,7 @@ public bool AddSocket(ISocket socket, [NotNullWhen(true)] out Connection? connec { ((NodeItem)outputSocket).LocalId = NextLocalId++; ((IGroupSocket)outputSocket).AssociatedProperty = inputSocket.Property?.Property; - if(inputSocket.Property?.Property == null) + if (inputSocket.Property?.Property == null) { ((CoreObject)outputSocket).Name = NodeDisplayNameHelper.GetDisplayName(inputSocket); } @@ -84,24 +84,12 @@ public override void ReadFromJson(JsonNode json) int index = 0; foreach (JsonObject itemJson in itemsArray.OfType()) { - if (itemJson.TryGetPropertyValue("@type", out JsonNode? atTypeNode) - && atTypeNode is JsonValue atTypeValue - && atTypeValue.TryGetValue(out string? atType)) + if (itemJson.TryGetDiscriminator(out Type? type) + && Activator.CreateInstance(type) is IOutputSocket socket) { - var type = TypeFormat.ToType(atType); - IOutputSocket? socket = null; - - if (type?.IsAssignableTo(typeof(IOutputSocket)) ?? false) - { - socket = Activator.CreateInstance(type) as IOutputSocket; - } - - if (socket != null) - { - (socket as IJsonSerializable)?.ReadFromJson(itemJson); - Items.Add(socket); - ((NodeItem)socket).LocalId = index; - } + (socket as IJsonSerializable)?.ReadFromJson(itemJson); + Items.Add(socket); + ((NodeItem)socket).LocalId = index; } index++; diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupOutput.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupOutput.cs index 4cb51dd2b..039d79ea2 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupOutput.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupOutput.cs @@ -84,24 +84,12 @@ public override void ReadFromJson(JsonNode json) int index = 0; foreach (JsonObject itemJson in itemsArray.OfType()) { - if (itemJson.TryGetPropertyValue("@type", out JsonNode? atTypeNode) - && atTypeNode is JsonValue atTypeValue - && atTypeValue.TryGetValue(out string? atType)) + if (itemJson.TryGetDiscriminator(out Type? type) + && Activator.CreateInstance(type) is IInputSocket socket) { - var type = TypeFormat.ToType(atType); - IInputSocket? socket = null; - - if (type?.IsAssignableTo(typeof(IInputSocket)) ?? false) - { - socket = Activator.CreateInstance(type) as IInputSocket; - } - - if (socket != null) - { - (socket as IJsonSerializable)?.ReadFromJson(itemJson); - Items.Add(socket); - ((NodeItem)socket).LocalId = index; - } + (socket as IJsonSerializable)?.ReadFromJson(itemJson); + Items.Add(socket); + ((NodeItem)socket).LocalId = index; } index++; diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/LayerInputNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/LayerInputNode.cs index b0a3f1b8c..35625ab68 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/LayerInputNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/LayerInputNode.cs @@ -146,24 +146,12 @@ public override void ReadFromJson(JsonNode json) int index = 0; foreach (JsonObject itemJson in itemsArray.OfType()) { - if (itemJson.TryGetPropertyValue("@type", out JsonNode? atTypeNode) - && atTypeNode is JsonValue atTypeValue - && atTypeValue.TryGetValue(out string? atType)) + if (itemJson.TryGetDiscriminator(out Type? type) + && Activator.CreateInstance(type) is ILayerInputSocket socket) { - var type = TypeFormat.ToType(atType); - ILayerInputSocket? socket = null; - - if (type?.IsAssignableTo(typeof(ILayerInputSocket)) ?? false) - { - socket = Activator.CreateInstance(type) as ILayerInputSocket; - } - - if (socket != null) - { - (socket as IJsonSerializable)?.ReadFromJson(itemJson); - Items.Add(socket); - ((NodeItem)socket).LocalId = index; - } + (socket as IJsonSerializable)?.ReadFromJson(itemJson); + Items.Add(socket); + ((NodeItem)socket).LocalId = index; } index++; diff --git a/src/Beutl.ProjectSystem/Operation/SourceOperation.cs b/src/Beutl.ProjectSystem/Operation/SourceOperation.cs index 6b4e12c0b..436295147 100644 --- a/src/Beutl.ProjectSystem/Operation/SourceOperation.cs +++ b/src/Beutl.ProjectSystem/Operation/SourceOperation.cs @@ -38,27 +38,21 @@ public override void ReadFromJson(JsonNode json) if (json is JsonObject jobject) { - if (jobject.TryGetPropertyValue("children", out JsonNode? childrenNode) + if (jobject.TryGetPropertyValue(nameof(Children), out JsonNode? childrenNode) && childrenNode is JsonArray childrenArray) { foreach (JsonObject operatorJson in childrenArray.OfType()) { - if (operatorJson.TryGetPropertyValue("@type", out JsonNode? atTypeNode) - && atTypeNode is JsonValue atTypeValue - && atTypeValue.TryGetValue(out string? atType)) + Type? type = operatorJson.GetDiscriminator(); + SourceOperator? @operator = null; + if (type?.IsAssignableTo(typeof(SourceOperator)) ?? false) { - var type = TypeFormat.ToType(atType); - SourceOperator? @operator = null; - - if (type?.IsAssignableTo(typeof(SourceOperator)) ?? false) - { - @operator = Activator.CreateInstance(type) as SourceOperator; - } - - @operator ??= new SourceOperator(); - @operator.ReadFromJson(operatorJson); - Children.Add(@operator); + @operator = Activator.CreateInstance(type) as SourceOperator; } + + @operator ??= new SourceOperator(); + @operator.ReadFromJson(operatorJson); + Children.Add(@operator); } } @@ -80,12 +74,12 @@ public override void WriteToJson(ref JsonNode json) { JsonNode node = new JsonObject(); item.WriteToJson(ref node); - node["@type"] = TypeFormat.ToString(item.GetType()); + node.WriteDiscriminator(item.GetType()); array.Add(node); } - jobject["children"] = array; + jobject[nameof(Children)] = array; } } diff --git a/src/Beutl.ProjectSystem/Operation/StylingOperator.cs b/src/Beutl.ProjectSystem/Operation/StylingOperator.cs index 5eef81052..20b102d51 100644 --- a/src/Beutl.ProjectSystem/Operation/StylingOperator.cs +++ b/src/Beutl.ProjectSystem/Operation/StylingOperator.cs @@ -171,7 +171,7 @@ public override void ReadFromJson(JsonNode json) { base.ReadFromJson(json); if (json is JsonObject obj - && obj.TryGetPropertyValue("style", out JsonNode? styleNode) + && obj.TryGetPropertyValue(nameof(Style), out JsonNode? styleNode) && styleNode is JsonObject styleObj) { var style = StyleSerializer.ToStyle(styleObj); @@ -189,7 +189,7 @@ public override void WriteToJson(ref JsonNode json) base.WriteToJson(ref json); if (json is JsonObject obj) { - obj["style"] = StyleSerializer.ToJson(Style); + obj[nameof(Style)] = StyleSerializer.ToJson(Style); } } diff --git a/src/Beutl/ViewModels/EditViewModel.cs b/src/Beutl/ViewModels/EditViewModel.cs index 57596160c..a56abae05 100644 --- a/src/Beutl/ViewModels/EditViewModel.cs +++ b/src/Beutl/ViewModels/EditViewModel.cs @@ -218,7 +218,7 @@ private void SaveState() JsonNode itemJson = new JsonObject(); item.Context.WriteToJson(ref itemJson); - itemJson["@type"] = TypeFormat.ToString(item.Context.Extension.GetType()); + itemJson.WriteDiscriminator(item.Context.Extension.GetType()); bottomItems.Add(itemJson); } @@ -230,7 +230,7 @@ private void SaveState() JsonNode itemJson = new JsonObject(); item.Context.WriteToJson(ref itemJson); - itemJson["@type"] = TypeFormat.ToString(item.Context.Extension.GetType()); + itemJson.WriteDiscriminator(item.Context.Extension.GetType()); rightItems.Add(itemJson); } @@ -316,11 +316,7 @@ void RestoreTabItems(JsonArray source, CoreList destination) foreach (JsonNode? item in source) { if (item is JsonObject itemObject - && itemObject.TryGetPropertyValue("@type", out JsonNode? typeNode) - && typeNode is JsonValue typeValue - && typeValue.TryGetValue(out string? typeStr) - && typeStr != null - && TypeFormat.ToType(typeStr) is Type type + && itemObject.TryGetDiscriminator(out Type? type) && _extensionProvider.AllExtensions.FirstOrDefault(x => x.GetType() == type) is ToolTabExtension extension && extension.TryCreateContext(this, out IToolContext? context)) { From 617d936ec5bd70a7a2d726e417241252cda4520d Mon Sep 17 00:00:00 2001 From: indigo-san Date: Wed, 29 Mar 2023 00:43:35 +0900 Subject: [PATCH 14/84] =?UTF-8?q?ShouldSerialize(false)=E3=82=92NotAutoSer?= =?UTF-8?q?ialize=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Configuration/ViewConfig.cs | 4 ++-- src/Beutl.Core/CoreObject.cs | 3 ++- src/Beutl.Core/CorePropertyMetadata.cs | 5 +---- src/Beutl.Core/Hierarchy/Hierarchical.cs | 2 +- src/Beutl.Core/ProjectItem.cs | 2 +- src/Beutl.Core/ShouldSerializeAttribute.cs | 7 ++----- src/Beutl.Graphics/Animation/KeyFrame.cs | 2 +- src/Beutl.Graphics/Audio/Effects/SoundEffectGroup.cs | 2 +- src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs | 2 +- src/Beutl.Graphics/Graphics/Effects/BitmapEffectGroup.cs | 2 +- src/Beutl.Graphics/Graphics/Filters/ImageFilterGroup.cs | 2 +- src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs | 2 +- src/Beutl.Graphics/Graphics/SourceImage.cs | 2 +- .../Graphics/Transformation/TransformGroup.cs | 2 +- src/Beutl.Graphics/Rendering/RenderLayerSpan.cs | 2 +- src/Beutl.Graphics/Styling/Styleable.cs | 4 ++-- src/Beutl.ProjectSystem/NodeTree/Node.cs | 2 +- src/Beutl.ProjectSystem/NodeTree/NodeItem.cs | 2 +- src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs | 4 ++-- src/Beutl.ProjectSystem/ProjectSystem/Scene.cs | 6 +++--- 20 files changed, 27 insertions(+), 32 deletions(-) diff --git a/src/Beutl.Configuration/ViewConfig.cs b/src/Beutl.Configuration/ViewConfig.cs index 1c9a7430d..572b49696 100644 --- a/src/Beutl.Configuration/ViewConfig.cs +++ b/src/Beutl.Configuration/ViewConfig.cs @@ -64,14 +64,14 @@ public bool IsMicaEffectEnabled set => SetValue(IsMicaEffectEnabledProperty, value); } - [ShouldSerialize(false)] + [NotAutoSerialized()] public CoreList RecentFiles { get => _recentFiles; set => _recentFiles.Replace(value); } - [ShouldSerialize(false)] + [NotAutoSerialized()] public CoreList RecentProjects { get => _recentProjects; diff --git a/src/Beutl.Core/CoreObject.cs b/src/Beutl.Core/CoreObject.cs index 1d0938f02..f63d4b542 100644 --- a/src/Beutl.Core/CoreObject.cs +++ b/src/Beutl.Core/CoreObject.cs @@ -360,7 +360,7 @@ public virtual void WriteToJson(ref JsonNode json) { CoreProperty item = list[i]; CorePropertyMetadata metadata = item.GetMetadata(ownerType); - if (metadata.ShouldSerialize) + if (metadata.ShouldSerialize && (item is not IStaticProperty sprop || sprop.CanWrite)) { JsonNode? valueNode = item.RouteWriteToJson(metadata, GetValue(item), out bool isDefault); if (!isDefault) @@ -384,6 +384,7 @@ public virtual void ReadFromJson(JsonNode json) CoreProperty item = list[i]; CorePropertyMetadata metadata = item.GetMetadata(ownerType); if (metadata.ShouldSerialize + && (item is not IStaticProperty sprop || sprop.CanWrite) && obj.TryGetPropertyValue(item.Name, out JsonNode? jsonNode) && jsonNode != null) { diff --git a/src/Beutl.Core/CorePropertyMetadata.cs b/src/Beutl.Core/CorePropertyMetadata.cs index 8d0210154..67e151882 100644 --- a/src/Beutl.Core/CorePropertyMetadata.cs +++ b/src/Beutl.Core/CorePropertyMetadata.cs @@ -30,6 +30,7 @@ private void UpdatedAttributes() { if (Attributes != null) { + ShouldSerialize = !Attributes.Any(x => x is NotAutoSerializedAttribute); foreach (Attribute item in Attributes) { switch (item) @@ -42,10 +43,6 @@ private void UpdatedAttributes() Notifiable = notifiable.Notifiable; break; - case ShouldSerializeAttribute shouldSerialize: - ShouldSerialize = shouldSerialize.ShouldSerialize; - break; - case BrowsableAttribute browsableAttribute: Browsable = browsableAttribute.Browsable; break; diff --git a/src/Beutl.Core/Hierarchy/Hierarchical.cs b/src/Beutl.Core/Hierarchy/Hierarchical.cs index b2b2004a6..6cd0e323c 100644 --- a/src/Beutl.Core/Hierarchy/Hierarchical.cs +++ b/src/Beutl.Core/Hierarchy/Hierarchical.cs @@ -29,7 +29,7 @@ public Hierarchical() _hierarchicalChildren.CollectionChanged += HierarchicalChildrenCollectionChanged; } - [ShouldSerialize(false)] + [NotAutoSerialized] public IHierarchical? HierarchicalParent { get => _parent; diff --git a/src/Beutl.Core/ProjectItem.cs b/src/Beutl.Core/ProjectItem.cs index 784c7c626..dc3cfedf7 100644 --- a/src/Beutl.Core/ProjectItem.cs +++ b/src/Beutl.Core/ProjectItem.cs @@ -12,7 +12,7 @@ static ProjectItem() .Register(); } - [ShouldSerialize(false)] + [NotAutoSerialized] public string FileName { get => _fileName!; diff --git a/src/Beutl.Core/ShouldSerializeAttribute.cs b/src/Beutl.Core/ShouldSerializeAttribute.cs index c31d12a17..4ed33582c 100644 --- a/src/Beutl.Core/ShouldSerializeAttribute.cs +++ b/src/Beutl.Core/ShouldSerializeAttribute.cs @@ -1,12 +1,9 @@ namespace Beutl; [AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)] -public sealed class ShouldSerializeAttribute : Attribute +public sealed class NotAutoSerializedAttribute : Attribute { - public ShouldSerializeAttribute(bool shouldSerialize) + public NotAutoSerializedAttribute() { - ShouldSerialize = shouldSerialize; } - - public bool ShouldSerialize { get; } } diff --git a/src/Beutl.Graphics/Animation/KeyFrame.cs b/src/Beutl.Graphics/Animation/KeyFrame.cs index 810e1ccaf..14207e978 100644 --- a/src/Beutl.Graphics/Animation/KeyFrame.cs +++ b/src/Beutl.Graphics/Animation/KeyFrame.cs @@ -36,7 +36,7 @@ static KeyFrame() // .Register(); } - [ShouldSerialize(false)] + [NotAutoSerialized] public Easing Easing { get => _easing; diff --git a/src/Beutl.Graphics/Audio/Effects/SoundEffectGroup.cs b/src/Beutl.Graphics/Audio/Effects/SoundEffectGroup.cs index 0aacd2cca..a526d2314 100644 --- a/src/Beutl.Graphics/Audio/Effects/SoundEffectGroup.cs +++ b/src/Beutl.Graphics/Audio/Effects/SoundEffectGroup.cs @@ -21,7 +21,7 @@ public SoundEffectGroup() _children.Invalidated += (_, e) => RaiseInvalidated(e); } - [ShouldSerialize(false)] + [NotAutoSerialized] public SoundEffects Children { get => _children; diff --git a/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs b/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs index 9bc0366b9..b65673861 100644 --- a/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs +++ b/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs @@ -87,7 +87,7 @@ public VideoPositionMode PositionMode set => SetAndRaise(PositionModeProperty, ref _positionMode, value); } - [ShouldSerialize(false)] + [NotAutoSerialized] public FileInfo? SourceFile { get => _sourceFile; diff --git a/src/Beutl.Graphics/Graphics/Effects/BitmapEffectGroup.cs b/src/Beutl.Graphics/Graphics/Effects/BitmapEffectGroup.cs index 29f26889a..819331f4d 100644 --- a/src/Beutl.Graphics/Graphics/Effects/BitmapEffectGroup.cs +++ b/src/Beutl.Graphics/Graphics/Effects/BitmapEffectGroup.cs @@ -38,7 +38,7 @@ public BitmapEffectGroup() }; } - [ShouldSerialize(false)] + [NotAutoSerialized] public BitmapEffects Children { get => _children; diff --git a/src/Beutl.Graphics/Graphics/Filters/ImageFilterGroup.cs b/src/Beutl.Graphics/Graphics/Filters/ImageFilterGroup.cs index 8a7ed1ea1..d53489cb0 100644 --- a/src/Beutl.Graphics/Graphics/Filters/ImageFilterGroup.cs +++ b/src/Beutl.Graphics/Graphics/Filters/ImageFilterGroup.cs @@ -24,7 +24,7 @@ public ImageFilterGroup() _children.Invalidated += (_, e) => RaiseInvalidated(e); } - [ShouldSerialize(false)] + [NotAutoSerialized] public ImageFilters Children { get => _children; diff --git a/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs b/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs index 19d8befce..f6b226d66 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs @@ -128,7 +128,7 @@ public Thickness Margin set => SetAndRaise(MarginProperty, ref _margin, value); } - [ShouldSerialize(false)] + [NotAutoSerialized] public TextElements? Elements { get => _elements; diff --git a/src/Beutl.Graphics/Graphics/SourceImage.cs b/src/Beutl.Graphics/Graphics/SourceImage.cs index 00827440d..81c4f7f45 100644 --- a/src/Beutl.Graphics/Graphics/SourceImage.cs +++ b/src/Beutl.Graphics/Graphics/SourceImage.cs @@ -24,7 +24,7 @@ static SourceImage() AffectsRender(SourceProperty); } - [ShouldSerialize(false)] + [NotAutoSerialized] public IImageSource? Source { get => _source; diff --git a/src/Beutl.Graphics/Graphics/Transformation/TransformGroup.cs b/src/Beutl.Graphics/Graphics/Transformation/TransformGroup.cs index 43413c043..ba523ff50 100644 --- a/src/Beutl.Graphics/Graphics/Transformation/TransformGroup.cs +++ b/src/Beutl.Graphics/Graphics/Transformation/TransformGroup.cs @@ -22,7 +22,7 @@ public TransformGroup() _children.Invalidated += (_, e) => RaiseInvalidated(e); } - [ShouldSerialize(false)] + [NotAutoSerialized] public Transforms Children { get => _children; diff --git a/src/Beutl.Graphics/Rendering/RenderLayerSpan.cs b/src/Beutl.Graphics/Rendering/RenderLayerSpan.cs index cda3e21dd..119cd9a0d 100644 --- a/src/Beutl.Graphics/Rendering/RenderLayerSpan.cs +++ b/src/Beutl.Graphics/Rendering/RenderLayerSpan.cs @@ -59,7 +59,7 @@ public TimeSpan Duration public Renderables Value => _value; - [ShouldSerialize(false)] + [NotAutoSerialized] public RenderLayer? RenderLayer { get => _renderLayer; diff --git a/src/Beutl.Graphics/Styling/Styleable.cs b/src/Beutl.Graphics/Styling/Styleable.cs index 0b6307fb6..bc51b4041 100644 --- a/src/Beutl.Graphics/Styling/Styleable.cs +++ b/src/Beutl.Graphics/Styling/Styleable.cs @@ -50,7 +50,7 @@ private void Style_Invalidated(object? sender, EventArgs e) _styleInstance = null; } - [ShouldSerialize(false)] + [NotAutoSerialized] public Styles Styles { get => _styles; @@ -156,7 +156,7 @@ public override void WriteToJson(ref JsonNode json) private IHierarchical? _parent; private IHierarchicalRoot? _root; - [ShouldSerialize(false)] + [NotAutoSerialized] public IHierarchical? HierarchicalParent { get => _parent; diff --git a/src/Beutl.ProjectSystem/NodeTree/Node.cs b/src/Beutl.ProjectSystem/NodeTree/Node.cs index ab487a0e8..b64463e38 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Node.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Node.cs @@ -69,7 +69,7 @@ public bool IsExpanded set => SetValue(IsExpandedProperty, value); } - [ShouldSerialize(false)] + [NotAutoSerialized] public (double X, double Y) Position { get => _position; diff --git a/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs b/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs index 76716ba52..ae636bb80 100644 --- a/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs +++ b/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs @@ -26,7 +26,7 @@ public NodeItem() Id = Guid.NewGuid(); } - [ShouldSerialize(false)] + [NotAutoSerialized] public bool? IsValid { get => _isValid; diff --git a/src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs b/src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs index 112e242fc..d8db23cd6 100644 --- a/src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs +++ b/src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs @@ -37,14 +37,14 @@ public NodeGroup() Nodes.Detached += OnNodeDetached; } - [ShouldSerialize(false)] + [NotAutoSerialized] public GroupInput? Input { get => _input; set => SetAndRaise(InputProperty, ref _input, value); } - [ShouldSerialize(false)] + [NotAutoSerialized] public GroupOutput? Output { get => _output; diff --git a/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs b/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs index 4cde5e872..2ce3ea889 100644 --- a/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs +++ b/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs @@ -116,21 +116,21 @@ public TimeSpan CurrentFrame } } - [ShouldSerialize(false)] + [NotAutoSerialized] public Layers Children { get => _children; set => _children.Replace(value); } - [ShouldSerialize(false)] + [NotAutoSerialized] public PreviewOptions? PreviewOptions { get => _previewOptions; set => SetAndRaise(PreviewOptionsProperty, ref _previewOptions, value); } - [ShouldSerialize(false)] + [NotAutoSerialized] public IRenderer Renderer { get => _renderer; From e8d40fab0c86944a939042c4e2db00654adf4cbe Mon Sep 17 00:00:00 2001 From: indigo-san Date: Wed, 29 Mar 2023 21:21:22 +0900 Subject: [PATCH 15/84] =?UTF-8?q?TransformParser=E3=81=A7Unsafe.SkipInit?= =?UTF-8?q?=E3=82=92=E4=BD=BF=E3=81=88=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Transformation/TransformParser.cs | 82 ++++++++----------- 1 file changed, 34 insertions(+), 48 deletions(-) diff --git a/src/Beutl.Graphics/Graphics/Transformation/TransformParser.cs b/src/Beutl.Graphics/Graphics/Transformation/TransformParser.cs index 6491d6cb5..608990596 100644 --- a/src/Beutl.Graphics/Graphics/Transformation/TransformParser.cs +++ b/src/Beutl.Graphics/Graphics/Transformation/TransformParser.cs @@ -1,4 +1,5 @@ using System.Globalization; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Beutl.Utilities; @@ -464,7 +465,7 @@ private static float ToRadians(in UnitValue value) _ => value.Value }; } - + private static float ToScale(in UnitValue value) { return value.Unit switch @@ -529,73 +530,58 @@ public Builder(int capacity) public void AppendTranslate(float x, float y) { - _data.Add(new DataLayout - { - Type = DataType.Translate, - Translate = - { - X = x, - Y = y - } - }); + Unsafe.SkipInit(out DataLayout value); + value.Type = DataType.Translate; + value.Translate.X = x; + value.Translate.Y = y; + + _data.Add(value); } public void AppendRotate(float angle) { - _data.Add(new DataLayout - { - Type = DataType.Rotate, - Rotate = - { - Angle = angle - } - }); + Unsafe.SkipInit(out DataLayout value); + value.Type = DataType.Rotate; + value.Rotate.Angle = angle; + + _data.Add(value); } public void AppendScale(float x, float y) { - _data.Add(new DataLayout - { - Type = DataType.Scale, - Scale = - { - X = x, - Y = y - } - }); + Unsafe.SkipInit(out DataLayout value); + value.Type = DataType.Scale; + value.Scale.X = x; + value.Scale.Y = y; + + _data.Add(value); } public void AppendSkew(float x, float y) { - _data.Add(new DataLayout - { - Type = DataType.Skew, - Scale = - { - X = x, - Y = y - } - }); + Unsafe.SkipInit(out DataLayout value); + value.Type = DataType.Skew; + value.Skew.X = x; + value.Skew.Y = y; + + _data.Add(value); } public void AppendMatrix(Matrix matrix) { - _data.Add(new DataLayout - { - Type = DataType.Matrix, - Matrix = - { - Value = matrix - } - }); + Unsafe.SkipInit(out DataLayout value); + value.Type = DataType.Matrix; + value.Matrix.Value = matrix; + + _data.Add(value); } public void AppendIdentity() { - _data.Add(new DataLayout - { - Type = DataType.Identity - }); + Unsafe.SkipInit(out DataLayout value); + value.Type = DataType.Identity; + + _data.Add(value); } public void Append(DataLayout toAdd) From 18690604121b88dcbbaba2ad820d639b301502bf Mon Sep 17 00:00:00 2001 From: indigo-san Date: Sat, 1 Apr 2023 17:42:43 +0900 Subject: [PATCH 16/84] =?UTF-8?q?-=20=E3=83=8E=E3=83=BC=E3=83=89=E3=81=AE?= =?UTF-8?q?=E3=82=BD=E3=82=B1=E3=83=83=E3=83=88=E9=96=93=E3=81=A7=E5=9E=8B?= =?UTF-8?q?=E3=81=8C=E4=B8=80=E8=87=B4=E3=81=97=E3=81=AA=E3=81=84=E3=81=A8?= =?UTF-8?q?=E3=81=8D=E3=80=81=E5=A4=89=E6=8F=9B=E3=81=AE=E3=81=9F=E3=82=81?= =?UTF-8?q?=E3=81=AE=E3=83=9C=E3=83=83=E3=82=AF=E3=82=B9=E5=86=8D=E5=88=A9?= =?UTF-8?q?=E7=94=A8=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=97?= =?UTF-8?q?=E3=81=9F=E3=80=82=20-=20=E3=82=BD=E3=82=B1=E3=83=83=E3=83=88?= =?UTF-8?q?=E3=81=AE=E6=8E=A5=E7=B6=9A=E3=81=A7=E5=A4=89=E6=8F=9B=E3=81=8C?= =?UTF-8?q?=E7=99=BA=E7=94=9F=E3=81=97=E3=81=A6=E3=81=84=E3=82=8B=E3=81=8B?= =?UTF-8?q?=E3=82=92=E8=A6=8B=E3=82=8C=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Graphics/Filters/ImageFilter.cs | 2 +- .../NodeTree/Connection.cs | 29 +++- .../NodeTree/IInputSocket.cs | 2 - .../NodeTree/InputSocket.cs | 134 ++++++++++++++---- src/Beutl.ProjectSystem/NodeTree/NodeItem.cs | 12 -- .../NodeTree/OutputSocket.cs | 50 ++++++- .../NodeTree/InputSocketViewModel.cs | 26 +++- .../NodeTree/OutputSocketViewModel.cs | 2 +- .../ViewModels/NodeTree/SocketViewModel.cs | 15 +- src/Beutl/Views/NodeTree/SocketPoint.cs | 42 ++++-- 10 files changed, 238 insertions(+), 76 deletions(-) diff --git a/src/Beutl.Graphics/Graphics/Filters/ImageFilter.cs b/src/Beutl.Graphics/Graphics/Filters/ImageFilter.cs index 7ac9eb6db..567d9f853 100644 --- a/src/Beutl.Graphics/Graphics/Filters/ImageFilter.cs +++ b/src/Beutl.Graphics/Graphics/Filters/ImageFilter.cs @@ -8,7 +8,7 @@ namespace Beutl.Graphics.Filters; public abstract class ImageFilter : Animatable, IMutableImageFilter { public static readonly CoreProperty IsEnabledProperty; - private bool _isEnabled; + private bool _isEnabled = true; static ImageFilter() { diff --git a/src/Beutl.ProjectSystem/NodeTree/Connection.cs b/src/Beutl.ProjectSystem/NodeTree/Connection.cs index b5357c1cc..200b97a05 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Connection.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Connection.cs @@ -1,13 +1,40 @@ namespace Beutl.NodeTree; -public sealed class Connection +[Flags] +public enum ConnectionStatus { + Disconnected = 0b1, + Connected = 0b10, + + Success = Connected | 0b100, + Convert = Connected | 0b1000, + Error = Connected | 0b10000, +} + +public sealed class Connection : CoreObject +{ + public static readonly CoreProperty StatusProperty; + private ConnectionStatus _status; + + static Connection() + { + StatusProperty = ConfigureProperty(nameof(Status)) + .Accessor(o => o.Status, (o, v) => o.Status = v) + .Register(); + } + public Connection(IInputSocket input, IOutputSocket output) { Input = input; Output = output; } + public ConnectionStatus Status + { + get => _status; + private set => SetAndRaise(StatusProperty, ref _status, value); + } + public IInputSocket Input { get; } public IOutputSocket Output { get; } diff --git a/src/Beutl.ProjectSystem/NodeTree/IInputSocket.cs b/src/Beutl.ProjectSystem/NodeTree/IInputSocket.cs index 90f64e715..5b8ff3d82 100644 --- a/src/Beutl.ProjectSystem/NodeTree/IInputSocket.cs +++ b/src/Beutl.ProjectSystem/NodeTree/IInputSocket.cs @@ -4,8 +4,6 @@ public interface IInputSocket : ISocket { Connection? Connection { get; } - bool? IsValid { get; } - void Receive(object? value); void NotifyConnected(Connection connection); diff --git a/src/Beutl.ProjectSystem/NodeTree/InputSocket.cs b/src/Beutl.ProjectSystem/NodeTree/InputSocket.cs index 56130da3e..8eb147227 100644 --- a/src/Beutl.ProjectSystem/NodeTree/InputSocket.cs +++ b/src/Beutl.ProjectSystem/NodeTree/InputSocket.cs @@ -1,4 +1,4 @@ -using System.Diagnostics.CodeAnalysis; +using System.ComponentModel; using System.Text.Json.Nodes; namespace Beutl.NodeTree; @@ -10,6 +10,14 @@ public class InputSocket : Socket, IInputSocket private Guid _outputId; private InputSocketReceiver? _onReceive; private bool _force; + private TypeConverter? _dstTypeConverter; + private TypeConverter? _srcTypeConverter; + private Type? _srcType; + + public InputSocket() + { + _dstTypeConverter = TypeDescriptor.GetConverter(typeof(T)); + } public Connection? Connection { get; private set; } @@ -20,8 +28,8 @@ public void NotifyConnected(Connection connection) _outputId = Guid.Empty; } - IsValid = null; Connection = connection; + UpdateStatus(ConnectionStatus.Connected); RaiseConnected(connection); } @@ -29,63 +37,122 @@ public void NotifyDisconnected(Connection connection) { if (Connection == connection) { - IsValid = null; Connection = null; RaiseDisconnected(connection); } } + private void UpdateStatus(ConnectionStatus status) + { + Connection!.SetValue(Connection.StatusProperty, status); + } + public virtual void Receive(T? value) { - if (_force && _onReceive != null) + if (Connection != null) { - IsValid = _onReceive(value, out T? received); - if (IsValid.Value) + if (_force && _onReceive != null) + { + if (_onReceive(value, out T? received)) + { + Value = received; + UpdateStatus(ConnectionStatus.Convert); + } + else + { + UpdateStatus(ConnectionStatus.Error); + } + } + else { - Value = received; + Value = value; + UpdateStatus(ConnectionStatus.Success); } } - else + } + + private void ReceiveWithConverter(object value) + { + ConnectionStatus status = ConnectionStatus.Error; + try { - Value = value; - IsValid = true; + Type srcType = value.GetType(); + + if (_dstTypeConverter?.CanConvertFrom(value.GetType()) == true) + { + Value = (T?)_dstTypeConverter.ConvertFrom(value); + status = ConnectionStatus.Convert; + } + else + { + if (_srcType != srcType + || _srcTypeConverter == null) + { + _srcTypeConverter = TypeDescriptor.GetConverter(srcType); + _srcType = srcType; + } + + if (_srcTypeConverter.CanConvertTo(typeof(T))) + { + Value = (T?)_srcTypeConverter.ConvertTo(value, typeof(T)); + status = ConnectionStatus.Convert; + } + } + } + catch + { + status = ConnectionStatus.Error; + } + finally + { + UpdateStatus(status); } } + private void ReceiveInvalidValue() + { + T? value1 = default; + if (Property != null) + { + value1 = Property.GetValue(); + if (value1 == null + && Property?.Property.GetMetadata>(Property.ImplementedType) is { } metadata + && metadata.HasDefaultValue) + { + value1 = metadata.DefaultValue; + } + } + + Receive(value1); + UpdateStatus(ConnectionStatus.Error); + } + public void Receive(object? value) { if (value is T t) { Receive(t); - IsValid = true; } else { - if (_onReceive != null) + //if (_onReceive != null) + //{ + // IsValid = _onReceive(value, out T? received); + // if (IsValid.Value) + // { + // Value = received; + // } + //} + //else { - IsValid = _onReceive(value, out T? received); - if (IsValid.Value) + if (value != null) { - Value = received; + ReceiveWithConverter(value); } - } - else - { - T? value1 = default; - if (Property != null) + else { - value1 = Property.GetValue(); - if (value1 == null - && Property?.Property.GetMetadata>(Property.ImplementedType) is { } metadata - && metadata.HasDefaultValue) - { - value1 = metadata.DefaultValue; - } + ReceiveInvalidValue(); } - - Receive(value1); - - IsValid = false; } } } @@ -97,6 +164,11 @@ public void RegisterReceiver(InputSocketReceiver onReceive, bool force = fals _force = force; } + public void RegisterConverter(TypeConverter typeConverter) + { + _dstTypeConverter = typeConverter; + } + public override void PreEvaluate(EvaluationContext context) { if (Connection == null) diff --git a/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs b/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs index ae636bb80..0d96df786 100644 --- a/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs +++ b/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs @@ -6,16 +6,11 @@ namespace Beutl.NodeTree; public abstract class NodeItem : Hierarchical { - public static readonly CoreProperty IsValidProperty; public static readonly CoreProperty LocalIdProperty; - private bool? _isValid; private int _localId = -1; static NodeItem() { - IsValidProperty = ConfigureProperty(o => o.IsValid) - .Register(); - LocalIdProperty = ConfigureProperty(o => o.LocalId) .DefaultValue(-1) .Register(); @@ -26,13 +21,6 @@ public NodeItem() Id = Guid.NewGuid(); } - [NotAutoSerialized] - public bool? IsValid - { - get => _isValid; - protected set => SetAndRaise(IsValidProperty, ref _isValid, value); - } - public int LocalId { get => _localId; diff --git a/src/Beutl.ProjectSystem/NodeTree/OutputSocket.cs b/src/Beutl.ProjectSystem/NodeTree/OutputSocket.cs index 55deb19c5..c397535c0 100644 --- a/src/Beutl.ProjectSystem/NodeTree/OutputSocket.cs +++ b/src/Beutl.ProjectSystem/NodeTree/OutputSocket.cs @@ -1,15 +1,58 @@ -using System.Text.Json.Nodes; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text.Json.Nodes; using Beutl.Collections; namespace Beutl.NodeTree; +file struct UnsafeBox : IDisposable +{ + public object? Object; + private GCHandle _handle; + private nint _ptr; + + public unsafe void Update(T? value) + { + bool managedType = RuntimeHelpers.IsReferenceOrContainsReferences(); + if (!managedType) + { + if (Object != null && _handle.IsAllocated) + { + Unsafe.Write((void*)_ptr, value); + } + else + { + Object = value; + _handle = GCHandle.Alloc(Object, GCHandleType.Pinned); + _ptr = _handle.AddrOfPinnedObject(); + } + } + else + { + Object = value; + } + } + + public void Dispose() + { + if (_handle.IsAllocated) + _handle.Free(); + } +} + public class OutputSocket : Socket, IOutputSocket { private readonly CoreList _connections = new(); private List? _inputIds = null; // 型が一致していない、ソケットの数 private int _unmatchSockets; + private UnsafeBox _box; + + ~OutputSocket() + { + _box.Dispose(); + } public ICoreReadOnlyList Connections => _connections; @@ -72,10 +115,9 @@ private bool TryConnectCore(IInputSocket socket) public override void PostEvaluate(EvaluationContext context) { base.PostEvaluate(context); - object? boxed = null; if (_unmatchSockets > 0) { - boxed = Value; + _box.Update(Value); } foreach (Connection item in _connections.GetMarshal().Value) @@ -86,7 +128,7 @@ public override void PostEvaluate(EvaluationContext context) } else { - item.Input.Receive(boxed); + item.Input.Receive(_box.Object); } } } diff --git a/src/Beutl/ViewModels/NodeTree/InputSocketViewModel.cs b/src/Beutl/ViewModels/NodeTree/InputSocketViewModel.cs index b4085c434..8f7e8a191 100644 --- a/src/Beutl/ViewModels/NodeTree/InputSocketViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/InputSocketViewModel.cs @@ -1,26 +1,40 @@ using Beutl.Framework; using Beutl.NodeTree; +using Reactive.Bindings; + namespace Beutl.ViewModels.NodeTree; public class InputSocketViewModel : SocketViewModel { + private readonly ReactivePropertySlim?> _statusSource = new(); + public InputSocketViewModel(IInputSocket? socket, IPropertyEditorContext? propertyEditorContext, Node node) : base(socket, propertyEditorContext, node) { + Status = _statusSource + .Select(o => o ?? Observable.Return(ConnectionStatus.Disconnected)) + .Switch() + .ToReadOnlyReactivePropertySlim(); } public new IInputSocket? Model => base.Model as IInputSocket; - protected override void OnIsConnectedChanged(bool? isValid) + public IReadOnlyReactiveProperty Status { get; } + + protected override void OnDispose() { - if (isValid == false) - { - IsConnected.Value = false; - } - else if (Model != null) + base.OnDispose(); + _statusSource.Dispose(); + Status.Dispose(); + } + + protected override void OnIsConnectedChanged() + { + if (Model != null) { IsConnected.Value = Model.Connection != null; + _statusSource.Value = Model.Connection?.GetObservable(Connection.StatusProperty); } } } diff --git a/src/Beutl/ViewModels/NodeTree/OutputSocketViewModel.cs b/src/Beutl/ViewModels/NodeTree/OutputSocketViewModel.cs index 56a5ebf44..0e0ac999d 100644 --- a/src/Beutl/ViewModels/NodeTree/OutputSocketViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/OutputSocketViewModel.cs @@ -12,7 +12,7 @@ public OutputSocketViewModel(IOutputSocket? socket, IPropertyEditorContext? prop public new IOutputSocket? Model => base.Model as IOutputSocket; - protected override void OnIsConnectedChanged(bool? isValid) + protected override void OnIsConnectedChanged() { if (Model != null) { diff --git a/src/Beutl/ViewModels/NodeTree/SocketViewModel.cs b/src/Beutl/ViewModels/NodeTree/SocketViewModel.cs index 6d8edbaa7..6f681dc5b 100644 --- a/src/Beutl/ViewModels/NodeTree/SocketViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/SocketViewModel.cs @@ -14,12 +14,9 @@ namespace Beutl.ViewModels.NodeTree; public class SocketViewModel : NodeItemViewModel { - private readonly IDisposable? _disposable; - public SocketViewModel(ISocket? socket, IPropertyEditorContext? propertyEditorContext, Node node) : base(socket, propertyEditorContext, node) { - _disposable = (socket as NodeItem)?.GetObservable(NodeItem.IsValidProperty)?.Subscribe(OnIsConnectedChanged); if (socket != null) { Brush = new(new ImmutableSolidColorBrush(socket.Color.ToAvalonia())); @@ -30,11 +27,12 @@ public SocketViewModel(ISocket? socket, IPropertyEditorContext? propertyEditorCo { Brush = new(Brushes.Gray); } + + OnIsConnectedChanged(); } public new ISocket? Model => base.Model as ISocket; - // IsValidがfalseの時、false public ReactivePropertySlim IsConnected { get; } = new(); public ReactivePropertySlim Brush { get; } @@ -147,14 +145,13 @@ public void Remove() public void UpdateName(string? e) { - new ChangePropertyCommand((ICoreObject)Model!, CoreObject.NameProperty, e, Model!.Name) + new ChangePropertyCommand(Model!, CoreObject.NameProperty, e, Model!.Name) .DoAndRecord(CommandRecorder.Default); } protected override void OnDispose() { base.OnDispose(); - _disposable?.Dispose(); if (Model != null) { Model.Connected -= OnSocketConnected; @@ -166,7 +163,7 @@ protected virtual void OnSocketDisconnected(object? sender, SocketConnectionChan { if (Model != null) { - OnIsConnectedChanged(((NodeItem)Model).IsValid); + OnIsConnectedChanged(); } } @@ -174,11 +171,11 @@ protected virtual void OnSocketConnected(object? sender, SocketConnectionChanged { if (Model != null) { - OnIsConnectedChanged(((NodeItem)Model).IsValid); + OnIsConnectedChanged(); } } - protected virtual void OnIsConnectedChanged(bool? isValid) + protected virtual void OnIsConnectedChanged() { } diff --git a/src/Beutl/Views/NodeTree/SocketPoint.cs b/src/Beutl/Views/NodeTree/SocketPoint.cs index 7f64131ef..5421c8a06 100644 --- a/src/Beutl/Views/NodeTree/SocketPoint.cs +++ b/src/Beutl/Views/NodeTree/SocketPoint.cs @@ -3,11 +3,14 @@ using Avalonia.Controls.Shapes; using Avalonia.Input; using Avalonia.Media; +using Avalonia.Media.Immutable; using Avalonia.VisualTree; using Beutl.NodeTree; using Beutl.ViewModels.NodeTree; +using FluentAvalonia.UI.Media; + using Reactive.Bindings.Extensions; namespace Beutl.Views.NodeTree; @@ -55,19 +58,30 @@ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) base.OnAttachedToVisualTree(e); _strokeBinding = this.GetResourceObservable("TextControlForeground") .CombineLatest( + this.GetResourceObservable("SystemFillColorSuccessBrush"), + this.GetResourceObservable("SystemFillColorCautionBrush"), this.GetResourceObservable("SystemFillColorCriticalBrush"), this.GetObservable(InputSocketProperty) - .SelectMany(o => (o?.Model as CoreObject)?.GetObservable(NodeItem.IsValidProperty) ?? Observable.Return(null))) + .Select(o => o?.Status ?? Observable.Return(ConnectionStatus.Disconnected)) + .Switch()) .ObserveOnUIDispatcher() .Subscribe(x => { - if (x.Third == false) + switch (x.Fifth) { - Stroke = x.Second as IBrush; - } - else - { - Stroke = x.First as IBrush; + case ConnectionStatus.Disconnected: + Stroke = x.First as IBrush; + break; + case ConnectionStatus.Connected: + case ConnectionStatus.Success: + Stroke = x.Second as IBrush; + break; + case ConnectionStatus.Convert: + Stroke = x.Third as IBrush; + break; + case ConnectionStatus.Error: + Stroke = x.Fourth as IBrush; + break; } }); } @@ -354,13 +368,23 @@ private void DisconnectAll() public override void Render(DrawingContext context) { base.Render(context); + IBrush brush = Brush ?? Brushes.Teal; if (IsConnected) { - context.FillRectangle(Brush ?? Brushes.Teal, new Rect(0, 0, 10, 10), 5); + context.FillRectangle(brush, new Rect(0, 0, 10, 10), 5); } else { - context.FillRectangle(Brushes.Gray, new Rect(0, 0, 10, 10), 5); + if (brush is ISolidColorBrush solidColorBrush) + { + var color = (Color2)solidColorBrush.Color; + color = color.WithSatf(color.Saturationf * 0.2f); + context.FillRectangle(new ImmutableSolidColorBrush(color), new Rect(0, 0, 10, 10), 5); + } + else + { + context.FillRectangle(Brushes.Gray, new Rect(0, 0, 10, 10), 5); + } } } } From 05ff8b5f9db35c98f771856301f932f5086f3263 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Sat, 1 Apr 2023 18:18:14 +0900 Subject: [PATCH 17/84] =?UTF-8?q?ImageSource=E3=81=ABJsonConverter?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Media/Source/IImageSource.cs | 2 ++ .../Media/Source/ImageSource.cs | 5 +++++ .../Media/Source/ImageSourceJsonConverter.cs | 21 +++++++++++++++++++ 3 files changed, 28 insertions(+) create mode 100644 src/Beutl.Graphics/Media/Source/ImageSourceJsonConverter.cs diff --git a/src/Beutl.Graphics/Media/Source/IImageSource.cs b/src/Beutl.Graphics/Media/Source/IImageSource.cs index 399d87f32..bfa313e1d 100644 --- a/src/Beutl.Graphics/Media/Source/IImageSource.cs +++ b/src/Beutl.Graphics/Media/Source/IImageSource.cs @@ -1,7 +1,9 @@ using System.Diagnostics.CodeAnalysis; +using System.Text.Json.Serialization; namespace Beutl.Media.Source; +[JsonConverter(typeof(ImageSourceJsonConverter))] public interface IImageSource : IMediaSource { PixelSize FrameSize { get; } diff --git a/src/Beutl.Graphics/Media/Source/ImageSource.cs b/src/Beutl.Graphics/Media/Source/ImageSource.cs index b12f9aa79..b57161b29 100644 --- a/src/Beutl.Graphics/Media/Source/ImageSource.cs +++ b/src/Beutl.Graphics/Media/Source/ImageSource.cs @@ -15,6 +15,11 @@ public ImageSource(Ref bitmap, string name) FrameSize = new PixelSize(_bitmap.Value.Width, _bitmap.Value.Height); } + ~ImageSource() + { + Dispose(); + } + public PixelSize FrameSize { get; } public bool IsDisposed { get; private set; } diff --git a/src/Beutl.Graphics/Media/Source/ImageSourceJsonConverter.cs b/src/Beutl.Graphics/Media/Source/ImageSourceJsonConverter.cs new file mode 100644 index 000000000..d121d6ea6 --- /dev/null +++ b/src/Beutl.Graphics/Media/Source/ImageSourceJsonConverter.cs @@ -0,0 +1,21 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Beutl.Media.Source; + +public sealed class ImageSourceJsonConverter : JsonConverter +{ + public override IImageSource? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + string? s = reader.GetString(); + + return s != null && MediaSourceManager.Shared.OpenImageSource(s, out var imageSource) + ? imageSource + : null; + } + + public override void Write(Utf8JsonWriter writer, IImageSource? value, JsonSerializerOptions options) + { + writer.WriteStringValue(value?.Name); + } +} From 161e81944eda166e55360401b851f768187f410d Mon Sep 17 00:00:00 2001 From: indigo-san Date: Sat, 1 Apr 2023 18:18:33 +0900 Subject: [PATCH 18/84] =?UTF-8?q?ImageFile=E3=82=AF=E3=83=A9=E3=82=B9?= =?UTF-8?q?=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Graphics/Graphics/DrawableBitmap.cs | 40 ------- src/Beutl.Graphics/Graphics/ImageFile.cs | 112 ------------------ src/Beutl.Operators/OperatorsRegistrar.cs | 1 - .../Source/ImageFileOperator.cs | 13 -- 4 files changed, 166 deletions(-) delete mode 100644 src/Beutl.Graphics/Graphics/DrawableBitmap.cs delete mode 100644 src/Beutl.Graphics/Graphics/ImageFile.cs delete mode 100644 src/Beutl.Operators/Source/ImageFileOperator.cs diff --git a/src/Beutl.Graphics/Graphics/DrawableBitmap.cs b/src/Beutl.Graphics/Graphics/DrawableBitmap.cs deleted file mode 100644 index 65fe9a179..000000000 --- a/src/Beutl.Graphics/Graphics/DrawableBitmap.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Beutl.Media; - -namespace Beutl.Graphics; - -public sealed class DrawableBitmap : Drawable -{ - public DrawableBitmap() - { - } - - public DrawableBitmap(IBitmap bitmap) - { - Bitmap = bitmap; - } - - public IBitmap? Bitmap { get; set; } - - protected override void OnDraw(ICanvas canvas) - { - if (Bitmap?.IsDisposed == false) - { - if (Width > 0 && Height > 0) - { - using (canvas.PushTransform(Matrix.CreateScale(Width / Bitmap.Width, Height / Bitmap.Height))) - { - canvas.DrawBitmap(Bitmap); - } - } - else - { - canvas.DrawBitmap(Bitmap); - } - } - } - - protected override Size MeasureCore(Size availableSize) - { - return new(Bitmap?.Width ?? 0, Bitmap?.Height ?? 0); - } -} diff --git a/src/Beutl.Graphics/Graphics/ImageFile.cs b/src/Beutl.Graphics/Graphics/ImageFile.cs deleted file mode 100644 index 0e0744e92..000000000 --- a/src/Beutl.Graphics/Graphics/ImageFile.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using System.Diagnostics.CodeAnalysis; -using System.Text.Json.Nodes; - -using Beutl.Language; -using Beutl.Media; -using Beutl.Media.Pixel; -using Beutl.Validation; - -namespace Beutl.Graphics; - -public class ImageFile : Drawable -{ - public static readonly CoreProperty SourceFileProperty; - private FileInfo? _sourceFile; - private IBitmap? _bitmap; - - static ImageFile() - { - SourceFileProperty = ConfigureProperty(nameof(SourceFile)) - .Accessor(o => o.SourceFile, (o, v) => o.SourceFile = v) - .Register(); - - AffectsRender(SourceFileProperty); - } - - [Display(Name = nameof(Strings.SourceFile), ResourceType = typeof(Strings))] - public FileInfo? SourceFile - { - get => _sourceFile; - set => SetAndRaise(SourceFileProperty, ref _sourceFile, value); - } - - public override void ReadFromJson(JsonNode json) - { - base.ReadFromJson(json); - if (json is JsonObject jobj - && jobj.TryGetPropertyValue("source-file", out JsonNode? fileNode) - && fileNode is JsonValue fileValue - && fileValue.TryGetValue(out string? fileStr) - && File.Exists(fileStr)) - { - SourceFile = new FileInfo(fileStr); - } - } - - public override void WriteToJson(ref JsonNode json) - { - base.WriteToJson(ref json); - if (json is JsonObject jobj - && _sourceFile != null) - { - jobj["source-file"] = _sourceFile.FullName; - } - } - - protected override Size MeasureCore(Size availableSize) - { - if (TryLoadBitmap()) - { - return new Size(_bitmap.Width, _bitmap.Height); - } - else - { - return default; - } - } - - protected override void OnDraw(ICanvas canvas) - { - if (TryLoadBitmap()) - { - canvas.DrawBitmap(_bitmap); - } - } - - protected override void OnPropertyChanged(PropertyChangedEventArgs args) - { - base.OnPropertyChanged(args); - if (args.PropertyName is nameof(SourceFile)) - { - _bitmap?.Dispose(); - _bitmap = null; - } - } - - [MemberNotNullWhen(true, "_bitmap")] - private bool TryLoadBitmap() - { - if (_sourceFile?.Exists == true) - { - try - { - if (_bitmap?.IsDisposed != false) - { - _bitmap = Bitmap.FromFile(_sourceFile.FullName); - } - - return true; - } - catch - { - return false; - } - } - else - { - return false; - } - } -} diff --git a/src/Beutl.Operators/OperatorsRegistrar.cs b/src/Beutl.Operators/OperatorsRegistrar.cs index 84fa9d77d..1eb4ed69e 100644 --- a/src/Beutl.Operators/OperatorsRegistrar.cs +++ b/src/Beutl.Operators/OperatorsRegistrar.cs @@ -12,7 +12,6 @@ public static void RegisterAll() .Add(Strings.Rectangle) .Add(Strings.RoundedRect) .Add(Strings.Text) - .Add(Strings.ImageFile) .Add("Video") .Add("SourceImage") .Add("SourceSound") diff --git a/src/Beutl.Operators/Source/ImageFileOperator.cs b/src/Beutl.Operators/Source/ImageFileOperator.cs deleted file mode 100644 index abd64ba8d..000000000 --- a/src/Beutl.Operators/Source/ImageFileOperator.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Beutl.Graphics; -using Beutl.Operation; -using Beutl.Styling; - -namespace Beutl.Operators.Source; - -public sealed class ImageFileOperator : DrawablePublishOperator -{ - protected override void OnInitializeSetters(IList initializing) - { - initializing.Add(new Setter(ImageFile.SourceFileProperty, null)); - } -} From 1456fd8d77226f6e81cd6413b388bbce584a3580 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Sat, 1 Apr 2023 18:20:04 +0900 Subject: [PATCH 19/84] =?UTF-8?q?MediaSource=E3=81=AE=E8=A7=A3=E6=94=BE?= =?UTF-8?q?=E3=82=92Drawable=E5=81=B4=E3=81=A7=E3=81=97=E3=81=AA=E3=81=84?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Graphics/Audio/SourceSound.cs | 37 +---------- src/Beutl.Graphics/Graphics/SourceImage.cs | 77 +--------------------- 2 files changed, 3 insertions(+), 111 deletions(-) diff --git a/src/Beutl.Graphics/Audio/SourceSound.cs b/src/Beutl.Graphics/Audio/SourceSound.cs index 27889dde1..fdd799846 100644 --- a/src/Beutl.Graphics/Audio/SourceSound.cs +++ b/src/Beutl.Graphics/Audio/SourceSound.cs @@ -1,6 +1,4 @@ -using System.Text.Json.Nodes; - -using Beutl.Media; +using Beutl.Media; using Beutl.Media.Music; using Beutl.Media.Source; @@ -10,7 +8,6 @@ public sealed class SourceSound : Sound { public static readonly CoreProperty SourceProperty; private ISoundSource? _source; - private string? _sourceName; static SourceSound() { @@ -28,38 +25,6 @@ public ISoundSource? Source set => SetAndRaise(SourceProperty, ref _source, value); } - protected override void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs args) - { - base.OnAttachedToHierarchy(args); - Open(); - } - - protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) - { - base.OnDetachedFromHierarchy(args); - Close(); - } - - private void Open() - { - if (_sourceName != null - && MediaSourceManager.Shared.OpenSoundSource(_sourceName, out ISoundSource? soundSource)) - { - Source = soundSource; - _sourceName = null; - } - } - - private void Close() - { - if (Source != null) - { - _sourceName = Source.Name; - Source.Dispose(); - Source = null; - } - } - protected override void OnRecord(IAudio audio, TimeRange range) { if (Source?.IsDisposed == false diff --git a/src/Beutl.Graphics/Graphics/SourceImage.cs b/src/Beutl.Graphics/Graphics/SourceImage.cs index 81c4f7f45..3d722e100 100644 --- a/src/Beutl.Graphics/Graphics/SourceImage.cs +++ b/src/Beutl.Graphics/Graphics/SourceImage.cs @@ -1,11 +1,5 @@ -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using System.Text.Json.Nodes; - -using Beutl.Media; -using Beutl.Media.Pixel; +using Beutl.Media; using Beutl.Media.Source; -using Beutl.Validation; namespace Beutl.Graphics; @@ -13,90 +7,23 @@ public class SourceImage : Drawable { public static readonly CoreProperty SourceProperty; private IImageSource? _source; - private string? _sourceName; static SourceImage() { SourceProperty = ConfigureProperty(nameof(Source)) .Accessor(o => o.Source, (o, v) => o.Source = v) + .DefaultValue(null) .Register(); AffectsRender(SourceProperty); } - [NotAutoSerialized] public IImageSource? Source { get => _source; set => SetAndRaise(SourceProperty, ref _source, value); } - public override void ReadFromJson(JsonNode json) - { - base.ReadFromJson(json); - if (json is JsonObject jobj - && jobj.TryGetPropertyValue("source", out JsonNode? fileNode) - && fileNode is JsonValue fileValue - && fileValue.TryGetValue(out string? fileStr)) - { - if (HierarchicalParent != null && _sourceName != fileStr) - { - Close(); - _sourceName = fileStr; - Open(); - } - else - { - _sourceName = fileStr; - } - } - else - { - _sourceName = null; - } - } - - public override void WriteToJson(ref JsonNode json) - { - base.WriteToJson(ref json); - if (json is JsonObject jobj - && _source != null) - { - jobj["source"] = _source.Name; - } - } - - protected override void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs args) - { - base.OnAttachedToHierarchy(args); - Open(); - } - - protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) - { - base.OnDetachedFromHierarchy(args); - Close(); - } - - private void Open() - { - if (_sourceName != null - && MediaSourceManager.Shared.OpenImageSource(_sourceName, out IImageSource? imageSource)) - { - Source = imageSource; - } - } - - private void Close() - { - if (Source != null) - { - _sourceName = Source.Name; - Source.Dispose(); - Source = null; - } - } - protected override Size MeasureCore(Size availableSize) { if (_source != null) From 81a87d11c06639227345f7f5bf074c78d760d6f2 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Sat, 1 Apr 2023 18:20:55 +0900 Subject: [PATCH 20/84] =?UTF-8?q?Hierarchy=E3=81=8B=E3=82=89StyledSourcePu?= =?UTF-8?q?blisher=E3=81=8CDetach=E3=81=95=E3=82=8C=E3=81=9F=E3=81=A8?= =?UTF-8?q?=E3=81=8DStyleInstance=E3=82=92Dispose=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.ProjectSystem/Operation/SourceOperation.cs | 1 - src/Beutl.ProjectSystem/Operation/StyledSourcePublisher.cs | 7 +++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Beutl.ProjectSystem/Operation/SourceOperation.cs b/src/Beutl.ProjectSystem/Operation/SourceOperation.cs index 436295147..a7d7d087a 100644 --- a/src/Beutl.ProjectSystem/Operation/SourceOperation.cs +++ b/src/Beutl.ProjectSystem/Operation/SourceOperation.cs @@ -1,6 +1,5 @@ using System.Buffers; using System.Collections.Specialized; -using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Text.Json.Nodes; diff --git a/src/Beutl.ProjectSystem/Operation/StyledSourcePublisher.cs b/src/Beutl.ProjectSystem/Operation/StyledSourcePublisher.cs index 0f4601238..24ee79a7f 100644 --- a/src/Beutl.ProjectSystem/Operation/StyledSourcePublisher.cs +++ b/src/Beutl.ProjectSystem/Operation/StyledSourcePublisher.cs @@ -39,6 +39,13 @@ public abstract class StyledSourcePublisher : StylingOperator, ISourcePublisher return IsEnabled ? renderable : null; } + protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) + { + base.OnDetachedFromHierarchy(args); + Instance?.Dispose(); + Instance = null; + } + protected virtual void OnPrePublish() { } From f1e488c8f1556cdec6944e5a2811e1411dc8d6b2 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Sat, 1 Apr 2023 19:42:43 +0900 Subject: [PATCH 21/84] =?UTF-8?q?CommandRecorder=E3=81=AEStack=E3=81=AB?= =?UTF-8?q?=E3=83=AA=E3=83=B3=E3=82=B0=E3=83=90=E3=83=83=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=82=92=E4=BD=BF=E3=81=86=E3=82=88=E3=81=86=E3=81=AB=E3=81=97?= =?UTF-8?q?=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Core/CommandRecorder.cs | 4 +- src/Beutl.Core/Properties/AssemblyInfo.cs | 1 + src/Beutl.Core/RingStack.cs | 77 ++++++++++ .../CommandRecorderTests.cs | 134 ++++++++++++++++++ .../PropertyChangeTrackerTests.cs | 3 +- tests/Beutl.Core.UnitTests/RingStackTests.cs | 126 ++++++++++++++++ 6 files changed, 341 insertions(+), 4 deletions(-) create mode 100644 src/Beutl.Core/RingStack.cs create mode 100644 tests/Beutl.Core.UnitTests/CommandRecorderTests.cs create mode 100644 tests/Beutl.Core.UnitTests/RingStackTests.cs diff --git a/src/Beutl.Core/CommandRecorder.cs b/src/Beutl.Core/CommandRecorder.cs index 2fae058e7..8ff2ea647 100644 --- a/src/Beutl.Core/CommandRecorder.cs +++ b/src/Beutl.Core/CommandRecorder.cs @@ -18,8 +18,8 @@ public class CommandRecorder : INotifyPropertyChanged private static readonly PropertyChangedEventArgs s_canUndoArgs = new(nameof(CanUndo)); private static readonly PropertyChangedEventArgs s_canRedoArgs = new(nameof(CanRedo)); private static readonly PropertyChangedEventArgs s_lastExecutedTimeArgs = new(nameof(LastExecutedTime)); - private readonly Stack _undoStack = new(); - private readonly Stack _redoStack = new(); + private readonly RingStack _undoStack = new(20000); + private readonly RingStack _redoStack = new(20000); private bool _isExecuting; private bool _canUndo; private bool _canRedo; diff --git a/src/Beutl.Core/Properties/AssemblyInfo.cs b/src/Beutl.Core/Properties/AssemblyInfo.cs index 1a7533f11..58d282105 100644 --- a/src/Beutl.Core/Properties/AssemblyInfo.cs +++ b/src/Beutl.Core/Properties/AssemblyInfo.cs @@ -1,6 +1,7 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Beutl")] +[assembly: InternalsVisibleTo("Beutl.Core.UnitTests")] [assembly: InternalsVisibleTo("Beutl.Configuration")] [assembly: InternalsVisibleTo("Beutl.Graphics")] [assembly: InternalsVisibleTo("Beutl.ProjectSystem")] diff --git a/src/Beutl.Core/RingStack.cs b/src/Beutl.Core/RingStack.cs new file mode 100644 index 000000000..0c4a01bd0 --- /dev/null +++ b/src/Beutl.Core/RingStack.cs @@ -0,0 +1,77 @@ +namespace Beutl; + +internal sealed class RingStack +{ + private readonly int _capacity; + private T[] _buffer; + private int _top; + + public RingStack(int size) + { + _capacity = size; + _buffer = new T[4]; + _top = -1; + Count = 0; + } + + public bool IsEmpty => Count == 0; + + public bool IsFull => Count == _capacity; + + public int Count { get; private set; } + + private void Resize(int newCapacity) + { + Array.Resize(ref _buffer, newCapacity); + + _top = Count; // 先頭位置を変更 + Count++; + } + + public void Push(T item) + { + if (Count == _buffer.Length + && _buffer.Length < _capacity) // 配列が満杯の場合 + { + int newCapacity = _buffer.Length * 2; // 新しい容量は現在の2倍にする + Resize(Math.Min(newCapacity, _capacity)); + } + else // 配列に空きがある場合 + { + Count++; + _top = (_top + 1) % _buffer.Length; + } + + _buffer[_top] = item; // 新しいアイテムを追加 + } + + public T Pop() + { + if (Count == 0) + { + throw new InvalidOperationException("Stack underflow"); + } + T item = _buffer[_top]; + _buffer[_top] = default!; + + _top = (_top - 1 + _buffer.Length) % _buffer.Length; + Count--; + return item; + } + + public T Peek() + { + if (Count == 0) + { + throw new InvalidOperationException("Stack is empty"); + } + return _buffer[_top]; + } + + public void Clear() + { + Array.Clear(_buffer); + _top = -1; + Count = 0; + } +} diff --git a/tests/Beutl.Core.UnitTests/CommandRecorderTests.cs b/tests/Beutl.Core.UnitTests/CommandRecorderTests.cs new file mode 100644 index 000000000..1c3462b10 --- /dev/null +++ b/tests/Beutl.Core.UnitTests/CommandRecorderTests.cs @@ -0,0 +1,134 @@ +using NUnit.Framework; + +namespace Beutl.Core.UnitTests; + +public class CommandRecorderTests +{ + [Test] + public void CanUndo_ReturnsFalse_WhenRecorderIsEmpty() + { + // Arrange + var recorder = new CommandRecorder(); + + // Act + bool canUndo = recorder.CanUndo; + + // Assert + Assert.False(canUndo); + } + + [Test] + public void CanRedo_ReturnsFalse_WhenRecorderIsEmpty() + { + // Arrange + var recorder = new CommandRecorder(); + + // Act + bool canRedo = recorder.CanRedo; + + // Assert + Assert.False(canRedo); + } + + [Test] + public void PushOnly_AddsCommandToTheRecorder() + { + // Arrange + var recorder = new CommandRecorder(); + var command = new TestCommand(); + + // Act + recorder.PushOnly(command); + + // Assert + Assert.True(recorder.CanUndo); + Assert.False(recorder.CanRedo); + } + + [Test] + public void DoAndPush_AddsCommandToTheRecorderAndExecutesIt() + { + // Arrange + var recorder = new CommandRecorder(); + var command = new TestCommand(); + + // Act + recorder.DoAndPush(command); + + // Assert + Assert.True(recorder.CanUndo); + Assert.False(recorder.CanRedo); + Assert.True(command.IsExecuted); + } + + [Test] + public void Undo_RollsBackTheLastCommand() + { + // Arrange + var recorder = new CommandRecorder(); + var command = new TestCommand(); + recorder.DoAndPush(command); + + // Act + recorder.Undo(); + + // Assert + Assert.False(recorder.CanUndo); + Assert.True(recorder.CanRedo); + Assert.False(command.IsExecuted); + } + + [Test] + public void Redo_RerunsTheLastUndoneCommand() + { + // Arrange + var recorder = new CommandRecorder(); + var command = new TestCommand(); + recorder.DoAndPush(command); + recorder.Undo(); + + // Act + recorder.Redo(); + + // Assert + Assert.True(recorder.CanUndo); + Assert.False(recorder.CanRedo); + Assert.True(command.IsExecuted); + } + + [Test] + public void Clear_RemovesAllCommandsFromTheRecorder() + { + // Arrange + var recorder = new CommandRecorder(); + var command = new TestCommand(); + recorder.DoAndPush(command); + + // Act + recorder.Clear(); + + // Assert + Assert.False(recorder.CanUndo); + Assert.False(recorder.CanRedo); + } + + private class TestCommand : IRecordableCommand + { + public bool IsExecuted { get; private set; } + + public void Do() + { + IsExecuted = true; + } + + public void Undo() + { + IsExecuted = false; + } + + public void Redo() + { + IsExecuted = true; + } + } +} diff --git a/tests/Beutl.Core.UnitTests/PropertyChangeTrackerTests.cs b/tests/Beutl.Core.UnitTests/PropertyChangeTrackerTests.cs index 04b9dabcd..223714856 100644 --- a/tests/Beutl.Core.UnitTests/PropertyChangeTrackerTests.cs +++ b/tests/Beutl.Core.UnitTests/PropertyChangeTrackerTests.cs @@ -60,7 +60,6 @@ static TestElement() { StringProperty = ConfigureProperty("String") .DefaultValue(string.Empty) - .PropertyFlags(PropertyFlags.NotifyChanged) .Register(); } @@ -72,5 +71,5 @@ public string String public HierarchicalList Children { get; set; } - IEnumerable IHierarchical.HierarchicalChildren => Children; + ICoreReadOnlyList IHierarchical.HierarchicalChildren => Children; } diff --git a/tests/Beutl.Core.UnitTests/RingStackTests.cs b/tests/Beutl.Core.UnitTests/RingStackTests.cs new file mode 100644 index 000000000..683bc7795 --- /dev/null +++ b/tests/Beutl.Core.UnitTests/RingStackTests.cs @@ -0,0 +1,126 @@ +using NUnit.Framework; + +namespace Beutl.Core.UnitTests; + +public class RingStackTests +{ + [Test] + public void Push_AddsItemToStack() + { + // Arrange + var stack = new RingStack(3); + + // Act + stack.Push(1); + stack.Push(2); + stack.Push(3); + + // Assert + Assert.AreEqual(3, stack.Count); + Assert.AreEqual(3, stack.Pop()); + Assert.AreEqual(2, stack.Pop()); + Assert.AreEqual(1, stack.Pop()); + Assert.True(stack.IsEmpty); + } + + [Test] + public void Pop_RemovesItemFromStack() + { + // Arrange + var stack = new RingStack(3); + stack.Push("foo"); + stack.Push("bar"); + + // Act + string item = stack.Pop(); + + // Assert + Assert.AreEqual("bar", item); + Assert.AreEqual(1, stack.Count); + Assert.False(stack.IsEmpty); + } + + [Test] + public void Pop_ThrowsExceptionIfStackIsEmpty() + { + // Arrange + var stack = new RingStack(1); + + // Act & Assert + InvalidOperationException? ex = Assert.Throws(() => stack.Pop()); + Assert.AreEqual("Stack underflow", ex?.Message); + } + + [Test] + public void Peek_ReturnsTopItemWithoutRemovingIt() + { + // Arrange + var stack = new RingStack(2); + stack.Push(1); + stack.Push(2); + + // Act + int item = stack.Peek(); + + // Assert + Assert.AreEqual(2, item); + Assert.AreEqual(2, stack.Count); + Assert.False(stack.IsEmpty); + } + + [Test] + public void Peek_ThrowsExceptionIfStackIsEmpty() + { + // Arrange + var stack = new RingStack(1); + + // Act & Assert + InvalidOperationException? ex = Assert.Throws(() => stack.Peek()); + Assert.AreEqual("Stack is empty", ex?.Message); + } + + [Test] + public void IsEmpty_ReturnsTrueIfStackIsEmpty() + { + // Arrange + var stack = new RingStack(2); + + // Act & Assert + Assert.True(stack.IsEmpty); + stack.Push(1.23); + Assert.False(stack.IsEmpty); + stack.Pop(); + Assert.True(stack.IsEmpty); + } + + [Test] + public void IsFull_ReturnsTrueIfStackIsFull() + { + // Arrange + var stack = new RingStack(2); + stack.Push("foo"); + + // Act & Assert + Assert.False(stack.IsFull); + stack.Push("bar"); + Assert.True(stack.IsFull); + } + + [Test] + public void Count_ReturnsNumberOfItemsInStack() + { + // Arrange + var stack = new RingStack(3); + + // Act & Assert + Assert.AreEqual(0, stack.Count); + stack.Push('a'); + Assert.AreEqual(1, stack.Count); + stack.Push('b'); + Assert.AreEqual(2, stack.Count); + stack.Push('c'); + Assert.AreEqual(3, stack.Count); + stack.Pop(); + Assert.AreEqual(2, stack.Count); + } +} From fd006038a338a453d7f716a4032776470decf5c1 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Sun, 2 Apr 2023 14:08:12 +0900 Subject: [PATCH 22/84] =?UTF-8?q?=E3=83=97=E3=83=AD=E3=82=B8=E3=82=A7?= =?UTF-8?q?=E3=82=AF=E3=83=88=E3=82=92=E9=96=89=E3=81=98=E3=81=A6=E3=82=82?= =?UTF-8?q?=E3=82=B7=E3=83=BC=E3=83=B3=E3=81=AA=E3=81=A9=E3=81=AE=E3=82=A4?= =?UTF-8?q?=E3=83=B3=E3=82=B9=E3=82=BF=E3=83=B3=E3=82=B9=E3=81=8C=E7=A0=B4?= =?UTF-8?q?=E6=A3=84=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84=E3=81=AE=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Source/SourceImageOperator.cs | 29 ++++++++ .../Operation/StyledSourcePublisher.cs | 3 +- src/Beutl/Services/EditorService.cs | 18 ++--- src/Beutl/Services/ProjectService.cs | 3 + src/Beutl/ViewModels/EditViewModel.cs | 31 +++++---- .../ViewModels/Editors/BaseEditorViewModel.cs | 4 +- src/Beutl/ViewModels/LayerHeaderViewModel.cs | 4 +- src/Beutl/ViewModels/LibraryViewModel.cs | 14 +++- .../NodeTree/NodeTreeInputTabViewModel.cs | 7 +- .../NodeTree/NodeTreeTabViewModel.cs | 26 +++++--- src/Beutl/ViewModels/PlayerViewModel.cs | 66 ++++++++++++++----- .../ViewModels/TimelineLayerViewModel.cs | 63 ++++++++++++++---- src/Beutl/ViewModels/TimelineViewModel.cs | 23 ++++++- .../Tools/GraphEditorTabViewModel.cs | 3 + .../Tools/SourceOperatorViewModel.cs | 15 ++++- .../Tools/SourceOperatorsTabViewModel.cs | 6 +- src/Beutl/Views/EditView.axaml | 8 +-- src/Beutl/Views/MainView.axaml | 6 +- src/Beutl/Views/MainView.axaml.cs | 24 +++++++ src/Beutl/Views/MainWindow.axaml.cs | 2 +- src/Beutl/Views/TimelineLayer.axaml | 12 ++-- src/Beutl/Views/TimelineLayer.axaml.cs | 7 ++ .../Views/Tools/SourceOperatorView.axaml | 2 +- 23 files changed, 294 insertions(+), 82 deletions(-) diff --git a/src/Beutl.Operators/Source/SourceImageOperator.cs b/src/Beutl.Operators/Source/SourceImageOperator.cs index 2bbe914fa..6cead792f 100644 --- a/src/Beutl.Operators/Source/SourceImageOperator.cs +++ b/src/Beutl.Operators/Source/SourceImageOperator.cs @@ -7,8 +7,37 @@ namespace Beutl.Operators.Source; public sealed class SourceImageOperator : DrawablePublishOperator { + private string? _sourceName; + protected override void OnInitializeSetters(IList initializing) { initializing.Add(new Setter(SourceImage.SourceProperty, null)); } + + protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) + { + base.OnDetachedFromHierarchy(args); + if (GetImageSourceSetter() is Setter { Value: { Name: string name } value } setter) + { + _sourceName = name; + setter.Value = null; + value.Dispose(); + } + } + + protected override void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs args) + { + base.OnAttachedToHierarchy(args); + if (GetImageSourceSetter() is Setter { Value: null } setter + && _sourceName != null + && MediaSourceManager.Shared.OpenImageSource(_sourceName, out IImageSource? imageSource)) + { + setter.Value = imageSource; + } + } + + private Setter? GetImageSourceSetter() + { + return Style.Setters.FirstOrDefault(x => x.Property == SourceImage.SourceProperty) as Setter; + } } diff --git a/src/Beutl.ProjectSystem/Operation/StyledSourcePublisher.cs b/src/Beutl.ProjectSystem/Operation/StyledSourcePublisher.cs index 24ee79a7f..5e8d80493 100644 --- a/src/Beutl.ProjectSystem/Operation/StyledSourcePublisher.cs +++ b/src/Beutl.ProjectSystem/Operation/StyledSourcePublisher.cs @@ -42,8 +42,9 @@ public abstract class StyledSourcePublisher : StylingOperator, ISourcePublisher protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) { base.OnDetachedFromHierarchy(args); - Instance?.Dispose(); + IStyleInstance? tmp = Instance; Instance = null; + tmp?.Dispose(); } protected virtual void OnPrePublish() diff --git a/src/Beutl/Services/EditorService.cs b/src/Beutl/Services/EditorService.cs index a02810107..db454cd61 100644 --- a/src/Beutl/Services/EditorService.cs +++ b/src/Beutl/Services/EditorService.cs @@ -26,14 +26,14 @@ public sealed class EditorTabItem : IDisposable public EditorTabItem(IEditorContext context, TabOpenMode tabOpenMode) { Context = new ReactiveProperty(context); - FilePath = Context.Select(ctxt => ctxt.EdittingFile) - .ToReadOnlyReactivePropertySlim(context.EdittingFile); - FileName = Context.Select(ctxt => Path.GetFileName(ctxt.EdittingFile)) - .ToReadOnlyReactivePropertySlim(Path.GetFileName(context.EdittingFile)); - Extension = Context.Select(ctxt => ctxt.Extension) - .ToReadOnlyReactivePropertySlim(context.Extension); - Commands = Context.Select(ctxt => ctxt.Commands) - .ToReadOnlyReactivePropertySlim(context.Commands); + FilePath = Context.Select(ctxt => ctxt?.EdittingFile!) + .ToReadOnlyReactivePropertySlim()!; + FileName = Context.Select(ctxt => Path.GetFileName(ctxt?.EdittingFile)!) + .ToReadOnlyReactivePropertySlim()!; + Extension = Context.Select(ctxt => ctxt?.Extension!) + .ToReadOnlyReactivePropertySlim()!; + Commands = Context.Select(ctxt => ctxt?.Commands) + .ToReadOnlyReactivePropertySlim(); TabOpenMode = tabOpenMode; } @@ -56,6 +56,8 @@ public EditorTabItem(IEditorContext context, TabOpenMode tabOpenMode) public void Dispose() { Context.Value.Dispose(); + Context.Value = null!; + Context.Dispose(); FilePath.Dispose(); FileName.Dispose(); diff --git a/src/Beutl/Services/ProjectService.cs b/src/Beutl/Services/ProjectService.cs index 20003e8d4..b519f001d 100644 --- a/src/Beutl/Services/ProjectService.cs +++ b/src/Beutl/Services/ProjectService.cs @@ -33,6 +33,7 @@ public ProjectService() { try { + CommandRecorder.Default.Clear(); var project = new Project(); project.Restore(file); @@ -56,6 +57,7 @@ public void CloseProject() { if (CurrentProject.Value is { } project) { + CommandRecorder.Default.Clear(); // 値を発行 _projectObservable.OnNext((New: null, project)); CurrentProject.Value = null; @@ -67,6 +69,7 @@ public void CloseProject() { try { + CommandRecorder.Default.Clear(); location = Path.Combine(location, name); IProjectItemContainer container = ServiceLocator.Current.GetRequiredService(); var scene = new Scene(width, height, name); diff --git a/src/Beutl/ViewModels/EditViewModel.cs b/src/Beutl/ViewModels/EditViewModel.cs index a56abae05..52fce0bd6 100644 --- a/src/Beutl/ViewModels/EditViewModel.cs +++ b/src/Beutl/ViewModels/EditViewModel.cs @@ -21,13 +21,14 @@ public ToolTabViewModel(IToolContext context) Context = context; } - public IToolContext Context { get; } + public IToolContext Context { get; private set; } public int Order { get; set; } = -1; public void Dispose() { Context.Dispose(); + Context = null!; } } @@ -39,8 +40,10 @@ public sealed class EditViewModel : IEditorContext, ITimelineOptionsProvider public EditViewModel(Scene scene) { Scene = scene; - Library = new LibraryViewModel(this); - Player = new PlayerViewModel(scene, IsEnabled); + Library = new LibraryViewModel(this) + .DisposeWith(_disposables); + Player = new PlayerViewModel(scene, IsEnabled) + .DisposeWith(_disposables); Commands = new KnownCommandsImpl(scene); SelectedObject = new ReactiveProperty() .DisposeWith(_disposables); @@ -54,9 +57,9 @@ public EditViewModel(Scene scene) RestoreState(); } - public Scene Scene { get; set; } + public Scene Scene { get; private set; } - public LibraryViewModel Library { get; } + public LibraryViewModel Library { get; private set; } public CoreList BottomTabItems { get; } @@ -66,17 +69,13 @@ public EditViewModel(Scene scene) public ReactivePropertySlim IsEnabled { get; } = new(true); - public PlayerViewModel Player { get; } - - //public EasingsViewModel Easings { get; } - - //public ReadOnlyReactivePropertySlim Property { get; } + public PlayerViewModel Player { get; private set; } public EditorExtension Extension => SceneEditorExtension.Instance; public string EdittingFile => Scene.FileName; - public IKnownEditorCommands? Commands { get; } + public IKnownEditorCommands? Commands { get; private set; } public IReactiveProperty Options { get; } = new ReactiveProperty(new TimelineOptions()); @@ -90,7 +89,8 @@ public void Dispose() { SaveState(); _disposables.Dispose(); - Player.Dispose(); + Library = null!; + Player = null!; foreach (ToolTabViewModel item in BottomTabItems.GetMarshal().Value) { @@ -100,6 +100,13 @@ public void Dispose() { item.Dispose(); } + BottomTabItems.Clear(); + RightTabItems.Clear(); + + SelectedObject.Value = null; + + Scene = null!; + Commands = null!; } public T? FindToolTab(Func condition) diff --git a/src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs b/src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs index 2c2ceb96a..1c4f17e63 100644 --- a/src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs +++ b/src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs @@ -104,7 +104,7 @@ protected BaseEditorViewModel(IAbstractProperty property) Dispose(false); } - public IAbstractProperty WrappedProperty { get; } + public IAbstractProperty WrappedProperty { get; private set; } public bool CanReset => GetDefaultValue() != null; @@ -208,6 +208,8 @@ protected virtual void Dispose(bool disposing) Disposables.Dispose(); _currentFrameRevoker?.Dispose(); _currentFrameRevoker = null; + _editViewModel = null!; + WrappedProperty = null!; } public virtual void InsertKeyFrame(TimeSpan keyTime) diff --git a/src/Beutl/ViewModels/LayerHeaderViewModel.cs b/src/Beutl/ViewModels/LayerHeaderViewModel.cs index aaa525f23..32afef327 100644 --- a/src/Beutl/ViewModels/LayerHeaderViewModel.cs +++ b/src/Beutl/ViewModels/LayerHeaderViewModel.cs @@ -113,7 +113,7 @@ void OnRemoved() public ReactiveProperty Number { get; } - public TimelineViewModel Timeline { get; } + public TimelineViewModel Timeline { get; private set; } public ReactivePropertySlim PosY { get; } = new(0); @@ -143,6 +143,8 @@ public void AnimationRequest(int layerNum, bool affectModel = true) public void Dispose() { _disposables.Dispose(); + Inlines.Clear(); + Timeline = null!; } public double CalculateInlineTop(int index) diff --git a/src/Beutl/ViewModels/LibraryViewModel.cs b/src/Beutl/ViewModels/LibraryViewModel.cs index a88b3cfcd..d68cae1a9 100644 --- a/src/Beutl/ViewModels/LibraryViewModel.cs +++ b/src/Beutl/ViewModels/LibraryViewModel.cs @@ -169,10 +169,10 @@ public int Match(Regex[] regexes) } } -public class LibraryViewModel +public sealed class LibraryViewModel : IDisposable { - private readonly EditViewModel _editViewModel; private readonly Nito.AsyncEx.AsyncLock _asyncLock = new(); + private EditViewModel _editViewModel; public LibraryViewModel(EditViewModel editViewModel) { @@ -272,4 +272,14 @@ await Task.Run(() => } } } + + public void Dispose() + { + Easings.Clear(); + Operators.Clear(); + Nodes.Clear(); + AllItems.Clear(); + SearchResult.Clear(); + _editViewModel = null!; + } } diff --git a/src/Beutl/ViewModels/NodeTree/NodeTreeInputTabViewModel.cs b/src/Beutl/ViewModels/NodeTree/NodeTreeInputTabViewModel.cs index 1d6d6dd7a..56a786b01 100644 --- a/src/Beutl/ViewModels/NodeTree/NodeTreeInputTabViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/NodeTreeInputTabViewModel.cs @@ -26,7 +26,8 @@ public NodeTreeInputTabViewModel(EditViewModel editViewModel) { Layer = editViewModel.SelectedObject .Select(x => x as Layer) - .ToReactiveProperty(); + .ToReactiveProperty() + .DisposeWith(_disposables); Layer.CombineWithPrevious() .Subscribe(obj => @@ -55,7 +56,11 @@ public NodeTreeInputTabViewModel(EditViewModel editViewModel) public void Dispose() { + _disposables.Dispose(); + InnerViewModel.Value?.Dispose(); + InnerViewModel.Value = null; + Layer.Value = null; } public void ReadFromJson(JsonNode json) diff --git a/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs b/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs index 9cab99ab1..8896fbe9d 100644 --- a/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs @@ -1,8 +1,6 @@ -using System.Buffers; -using System.Text.Json.Nodes; +using System.Text.Json.Nodes; using Avalonia.Collections.Pooled; -using Avalonia.Layout; using Beutl.Framework; using Beutl.NodeTree; @@ -13,9 +11,9 @@ namespace Beutl.ViewModels.NodeTree; -public class NodeTreeNavigationItem : IDisposable +public sealed class NodeTreeNavigationItem : IDisposable { - internal readonly Lazy _lazyViewModel; + internal Lazy _lazyViewModel; public NodeTreeNavigationItem(NodeTreeViewModel viewModel, ReadOnlyReactivePropertySlim name, NodeTreeSpace nodeTree) { @@ -35,7 +33,7 @@ public NodeTreeNavigationItem(ReadOnlyReactivePropertySlim name, NodeTre public ReadOnlyReactivePropertySlim Name { get; } - public NodeTreeSpace NodeTree { get; } + public NodeTreeSpace NodeTree { get; private set; } public void Dispose() { @@ -45,14 +43,16 @@ public void Dispose() } Name.Dispose(); + _lazyViewModel = null!; + NodeTree = null!; } } public sealed class NodeTreeTabViewModel : IToolContext { private readonly ReactiveProperty _isSelected = new(true); - private readonly EditViewModel _editViewModel; private readonly CompositeDisposable _disposables = new(); + private EditViewModel _editViewModel; public NodeTreeTabViewModel(EditViewModel editViewModel) { @@ -102,8 +102,18 @@ public NodeTreeTabViewModel(EditViewModel editViewModel) public void Dispose() { - NodeTree.Value?.Dispose(); _disposables.Dispose(); + foreach (NodeTreeNavigationItem item in Items) + { + item.Dispose(); + } + + Items.Clear(); + NodeTree.Dispose(); + NodeTree.Value?.Dispose(); + NodeTree.Value = null; + Layer.Value = null; + _editViewModel = null!; } public void NavigateTo(int index) diff --git a/src/Beutl/ViewModels/PlayerViewModel.cs b/src/Beutl/ViewModels/PlayerViewModel.cs index d72a0d6c6..fec301383 100644 --- a/src/Beutl/ViewModels/PlayerViewModel.cs +++ b/src/Beutl/ViewModels/PlayerViewModel.cs @@ -22,8 +22,7 @@ namespace Beutl.ViewModels; public sealed class PlayerViewModel : IDisposable { private static readonly TimeSpan s_second = TimeSpan.FromSeconds(1); - private readonly IDisposable _disposable0; - private readonly IDisposable _disposable1; + private readonly CompositeDisposable _disposables = new(); private readonly ReactivePropertySlim _isEnabled; public PlayerViewModel(Scene scene, ReactivePropertySlim isEnabled) @@ -41,7 +40,8 @@ public PlayerViewModel(Scene scene, ReactivePropertySlim isEnabled) { Play(); } - }); + }) + .DisposeWith(_disposables); Next = new ReactiveCommand(_isEnabled) .WithSubscribe(() => @@ -49,7 +49,8 @@ public PlayerViewModel(Scene scene, ReactivePropertySlim isEnabled) int rate = GetFrameRate(); Scene.CurrentFrame += TimeSpan.FromSeconds(1d / rate); - }); + }) + .DisposeWith(_disposables); Previous = new ReactiveCommand(_isEnabled) .WithSubscribe(() => @@ -57,16 +58,19 @@ public PlayerViewModel(Scene scene, ReactivePropertySlim isEnabled) int rate = GetFrameRate(); Scene.CurrentFrame -= TimeSpan.FromSeconds(1d / rate); - }); + }) + .DisposeWith(_disposables); Start = new ReactiveCommand(_isEnabled) - .WithSubscribe(() => Scene.CurrentFrame = TimeSpan.Zero); + .WithSubscribe(() => Scene.CurrentFrame = TimeSpan.Zero) + .DisposeWith(_disposables); End = new ReactiveCommand(_isEnabled) - .WithSubscribe(() => Scene.CurrentFrame = Scene.Duration); + .WithSubscribe(() => Scene.CurrentFrame = Scene.Duration) + .DisposeWith(_disposables); Scene.Renderer.RenderInvalidated += Renderer_RenderInvalidated; - _disposable0 = Scene.GetPropertyChangedObservable(Scene.RendererProperty) + Scene.GetPropertyChangedObservable(Scene.RendererProperty) .Subscribe(a => { if (a.OldValue != null) @@ -78,18 +82,30 @@ public PlayerViewModel(Scene scene, ReactivePropertySlim isEnabled) { a.NewValue.RenderInvalidated += Renderer_RenderInvalidated; } - }); + }) + .DisposeWith(_disposables); - _disposable1 = _isEnabled.Subscribe(v => - { - if (!v && IsPlaying.Value) + _isEnabled.Subscribe(v => { - Pause(); - } - }); + if (!v && IsPlaying.Value) + { + Pause(); + } + }) + .DisposeWith(_disposables); + + CurrentFrame = Scene.GetObservable(Scene.CurrentFrameProperty) + .ToReactiveProperty() + .DisposeWith(_disposables); + CurrentFrame.Subscribe(UpdateCurrentFrame) + .DisposeWith(_disposables); + + Duration = Scene.GetObservable(Scene.DurationProperty) + .ToReadOnlyReactiveProperty() + .DisposeWith(_disposables); } - public Scene Scene { get; } + public Scene Scene { get; set; } public Project? Project => Scene.FindHierarchicalParent(); @@ -97,6 +113,10 @@ public PlayerViewModel(Scene scene, ReactivePropertySlim isEnabled) public ReactivePropertySlim IsPlaying { get; } = new(); + public ReactiveProperty CurrentFrame { get; } + + public ReadOnlyReactiveProperty Duration { get; } + public ReactiveCommand PlayPause { get; } public ReactiveCommand Next { get; } @@ -363,9 +383,19 @@ await renderer.Dispatcher.InvokeAsync(() => } } + private void UpdateCurrentFrame(TimeSpan timeSpan) + { + if (Scene.CurrentFrame != timeSpan) + { + int rate = Project?.GetFrameRate() ?? 30; + Scene.CurrentFrame = timeSpan.RoundToRate(rate); + } + } + public void Dispose() { - _disposable0.Dispose(); - _disposable1.Dispose(); + _disposables.Dispose(); + PreviewInvalidated = null; + Scene = null!; } } diff --git a/src/Beutl/ViewModels/TimelineLayerViewModel.cs b/src/Beutl/ViewModels/TimelineLayerViewModel.cs index 1ff3d05f1..b0d67499a 100644 --- a/src/Beutl/ViewModels/TimelineLayerViewModel.cs +++ b/src/Beutl/ViewModels/TimelineLayerViewModel.cs @@ -22,6 +22,24 @@ public TimelineLayerViewModel(Layer sceneLayer, TimelineViewModel timeline) Model = sceneLayer; Timeline = timeline; + IsEnabled = sceneLayer.GetObservable(Layer.IsEnabledProperty) + .ToReadOnlyReactivePropertySlim() + .AddTo(_disposables); + + AllowOutflow = sceneLayer.GetObservable(Layer.AllowOutflowProperty) + .ToReadOnlyReactivePropertySlim() + .AddTo(_disposables); + + UseNode = sceneLayer.GetObservable(Layer.UseNodeProperty) + .ToReadOnlyReactivePropertySlim() + .AddTo(_disposables); + + Name = sceneLayer.GetObservable(CoreObject.NameProperty) + .ToReactiveProperty() + .AddTo(_disposables)!; + Name.Subscribe(v => Model.Name = v) + .AddTo(_disposables); + IObservable zIndexSubject = sceneLayer.GetObservable(Layer.ZIndexProperty); Margin = Timeline.GetTrackedLayerTopObservable(zIndexSubject) .Select(item => new Thickness(0, item, 0, 0)) @@ -45,7 +63,8 @@ public TimelineLayerViewModel(Layer sceneLayer, TimelineViewModel timeline) .ToReactiveProperty() .AddTo(_disposables); - Split.Where(func => func != null).Subscribe(func => + Split.Where(func => func != null) + .Subscribe(func => { int rate = Scene.FindHierarchicalParent() is { } proj ? proj.GetFrameRate() : 30; TimeSpan absTime = func!().RoundToRate(rate); @@ -64,7 +83,8 @@ public TimelineLayerViewModel(Layer sceneLayer, TimelineViewModel timeline) backwardLayer.Save(Helper.RandomLayerFileName(Path.GetDirectoryName(Scene.FileName)!, Constants.LayerFileExtension)); Scene.AddChild(backwardLayer).DoAndRecord(CommandRecorder.Default); - }); + }) + .AddTo(_disposables); Cut.Subscribe(async () => { @@ -72,11 +92,14 @@ public TimelineLayerViewModel(Layer sceneLayer, TimelineViewModel timeline) { Exclude.Execute(); } - }); + }) + .AddTo(_disposables); - Copy.Subscribe(async () => await SetClipboard()); + Copy.Subscribe(async () => await SetClipboard()) + .AddTo(_disposables); - Exclude.Subscribe(() => Scene.RemoveChild(Model).DoAndRecord(CommandRecorder.Default)); + Exclude.Subscribe(() => Scene.RemoveChild(Model).DoAndRecord(CommandRecorder.Default)) + .AddTo(_disposables); Delete.Subscribe(() => { @@ -85,7 +108,8 @@ public TimelineLayerViewModel(Layer sceneLayer, TimelineViewModel timeline) { File.Delete(Model.FileName); } - }); + }) + .AddTo(_disposables); Color.Subscribe(c => Model.AccentColor = Media.Color.FromArgb(c.A, c.R, c.G, c.B)) .AddTo(_disposables); @@ -96,7 +120,8 @@ public TimelineLayerViewModel(Layer sceneLayer, TimelineViewModel timeline) { Timeline.DetachInline(item); } - }); + }) + .AddTo(_disposables); BringAnimationToTop.Subscribe(() => { @@ -115,7 +140,8 @@ public TimelineLayerViewModel(Layer sceneLayer, TimelineViewModel timeline) } } } - }); + }) + .AddTo(_disposables); zIndexSubject.Subscribe(number => { @@ -127,7 +153,8 @@ public TimelineLayerViewModel(Layer sceneLayer, TimelineViewModel timeline) if (newLH != null) newLH.ItemsCount.Value++; LayerHeader.Value = newLH; - }).AddTo(_disposables); + }) + .AddTo(_disposables); } ~TimelineLayerViewModel() @@ -137,12 +164,20 @@ public TimelineLayerViewModel(Layer sceneLayer, TimelineViewModel timeline) public Func<(Thickness Margin, Thickness BorderMargin, double Width), CancellationToken, Task> AnimationRequested { get; set; } = (_, _) => Task.CompletedTask; - public TimelineViewModel Timeline { get; } + public TimelineViewModel Timeline { get; private set; } - public Layer Model { get; } + public Layer Model { get; private set; } public Scene Scene => (Scene)Model.HierarchicalParent!; + public ReadOnlyReactivePropertySlim IsEnabled { get; } + + public ReadOnlyReactivePropertySlim AllowOutflow { get; } + + public ReadOnlyReactivePropertySlim UseNode { get; } + + public ReactiveProperty Name { get; } + public ReactiveProperty Margin { get; } public ReactiveProperty BorderMargin { get; } @@ -172,6 +207,12 @@ public TimelineLayerViewModel(Layer sceneLayer, TimelineViewModel timeline) public void Dispose() { _disposables.Dispose(); + LayerHeader.Dispose(); + + LayerHeader.Value = null!; + Timeline = null!; + Model = null!; + AnimationRequested = (_, _) => Task.CompletedTask; GC.SuppressFinalize(this); } diff --git a/src/Beutl/ViewModels/TimelineViewModel.cs b/src/Beutl/ViewModels/TimelineViewModel.cs index d7d88ae7d..a42dc81ba 100644 --- a/src/Beutl/ViewModels/TimelineViewModel.cs +++ b/src/Beutl/ViewModels/TimelineViewModel.cs @@ -102,11 +102,11 @@ public TimelineViewModel(EditViewModel editViewModel) Header = new ReactivePropertySlim(Strings.Timeline); } - public Scene Scene { get; } + public Scene Scene { get; private set; } - public PlayerViewModel Player { get; } + public PlayerViewModel Player { get; private set; } - public EditViewModel EditorContext { get; } + public EditViewModel EditorContext { get; private set; } public ReadOnlyReactivePropertySlim PanelWidth { get; } @@ -147,6 +147,23 @@ public void Dispose() { item.Dispose(); } + foreach (LayerHeaderViewModel item in LayerHeaders) + { + item.Dispose(); + } + foreach (InlineAnimationLayerViewModel item in Inlines) + { + item.Dispose(); + } + + _layerHeightChanged.Dispose(); + + Inlines.Clear(); + LayerHeaders.Clear(); + Layers.Clear(); + Scene = null!; + Player = null!; + EditorContext = null!; } public void ReadFromJson(JsonNode json) diff --git a/src/Beutl/ViewModels/Tools/GraphEditorTabViewModel.cs b/src/Beutl/ViewModels/Tools/GraphEditorTabViewModel.cs index 9e4a783bf..7d04ac09c 100644 --- a/src/Beutl/ViewModels/Tools/GraphEditorTabViewModel.cs +++ b/src/Beutl/ViewModels/Tools/GraphEditorTabViewModel.cs @@ -30,6 +30,9 @@ public GraphEditorTabViewModel() public void Dispose() { + SelectedAnimation.Dispose(); + SelectedAnimation.Value?.Dispose(); + SelectedAnimation.Value = null; } public void ReadFromJson(JsonNode json) diff --git a/src/Beutl/ViewModels/Tools/SourceOperatorViewModel.cs b/src/Beutl/ViewModels/Tools/SourceOperatorViewModel.cs index 1ec08ea8d..3e436b061 100644 --- a/src/Beutl/ViewModels/Tools/SourceOperatorViewModel.cs +++ b/src/Beutl/ViewModels/Tools/SourceOperatorViewModel.cs @@ -17,6 +17,10 @@ public SourceOperatorViewModel(SourceOperator model, EditViewModel editViewModel { Model = model; EditViewModel = editViewModel; + IsEnabled = model.GetObservable(SourceOperator.IsEnabledProperty) + .ToReactiveProperty(); + IsEnabled.Subscribe(v => Model.IsEnabled = v); + Init(); model.Properties.CollectionChanged += Properties_CollectionChanged; @@ -33,12 +37,14 @@ private void Properties_CollectionChanged(object? sender, NotifyCollectionChange Init(); } - public SourceOperator Model { get; } + public SourceOperator Model { get; private set; } - public EditViewModel EditViewModel { get; } + public EditViewModel EditViewModel { get; private set; } public ReactiveProperty IsExpanded { get; } = new(true); + public ReactiveProperty IsEnabled { get; } + public CoreList Properties { get; } = new(); public void RestoreState(JsonNode json) @@ -98,6 +104,11 @@ public void Dispose() { item?.Dispose(); } + Properties.Clear(); + IsEnabled.Dispose(); + + Model = null!; + EditViewModel = null!; } private void Init() diff --git a/src/Beutl/ViewModels/Tools/SourceOperatorsTabViewModel.cs b/src/Beutl/ViewModels/Tools/SourceOperatorsTabViewModel.cs index 02d40ae7f..3bfdac9e3 100644 --- a/src/Beutl/ViewModels/Tools/SourceOperatorsTabViewModel.cs +++ b/src/Beutl/ViewModels/Tools/SourceOperatorsTabViewModel.cs @@ -16,7 +16,7 @@ namespace Beutl.ViewModels.Tools; public sealed class SourceOperatorsTabViewModel : IToolContext { private readonly IDisposable _disposable0; - private readonly EditViewModel _editViewModel; + private EditViewModel _editViewModel; private IDisposable? _disposable1; private Layer? _oldLayer; @@ -131,12 +131,14 @@ public void Dispose() if (Layer.Value != null) { SaveState(Layer.Value); + Layer.Value = null; } _disposable0.Dispose(); _disposable1?.Dispose(); - ClearItems(); Layer.Dispose(); + _editViewModel = null!; + RequestScroll = null; } private static string ViewStateDirectory(Layer layer) diff --git a/src/Beutl/Views/EditView.axaml b/src/Beutl/Views/EditView.axaml index c42e5f1ff..2cfee4d15 100644 --- a/src/Beutl/Views/EditView.axaml +++ b/src/Beutl/Views/EditView.axaml @@ -99,17 +99,17 @@ + Value="{CompiledBinding Player.CurrentFrame.Value, Converter={StaticResource TimeSpanToDoubleConverter}}" + Duration="{CompiledBinding Player.Duration.Value, StringFormat={}{0:hh\\:mm\\:ss\\.ff}}" /> diff --git a/src/Beutl/Views/MainView.axaml b/src/Beutl/Views/MainView.axaml index e5e5cded1..410f72b0f 100644 --- a/src/Beutl/Views/MainView.axaml +++ b/src/Beutl/Views/MainView.axaml @@ -172,7 +172,11 @@ - + + + list, string item) _rawRecentProjItems.Clear) .AddTo(_disposables); } + + [Conditional("DEBUG")] + private void GC_Collect_Click(object? sender, RoutedEventArgs e) + { + DateTime dateTime = DateTime.UtcNow; + long totalBytes = GC.GetTotalMemory(false); + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + + TimeSpan elapsed = DateTime.UtcNow - dateTime; + + long deltaBytes = GC.GetTotalMemory(false) - totalBytes; + string str = StringFormats.ToHumanReadableSize(Math.Abs(deltaBytes)); + str = (deltaBytes >= 0 ? "+" : "-") + str; + + _notificationService.Show(new Notification( + "結果", + $""" + 経過時間: {elapsed.TotalMilliseconds}ms + 差: {str} + """)); + } } diff --git a/src/Beutl/Views/MainWindow.axaml.cs b/src/Beutl/Views/MainWindow.axaml.cs index de9229c93..f26142a39 100644 --- a/src/Beutl/Views/MainWindow.axaml.cs +++ b/src/Beutl/Views/MainWindow.axaml.cs @@ -25,7 +25,7 @@ public MainWindow() NotificationManager = new WindowNotificationManager(this) { - HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Right, + Position = NotificationPosition.TopRight, Margin = new Thickness(64, 40, 0, 0) }; TitleBar.Height = 40; diff --git a/src/Beutl/Views/TimelineLayer.axaml b/src/Beutl/Views/TimelineLayer.axaml index fb4f24a4b..37cc6e60e 100644 --- a/src/Beutl/Views/TimelineLayer.axaml +++ b/src/Beutl/Views/TimelineLayer.axaml @@ -18,7 +18,7 @@ x:DataType="vm:TimelineLayerViewModel" ClipToBounds="True" Focusable="True" - IsEnabled="{Binding Model.IsEnabled}" + IsEnabled="{CompiledBinding IsEnabled.Value}" mc:Ignorable="d"> @@ -81,7 +81,7 @@ + + @@ -121,8 +134,10 @@ @@ -141,6 +156,7 @@ diff --git a/src/Beutl/Views/GraphEditorView.axaml.cs b/src/Beutl/Views/GraphEditorView.axaml.cs index 65cd3882e..98940c05c 100644 --- a/src/Beutl/Views/GraphEditorView.axaml.cs +++ b/src/Beutl/Views/GraphEditorView.axaml.cs @@ -461,4 +461,12 @@ private void SelectedView_Click(object? sender, RoutedEventArgs e) viewModel.SelectedView.Value = itemViewModel; } } + + private void UseGlobalClock_Click(object? sender, RoutedEventArgs e) + { + if (DataContext is GraphEditorViewModel viewModel) + { + viewModel.UpdateUseGlobalClock(!viewModel.UseGlobalClock.Value); + } + } } diff --git a/src/Beutl/Views/InlineAnimationLayer.axaml b/src/Beutl/Views/InlineAnimationLayer.axaml index e381a102a..05f16d4b0 100644 --- a/src/Beutl/Views/InlineAnimationLayer.axaml +++ b/src/Beutl/Views/InlineAnimationLayer.axaml @@ -24,8 +24,10 @@ BorderThickness="1,0" /> diff --git a/src/Beutl/Views/InlineAnimationLayer.axaml.cs b/src/Beutl/Views/InlineAnimationLayer.axaml.cs index c334e1748..23cb4940a 100644 --- a/src/Beutl/Views/InlineAnimationLayer.axaml.cs +++ b/src/Beutl/Views/InlineAnimationLayer.axaml.cs @@ -34,7 +34,7 @@ public InlineAnimationLayer() private void OnDrap(object? sender, DragEventArgs e) { if (e.Data.Get("Easing") is Easing easing - && DataContext is InlineAnimationLayerViewModel { Timeline: { Options.Value.Scale: { } scale, Scene:{ }scene } } viewModel) + && DataContext is InlineAnimationLayerViewModel { Timeline: { Options.Value.Scale: { } scale, Scene: { } scene } } viewModel) { Project? proj = scene.FindHierarchicalParent(); int rate = proj?.GetFrameRate() ?? 30; @@ -81,11 +81,11 @@ private void OnDataContextDetached(InlineAnimationLayerViewModel obj) private void OnDataContextAttached(InlineAnimationLayerViewModel obj) { - obj.AnimationRequested = async (margin, token) => + obj.AnimationRequested = async (margin, leftMargin, token) => { await Dispatcher.UIThread.InvokeAsync(async () => { - var animation = new Avalonia.Animation.Animation + var animation1 = new Avalonia.Animation.Animation { Easing = new Avalonia.Animation.Easings.SplineEasing(0.1, 0.9, 0.2, 1.0), Duration = TimeSpan.FromSeconds(0.25), @@ -110,8 +110,35 @@ await Dispatcher.UIThread.InvokeAsync(async () => } } }; + var animation2 = new Avalonia.Animation.Animation + { + Easing = new Avalonia.Animation.Easings.SplineEasing(0.1, 0.9, 0.2, 1.0), + Duration = TimeSpan.FromSeconds(0.25), + FillMode = FillMode.Forward, + Children = + { + new KeyFrame() + { + Cue = new Cue(0), + Setters = + { + new Setter(MarginProperty, items.Margin) + } + }, + new KeyFrame() + { + Cue = new Cue(1), + Setters = + { + new Setter(MarginProperty, leftMargin) + } + } + } + }; - await animation.RunAsync(this, null, token); + Task task1 = animation1.RunAsync(this, null, token); + Task task2 = animation2.RunAsync(items, null, token); + await Task.WhenAll(task1, task2); }); }; } diff --git a/src/Beutl/Views/InlineAnimationLayerHeader.axaml.cs b/src/Beutl/Views/InlineAnimationLayerHeader.axaml.cs index c1bc27e32..93ae7b52d 100644 --- a/src/Beutl/Views/InlineAnimationLayerHeader.axaml.cs +++ b/src/Beutl/Views/InlineAnimationLayerHeader.axaml.cs @@ -32,7 +32,7 @@ private void OpenTab_Click(object? sender, RoutedEventArgs e) { // タイムラインのタブを開く var anmTimelineViewModel = new GraphEditorTabViewModel(); - anmTimelineViewModel.SelectedAnimation.Value = new GraphEditorViewModel(editViewModel, kfAnimation); + anmTimelineViewModel.SelectedAnimation.Value = new GraphEditorViewModel(editViewModel, kfAnimation, viewModel.Layer.Model); editViewModel.OpenToolTab(anmTimelineViewModel); } } From c8cb580b4e1bb6cfda667faf0246c7b7cfe03833 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Sun, 9 Apr 2023 23:29:38 +0900 Subject: [PATCH 29/84] =?UTF-8?q?IJsonSerializable=E3=81=AE=E5=BC=95?= =?UTF-8?q?=E6=95=B0=E3=82=92JsonNode=E3=81=8B=E3=82=89JsonObject=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Configuration/ExtensionConfig.cs | 29 +++--- src/Beutl.Configuration/FontConfig.cs | 31 +++---- .../GlobalConfiguration.cs | 24 ++--- src/Beutl.Configuration/ViewConfig.cs | 23 +++-- src/Beutl.Core/CoreObject.cs | 51 +++++------ src/Beutl.Core/CoreObjectJsonConverter.cs | 38 ++++++++ src/Beutl.Core/CoreProperty.cs | 47 +++++----- src/Beutl.Core/IJsonSerializable.cs | 4 +- src/Beutl.Core/JsonHelper.cs | 11 +-- src/Beutl.Core/Project.cs | 86 +++++++++--------- src/Beutl.Graphics/Animation/Animatable.cs | 49 +++++------ .../Animation/AnimationSerializer.cs | 30 ++----- src/Beutl.Graphics/Animation/KeyFrame.cs | 64 ++++++-------- .../Animation/KeyFrameAnimation{T}.cs | 33 ++++--- .../Audio/Effects/SoundEffectGroup.cs | 54 +++++------- .../Converters/BrushJsonConverter.cs | 8 +- .../Graphics/Drawables/VideoFrame.cs | 14 ++- .../Graphics/Effects/BitmapEffectGroup.cs | 54 +++++------- .../Graphics/Filters/ImageFilterGroup.cs | 54 +++++------- .../Graphics/Shapes/TextBlock.cs | 19 ++-- .../Graphics/Transformation/TransformGroup.cs | 54 +++++------- src/Beutl.Graphics/Media/GradientStops.cs | 37 +------- src/Beutl.Graphics/Styling/Styleable.cs | 44 ++++------ .../NodeTree/InputSocket.cs | 19 ++-- .../NodeTree/InputSocketForSetter.cs | 8 +- src/Beutl.ProjectSystem/NodeTree/Node.cs | 75 ++++++++-------- .../NodeTree/NodeItemForSetter.cs | 8 +- .../NodeTree/NodeTreeSpace.cs | 50 +++++------ .../NodeTree/Nodes/Group/GroupInput.cs | 35 ++++---- .../NodeTree/Nodes/Group/GroupNode.cs | 57 ++++++------ .../NodeTree/Nodes/Group/GroupOutput.cs | 35 ++++---- .../NodeTree/Nodes/LayerInputNode.cs | 37 ++++---- .../NodeTree/OutputSocket.cs | 43 +++++---- .../NodeTree/SetterPropertyImpl.cs | 21 ++--- .../Operation/SourceOperation.cs | 56 +++++------- .../Operation/StylingOperator.cs | 14 ++- .../ProjectSystem/Layer.cs | 40 ++++----- .../ProjectSystem/Scene.cs | 65 +++++++------- src/Beutl/Services/OutputService.cs | 6 +- src/Beutl/ViewModels/EditViewModel.cs | 10 +-- .../ViewModels/Editors/BaseEditorViewModel.cs | 4 +- .../NodeTree/NodeTreeInputTabViewModel.cs | 4 +- .../NodeTree/NodeTreeTabViewModel.cs | 6 +- src/Beutl/ViewModels/OutputViewModel.cs | 88 +++++++++---------- .../ViewModels/TimelineLayerViewModel.cs | 10 +-- src/Beutl/ViewModels/TimelineViewModel.cs | 4 +- .../Tools/GraphEditorTabViewModel.cs | 4 +- .../Tools/ObjectPropertyEditorViewModel.cs | 4 +- .../Tools/SourceOperatorViewModel.cs | 6 +- .../Tools/SourceOperatorsTabViewModel.cs | 4 +- .../ViewModels/Tools/StyleEditorViewModel.cs | 4 +- src/Beutl/Views/MainView.axaml.cs | 8 +- src/Beutl/Views/Timeline.axaml.cs | 2 +- 53 files changed, 725 insertions(+), 860 deletions(-) create mode 100644 src/Beutl.Core/CoreObjectJsonConverter.cs diff --git a/src/Beutl.Configuration/ExtensionConfig.cs b/src/Beutl.Configuration/ExtensionConfig.cs index c11131463..3e6d208be 100644 --- a/src/Beutl.Configuration/ExtensionConfig.cs +++ b/src/Beutl.Configuration/ExtensionConfig.cs @@ -28,33 +28,30 @@ public TypeLazy(string formattedTypeName) // Keyには拡張子を含める public CoreDictionary> EditorExtensions { get; } = new(); - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject jsonObject) + if (json.TryGetPropertyValue("editor-extensions", out JsonNode? eeNode) + && eeNode is JsonObject eeObject) { - if (jsonObject.TryGetPropertyValue("editor-extensions", out JsonNode? eeNode) - && eeNode is JsonObject eeObject) + EditorExtensions.Clear(); + foreach (KeyValuePair item in eeObject) { - EditorExtensions.Clear(); - foreach (KeyValuePair item in eeObject) + if (item.Value is JsonArray jsonArray) { - if (item.Value is JsonArray jsonArray) - { - EditorExtensions.Add(item.Key, new CoreList(jsonArray.OfType() - .Select(value => value.TryGetValue(out string? type) ? type : null) - .Select(str => new TypeLazy(str!)) - .Where(type => type.FormattedTypeName != null)!)); - } + EditorExtensions.Add(item.Key, new CoreList(jsonArray.OfType() + .Select(value => value.TryGetValue(out string? type) ? type : null) + .Select(str => new TypeLazy(str!)) + .Where(type => type.FormattedTypeName != null)!)); } } } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); - + base.WriteToJson(json); + var eeObject = new JsonObject(); foreach ((string key, ICoreList value) in EditorExtensions) { diff --git a/src/Beutl.Configuration/FontConfig.cs b/src/Beutl.Configuration/FontConfig.cs index fda59f623..2ee173fa5 100644 --- a/src/Beutl.Configuration/FontConfig.cs +++ b/src/Beutl.Configuration/FontConfig.cs @@ -12,33 +12,30 @@ public FontConfig() public ObservableCollection FontDirectories { get; } = CreateDefaultFontDirectories(); - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject jsonObject) + if (json.TryGetPropertyValue("directories", out JsonNode? dirsNode) + && dirsNode is JsonArray dirsArray) { - if (jsonObject.TryGetPropertyValue("directories", out JsonNode? dirsNode) - && dirsNode is JsonArray dirsArray) - { - string[] array = dirsArray.Select(i => (string?)i).Where(i => i != null).ToArray()!; - string[] fontDirs = FontDirectories.ToArray(); + string[] array = dirsArray.Select(i => (string?)i).Where(i => i != null).ToArray()!; + string[] fontDirs = FontDirectories.ToArray(); - foreach (string item in array.Except(fontDirs)) - { - FontDirectories.Add(item); - } + foreach (string item in array.Except(fontDirs)) + { + FontDirectories.Add(item); + } - foreach (string item in fontDirs.Except(array)) - { - FontDirectories.Remove(item); - } + foreach (string item in fontDirs.Except(array)) + { + FontDirectories.Remove(item); } } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); + base.WriteToJson(json); json["directories"] = new JsonArray(FontDirectories.Select(i => JsonValue.Create(i)).ToArray()); } diff --git a/src/Beutl.Configuration/GlobalConfiguration.cs b/src/Beutl.Configuration/GlobalConfiguration.cs index 91df9cf62..dea0c027a 100644 --- a/src/Beutl.Configuration/GlobalConfiguration.cs +++ b/src/Beutl.Configuration/GlobalConfiguration.cs @@ -45,20 +45,20 @@ public void Save(string file) Directory.CreateDirectory(dir); } - JsonNode fontNode = new JsonObject(); - FontConfig.WriteToJson(ref fontNode); + var fontNode = new JsonObject(); + FontConfig.WriteToJson(fontNode); _json["font"] = fontNode; - JsonNode viewNode = new JsonObject(); - ViewConfig.WriteToJson(ref viewNode); + var viewNode = new JsonObject(); + ViewConfig.WriteToJson(viewNode); _json["view"] = viewNode; - JsonNode extensionNode = new JsonObject(); - ExtensionConfig.WriteToJson(ref extensionNode); + var extensionNode = new JsonObject(); + ExtensionConfig.WriteToJson(extensionNode); _json["extension"] = extensionNode; - JsonNode backupNode = new JsonObject(); - BackupConfig.WriteToJson(ref backupNode); + var backupNode = new JsonObject(); + BackupConfig.WriteToJson(backupNode); _json["backup"] = backupNode; _json.JsonSave(file); @@ -76,10 +76,10 @@ public void Restore(string file) RemoveHandlers(); if (JsonHelper.JsonRestore(file) is JsonObject json) { - FontConfig.ReadFromJson(json["font"]!); - ViewConfig.ReadFromJson(json["view"]!); - ExtensionConfig.ReadFromJson(json["extension"]!); - BackupConfig.ReadFromJson(json["backup"]!); + FontConfig.ReadFromJson((JsonObject)json["font"]!); + ViewConfig.ReadFromJson((JsonObject)json["view"]!); + ExtensionConfig.ReadFromJson((JsonObject)json["extension"]!); + BackupConfig.ReadFromJson((JsonObject)json["backup"]!); _json = json; } diff --git a/src/Beutl.Configuration/ViewConfig.cs b/src/Beutl.Configuration/ViewConfig.cs index 572b49696..3d997ea3d 100644 --- a/src/Beutl.Configuration/ViewConfig.cs +++ b/src/Beutl.Configuration/ViewConfig.cs @@ -86,27 +86,24 @@ public enum ViewTheme System } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject jsonObject) + if (json["recent-files"] is JsonArray recentFiles) { - if (jsonObject["recent-files"] is JsonArray recentFiles) - { - _recentFiles.Replace(recentFiles.Select(i => (string?)i).Where(i => i != null && File.Exists(i)).ToArray()!); - } - - if (jsonObject["recent-projects"] is JsonArray recentProjects) - { - _recentProjects.Replace(recentProjects.Select(i => (string?)i).Where(i => i != null && File.Exists(i)).ToArray()!); - } + _recentFiles.Replace(recentFiles.Select(i => (string?)i).Where(i => i != null && File.Exists(i)).ToArray()!); + } + + if (json["recent-projects"] is JsonArray recentProjects) + { + _recentProjects.Replace(recentProjects.Select(i => (string?)i).Where(i => i != null && File.Exists(i)).ToArray()!); } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); + base.WriteToJson(json); json["recent-files"] = JsonSerializer.SerializeToNode(_recentFiles, JsonHelper.SerializerOptions); json["recent-projects"] = JsonSerializer.SerializeToNode(_recentProjects, JsonHelper.SerializerOptions); } diff --git a/src/Beutl.Core/CoreObject.cs b/src/Beutl.Core/CoreObject.cs index f63d4b542..ab498a92c 100644 --- a/src/Beutl.Core/CoreObject.cs +++ b/src/Beutl.Core/CoreObject.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using System.Linq.Expressions; using System.Text.Json.Nodes; +using System.Text.Json.Serialization; using Beutl.Validation; @@ -349,49 +350,43 @@ public void ClearValue(CoreProperty property) SetValue(property, property.GetMetadata(GetType()).GetDefaultValue()); } - public virtual void WriteToJson(ref JsonNode json) + public virtual void WriteToJson(JsonObject json) { - if (json is JsonObject jsonObject) - { - Type ownerType = GetType(); + Type ownerType = GetType(); - IReadOnlyList list = PropertyRegistry.GetRegistered(ownerType); - for (int i = 0; i < list.Count; i++) + IReadOnlyList list = PropertyRegistry.GetRegistered(ownerType); + for (int i = 0; i < list.Count; i++) + { + CoreProperty item = list[i]; + CorePropertyMetadata metadata = item.GetMetadata(ownerType); + if (metadata.ShouldSerialize && (item is not IStaticProperty sprop || sprop.CanWrite)) { - CoreProperty item = list[i]; - CorePropertyMetadata metadata = item.GetMetadata(ownerType); - if (metadata.ShouldSerialize && (item is not IStaticProperty sprop || sprop.CanWrite)) + JsonNode? valueNode = item.RouteWriteToJson(metadata, GetValue(item), out bool isDefault); + if (!isDefault) { - JsonNode? valueNode = item.RouteWriteToJson(metadata, GetValue(item), out bool isDefault); - if (!isDefault) - { - jsonObject[item.Name] = valueNode; - } + json[item.Name] = valueNode; } } } } - public virtual void ReadFromJson(JsonNode json) + public virtual void ReadFromJson(JsonObject json) { Type ownerType = GetType(); - if (json is JsonObject obj) + IReadOnlyList list = PropertyRegistry.GetRegistered(ownerType); + for (int i = 0; i < list.Count; i++) { - IReadOnlyList list = PropertyRegistry.GetRegistered(ownerType); - for (int i = 0; i < list.Count; i++) + CoreProperty item = list[i]; + CorePropertyMetadata metadata = item.GetMetadata(ownerType); + if (metadata.ShouldSerialize + && (item is not IStaticProperty sprop || sprop.CanWrite) + && json.TryGetPropertyValue(item.Name, out JsonNode? jsonNode) + && jsonNode != null) { - CoreProperty item = list[i]; - CorePropertyMetadata metadata = item.GetMetadata(ownerType); - if (metadata.ShouldSerialize - && (item is not IStaticProperty sprop || sprop.CanWrite) - && obj.TryGetPropertyValue(item.Name, out JsonNode? jsonNode) - && jsonNode != null) + if (item.RouteReadFromJson(metadata, jsonNode) is { } value) { - if (item.RouteReadFromJson(metadata, jsonNode) is { } value) - { - SetValue(item, value); - } + SetValue(item, value); } } } diff --git a/src/Beutl.Core/CoreObjectJsonConverter.cs b/src/Beutl.Core/CoreObjectJsonConverter.cs new file mode 100644 index 000000000..bb3ef9359 --- /dev/null +++ b/src/Beutl.Core/CoreObjectJsonConverter.cs @@ -0,0 +1,38 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; + +namespace Beutl; + +public sealed class CoreObjectJsonConverter : JsonConverter +{ + public override bool CanConvert(Type typeToConvert) + { + return typeToConvert.IsAssignableTo(typeof(ICoreObject)); + } + + public override ICoreObject? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var jsonNode = JsonNode.Parse(ref reader); + if (jsonNode is JsonObject jsonObject) + { + Type? actualType = jsonObject.GetDiscriminator(); + if (actualType?.IsAssignableTo(typeToConvert) == true + && Activator.CreateInstance(actualType) is ICoreObject instance) + { + instance.ReadFromJson(jsonObject); + + return instance; + } + } + + throw new JsonException(); + } + + public override void Write(Utf8JsonWriter writer, ICoreObject value, JsonSerializerOptions options) + { + var json = new JsonObject(); + value.WriteToJson(json); + json.WriteTo(writer, options); + } +} diff --git a/src/Beutl.Core/CoreProperty.cs b/src/Beutl.Core/CoreProperty.cs index 54b6421b4..9110fd88b 100644 --- a/src/Beutl.Core/CoreProperty.cs +++ b/src/Beutl.Core/CoreProperty.cs @@ -241,16 +241,16 @@ internal override void RouteSetValue(ICoreObject o, object? value) } else if (value is IJsonSerializable child) { - JsonNode jsonNode = new JsonObject(); - child.WriteToJson(ref jsonNode!); + var jsonobj = new JsonObject(); + child.WriteToJson(jsonobj!); Type objType = value.GetType(); - if (objType != PropertyType && jsonNode is JsonObject) + if (objType != PropertyType) { - jsonNode.WriteDiscriminator(objType); + jsonobj.WriteDiscriminator(objType); } - return jsonNode; + return jsonobj; } else { @@ -263,35 +263,36 @@ internal override void RouteSetValue(ICoreObject o, object? value) { var typedMetadata = (CorePropertyMetadata)metadata; Type type = PropertyType; + JsonSerializerOptions? options; if (typedMetadata.JsonConverter is { } jsonConverter) { - var options = new JsonSerializerOptions(JsonHelper.SerializerOptions); + options = new JsonSerializerOptions(JsonHelper.SerializerOptions); options.Converters.Add(jsonConverter); return JsonSerializer.Deserialize(node, type, options); } - else if (node is JsonObject jsonObject - && jsonObject.TryGetDiscriminator(out Type? realType) - && realType.IsAssignableTo(typeof(IJsonSerializable))) + else if (node is JsonObject jsonObject) { - var sobj = (IJsonSerializable?)Activator.CreateInstance(realType); - sobj?.ReadFromJson(node!); + if (jsonObject.TryGetDiscriminator(out Type? realType) + && realType.IsAssignableTo(typeof(IJsonSerializable))) + { + var sobj = (IJsonSerializable?)Activator.CreateInstance(realType); + sobj?.ReadFromJson(jsonObject!); - return sobj; - } - else if (type.IsAssignableTo(typeof(IJsonSerializable))) - { - var sobj = (IJsonSerializable?)Activator.CreateInstance(type); - sobj?.ReadFromJson(node!); + return sobj; + } + else if (type.IsAssignableTo(typeof(IJsonSerializable))) + { + var sobj = (IJsonSerializable?)Activator.CreateInstance(type); + sobj?.ReadFromJson(jsonObject!); - return sobj; - } - else - { - var options = new JsonSerializerOptions(JsonHelper.SerializerOptions); - return JsonSerializer.Deserialize(node, type, options); + return sobj; + } } + + options = new JsonSerializerOptions(JsonHelper.SerializerOptions); + return JsonSerializer.Deserialize(node, type, options); } protected override IObservable GetChanged() diff --git a/src/Beutl.Core/IJsonSerializable.cs b/src/Beutl.Core/IJsonSerializable.cs index 31a5311da..6daf49fb6 100644 --- a/src/Beutl.Core/IJsonSerializable.cs +++ b/src/Beutl.Core/IJsonSerializable.cs @@ -4,7 +4,7 @@ namespace Beutl; public interface IJsonSerializable { - void WriteToJson(ref JsonNode json); + void WriteToJson(JsonObject json); - void ReadFromJson(JsonNode json); + void ReadFromJson(JsonObject json); } diff --git a/src/Beutl.Core/JsonHelper.cs b/src/Beutl.Core/JsonHelper.cs index 044e5d6ce..9f980fc81 100644 --- a/src/Beutl.Core/JsonHelper.cs +++ b/src/Beutl.Core/JsonHelper.cs @@ -27,7 +27,8 @@ public static class JsonHelper { new CultureInfoConverter(), new DirectoryInfoConverter(), - new FileInfoConverter() + new FileInfoConverter(), + new CoreObjectJsonConverter() } }; @@ -49,9 +50,9 @@ public static void JsonSave(this IJsonSerializable serializable, string filename { using var stream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.Write); using var writer = new Utf8JsonWriter(stream, WriterOptions); - JsonNode json = new JsonObject(); + var json = new JsonObject(); - serializable.WriteToJson(ref json); + serializable.WriteToJson(json); json.WriteTo(writer, SerializerOptions); } @@ -60,9 +61,9 @@ public static void JsonRestore(this IJsonSerializable serializable, string filen using var stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read); var node = JsonNode.Parse(stream); - if (node != null) + if (node is JsonObject obj) { - serializable.ReadFromJson(node); + serializable.ReadFromJson(obj); } } diff --git a/src/Beutl.Core/Project.cs b/src/Beutl.Core/Project.cs index 89ca46e24..7408e0604 100644 --- a/src/Beutl.Core/Project.cs +++ b/src/Beutl.Core/Project.cs @@ -94,70 +94,64 @@ public void Save(string filename) _saved?.Invoke(this, EventArgs.Empty); } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject jobject) + if (json.TryGetPropertyValue("appVersion", out JsonNode? versionNode) + && versionNode!.AsValue().TryGetValue(out Version? version)) { - if (jobject.TryGetPropertyValue("appVersion", out JsonNode? versionNode) - && versionNode!.AsValue().TryGetValue(out Version? version)) - { - AppVersion = version; - } + AppVersion = version; + } - if (jobject.TryGetPropertyValue("minAppVersion", out JsonNode? minVersionNode) - && minVersionNode!.AsValue().TryGetValue(out Version? minVersion)) - { - MinAppVersion = minVersion; - } + if (json.TryGetPropertyValue("minAppVersion", out JsonNode? minVersionNode) + && minVersionNode!.AsValue().TryGetValue(out Version? minVersion)) + { + MinAppVersion = minVersion; + } - if (jobject.TryGetPropertyValue("items", out JsonNode? itemsNode)) - { - SyncronizeScenes(itemsNode!.AsArray() - .Select(i => (string)i!)); - } + if (json.TryGetPropertyValue("items", out JsonNode? itemsNode)) + { + SyncronizeScenes(itemsNode!.AsArray() + .Select(i => (string)i!)); + } - if (jobject.TryGetPropertyValue("variables", out JsonNode? variablesNode) - && variablesNode is JsonObject variablesObj) + if (json.TryGetPropertyValue("variables", out JsonNode? variablesNode) + && variablesNode is JsonObject variablesObj) + { + Variables.Clear(); + foreach (KeyValuePair item in variablesObj) { - Variables.Clear(); - foreach (KeyValuePair item in variablesObj) - { - if (item.Value != null) - Variables[item.Key] = item.Value.AsValue().ToString(); - } + if (item.Value != null) + Variables[item.Key] = item.Value.AsValue().ToString(); } } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); - - if (json is JsonObject jobject) - { - jobject["appVersion"] = JsonValue.Create(AppVersion); - jobject["minAppVersion"] = JsonValue.Create(MinAppVersion); + base.WriteToJson(json); - var items = new JsonArray(); - foreach (ProjectItem item in Items) - { - string path = Path.GetRelativePath(RootDirectory, item.FileName).Replace('\\', '/'); - var value = JsonValue.Create(path); - items.Add(value); - } + json["appVersion"] = JsonValue.Create(AppVersion); + json["minAppVersion"] = JsonValue.Create(MinAppVersion); - jobject["items"] = items; + var items = new JsonArray(); + foreach (ProjectItem item in Items) + { + string path = Path.GetRelativePath(RootDirectory, item.FileName).Replace('\\', '/'); + var value = JsonValue.Create(path); + items.Add(value); + } - var variables = new JsonObject(); - foreach (KeyValuePair item in Variables) - { - variables.Add(item.Key, JsonValue.Create(item.Value)); - } + json["items"] = items; - jobject["variables"] = variables; + var variables = new JsonObject(); + foreach (KeyValuePair item in Variables) + { + variables.Add(item.Key, JsonValue.Create(item.Value)); } + + json["variables"] = variables; } public void Dispose() diff --git a/src/Beutl.Graphics/Animation/Animatable.cs b/src/Beutl.Graphics/Animation/Animatable.cs index c51cea723..cbfdb610b 100644 --- a/src/Beutl.Graphics/Animation/Animatable.cs +++ b/src/Beutl.Graphics/Animation/Animatable.cs @@ -28,49 +28,42 @@ public virtual void ApplyAnimations(IClock clock) } } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject jobject) + if (json.TryGetPropertyValue("animations", out JsonNode? animationsNode) + && animationsNode is JsonObject animationsObj) { - if (jobject.TryGetPropertyValue("animations", out JsonNode? animationsNode) - && animationsNode is JsonObject animationsObj) - { - Animations.Clear(); - Animations.EnsureCapacity(animationsObj.Count); + Animations.Clear(); + Animations.EnsureCapacity(animationsObj.Count); - Type type = GetType(); - foreach ((string name, JsonNode? node) in animationsObj) + Type type = GetType(); + foreach ((string name, JsonNode? node) in animationsObj) + { + if (node?.ToAnimation(name, type) is IAnimation animation) { - if (node?.ToAnimation(name, type) is IAnimation animation) - { - Animations.Add(animation); - } + Animations.Add(animation); } } } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); - if (json is JsonObject jobject) - { - Type type = GetType(); - var animations = new JsonObject(); + base.WriteToJson(json); + var animations = new JsonObject(); - foreach (IAnimation item in Animations.GetMarshal().Value) + foreach (IAnimation item in Animations.GetMarshal().Value) + { + if (item.ToJson() is { } itemJson) { - if (item.ToJson(type) is (string name, JsonNode node)) - { - animations.Add(name, node); - } + animations.Add(item.Property.Name, itemJson); } + } - if (animations.Count > 0) - { - jobject["animations"] = animations; - } + if (animations.Count > 0) + { + json["animations"] = animations; } } } diff --git a/src/Beutl.Graphics/Animation/AnimationSerializer.cs b/src/Beutl.Graphics/Animation/AnimationSerializer.cs index a20d9863d..d596cacce 100644 --- a/src/Beutl.Graphics/Animation/AnimationSerializer.cs +++ b/src/Beutl.Graphics/Animation/AnimationSerializer.cs @@ -4,30 +4,16 @@ namespace Beutl.Animation; internal static class AnimationSerializer { - public static (string?, JsonNode?) ToJson(this IAnimation animation, Type targetType) - { - JsonNode? node = animation.ToJson(); - string name = animation.Property.Name; - if (node != null) - { - return (name, node); - } - else - { - return default; - } - } - public static JsonNode? ToJson(this IAnimation animation) { - JsonNode node = new JsonObject(); + var json = new JsonObject(); if (animation is IKeyFrameAnimation keyFrameAnimation) { if (keyFrameAnimation.KeyFrames.Count > 0) { - keyFrameAnimation.WriteToJson(ref node); - node.WriteDiscriminator(animation.GetType()); - return node; + keyFrameAnimation.WriteToJson(json); + json.WriteDiscriminator(animation.GetType()); + return json; } else { @@ -36,9 +22,9 @@ public static (string?, JsonNode?) ToJson(this IAnimation animation, Type target } else { - animation.WriteToJson(ref node); - node.WriteDiscriminator(animation.GetType()); - return node; + animation.WriteToJson(json); + json.WriteDiscriminator(animation.GetType()); + return json; } } @@ -59,7 +45,7 @@ public static (string?, JsonNode?) ToJson(this IAnimation animation, Type target if (obj.TryGetDiscriminator(out Type? type) && Activator.CreateInstance(type, property) is IAnimation animation) { - animation.ReadFromJson(json); + animation.ReadFromJson(obj); return animation; } } diff --git a/src/Beutl.Graphics/Animation/KeyFrame.cs b/src/Beutl.Graphics/Animation/KeyFrame.cs index 14207e978..6816441f1 100644 --- a/src/Beutl.Graphics/Animation/KeyFrame.cs +++ b/src/Beutl.Graphics/Animation/KeyFrame.cs @@ -57,57 +57,51 @@ public TimeSpan KeyTime internal virtual CoreProperty? Property { get; set; } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); + base.WriteToJson(json); - if (json is JsonObject jsonObject) + if (Easing is SplineEasing splineEasing) { - if (Easing is SplineEasing splineEasing) + json[nameof(Easing)] = new JsonObject { - jsonObject[nameof(Easing)] = new JsonObject - { - ["X1"] = splineEasing.X1, - ["Y1"] = splineEasing.Y1, - ["X2"] = splineEasing.X2, - ["Y2"] = splineEasing.Y2, - }; - } - else - { - jsonObject[nameof(Easing)] = TypeFormat.ToString(Easing.GetType()); - } + ["X1"] = splineEasing.X1, + ["Y1"] = splineEasing.Y1, + ["X2"] = splineEasing.X2, + ["Y2"] = splineEasing.Y2, + }; + } + else + { + json[nameof(Easing)] = TypeFormat.ToString(Easing.GetType()); } } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject jsonObject) + if (json.TryGetPropertyValue(nameof(Easing), out JsonNode? easingNode)) { - if (jsonObject.TryGetPropertyValue(nameof(Easing), out JsonNode? easingNode)) + if (easingNode is JsonValue easingTypeValue + && easingTypeValue.TryGetValue(out string? easingType)) { - if (easingNode is JsonValue easingTypeValue - && easingTypeValue.TryGetValue(out string? easingType)) - { - Type type = TypeFormat.ToType(easingType) ?? typeof(LinearEasing); + Type type = TypeFormat.ToType(easingType) ?? typeof(LinearEasing); - if (Activator.CreateInstance(type) is Easing easing) - { - Easing = easing; - } - } - else if (easingNode is JsonObject easingObject) + if (Activator.CreateInstance(type) is Easing easing) { - float x1 = (float?)easingObject["X1"] ?? 0; - float y1 = (float?)easingObject["Y1"] ?? 0; - float x2 = (float?)easingObject["X2"] ?? 1; - float y2 = (float?)easingObject["Y2"] ?? 1; - - Easing = new SplineEasing(x1, y1, x2, y2); + Easing = easing; } } + else if (easingNode is JsonObject easingObject) + { + float x1 = (float?)easingObject["X1"] ?? 0; + float y1 = (float?)easingObject["Y1"] ?? 0; + float x2 = (float?)easingObject["X2"] ?? 1; + float y2 = (float?)easingObject["Y2"] ?? 1; + + Easing = new SplineEasing(x1, y1, x2, y2); + } } } } diff --git a/src/Beutl.Graphics/Animation/KeyFrameAnimation{T}.cs b/src/Beutl.Graphics/Animation/KeyFrameAnimation{T}.cs index d97935096..2f227467b 100644 --- a/src/Beutl.Graphics/Animation/KeyFrameAnimation{T}.cs +++ b/src/Beutl.Graphics/Animation/KeyFrameAnimation{T}.cs @@ -60,39 +60,36 @@ public T Interpolate(TimeSpan timeSpan) } } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject obj) + if (json.TryGetPropertyValue(nameof(KeyFrames), out JsonNode? keyframesNode) + && keyframesNode is JsonArray keyframesArray) { - if (obj.TryGetPropertyValue(nameof(KeyFrames), out JsonNode? keyframesNode) - && keyframesNode is JsonArray keyframesArray) + KeyFrames.Clear(); + KeyFrames.EnsureCapacity(keyframesArray.Count); + + foreach (JsonObject childJson in keyframesArray.OfType()) { - KeyFrames.Clear(); - KeyFrames.EnsureCapacity(keyframesArray.Count); - - foreach (JsonObject childJson in keyframesArray.OfType()) - { - var item = new KeyFrame(); - item.ReadFromJson(childJson); - KeyFrames.Add(item); - } + var item = new KeyFrame(); + item.ReadFromJson(childJson); + KeyFrames.Add(item); } } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); + base.WriteToJson(json); var array = new JsonArray(); foreach (KeyFrame item in KeyFrames.GetMarshal().Value) { - JsonNode node = new JsonObject(); - item.WriteToJson(ref node); + var itemJson = new JsonObject(); + item.WriteToJson(itemJson); - array.Add(node); + array.Add(itemJson); } json[nameof(KeyFrames)] = array; diff --git a/src/Beutl.Graphics/Audio/Effects/SoundEffectGroup.cs b/src/Beutl.Graphics/Audio/Effects/SoundEffectGroup.cs index a526d2314..392b3e57f 100644 --- a/src/Beutl.Graphics/Audio/Effects/SoundEffectGroup.cs +++ b/src/Beutl.Graphics/Audio/Effects/SoundEffectGroup.cs @@ -41,53 +41,47 @@ private int ValidEffectCount() return count; } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject jobject) + if (json.TryGetPropertyValue(nameof(Children), out JsonNode? childrenNode) + && childrenNode is JsonArray childrenArray) { - if (jobject.TryGetPropertyValue(nameof(Children), out JsonNode? childrenNode) - && childrenNode is JsonArray childrenArray) - { - _children.Clear(); - _children.EnsureCapacity(childrenArray.Count); + _children.Clear(); + _children.EnsureCapacity(childrenArray.Count); - foreach (JsonObject childJson in childrenArray.OfType()) + foreach (JsonObject childJson in childrenArray.OfType()) + { + if (childJson.TryGetDiscriminator(out Type? type) + && type.IsAssignableTo(typeof(SoundEffect)) + && Activator.CreateInstance(type) is IMutableSoundEffect soundEffect) { - if (childJson.TryGetDiscriminator(out Type? type) - && type.IsAssignableTo(typeof(SoundEffect)) - && Activator.CreateInstance(type) is IMutableSoundEffect soundEffect) - { - soundEffect.ReadFromJson(childJson); - _children.Add(soundEffect); - } + soundEffect.ReadFromJson(childJson); + _children.Add(soundEffect); } } } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); + base.WriteToJson(json); - if (json is JsonObject jobject) - { - var array = new JsonArray(); + var array = new JsonArray(); - foreach (ISoundEffect item in _children.GetMarshal().Value) + foreach (ISoundEffect item in _children.GetMarshal().Value) + { + if (item is IMutableSoundEffect obj) { - if (item is IMutableSoundEffect obj) - { - JsonNode node = new JsonObject(); - obj.WriteToJson(ref node); - node.WriteDiscriminator(item.GetType()); + var itemJson = new JsonObject(); + obj.WriteToJson(itemJson); + itemJson.WriteDiscriminator(item.GetType()); - array.Add(node); - } + array.Add(itemJson); } - - jobject[nameof(Children)] = array; } + + json[nameof(Children)] = array; } public override ISoundProcessor CreateProcessor() diff --git a/src/Beutl.Graphics/Converters/BrushJsonConverter.cs b/src/Beutl.Graphics/Converters/BrushJsonConverter.cs index 8cc3032ed..dd324626d 100644 --- a/src/Beutl.Graphics/Converters/BrushJsonConverter.cs +++ b/src/Beutl.Graphics/Converters/BrushJsonConverter.cs @@ -21,7 +21,7 @@ public override IBrush Read(ref Utf8JsonReader reader, Type typeToConvert, JsonS && Activator.CreateInstance(type) is IJsonSerializable jsonSerializable && jsonSerializable is IBrush brush) { - jsonSerializable.ReadFromJson(node); + jsonSerializable.ReadFromJson(node.AsObject()); return brush; } } @@ -35,10 +35,10 @@ public override void Write(Utf8JsonWriter writer, IBrush value, JsonSerializerOp { writer.WriteStringValue(solidColorBrush.Color.ToString()); } - else if(value is IJsonSerializable jsonSerializable) + else if (value is IJsonSerializable jsonSerializable) { - JsonNode json = new JsonObject(); - jsonSerializable.WriteToJson(ref json); + var json = new JsonObject(); + jsonSerializable.WriteToJson(json); json.WriteDiscriminator(value.GetType()); json.WriteTo(writer); diff --git a/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs b/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs index b65673861..4ebcf6da9 100644 --- a/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs +++ b/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs @@ -94,11 +94,10 @@ public FileInfo? SourceFile set => SetAndRaise(SourceFileProperty, ref _sourceFile, value); } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject jobj - && jobj.TryGetPropertyValue("source-file", out JsonNode? fileNode) + if (json.TryGetPropertyValue("source-file", out JsonNode? fileNode) && fileNode is JsonValue fileValue && fileValue.TryGetValue(out string? fileStr) && File.Exists(fileStr)) @@ -107,13 +106,12 @@ public override void ReadFromJson(JsonNode json) } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); - if (json is JsonObject jobj - && _sourceFile != null) + base.WriteToJson(json); + if (_sourceFile != null) { - jobj["source-file"] = _sourceFile.FullName; + json["source-file"] = _sourceFile.FullName; } } diff --git a/src/Beutl.Graphics/Graphics/Effects/BitmapEffectGroup.cs b/src/Beutl.Graphics/Graphics/Effects/BitmapEffectGroup.cs index 819331f4d..ea5aab78f 100644 --- a/src/Beutl.Graphics/Graphics/Effects/BitmapEffectGroup.cs +++ b/src/Beutl.Graphics/Graphics/Effects/BitmapEffectGroup.cs @@ -79,52 +79,46 @@ public override void ApplyAnimations(IClock clock) } } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject jobject) + if (json.TryGetPropertyValue(nameof(Children), out JsonNode? childrenNode) + && childrenNode is JsonArray childrenArray) { - if (jobject.TryGetPropertyValue(nameof(Children), out JsonNode? childrenNode) - && childrenNode is JsonArray childrenArray) - { - _children.Clear(); - _children.EnsureCapacity(childrenArray.Count); + _children.Clear(); + _children.EnsureCapacity(childrenArray.Count); - foreach (JsonObject childJson in childrenArray.OfType()) + foreach (JsonObject childJson in childrenArray.OfType()) + { + if (childJson.TryGetDiscriminator(out Type? type) + && type.IsAssignableTo(typeof(BitmapEffect)) + && Activator.CreateInstance(type) is IMutableBitmapEffect bitmapEffect) { - if (childJson.TryGetDiscriminator(out Type? type) - && type.IsAssignableTo(typeof(BitmapEffect)) - && Activator.CreateInstance(type) is IMutableBitmapEffect bitmapEffect) - { - bitmapEffect.ReadFromJson(childJson); - _children.Add(bitmapEffect); - } + bitmapEffect.ReadFromJson(childJson); + _children.Add(bitmapEffect); } } } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); + base.WriteToJson(json); - if (json is JsonObject jobject) - { - var array = new JsonArray(); + var array = new JsonArray(); - foreach (IBitmapEffect item in _children.GetMarshal().Value) + foreach (IBitmapEffect item in _children.GetMarshal().Value) + { + if (item is IMutableBitmapEffect obj) { - if (item is IMutableBitmapEffect obj) - { - JsonNode node = new JsonObject(); - obj.WriteToJson(ref node); - node.WriteDiscriminator(item.GetType()); + var itemJson = new JsonObject(); + obj.WriteToJson(itemJson); + itemJson.WriteDiscriminator(item.GetType()); - array.Add(node); - } + array.Add(itemJson); } - - jobject[nameof(Children)] = array; } + + json[nameof(Children)] = array; } } diff --git a/src/Beutl.Graphics/Graphics/Filters/ImageFilterGroup.cs b/src/Beutl.Graphics/Graphics/Filters/ImageFilterGroup.cs index d53489cb0..322853bd0 100644 --- a/src/Beutl.Graphics/Graphics/Filters/ImageFilterGroup.cs +++ b/src/Beutl.Graphics/Graphics/Filters/ImageFilterGroup.cs @@ -44,53 +44,47 @@ public override Rect TransformBounds(Rect rect) return rect; } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject jobject) + if (json.TryGetPropertyValue(nameof(Children), out JsonNode? childrenNode) + && childrenNode is JsonArray childrenArray) { - if (jobject.TryGetPropertyValue(nameof(Children), out JsonNode? childrenNode) - && childrenNode is JsonArray childrenArray) - { - _children.Clear(); - _children.EnsureCapacity(childrenArray.Count); + _children.Clear(); + _children.EnsureCapacity(childrenArray.Count); - foreach (JsonObject childJson in childrenArray.OfType()) + foreach (JsonObject childJson in childrenArray.OfType()) + { + if (childJson.TryGetDiscriminator(out Type? type) + && type.IsAssignableTo(typeof(ImageFilter)) + && Activator.CreateInstance(type) is IMutableImageFilter imageFilter) { - if (childJson.TryGetDiscriminator(out Type? type) - && type.IsAssignableTo(typeof(ImageFilter)) - && Activator.CreateInstance(type) is IMutableImageFilter imageFilter) - { - imageFilter.ReadFromJson(childJson); - _children.Add(imageFilter); - } + imageFilter.ReadFromJson(childJson); + _children.Add(imageFilter); } } } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); + base.WriteToJson(json); - if (json is JsonObject jobject) - { - var array = new JsonArray(); + var array = new JsonArray(); - foreach (IImageFilter item in _children.GetMarshal().Value) + foreach (IImageFilter item in _children.GetMarshal().Value) + { + if (item is IMutableImageFilter obj) { - if (item is IMutableImageFilter obj) - { - JsonNode node = new JsonObject(); - obj.WriteToJson(ref node); - node.WriteDiscriminator(item.GetType()); + var itemJson = new JsonObject(); + obj.WriteToJson(itemJson); + itemJson.WriteDiscriminator(item.GetType()); - array.Add(node); - } + array.Add(itemJson); } - - jobject[nameof(Children)] = array; } + + json[nameof(Children)] = array; } public override void ApplyAnimations(IClock clock) diff --git a/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs b/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs index f6b226d66..d5bea17de 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs @@ -135,17 +135,16 @@ public TextElements? Elements set => SetAndRaise(ElementsProperty, ref _elements, value); } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject jobj - && jobj.TryGetPropertyValue("elements", out JsonNode? elmsNode) + if (json.TryGetPropertyValue("elements", out JsonNode? elmsNode) && elmsNode is JsonArray elnsArray) { var array = new TextElement[elnsArray.Count]; for (int i = 0; i < elnsArray.Count; i++) { - if (elnsArray[i] is JsonNode elmNode) + if (elnsArray[i] is JsonObject elmNode) { var elm = new TextElement(); elm.ReadFromJson(elmNode); @@ -157,20 +156,20 @@ public override void ReadFromJson(JsonNode json) } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); - if (json is JsonObject jobj && _elements != null) + base.WriteToJson(json); + if (_elements != null) { var array = new JsonArray(_elements.Count); for (int i = 0; i < _elements.Count; i++) { - JsonNode node = new JsonObject(); - _elements[i].WriteToJson(ref node); + var node = new JsonObject(); + _elements[i].WriteToJson(node); array[i] = node; } - jobj["elements"] = array; + json["elements"] = array; } } diff --git a/src/Beutl.Graphics/Graphics/Transformation/TransformGroup.cs b/src/Beutl.Graphics/Graphics/Transformation/TransformGroup.cs index ba523ff50..e0f24bf66 100644 --- a/src/Beutl.Graphics/Graphics/Transformation/TransformGroup.cs +++ b/src/Beutl.Graphics/Graphics/Transformation/TransformGroup.cs @@ -45,53 +45,47 @@ public override Matrix Value } } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject jobject) + if (json.TryGetPropertyValue(nameof(Children), out JsonNode? childrenNode) + && childrenNode is JsonArray childrenArray) { - if (jobject.TryGetPropertyValue(nameof(Children), out JsonNode? childrenNode) - && childrenNode is JsonArray childrenArray) - { - _children.Clear(); - _children.EnsureCapacity(childrenArray.Count); + _children.Clear(); + _children.EnsureCapacity(childrenArray.Count); - foreach (JsonObject childJson in childrenArray.OfType()) + foreach (JsonObject childJson in childrenArray.OfType()) + { + if (childJson.TryGetDiscriminator(out Type? type) + && type.IsAssignableTo(typeof(Transform)) + && Activator.CreateInstance(type) is Transform transform) { - if (childJson.TryGetDiscriminator(out Type? type) - && type.IsAssignableTo(typeof(Transform)) - && Activator.CreateInstance(type) is Transform transform) - { - transform.ReadFromJson(childJson); - _children.Add(transform); - } + transform.ReadFromJson(childJson); + _children.Add(transform); } } } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); - if (json is JsonObject jobject) - { - var array = new JsonArray(); + base.WriteToJson(json); + var array = new JsonArray(); - foreach (ITransform item in _children.GetMarshal().Value) + foreach (ITransform item in _children.GetMarshal().Value) + { + if (item is Transform transform) { - if (item is Transform transform) - { - JsonNode node = new JsonObject(); - transform.WriteToJson(ref node); + var itemJson = new JsonObject(); + transform.WriteToJson(itemJson); - node.WriteDiscriminator(item.GetType()); + itemJson.WriteDiscriminator(item.GetType()); - array.Add(node); - } + array.Add(itemJson); } - - jobject[nameof(Children)] = array; } + + json[nameof(Children)] = array; } public override void ApplyAnimations(IClock clock) diff --git a/src/Beutl.Graphics/Media/GradientStops.cs b/src/Beutl.Graphics/Media/GradientStops.cs index e35d4a7d2..08daae177 100644 --- a/src/Beutl.Graphics/Media/GradientStops.cs +++ b/src/Beutl.Graphics/Media/GradientStops.cs @@ -1,47 +1,14 @@ -using System.Text.Json.Nodes; - -using Beutl.Media.Immutable; +using Beutl.Media.Immutable; namespace Beutl.Media; /// /// A collection of s. /// -public sealed class GradientStops : AffectsRenders, IJsonSerializable +public sealed class GradientStops : AffectsRenders { public IReadOnlyList ToImmutable() { return this.Select(x => new ImmutableGradientStop(x.Offset, x.Color)).ToArray(); } - - public void ReadFromJson(JsonNode json) - { - if (json is JsonArray childrenArray) - { - Clear(); - EnsureCapacity(childrenArray.Count); - - foreach (JsonObject childJson in childrenArray.OfType()) - { - var item = new GradientStop(); - item.ReadFromJson(childJson); - Add(item); - } - } - } - - public void WriteToJson(ref JsonNode json) - { - var array = new JsonArray(); - - foreach (GradientStop item in GetMarshal().Value) - { - JsonNode node = new JsonObject(); - item.WriteToJson(ref node); - - array.Add(node); - } - - json = array; - } } diff --git a/src/Beutl.Graphics/Styling/Styleable.cs b/src/Beutl.Graphics/Styling/Styleable.cs index 21f57f5ee..bd3f1acd1 100644 --- a/src/Beutl.Graphics/Styling/Styleable.cs +++ b/src/Beutl.Graphics/Styling/Styleable.cs @@ -107,45 +107,39 @@ void IStyleable.StyleApplied(IStyleInstance instance) _styleInstance = instance; } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject jobject) + if (json.TryGetPropertyValue("styles", out JsonNode? stylesNode) + && stylesNode is JsonArray stylesArray) { - if (jobject.TryGetPropertyValue("styles", out JsonNode? stylesNode) - && stylesNode is JsonArray stylesArray) - { - Styles.Clear(); - Styles.EnsureCapacity(stylesArray.Count); + Styles.Clear(); + Styles.EnsureCapacity(stylesArray.Count); - foreach (JsonNode? styleNode in stylesArray) + foreach (JsonNode? styleNode in stylesArray) + { + if (styleNode is JsonObject styleObject + && styleObject.ToStyle() is Style style) { - if (styleNode is JsonObject styleObject - && styleObject.ToStyle() is Style style) - { - Styles.Add(style); - } + Styles.Add(style); } } } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); - if (json is JsonObject jobject) + base.WriteToJson(json); + if (Styles.Count > 0) { - if (Styles.Count > 0) - { - var styles = new JsonArray(); - - foreach (IStyle style in Styles.GetMarshal().Value) - { - styles.Add(style.ToJson()); - } + var styles = new JsonArray(); - jobject["styles"] = styles; + foreach (IStyle style in Styles.GetMarshal().Value) + { + styles.Add(style.ToJson()); } + + json["styles"] = styles; } } diff --git a/src/Beutl.ProjectSystem/NodeTree/InputSocket.cs b/src/Beutl.ProjectSystem/NodeTree/InputSocket.cs index 8eb147227..202492a64 100644 --- a/src/Beutl.ProjectSystem/NodeTree/InputSocket.cs +++ b/src/Beutl.ProjectSystem/NodeTree/InputSocket.cs @@ -178,24 +178,21 @@ public override void PreEvaluate(EvaluationContext context) } } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject obj) + if (json.TryGetPropertyValue("connection-output", out var destNode) + && destNode is JsonValue destValue + && destValue.TryGetValue(out Guid outputId)) { - if (obj.TryGetPropertyValue("connection-output", out var destNode) - && destNode is JsonValue destValue - && destValue.TryGetValue(out Guid outputId)) - { - _outputId = outputId; - TryRestoreConnection(); - } + _outputId = outputId; + TryRestoreConnection(); } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); + base.WriteToJson(json); if (Connection != null) { json["connection-output"] = Connection.Output.Id; diff --git a/src/Beutl.ProjectSystem/NodeTree/InputSocketForSetter.cs b/src/Beutl.ProjectSystem/NodeTree/InputSocketForSetter.cs index 699119419..8e66e18d5 100644 --- a/src/Beutl.ProjectSystem/NodeTree/InputSocketForSetter.cs +++ b/src/Beutl.ProjectSystem/NodeTree/InputSocketForSetter.cs @@ -29,15 +29,15 @@ private void OnSetterInvalidated(object? sender, EventArgs e) return Property as SetterPropertyImpl; } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); GetProperty()?.ReadFromJson(json); } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); - GetProperty()?.WriteToJson(ref json); + base.WriteToJson(json); + GetProperty()?.WriteToJson(json); } } diff --git a/src/Beutl.ProjectSystem/NodeTree/Node.cs b/src/Beutl.ProjectSystem/NodeTree/Node.cs index b64463e38..5bf7fe6d8 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Node.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Node.cs @@ -403,69 +403,66 @@ private int GetLocalId(int requestedLocalId) return requestedLocalId; } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject obj) + if (json.TryGetPropertyValue(nameof(Position), out JsonNode? posNode) + && posNode is JsonValue posVal + && posVal.TryGetValue(out string? posStr)) { - if (obj.TryGetPropertyValue(nameof(Position), out JsonNode? posNode) - && posNode is JsonValue posVal - && posVal.TryGetValue(out string? posStr)) + var tokenizer = new RefStringTokenizer(posStr); + if (tokenizer.TryReadDouble(out double x) + && tokenizer.TryReadDouble(out double y)) { - var tokenizer = new RefStringTokenizer(posStr); - if (tokenizer.TryReadDouble(out double x) - && tokenizer.TryReadDouble(out double y)) - { - Position = (x, y); - } + Position = (x, y); } + } - if (obj.TryGetPropertyValue(nameof(Items), out var itemsNode) - && itemsNode is JsonArray itemsArray) + if (json.TryGetPropertyValue(nameof(Items), out var itemsNode) + && itemsNode is JsonArray itemsArray) + { + int index = 0; + foreach (JsonNode? item in itemsArray) { - int index = 0; - foreach (JsonNode? item in itemsArray) + if (item is JsonObject itemObj) { - if (item is JsonObject itemObj) + int localId; + if (itemObj.TryGetPropertyValue("LocalId", out var localIdNode) + && localIdNode is JsonValue localIdValue + && localIdValue.TryGetValue(out int actualLId)) + { + localId = actualLId; + } + else { - int localId; - if (itemObj.TryGetPropertyValue("LocalId", out var localIdNode) - && localIdNode is JsonValue localIdValue - && localIdValue.TryGetValue(out int actualLId)) - { - localId = actualLId; - } - else - { - localId = index; - } - - INodeItem? nodeItem = Items.FirstOrDefault(x => x.LocalId == localId); - - if (nodeItem is IJsonSerializable serializable) - { - serializable.ReadFromJson(itemObj); - } + localId = index; } - index++; + INodeItem? nodeItem = Items.FirstOrDefault(x => x.LocalId == localId); + + if (nodeItem is IJsonSerializable serializable) + { + serializable.ReadFromJson(itemObj); + } } + + index++; } } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); + base.WriteToJson(json); json[nameof(Position)] = $"{Position.X},{Position.Y}"; var array = new JsonArray(); foreach (INodeItem item in Items) { - JsonNode itemJson = new JsonObject(); + var itemJson = new JsonObject(); if (item is IJsonSerializable serializable) { - serializable.WriteToJson(ref itemJson); + serializable.WriteToJson(itemJson); itemJson.WriteDiscriminator(item.GetType()); } array.Add(itemJson); diff --git a/src/Beutl.ProjectSystem/NodeTree/NodeItemForSetter.cs b/src/Beutl.ProjectSystem/NodeTree/NodeItemForSetter.cs index e8db75cd8..d195ca67c 100644 --- a/src/Beutl.ProjectSystem/NodeTree/NodeItemForSetter.cs +++ b/src/Beutl.ProjectSystem/NodeTree/NodeItemForSetter.cs @@ -22,15 +22,15 @@ private void OnSetterInvalidated(object? sender, EventArgs e) return Property as SetterPropertyImpl; } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); GetProperty()?.ReadFromJson(json); } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); - GetProperty()?.WriteToJson(ref json); + base.WriteToJson(json); + GetProperty()?.WriteToJson(json); } } diff --git a/src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs b/src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs index d8db23cd6..153166545 100644 --- a/src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs +++ b/src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs @@ -239,51 +239,45 @@ protected void RaiseInvalidated(RenderInvalidatedEventArgs args) return null; } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject jobject) + if (json.TryGetPropertyValue(nameof(Nodes), out JsonNode? nodesNode) + && nodesNode is JsonArray nodesArray) { - if (jobject.TryGetPropertyValue(nameof(Nodes), out JsonNode? nodesNode) - && nodesNode is JsonArray nodesArray) + foreach (JsonObject nodeJson in nodesArray.OfType()) { - foreach (JsonObject nodeJson in nodesArray.OfType()) + if (nodeJson.TryGetDiscriminator(out Type? type) + && Activator.CreateInstance(type) is Node node) { - if (nodeJson.TryGetDiscriminator(out Type? type) - && Activator.CreateInstance(type) is Node node) - { - // Todo: 型が見つからない場合、SourceOperatorと同じようにする - node.ReadFromJson(nodeJson); - Nodes.Add(node); - } + // Todo: 型が見つからない場合、SourceOperatorと同じようにする + node.ReadFromJson(nodeJson); + Nodes.Add(node); } } } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); + base.WriteToJson(json); - if (json is JsonObject jobject) + Span nodes = _nodes.GetMarshal().Value; + if (nodes.Length > 0) { - Span nodes = _nodes.GetMarshal().Value; - if (nodes.Length > 0) - { - var array = new JsonArray(); - - foreach (Node item in nodes) - { - JsonNode jsonNode = new JsonObject(); - item.WriteToJson(ref jsonNode); - jsonNode.WriteDiscriminator(item.GetType()); + var array = new JsonArray(); - array.Add(jsonNode); - } + foreach (Node item in nodes) + { + var itemJson = new JsonObject(); + item.WriteToJson(itemJson); + itemJson.WriteDiscriminator(item.GetType()); - jobject[nameof(Nodes)] = array; + array.Add(itemJson); } + + json[nameof(Nodes)] = array; } } } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupInput.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupInput.cs index 6efc2fd41..b876825df 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupInput.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupInput.cs @@ -11,7 +11,7 @@ public class GroupInputSocket : OutputSocket, IGroupSocket, IAutomatically { public CoreProperty? AssociatedProperty { get; set; } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); JsonNode propertyJson = json[nameof(AssociatedProperty)]!; @@ -24,9 +24,9 @@ public override void ReadFromJson(JsonNode json) .FirstOrDefault(x => x.Name == name); } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); + base.WriteToJson(json); if (AssociatedProperty is { OwnerType: Type ownerType } property) { string name = property.Name; @@ -73,30 +73,27 @@ public bool AddSocket(ISocket socket, [NotNullWhen(true)] out Connection? connec return false; } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject obj) + if (json.TryGetPropertyValue("Items", out var itemsNode) + && itemsNode is JsonArray itemsArray) { - if (obj.TryGetPropertyValue("Items", out var itemsNode) - && itemsNode is JsonArray itemsArray) + int index = 0; + foreach (JsonObject itemJson in itemsArray.OfType()) { - int index = 0; - foreach (JsonObject itemJson in itemsArray.OfType()) + if (itemJson.TryGetDiscriminator(out Type? type) + && Activator.CreateInstance(type) is IOutputSocket socket) { - if (itemJson.TryGetDiscriminator(out Type? type) - && Activator.CreateInstance(type) is IOutputSocket socket) - { - (socket as IJsonSerializable)?.ReadFromJson(itemJson); - Items.Add(socket); - ((NodeItem)socket).LocalId = index; - } - - index++; + (socket as IJsonSerializable)?.ReadFromJson(itemJson); + Items.Add(socket); + ((NodeItem)socket).LocalId = index; } - NextLocalId = index; + index++; } + + NextLocalId = index; } } } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupNode.cs index d65617b47..fd343b756 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupNode.cs @@ -301,54 +301,51 @@ void Remove(int index, IList items) } } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject obj) + if (json.TryGetPropertyValue("node-tree", out JsonNode? nodeTreeNode) + && nodeTreeNode is JsonObject nodeTreeJson) { - if (obj.TryGetPropertyValue("node-tree", out var nodeTreeNode) - && nodeTreeNode is JsonObject) - { - Group.ReadFromJson(nodeTreeNode); - } + Group.ReadFromJson(nodeTreeJson); + } - OnOutputChanged(Group.Output, null); - OnInputChanged(Group.Input, null); + OnOutputChanged(Group.Output, null); + OnInputChanged(Group.Input, null); - if (obj.TryGetPropertyValue("items", out var itemsNode) - && itemsNode is JsonArray itemsArray) + if (json.TryGetPropertyValue("items", out var itemsNode) + && itemsNode is JsonArray itemsArray) + { + int index = 0; + foreach (JsonNode? item in itemsArray) { - int index = 0; - foreach (JsonNode? item in itemsArray) + if (item is JsonObject itemObj) { - if (item is JsonObject itemObj) + if (index < Items.Count) { - if (index < Items.Count) + INodeItem? nodeItem = Items[index]; + if (nodeItem is IJsonSerializable serializable) { - INodeItem? nodeItem = Items[index]; - if (nodeItem is IJsonSerializable serializable) - { - serializable.ReadFromJson(itemObj); - } - - ((NodeItem)nodeItem).LocalId = index; + serializable.ReadFromJson(itemObj); } - } - index++; + ((NodeItem)nodeItem).LocalId = index; + } } - NextLocalId = index; + index++; } + + NextLocalId = index; } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); - JsonNode node = new JsonObject(); - Group.WriteToJson(ref node); + base.WriteToJson(json); + var groupJson = new JsonObject(); + Group.WriteToJson(groupJson); - json["node-tree"] = node; + json["node-tree"] = groupJson; } } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupOutput.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupOutput.cs index 039d79ea2..18843ae54 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupOutput.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupOutput.cs @@ -11,7 +11,7 @@ public class GroupOutputSocket : InputSocket, IAutomaticallyGeneratedSocke { public CoreProperty? AssociatedProperty { get; set; } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); JsonNode propertyJson = json[nameof(AssociatedProperty)]!; @@ -24,9 +24,9 @@ public override void ReadFromJson(JsonNode json) .FirstOrDefault(x => x.Name == name); } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); + base.WriteToJson(json); if (AssociatedProperty is { OwnerType: Type ownerType } property) { string name = property.Name; @@ -73,30 +73,27 @@ public bool AddSocket(ISocket socket, [NotNullWhen(true)] out Connection? connec return false; } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject obj) + if (json.TryGetPropertyValue("Items", out var itemsNode) + && itemsNode is JsonArray itemsArray) { - if (obj.TryGetPropertyValue("Items", out var itemsNode) - && itemsNode is JsonArray itemsArray) + int index = 0; + foreach (JsonObject itemJson in itemsArray.OfType()) { - int index = 0; - foreach (JsonObject itemJson in itemsArray.OfType()) + if (itemJson.TryGetDiscriminator(out Type? type) + && Activator.CreateInstance(type) is IInputSocket socket) { - if (itemJson.TryGetDiscriminator(out Type? type) - && Activator.CreateInstance(type) is IInputSocket socket) - { - (socket as IJsonSerializable)?.ReadFromJson(itemJson); - Items.Add(socket); - ((NodeItem)socket).LocalId = index; - } - - index++; + (socket as IJsonSerializable)?.ReadFromJson(itemJson); + Items.Add(socket); + ((NodeItem)socket).LocalId = index; } - NextLocalId = index; + index++; } + + NextLocalId = index; } } } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/LayerInputNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/LayerInputNode.cs index 4cb5112c6..8ce386b06 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/LayerInputNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/LayerInputNode.cs @@ -81,7 +81,7 @@ public override void PreEvaluate(EvaluationContext context) } } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); string name = (string)json["Property"]!; @@ -99,10 +99,10 @@ public override void ReadFromJson(JsonNode json) } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); - GetProperty()?.WriteToJson(ref json); + base.WriteToJson(json); + GetProperty()?.WriteToJson(json); } } @@ -135,30 +135,27 @@ public bool AddSocket(ISocket socket, [NotNullWhen(true)] out Connection? connec return false; } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject obj) + if (json.TryGetPropertyValue("Items", out JsonNode? itemsNode) + && itemsNode is JsonArray itemsArray) { - if (obj.TryGetPropertyValue("Items", out JsonNode? itemsNode) - && itemsNode is JsonArray itemsArray) + int index = 0; + foreach (JsonObject itemJson in itemsArray.OfType()) { - int index = 0; - foreach (JsonObject itemJson in itemsArray.OfType()) + if (itemJson.TryGetDiscriminator(out Type? type) + && Activator.CreateInstance(type) is ILayerInputSocket socket) { - if (itemJson.TryGetDiscriminator(out Type? type) - && Activator.CreateInstance(type) is ILayerInputSocket socket) - { - (socket as IJsonSerializable)?.ReadFromJson(itemJson); - Items.Add(socket); - ((NodeItem)socket).LocalId = index; - } - - index++; + (socket as IJsonSerializable)?.ReadFromJson(itemJson); + Items.Add(socket); + ((NodeItem)socket).LocalId = index; } - NextLocalId = index; + index++; } + + NextLocalId = index; } } } diff --git a/src/Beutl.ProjectSystem/NodeTree/OutputSocket.cs b/src/Beutl.ProjectSystem/NodeTree/OutputSocket.cs index c397535c0..260c162f7 100644 --- a/src/Beutl.ProjectSystem/NodeTree/OutputSocket.cs +++ b/src/Beutl.ProjectSystem/NodeTree/OutputSocket.cs @@ -133,41 +133,38 @@ public override void PostEvaluate(EvaluationContext context) } } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject obj) + if (json.TryGetPropertyValue("connection-inputs", out var srcNode) + && srcNode is JsonArray srcArray) { - if (obj.TryGetPropertyValue("connection-inputs", out var srcNode) - && srcNode is JsonArray srcArray) + if (_inputIds != null) { - if (_inputIds != null) - { - _inputIds.Clear(); - _inputIds.EnsureCapacity(srcArray.Count); - } - else - { - _inputIds = new(srcArray.Count); - } + _inputIds.Clear(); + _inputIds.EnsureCapacity(srcArray.Count); + } + else + { + _inputIds = new(srcArray.Count); + } - foreach (JsonNode? item in srcArray) + foreach (JsonNode? item in srcArray) + { + if (item is JsonValue itemv + && itemv.TryGetValue(out Guid id)) { - if (item is JsonValue itemv - && itemv.TryGetValue(out Guid id)) - { - _inputIds.Add(id); - } + _inputIds.Add(id); } - - TryRestoreConnection(); } + + TryRestoreConnection(); } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); + base.WriteToJson(json); if (_connections.Count > 0) { diff --git a/src/Beutl.ProjectSystem/NodeTree/SetterPropertyImpl.cs b/src/Beutl.ProjectSystem/NodeTree/SetterPropertyImpl.cs index bcd71e498..dcf4be4e8 100644 --- a/src/Beutl.ProjectSystem/NodeTree/SetterPropertyImpl.cs +++ b/src/Beutl.ProjectSystem/NodeTree/SetterPropertyImpl.cs @@ -82,7 +82,7 @@ public void SetValue(T? value) Setter.Value = value; } - public void WriteToJson(ref JsonNode json) + public void WriteToJson(JsonObject json) { json[nameof(Property)] = Property.Name; json["Target"] = TypeFormat.ToString(ImplementedType); @@ -90,22 +90,19 @@ public void WriteToJson(ref JsonNode json) json[nameof(Setter)] = StyleSerializer.ToJson(Setter, ImplementedType).Item2; } - public void ReadFromJson(JsonNode json) + public void ReadFromJson(JsonObject json) { - if (json is JsonObject obj) + if (json.TryGetPropertyValue(nameof(Setter), out JsonNode? setterNode) + && setterNode != null) { - if (obj.TryGetPropertyValue(nameof(Setter), out JsonNode? setterNode) - && setterNode != null) + if (StyleSerializer.ToSetter(setterNode, Property.Name, ImplementedType) is Setter setter) { - if (StyleSerializer.ToSetter(setterNode, Property.Name, ImplementedType) is Setter setter) + if (setter.Animation != null) { - if (setter.Animation != null) - { - Setter.Animation = setter.Animation; - } - - Setter.Value = setter.Value; + Setter.Animation = setter.Animation; } + + Setter.Value = setter.Value; } } } diff --git a/src/Beutl.ProjectSystem/Operation/SourceOperation.cs b/src/Beutl.ProjectSystem/Operation/SourceOperation.cs index 9be944457..91bc8f912 100644 --- a/src/Beutl.ProjectSystem/Operation/SourceOperation.cs +++ b/src/Beutl.ProjectSystem/Operation/SourceOperation.cs @@ -32,56 +32,48 @@ public SourceOperation() public ICoreList Children => _children; - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject jobject) + if (json.TryGetPropertyValue(nameof(Children), out JsonNode? childrenNode) + && childrenNode is JsonArray childrenArray) { - if (jobject.TryGetPropertyValue(nameof(Children), out JsonNode? childrenNode) - && childrenNode is JsonArray childrenArray) + foreach (JsonObject operatorJson in childrenArray.OfType()) { - foreach (JsonObject operatorJson in childrenArray.OfType()) + Type? type = operatorJson.GetDiscriminator(); + SourceOperator? @operator = null; + if (type?.IsAssignableTo(typeof(SourceOperator)) ?? false) { - Type? type = operatorJson.GetDiscriminator(); - SourceOperator? @operator = null; - if (type?.IsAssignableTo(typeof(SourceOperator)) ?? false) - { - @operator = Activator.CreateInstance(type) as SourceOperator; - } - - @operator ??= new SourceOperator(); - @operator.ReadFromJson(operatorJson); - Children.Add(@operator); + @operator = Activator.CreateInstance(type) as SourceOperator; } - } + @operator ??= new SourceOperator(); + @operator.ReadFromJson(operatorJson); + Children.Add(@operator); + } } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); + base.WriteToJson(json); - if (json is JsonObject jobject) + Span children = _children.GetMarshal().Value; + if (children.Length > 0) { - Span children = _children.GetMarshal().Value; - if (children.Length > 0) - { - var array = new JsonArray(); + var array = new JsonArray(); - foreach (SourceOperator item in children) - { - JsonNode node = new JsonObject(); - item.WriteToJson(ref node); - node.WriteDiscriminator(item.GetType()); - - array.Add(node); - } + foreach (SourceOperator item in children) + { + var itemJson = new JsonObject(); + item.WriteToJson(itemJson); + itemJson.WriteDiscriminator(item.GetType()); - jobject[nameof(Children)] = array; + array.Add(itemJson); } + json[nameof(Children)] = array; } } diff --git a/src/Beutl.ProjectSystem/Operation/StylingOperator.cs b/src/Beutl.ProjectSystem/Operation/StylingOperator.cs index 20b102d51..ad3a3f79f 100644 --- a/src/Beutl.ProjectSystem/Operation/StylingOperator.cs +++ b/src/Beutl.ProjectSystem/Operation/StylingOperator.cs @@ -167,11 +167,10 @@ private void OnInvalidated(object? s, EventArgs e) RaiseInvalidated(new RenderInvalidatedEventArgs(this, nameof(Style))); } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject obj - && obj.TryGetPropertyValue(nameof(Style), out JsonNode? styleNode) + if (json.TryGetPropertyValue(nameof(Style), out JsonNode? styleNode) && styleNode is JsonObject styleObj) { var style = StyleSerializer.ToStyle(styleObj); @@ -184,13 +183,10 @@ public override void ReadFromJson(JsonNode json) } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); - if (json is JsonObject obj) - { - obj[nameof(Style)] = StyleSerializer.ToJson(Style); - } + base.WriteToJson(json); + json[nameof(Style)] = StyleSerializer.ToJson(Style); } private void Properties_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) diff --git a/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs b/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs index dc61f5f5e..27ea2c222 100644 --- a/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs +++ b/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs @@ -242,40 +242,34 @@ protected override void RestoreCore(string filename) this.JsonRestore(filename); } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json is JsonObject jobject) + if (json.TryGetPropertyValue(nameof(Operation), out JsonNode? operationNode) + && operationNode is JsonObject operationObj) { - if (jobject.TryGetPropertyValue(nameof(Operation), out JsonNode? operationNode) - && operationNode != null) - { - Operation.ReadFromJson(operationNode); - } + Operation.ReadFromJson(operationObj); + } - if (jobject.TryGetPropertyValue(nameof(NodeTree), out JsonNode? nodeTreeNode) - && nodeTreeNode != null) - { - NodeTree.ReadFromJson(nodeTreeNode); - } + if (json.TryGetPropertyValue(nameof(NodeTree), out JsonNode? nodeTreeNode) + && nodeTreeNode is JsonObject nodeTreeObj) + { + NodeTree.ReadFromJson(nodeTreeObj); } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { - base.WriteToJson(ref json); + base.WriteToJson(json); - if (json is JsonObject jobject) - { - JsonNode operationNode = new JsonObject(); - Operation.WriteToJson(ref operationNode); - jobject[nameof(Operation)] = operationNode; + var operationJson = new JsonObject(); + Operation.WriteToJson(operationJson); + json[nameof(Operation)] = operationJson; - JsonNode nodeTreeNode = new JsonObject(); - NodeTree.WriteToJson(ref nodeTreeNode); - jobject[nameof(NodeTree)] = nodeTreeNode; - } + var nodeTreeJson = new JsonObject(); + NodeTree.WriteToJson(nodeTreeJson); + json[nameof(NodeTree)] = nodeTreeJson; } public void Evaluate(IRenderer renderer, List unhandleds) diff --git a/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs b/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs index ad9030010..007e8e025 100644 --- a/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs +++ b/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs @@ -212,7 +212,7 @@ public IRecordableCommand MoveChildren(int deltaIndex, TimeSpan deltaStart, Laye return new MultipleMoveCommand(this, layers, deltaIndex, deltaStart); } - public override void ReadFromJson(JsonNode json) + public override void ReadFromJson(JsonObject json) { static void Process(Func add, JsonNode node, List list) { @@ -238,47 +238,44 @@ static void Process(Func add, JsonNode node, List list) base.ReadFromJson(json); - if (json is JsonObject jobject) + if (json.TryGetPropertyValue(nameof(Width), out JsonNode? widthNode) + && json.TryGetPropertyValue(nameof(Height), out JsonNode? heightNode) + && widthNode != null + && heightNode != null + && widthNode.AsValue().TryGetValue(out int width) + && heightNode.AsValue().TryGetValue(out int height)) { - if (jobject.TryGetPropertyValue(nameof(Width), out JsonNode? widthNode) - && jobject.TryGetPropertyValue(nameof(Height), out JsonNode? heightNode) - && widthNode != null - && heightNode != null - && widthNode.AsValue().TryGetValue(out int width) - && heightNode.AsValue().TryGetValue(out int height)) - { - Initialize(width, height); - } - - if (jobject.TryGetPropertyValue(nameof(Layers), out JsonNode? layersNode) - && layersNode is JsonObject layersJson) - { - var matcher = new Matcher(); - var directory = new DirectoryInfoWrapper(new DirectoryInfo(Path.GetDirectoryName(FileName)!)); - - // 含めるクリップ - if (layersJson.TryGetPropertyValue("Include", out JsonNode? includeNode)) - { - Process(matcher.AddInclude, includeNode!, _includeLayers); - } + Initialize(width, height); + } - // 除外するクリップ - if (layersJson.TryGetPropertyValue("Exclude", out JsonNode? excludeNode)) - { - Process(matcher.AddExclude, excludeNode!, _excludeLayers); - } + if (json.TryGetPropertyValue(nameof(Layers), out JsonNode? layersNode) + && layersNode is JsonObject layersJson) + { + var matcher = new Matcher(); + var directory = new DirectoryInfoWrapper(new DirectoryInfo(Path.GetDirectoryName(FileName)!)); - PatternMatchingResult result = matcher.Execute(directory); - SyncronizeLayers(result.Files.Select(x => x.Path)); + // 含めるクリップ + if (layersJson.TryGetPropertyValue("Include", out JsonNode? includeNode)) + { + Process(matcher.AddInclude, includeNode!, _includeLayers); } - else + + // 除外するクリップ + if (layersJson.TryGetPropertyValue("Exclude", out JsonNode? excludeNode)) { - Children.Clear(); + Process(matcher.AddExclude, excludeNode!, _excludeLayers); } + + PatternMatchingResult result = matcher.Execute(directory); + SyncronizeLayers(result.Files.Select(x => x.Path)); + } + else + { + Children.Clear(); } } - public override void WriteToJson(ref JsonNode json) + public override void WriteToJson(JsonObject json) { static void Process(JsonObject jobject, string jsonName, List list) { @@ -302,7 +299,7 @@ static void Process(JsonObject jobject, string jsonName, List list) } } - base.WriteToJson(ref json); + base.WriteToJson(json); if (_renderer != null) { json[nameof(Width)] = _renderer.Graphics.Size.Width; diff --git a/src/Beutl/Services/OutputService.cs b/src/Beutl/Services/OutputService.cs index 959fb8f07..4b1d2ba3b 100644 --- a/src/Beutl/Services/OutputService.cs +++ b/src/Beutl/Services/OutputService.cs @@ -52,8 +52,8 @@ public void Dispose() public static JsonNode ToJson(OutputQueueItem item) { - JsonNode ctxJson = new JsonObject(); - item.Context.WriteToJson(ref ctxJson); + var ctxJson = new JsonObject(); + item.Context.WriteToJson(ctxJson); return new JsonObject { ["Extension"] = TypeFormat.ToString(item.Context.Extension.GetType()), @@ -81,7 +81,7 @@ public static JsonNode ToJson(OutputQueueItem item) && File.Exists(file) && extension.TryCreateContext(file, out IOutputContext? context)) { - context.ReadFromJson(contextJson); + context.ReadFromJson(contextJson.AsObject()); return new OutputQueueItem(context); } else diff --git a/src/Beutl/ViewModels/EditViewModel.cs b/src/Beutl/ViewModels/EditViewModel.cs index 14891cddc..9ab7c1be0 100644 --- a/src/Beutl/ViewModels/EditViewModel.cs +++ b/src/Beutl/ViewModels/EditViewModel.cs @@ -224,8 +224,8 @@ private void SaveState() var bottomItems = new JsonArray(); foreach (ToolTabViewModel? item in BottomTabItems.OrderBy(x => x.Order)) { - JsonNode itemJson = new JsonObject(); - item.Context.WriteToJson(ref itemJson); + var itemJson = new JsonObject(); + item.Context.WriteToJson(itemJson); itemJson.WriteDiscriminator(item.Context.Extension.GetType()); bottomItems.Add(itemJson); @@ -236,8 +236,8 @@ private void SaveState() var rightItems = new JsonArray(); foreach (ToolTabViewModel? item in RightTabItems.OrderBy(x => x.Order)) { - JsonNode itemJson = new JsonObject(); - item.Context.WriteToJson(ref itemJson); + var itemJson = new JsonObject(); + item.Context.WriteToJson(itemJson); itemJson.WriteDiscriminator(item.Context.Extension.GetType()); rightItems.Add(itemJson); @@ -329,7 +329,7 @@ void RestoreTabItems(JsonArray source, CoreList destination) && _extensionProvider.AllExtensions.FirstOrDefault(x => x.GetType() == type) is ToolTabExtension extension && extension.TryCreateContext(this, out IToolContext? context)) { - context.ReadFromJson(item); + context.ReadFromJson(itemObject); destination.Add(new ToolTabViewModel(context) { Order = count diff --git a/src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs b/src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs index 5b18eb126..45b484cfe 100644 --- a/src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs +++ b/src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs @@ -156,11 +156,11 @@ public void Dispose() public abstract void Reset(); - public void WriteToJson(ref JsonNode json) + public void WriteToJson(JsonObject json) { } - public void ReadFromJson(JsonNode json) + public void ReadFromJson(JsonObject json) { } diff --git a/src/Beutl/ViewModels/NodeTree/NodeTreeInputTabViewModel.cs b/src/Beutl/ViewModels/NodeTree/NodeTreeInputTabViewModel.cs index 186ad4dbf..c767825f7 100644 --- a/src/Beutl/ViewModels/NodeTree/NodeTreeInputTabViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/NodeTreeInputTabViewModel.cs @@ -64,12 +64,12 @@ public void Dispose() return _editViewModel.GetService(serviceType); } - public void ReadFromJson(JsonNode json) + public void ReadFromJson(JsonObject json) { } - public void WriteToJson(ref JsonNode json) + public void WriteToJson(JsonObject json) { } diff --git a/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs b/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs index 4d7424cdc..09a99b571 100644 --- a/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs @@ -197,10 +197,10 @@ public void NavigateTo(NodeTreeSpace nodeTree) } } - public void ReadFromJson(JsonNode json) + public void ReadFromJson(JsonObject json) { if (Layer.Value == null - && (json as JsonObject)?.TryGetPropertyValue("layer-filename", out JsonNode? filenameNode) == true + && json?.TryGetPropertyValue("layer-filename", out JsonNode? filenameNode) == true && (filenameNode as JsonValue)?.TryGetValue(out string? filename) == true && filename != null) { @@ -208,7 +208,7 @@ public void ReadFromJson(JsonNode json) } } - public void WriteToJson(ref JsonNode json) + public void WriteToJson(JsonObject json) { if (Layer.Value is { FileName: { } filename }) { diff --git a/src/Beutl/ViewModels/OutputViewModel.cs b/src/Beutl/ViewModels/OutputViewModel.cs index 8ec8113cb..696140edb 100644 --- a/src/Beutl/ViewModels/OutputViewModel.cs +++ b/src/Beutl/ViewModels/OutputViewModel.cs @@ -513,67 +513,59 @@ public void Dispose() _disposable1.Dispose(); } - public void WriteToJson(ref JsonNode json) + public void WriteToJson(JsonObject json) { - if (json is not JsonObject obj) - { - obj = new JsonObject(); - } - - obj[nameof(DestinationFile)] = DestinationFile.Value; + json[nameof(DestinationFile)] = DestinationFile.Value; if (SelectedEncoder.Value != null) { - obj[nameof(SelectedEncoder)] = TypeFormat.ToString(SelectedEncoder.Value.GetType()); + json[nameof(SelectedEncoder)] = TypeFormat.ToString(SelectedEncoder.Value.GetType()); } - obj[nameof(IsEncodersExpanded)] = IsEncodersExpanded.Value; - obj[nameof(ScrollOffset)] = ScrollOffset.Value.ToString(); - obj[nameof(VideoSettings)] = VideoSettings.WriteToJson(); - obj[nameof(AudioSettings)] = AudioSettings.WriteToJson(); - - json = obj; + json[nameof(IsEncodersExpanded)] = IsEncodersExpanded.Value; + json[nameof(ScrollOffset)] = ScrollOffset.Value.ToString(); + json[nameof(VideoSettings)] = VideoSettings.WriteToJson(); + json[nameof(AudioSettings)] = AudioSettings.WriteToJson(); } - public void ReadFromJson(JsonNode json) + public void ReadFromJson(JsonObject json) { - if (json is JsonObject jobj) + if (json.TryGetPropertyValue(nameof(DestinationFile), out JsonNode? dstFileNode) + && dstFileNode is JsonValue dstFileValue + && dstFileValue.TryGetValue(out string? dstFile)) { - if (jobj.TryGetPropertyValue(nameof(DestinationFile), out JsonNode? dstFileNode) - && dstFileNode is JsonValue dstFileValue - && dstFileValue.TryGetValue(out string? dstFile)) - { - DestinationFile.Value = dstFile; - } + DestinationFile.Value = dstFile; + } - if (jobj.TryGetPropertyValue(nameof(SelectedEncoder), out JsonNode? encoderNode) - && encoderNode is JsonValue encoderValue - && encoderValue.TryGetValue(out string? encoderStr) - && TypeFormat.ToType(encoderStr) is Type encoderType - && EncoderRegistry.EnumerateEncoders().FirstOrDefault(x => x.GetType() == encoderType) is { } encoder) - { - SelectedEncoder.Value = encoder; - } + if (json.TryGetPropertyValue(nameof(SelectedEncoder), out JsonNode? encoderNode) + && encoderNode is JsonValue encoderValue + && encoderValue.TryGetValue(out string? encoderStr) + && TypeFormat.ToType(encoderStr) is Type encoderType + && EncoderRegistry.EnumerateEncoders().FirstOrDefault(x => x.GetType() == encoderType) is { } encoder) + { + SelectedEncoder.Value = encoder; + } - if (jobj.TryGetPropertyValue(nameof(IsEncodersExpanded), out JsonNode? isExpandedNode) - && isExpandedNode is JsonValue isExpandedValue - && isExpandedValue.TryGetValue(out bool isExpanded)) - { - IsEncodersExpanded.Value = isExpanded; - } + if (json.TryGetPropertyValue(nameof(IsEncodersExpanded), out JsonNode? isExpandedNode) + && isExpandedNode is JsonValue isExpandedValue + && isExpandedValue.TryGetValue(out bool isExpanded)) + { + IsEncodersExpanded.Value = isExpanded; + } - if (jobj.TryGetPropertyValue(nameof(ScrollOffset), out JsonNode? scrollOfstNode) - && scrollOfstNode is JsonValue scrollOfstValue - && scrollOfstValue.TryGetValue(out string? scrollOfstStr) - && Graphics.Vector.TryParse(scrollOfstStr, out Graphics.Vector vec)) - { - ScrollOffset.Value = new Avalonia.Vector(vec.X, vec.Y); - } + if (json.TryGetPropertyValue(nameof(ScrollOffset), out JsonNode? scrollOfstNode) + && scrollOfstNode is JsonValue scrollOfstValue + && scrollOfstValue.TryGetValue(out string? scrollOfstStr) + && Graphics.Vector.TryParse(scrollOfstStr, out Graphics.Vector vec)) + { + ScrollOffset.Value = new Avalonia.Vector(vec.X, vec.Y); + } - if (jobj.TryGetPropertyValue(nameof(VideoSettings), out JsonNode? videoNode) && videoNode != null) - VideoSettings.ReadFromJson(videoNode); + if (json.TryGetPropertyValue(nameof(VideoSettings), out JsonNode? videoNode) + && videoNode is JsonObject videoObj) + VideoSettings.ReadFromJson(videoObj); - if (jobj.TryGetPropertyValue(nameof(AudioSettings), out JsonNode? audioNode) && audioNode != null) - AudioSettings.ReadFromJson(audioNode); - } + if (json.TryGetPropertyValue(nameof(AudioSettings), out JsonNode? audioNode) + && audioNode is JsonObject audioObj) + AudioSettings.ReadFromJson(audioObj); } } diff --git a/src/Beutl/ViewModels/TimelineLayerViewModel.cs b/src/Beutl/ViewModels/TimelineLayerViewModel.cs index bb7bb8305..815450a2d 100644 --- a/src/Beutl/ViewModels/TimelineLayerViewModel.cs +++ b/src/Beutl/ViewModels/TimelineLayerViewModel.cs @@ -71,11 +71,11 @@ public TimelineLayerViewModel(Layer sceneLayer, TimelineViewModel timeline) TimeSpan forwardLength = absTime - Model.Start; TimeSpan backwardLength = Model.Length - forwardLength; - JsonNode jsonNode = new JsonObject(); - Model.WriteToJson(ref jsonNode); + var jsonNode = new JsonObject(); + Model.WriteToJson(jsonNode); string json = jsonNode.ToJsonString(JsonHelper.SerializerOptions); var backwardLayer = new Layer(); - backwardLayer.ReadFromJson(JsonNode.Parse(json)!); + backwardLayer.ReadFromJson(JsonNode.Parse(json)!.AsObject()); Scene.MoveChild(Model.ZIndex, Model.Start, forwardLength, Model).DoAndRecord(CommandRecorder.Default); backwardLayer.Start = absTime; @@ -289,8 +289,8 @@ private async ValueTask SetClipboard() IClipboard? clipboard = Application.Current?.Clipboard; if (clipboard != null) { - JsonNode jsonNode = new JsonObject(); - Model.WriteToJson(ref jsonNode); + var jsonNode = new JsonObject(); + Model.WriteToJson(jsonNode); string json = jsonNode.ToJsonString(JsonHelper.SerializerOptions); var data = new DataObject(); data.Set(DataFormats.Text, json); diff --git a/src/Beutl/ViewModels/TimelineViewModel.cs b/src/Beutl/ViewModels/TimelineViewModel.cs index 055e93b0c..46d9c25ab 100644 --- a/src/Beutl/ViewModels/TimelineViewModel.cs +++ b/src/Beutl/ViewModels/TimelineViewModel.cs @@ -165,11 +165,11 @@ public void Dispose() EditorContext = null!; } - public void ReadFromJson(JsonNode json) + public void ReadFromJson(JsonObject json) { } - public void WriteToJson(ref JsonNode json) + public void WriteToJson(JsonObject json) { } diff --git a/src/Beutl/ViewModels/Tools/GraphEditorTabViewModel.cs b/src/Beutl/ViewModels/Tools/GraphEditorTabViewModel.cs index b7e4e43c2..24b1b00a4 100644 --- a/src/Beutl/ViewModels/Tools/GraphEditorTabViewModel.cs +++ b/src/Beutl/ViewModels/Tools/GraphEditorTabViewModel.cs @@ -31,11 +31,11 @@ public void Dispose() return null; } - public void ReadFromJson(JsonNode json) + public void ReadFromJson(JsonObject json) { } - public void WriteToJson(ref JsonNode json) + public void WriteToJson(JsonObject json) { } } diff --git a/src/Beutl/ViewModels/Tools/ObjectPropertyEditorViewModel.cs b/src/Beutl/ViewModels/Tools/ObjectPropertyEditorViewModel.cs index bf342907b..a77f13ef6 100644 --- a/src/Beutl/ViewModels/Tools/ObjectPropertyEditorViewModel.cs +++ b/src/Beutl/ViewModels/Tools/ObjectPropertyEditorViewModel.cs @@ -127,11 +127,11 @@ public void Dispose() _disposables.Dispose(); } - public void ReadFromJson(JsonNode json) + public void ReadFromJson(JsonObject json) { } - public void WriteToJson(ref JsonNode json) + public void WriteToJson(JsonObject json) { } diff --git a/src/Beutl/ViewModels/Tools/SourceOperatorViewModel.cs b/src/Beutl/ViewModels/Tools/SourceOperatorViewModel.cs index de0355349..fea6a8fad 100644 --- a/src/Beutl/ViewModels/Tools/SourceOperatorViewModel.cs +++ b/src/Beutl/ViewModels/Tools/SourceOperatorViewModel.cs @@ -65,7 +65,7 @@ public void RestoreState(JsonNode json) { if (context != null && node != null) { - context.ReadFromJson(node); + context.ReadFromJson(node.AsObject()); } } } @@ -84,8 +84,8 @@ public JsonNode SaveState() } else { - JsonNode node = new JsonObject(); - item.WriteToJson(ref node); + var node = new JsonObject(); + item.WriteToJson(node); array.Add(node); } } diff --git a/src/Beutl/ViewModels/Tools/SourceOperatorsTabViewModel.cs b/src/Beutl/ViewModels/Tools/SourceOperatorsTabViewModel.cs index 896c23380..c565ecaf9 100644 --- a/src/Beutl/ViewModels/Tools/SourceOperatorsTabViewModel.cs +++ b/src/Beutl/ViewModels/Tools/SourceOperatorsTabViewModel.cs @@ -197,11 +197,11 @@ private void ClearItems() Items.Clear(); } - public void ReadFromJson(JsonNode json) + public void ReadFromJson(JsonObject json) { } - public void WriteToJson(ref JsonNode json) + public void WriteToJson(JsonObject json) { } diff --git a/src/Beutl/ViewModels/Tools/StyleEditorViewModel.cs b/src/Beutl/ViewModels/Tools/StyleEditorViewModel.cs index f826f35c7..1e1c8faca 100644 --- a/src/Beutl/ViewModels/Tools/StyleEditorViewModel.cs +++ b/src/Beutl/ViewModels/Tools/StyleEditorViewModel.cs @@ -126,11 +126,11 @@ public void Dispose() ClearItems(); } - public void ReadFromJson(JsonNode json) + public void ReadFromJson(JsonObject json) { } - public void WriteToJson(ref JsonNode json) + public void WriteToJson(JsonObject json) { } diff --git a/src/Beutl/Views/MainView.axaml.cs b/src/Beutl/Views/MainView.axaml.cs index 105d639d6..3786b5100 100644 --- a/src/Beutl/Views/MainView.axaml.cs +++ b/src/Beutl/Views/MainView.axaml.cs @@ -634,8 +634,8 @@ private void InitCommands(MainViewModel viewModel) IClipboard? clipboard = Application.Current?.Clipboard; if (clipboard != null) { - JsonNode jsonNode = new JsonObject(); - layer.WriteToJson(ref jsonNode); + var jsonNode = new JsonObject(); + layer.WriteToJson(jsonNode); string json = jsonNode.ToJsonString(JsonHelper.SerializerOptions); var data = new DataObject(); data.Set(DataFormats.Text, json); @@ -656,8 +656,8 @@ private void InitCommands(MainViewModel viewModel) IClipboard? clipboard = Application.Current?.Clipboard; if (clipboard != null) { - JsonNode jsonNode = new JsonObject(); - layer.WriteToJson(ref jsonNode); + var jsonNode = new JsonObject(); + layer.WriteToJson(jsonNode); string json = jsonNode.ToJsonString(JsonHelper.SerializerOptions); var data = new DataObject(); data.Set(DataFormats.Text, json); diff --git a/src/Beutl/Views/Timeline.axaml.cs b/src/Beutl/Views/Timeline.axaml.cs index 5e941f59f..147939f27 100644 --- a/src/Beutl/Views/Timeline.axaml.cs +++ b/src/Beutl/Views/Timeline.axaml.cs @@ -98,7 +98,7 @@ private void OnDataContextAttached(TimelineViewModel vm) { string json = await clipboard.GetTextAsync(); var layer = new Layer(); - layer.ReadFromJson(JsonNode.Parse(json)!); + layer.ReadFromJson(JsonNode.Parse(json)!.AsObject()); layer.Start = ViewModel.ClickedFrame; layer.ZIndex = ViewModel.ClickedLayer; From d60b6be9d392d8b19d42aed036547bd9c86f616e Mon Sep 17 00:00:00 2001 From: indigo-san Date: Tue, 11 Apr 2023 15:26:56 +0900 Subject: [PATCH 30/84] =?UTF-8?q?SourceOperation.Evaluate=E3=81=A7Clear,?= =?UTF-8?q?=20AddRange=E3=81=97=E3=81=A6=E3=81=84=E3=82=8B=E5=A0=B4?= =?UTF-8?q?=E6=89=80=E3=82=92Replace=E3=81=AB=E5=A4=89=E6=9B=B4=E3=81=97?= =?UTF-8?q?=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Core/Collections/CoreList.cs | 27 +++---------------- .../Operation/SourceOperation.cs | 6 ++--- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/src/Beutl.Core/Collections/CoreList.cs b/src/Beutl.Core/Collections/CoreList.cs index c61785059..1b6c811c7 100644 --- a/src/Beutl.Core/Collections/CoreList.cs +++ b/src/Beutl.Core/Collections/CoreList.cs @@ -185,31 +185,9 @@ public virtual void Clear() public virtual void Replace(IList source) { - static bool AreEquals(IList items1, IList items2) - { - bool result = items1.Count == items2.Count; - if (!result) - { - return result; - } - - EqualityComparer comparer = EqualityComparer.Default; - for (int i = 0; i < items2.Count; i++) - { - result = comparer.Equals(items1[i], items2[i]); - - if (!result) - { - break; - } - } - - return result; - } - Span span = CollectionsMarshal.AsSpan(Inner); T[] oldItems = Count > 0 ? span.ToArray() : Array.Empty(); - if (!AreEquals(oldItems, source)) + if (!oldItems.SequenceEqual(source)) { Inner.Clear(); foreach (T? item in oldItems) @@ -228,7 +206,8 @@ static bool AreEquals(IList items1, IList items2) CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Replace, (IList)source, - oldItems)); + oldItems, + 0)); } } diff --git a/src/Beutl.ProjectSystem/Operation/SourceOperation.cs b/src/Beutl.ProjectSystem/Operation/SourceOperation.cs index 91bc8f912..3c63a2167 100644 --- a/src/Beutl.ProjectSystem/Operation/SourceOperation.cs +++ b/src/Beutl.ProjectSystem/Operation/SourceOperation.cs @@ -91,8 +91,6 @@ void Detach(IList renderables) } } - layer.Span.Value.Clear(); - Initialize(renderer, layer.Clock); if (_contexts != null) { @@ -106,7 +104,7 @@ void Detach(IList renderables) } Detach(flow); - layer.Span.Value.AddRange(flow); + layer.Span.Value.Replace(flow); } else { @@ -119,7 +117,7 @@ void Detach(IList renderables) } Detach(pooled); - layer.Span.Value.AddRange(pooled); + layer.Span.Value.Replace(pooled); } foreach (Renderable item in layer.Span.Value.GetMarshal().Value) From f2c116acdd476c4bef64192c6f40f6ef04901407 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Tue, 11 Apr 2023 15:38:19 +0900 Subject: [PATCH 31/84] =?UTF-8?q?TimelineLayerViewModel=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build/props/Avalonia.props | 1 - .../ViewModels/TimelineLayerViewModel.cs | 170 +++++++++--------- src/Beutl/Views/TimelineLayer.axaml.cs | 4 +- 3 files changed, 88 insertions(+), 87 deletions(-) diff --git a/build/props/Avalonia.props b/build/props/Avalonia.props index c5e7fa805..fc18011e4 100644 --- a/build/props/Avalonia.props +++ b/build/props/Avalonia.props @@ -2,7 +2,6 @@ - diff --git a/src/Beutl/ViewModels/TimelineLayerViewModel.cs b/src/Beutl/ViewModels/TimelineLayerViewModel.cs index 815450a2d..9d602281f 100644 --- a/src/Beutl/ViewModels/TimelineLayerViewModel.cs +++ b/src/Beutl/ViewModels/TimelineLayerViewModel.cs @@ -4,7 +4,6 @@ using Avalonia.Input; using Avalonia.Input.Platform; -using Beutl.Media; using Beutl.Models; using Beutl.ProjectSystem; @@ -22,10 +21,11 @@ public TimelineLayerViewModel(Layer sceneLayer, TimelineViewModel timeline) Model = sceneLayer; Timeline = timeline; + // プロパティを構成 IsEnabled = sceneLayer.GetObservable(Layer.IsEnabledProperty) .ToReadOnlyReactivePropertySlim() .AddTo(_disposables); - + AllowOutflow = sceneLayer.GetObservable(Layer.AllowOutflowProperty) .ToReadOnlyReactivePropertySlim() .AddTo(_disposables); @@ -63,36 +63,12 @@ public TimelineLayerViewModel(Layer sceneLayer, TimelineViewModel timeline) .ToReactiveProperty() .AddTo(_disposables); + // コマンドを構成 Split.Where(func => func != null) - .Subscribe(func => - { - int rate = Scene.FindHierarchicalParent() is { } proj ? proj.GetFrameRate() : 30; - TimeSpan absTime = func!().RoundToRate(rate); - TimeSpan forwardLength = absTime - Model.Start; - TimeSpan backwardLength = Model.Length - forwardLength; - - var jsonNode = new JsonObject(); - Model.WriteToJson(jsonNode); - string json = jsonNode.ToJsonString(JsonHelper.SerializerOptions); - var backwardLayer = new Layer(); - backwardLayer.ReadFromJson(JsonNode.Parse(json)!.AsObject()); - - Scene.MoveChild(Model.ZIndex, Model.Start, forwardLength, Model).DoAndRecord(CommandRecorder.Default); - backwardLayer.Start = absTime; - backwardLayer.Length = backwardLength; - - backwardLayer.Save(Helper.RandomLayerFileName(Path.GetDirectoryName(Scene.FileName)!, Constants.LayerFileExtension)); - Scene.AddChild(backwardLayer).DoAndRecord(CommandRecorder.Default); - }) + .Subscribe(func => OnSplit(func!())) .AddTo(_disposables); - Cut.Subscribe(async () => - { - if (await SetClipboard()) - { - Exclude.Execute(); - } - }) + Cut.Subscribe(OnCut) .AddTo(_disposables); Copy.Subscribe(async () => await SetClipboard()) @@ -101,59 +77,30 @@ public TimelineLayerViewModel(Layer sceneLayer, TimelineViewModel timeline) Exclude.Subscribe(() => Scene.RemoveChild(Model).DoAndRecord(CommandRecorder.Default)) .AddTo(_disposables); - Delete.Subscribe(() => - { - Scene.RemoveChild(Model).Do(); - if (File.Exists(Model.FileName)) - { - File.Delete(Model.FileName); - } - }) + Delete.Subscribe(OnDelete) .AddTo(_disposables); Color.Subscribe(c => Model.AccentColor = Media.Color.FromArgb(c.A, c.R, c.G, c.B)) .AddTo(_disposables); - FinishEditingAnimation.Subscribe(() => - { - foreach (InlineAnimationLayerViewModel item in Timeline.Inlines.Where(x => x.Layer == this).ToArray()) - { - Timeline.DetachInline(item); - } - }) + FinishEditingAnimation.Subscribe(OnFinishEditingAnimation) .AddTo(_disposables); - BringAnimationToTop.Subscribe(() => - { - if (LayerHeader.Value is { } layerHeader) - { - var inlines = Timeline.Inlines.Where(x => x.Layer == this).ToArray(); - Array.Sort(inlines, (x, y) => x.Index.Value - y.Index.Value); - - for (int i = 0; i < inlines.Length; i++) - { - InlineAnimationLayerViewModel? item = inlines[i]; - int oldIndex = layerHeader.Inlines.IndexOf(item); - if (oldIndex >= 0) - { - layerHeader.Inlines.Move(oldIndex, i); - } - } - } - }) + BringAnimationToTop.Subscribe(OnBringAnimationToTop) .AddTo(_disposables); + // ZIndexが変更されたら、LayerHeaderのカウントを増減して、新しいLayerHeaderを設定する。 zIndexSubject.Subscribe(number => - { - LayerHeaderViewModel? newLH = Timeline.LayerHeaders.FirstOrDefault(i => i.Number.Value == number); + { + LayerHeaderViewModel? newLH = Timeline.LayerHeaders.FirstOrDefault(i => i.Number.Value == number); - if (LayerHeader.Value != null) - LayerHeader.Value.ItemsCount.Value--; + if (LayerHeader.Value != null) + LayerHeader.Value.ItemsCount.Value--; - if (newLH != null) - newLH.ItemsCount.Value++; - LayerHeader.Value = newLH; - }) + if (newLH != null) + newLH.ItemsCount.Value++; + LayerHeader.Value = newLH; + }) .AddTo(_disposables); } @@ -171,7 +118,7 @@ public TimelineLayerViewModel(Layer sceneLayer, TimelineViewModel timeline) public Scene Scene => (Scene)Model.HierarchicalParent!; public ReadOnlyReactivePropertySlim IsEnabled { get; } - + public ReadOnlyReactivePropertySlim AllowOutflow { get; } public ReadOnlyReactivePropertySlim UseNode { get; } @@ -192,7 +139,7 @@ public TimelineLayerViewModel(Layer sceneLayer, TimelineViewModel timeline) public ReactiveCommand?> Split { get; } = new(); - public ReactiveCommand Cut { get; } = new(); + public AsyncReactiveCommand Cut { get; } = new(); public ReactiveCommand Copy { get; } = new(); @@ -258,7 +205,7 @@ public async Task AnimationRequest(PrepareAnimationContext context, Cancellation Width.Value = width; } - public async Task SyncModelToViewModel() + public async Task SubmitViewModelChanges() { PrepareAnimationContext context = PrepareAnimation(); @@ -273,17 +220,6 @@ public async Task SyncModelToViewModel() await AnimationRequest(context); } - public void PullFromModel() - { - var margin = new Thickness(0, Timeline.CalculateLayerTop(Model.ZIndex), 0, 0); - var borderMargin = new Thickness(Model.Start.ToPixel(Timeline.Options.Value.Scale), 0, 0, 0); - double width = Model.Length.ToPixel(Timeline.Options.Value.Scale); - - BorderMargin.Value = borderMargin; - Margin.Value = margin; - Width.Value = width; - } - private async ValueTask SetClipboard() { IClipboard? clipboard = Application.Current?.Clipboard; @@ -317,6 +253,72 @@ public PrepareAnimationContext PrepareAnimation() .ToArray()); } + private void OnDelete() + { + string fileName = Model.FileName; + Scene.RemoveChild(Model).Do(); + if (File.Exists(fileName)) + { + File.Delete(fileName); + } + } + + private void OnBringAnimationToTop() + { + if (LayerHeader.Value is { } layerHeader) + { + InlineAnimationLayerViewModel[] inlines = Timeline.Inlines.Where(x => x.Layer == this).ToArray(); + Array.Sort(inlines, (x, y) => x.Index.Value - y.Index.Value); + + for (int i = 0; i < inlines.Length; i++) + { + InlineAnimationLayerViewModel? item = inlines[i]; + int oldIndex = layerHeader.Inlines.IndexOf(item); + if (oldIndex >= 0) + { + layerHeader.Inlines.Move(oldIndex, i); + } + } + } + } + + private void OnFinishEditingAnimation() + { + foreach (InlineAnimationLayerViewModel item in Timeline.Inlines.Where(x => x.Layer == this).ToArray()) + { + Timeline.DetachInline(item); + } + } + + private async Task OnCut() + { + if (await SetClipboard()) + { + Exclude.Execute(); + } + } + + private void OnSplit(TimeSpan timeSpan) + { + int rate = Scene.FindHierarchicalParent() is { } proj ? proj.GetFrameRate() : 30; + TimeSpan absTime = timeSpan.RoundToRate(rate); + TimeSpan forwardLength = absTime - Model.Start; + TimeSpan backwardLength = Model.Length - forwardLength; + + var jsonNode = new JsonObject(); + Model.WriteToJson(jsonNode); + string json = jsonNode.ToJsonString(JsonHelper.SerializerOptions); + var backwardLayer = new Layer(); + backwardLayer.ReadFromJson(JsonNode.Parse(json)!.AsObject()); + + Scene.MoveChild(Model.ZIndex, Model.Start, forwardLength, Model).DoAndRecord(CommandRecorder.Default); + backwardLayer.Start = absTime; + backwardLayer.Length = backwardLength; + + backwardLayer.Save(Helper.RandomLayerFileName(Path.GetDirectoryName(Scene.FileName)!, Constants.LayerFileExtension)); + Scene.AddChild(backwardLayer).DoAndRecord(CommandRecorder.Default); + } + public record struct PrepareAnimationContext( Thickness Margin, Thickness BorderMargin, diff --git a/src/Beutl/Views/TimelineLayer.axaml.cs b/src/Beutl/Views/TimelineLayer.axaml.cs index 16e0274bb..485a7a6df 100644 --- a/src/Beutl/Views/TimelineLayer.axaml.cs +++ b/src/Beutl/Views/TimelineLayer.axaml.cs @@ -312,7 +312,7 @@ private async void OnBorderPointerReleased(object? sender, PointerReleasedEventA if (AssociatedObject is { ViewModel: { } viewModel }) { - await viewModel.SyncModelToViewModel(); + await viewModel.SubmitViewModelChanges(); e.Handled = true; } } @@ -443,7 +443,7 @@ private async void OnBorderPointerReleased(object? sender, PointerReleasedEventA if (layers.Count == 1) { - await viewModel.SyncModelToViewModel(); + await viewModel.SubmitViewModelChanges(); } else { From fc834c28bc47d0dadb101346b174c1d85754693e Mon Sep 17 00:00:00 2001 From: indigo-san Date: Tue, 11 Apr 2023 15:39:33 +0900 Subject: [PATCH 32/84] =?UTF-8?q?NodeTreeSpace=E3=82=92nodeTreeModel?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.ProjectSystem/NodeTree/INodeItem.cs | 4 +- .../NodeTree/InputSocket.cs | 4 +- .../NodeTree/LayerNodeTreeModel.cs | 2 +- src/Beutl.ProjectSystem/NodeTree/Node.cs | 6 +- .../{NodeTreeSpace.cs => NodeGroup.cs} | 87 +------------------ src/Beutl.ProjectSystem/NodeTree/NodeItem.cs | 10 +-- .../NodeTree/NodeTreeModel.cs | 84 ++++++++++++++++++ .../NodeTree/OutputSocket.cs | 4 +- .../ViewModels/NodeTree/NodeInputViewModel.cs | 2 +- .../NodeTree/NodeTreeInputViewModel.cs | 2 +- .../NodeTree/NodeTreeTabViewModel.cs | 16 ++-- .../ViewModels/NodeTree/NodeTreeViewModel.cs | 4 +- .../ViewModels/NodeTree/NodeViewModel.cs | 2 +- 13 files changed, 113 insertions(+), 114 deletions(-) rename src/Beutl.ProjectSystem/NodeTree/{NodeTreeSpace.cs => NodeGroup.cs} (70%) create mode 100644 src/Beutl.ProjectSystem/NodeTree/NodeTreeModel.cs diff --git a/src/Beutl.ProjectSystem/NodeTree/INodeItem.cs b/src/Beutl.ProjectSystem/NodeTree/INodeItem.cs index 125e5d30d..96b00b946 100644 --- a/src/Beutl.ProjectSystem/NodeTree/INodeItem.cs +++ b/src/Beutl.ProjectSystem/NodeTree/INodeItem.cs @@ -26,7 +26,7 @@ public interface INodeItem : ICoreObject, IHierarchical, IAffectsRender void PostEvaluate(EvaluationContext context); - void NotifyAttachedToNodeTree(NodeTreeSpace nodeTree); + void NotifyAttachedToNodeTree(NodeTreeModel nodeTree); - void NotifyDetachedFromNodeTree(NodeTreeSpace nodeTree); + void NotifyDetachedFromNodeTree(NodeTreeModel nodeTree); } diff --git a/src/Beutl.ProjectSystem/NodeTree/InputSocket.cs b/src/Beutl.ProjectSystem/NodeTree/InputSocket.cs index 202492a64..7255aa279 100644 --- a/src/Beutl.ProjectSystem/NodeTree/InputSocket.cs +++ b/src/Beutl.ProjectSystem/NodeTree/InputSocket.cs @@ -214,13 +214,13 @@ private void TryRestoreConnection() } } - protected override void OnAttachedToNodeTree(NodeTreeSpace nodeTree) + protected override void OnAttachedToNodeTree(NodeTreeModel nodeTree) { base.OnAttachedToNodeTree(nodeTree); TryRestoreConnection(); } - protected override void OnDetachedFromNodeTree(NodeTreeSpace nodeTree) + protected override void OnDetachedFromNodeTree(NodeTreeModel nodeTree) { base.OnDetachedFromNodeTree(nodeTree); if (Connection != null && _outputId == Guid.Empty) diff --git a/src/Beutl.ProjectSystem/NodeTree/LayerNodeTreeModel.cs b/src/Beutl.ProjectSystem/NodeTree/LayerNodeTreeModel.cs index 40fd4f23f..6be69ea3f 100644 --- a/src/Beutl.ProjectSystem/NodeTree/LayerNodeTreeModel.cs +++ b/src/Beutl.ProjectSystem/NodeTree/LayerNodeTreeModel.cs @@ -9,7 +9,7 @@ namespace Beutl.NodeTree; -public class LayerNodeTreeModel : NodeTreeSpace +public class LayerNodeTreeModel : NodeTreeModel { // 評価する順番 private readonly List _evalContexts = new(); diff --git a/src/Beutl.ProjectSystem/NodeTree/Node.cs b/src/Beutl.ProjectSystem/NodeTree/Node.cs index 5bf7fe6d8..900c918f1 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Node.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Node.cs @@ -14,7 +14,7 @@ public abstract class Node : Hierarchical public static readonly CoreProperty<(double X, double Y)> PositionProperty; private readonly HierarchicalList _items; private (double X, double Y) _position; - private NodeTreeSpace? _nodeTree; + private NodeTreeModel? _nodeTree; static Node() { @@ -474,7 +474,7 @@ public override void WriteToJson(JsonObject json) protected override void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs args) { base.OnAttachedToHierarchy(args); - if (args.Parent is NodeTreeSpace nodeTree) + if (args.Parent is NodeTreeModel nodeTree) { _nodeTree = nodeTree; foreach (INodeItem item in _items.GetMarshal().Value) @@ -487,7 +487,7 @@ protected override void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs ar protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) { base.OnDetachedFromHierarchy(args); - if (args.Parent is NodeTreeSpace nodeTree) + if (args.Parent is NodeTreeModel nodeTree) { _nodeTree = null; foreach (INodeItem item in _items.GetMarshal().Value) diff --git a/src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs b/src/Beutl.ProjectSystem/NodeTree/NodeGroup.cs similarity index 70% rename from src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs rename to src/Beutl.ProjectSystem/NodeTree/NodeGroup.cs index 153166545..7569f1ac5 100644 --- a/src/Beutl.ProjectSystem/NodeTree/NodeTreeSpace.cs +++ b/src/Beutl.ProjectSystem/NodeTree/NodeGroup.cs @@ -1,19 +1,13 @@ using System.ComponentModel; using System.Runtime.InteropServices; -using System.Text.Json.Nodes; -using Avalonia.Collections.Pooled; - -using Beutl.Animation; -using Beutl.Collections; using Beutl.Media; using Beutl.NodeTree.Nodes.Group; -using Beutl.ProjectSystem; using Beutl.Rendering; namespace Beutl.NodeTree; -public class NodeGroup : NodeTreeSpace +public class NodeGroup : NodeTreeModel { public static readonly CoreProperty InputProperty; public static readonly CoreProperty OutputProperty; @@ -202,82 +196,3 @@ protected override void OnPropertyChanged(PropertyChangedEventArgs args) } } } - -// Todo: NodeTreeModel -public abstract class NodeTreeSpace : Hierarchical, IAffectsRender -{ - private readonly HierarchicalList _nodes; - - public event EventHandler? Invalidated; - - public NodeTreeSpace() - { - _nodes = new HierarchicalList(this); - } - - public ICoreList Nodes => _nodes; - - protected void RaiseInvalidated(RenderInvalidatedEventArgs args) - { - Invalidated?.Invoke(this, args); - } - - public ISocket? FindSocket(Guid id) - { - foreach (Node node in Nodes.GetMarshal().Value) - { - foreach (INodeItem item in node.Items.GetMarshal().Value) - { - if (item is ISocket socket - && socket.Id == id) - { - return socket; - } - } - } - - return null; - } - - public override void ReadFromJson(JsonObject json) - { - base.ReadFromJson(json); - - if (json.TryGetPropertyValue(nameof(Nodes), out JsonNode? nodesNode) - && nodesNode is JsonArray nodesArray) - { - foreach (JsonObject nodeJson in nodesArray.OfType()) - { - if (nodeJson.TryGetDiscriminator(out Type? type) - && Activator.CreateInstance(type) is Node node) - { - // Todo: 型が見つからない場合、SourceOperatorと同じようにする - node.ReadFromJson(nodeJson); - Nodes.Add(node); - } - } - } - } - - public override void WriteToJson(JsonObject json) - { - base.WriteToJson(json); - - Span nodes = _nodes.GetMarshal().Value; - if (nodes.Length > 0) - { - var array = new JsonArray(); - - foreach (Node item in nodes) - { - var itemJson = new JsonObject(); - item.WriteToJson(itemJson); - itemJson.WriteDiscriminator(item.GetType()); - - array.Add(itemJson); - } - - json[nameof(Nodes)] = array; - } - } -} diff --git a/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs b/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs index ffac41209..49ff98ea0 100644 --- a/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs +++ b/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs @@ -50,7 +50,7 @@ public IAbstractProperty? Property public virtual Type? AssociatedType => typeof(T); - public NodeTreeSpace? NodeTree { get; private set; } + public NodeTreeModel? NodeTree { get; private set; } public event EventHandler? Invalidated; @@ -82,15 +82,15 @@ protected void RaiseInvalidated(RenderInvalidatedEventArgs args) Invalidated?.Invoke(this, args); } - protected virtual void OnAttachedToNodeTree(NodeTreeSpace nodeTree) + protected virtual void OnAttachedToNodeTree(NodeTreeModel nodeTree) { } - protected virtual void OnDetachedFromNodeTree(NodeTreeSpace nodeTree) + protected virtual void OnDetachedFromNodeTree(NodeTreeModel nodeTree) { } - void INodeItem.NotifyAttachedToNodeTree(NodeTreeSpace nodeTree) + void INodeItem.NotifyAttachedToNodeTree(NodeTreeModel nodeTree) { if (NodeTree != null) throw new InvalidOperationException("Already attached to the node tree."); @@ -99,7 +99,7 @@ void INodeItem.NotifyAttachedToNodeTree(NodeTreeSpace nodeTree) OnAttachedToNodeTree(nodeTree); } - void INodeItem.NotifyDetachedFromNodeTree(NodeTreeSpace nodeTree) + void INodeItem.NotifyDetachedFromNodeTree(NodeTreeModel nodeTree) { if (NodeTree == null) throw new InvalidOperationException("Already detached from the node tree."); diff --git a/src/Beutl.ProjectSystem/NodeTree/NodeTreeModel.cs b/src/Beutl.ProjectSystem/NodeTree/NodeTreeModel.cs new file mode 100644 index 000000000..ae753d005 --- /dev/null +++ b/src/Beutl.ProjectSystem/NodeTree/NodeTreeModel.cs @@ -0,0 +1,84 @@ +using System.Text.Json.Nodes; + +using Beutl.Collections; +using Beutl.Media; + +namespace Beutl.NodeTree; + +public abstract class NodeTreeModel : Hierarchical, IAffectsRender +{ + private readonly HierarchicalList _nodes; + + public event EventHandler? Invalidated; + + public NodeTreeModel() + { + _nodes = new HierarchicalList(this); + } + + public ICoreList Nodes => _nodes; + + protected void RaiseInvalidated(RenderInvalidatedEventArgs args) + { + Invalidated?.Invoke(this, args); + } + + public ISocket? FindSocket(Guid id) + { + foreach (Node node in Nodes.GetMarshal().Value) + { + foreach (INodeItem item in node.Items.GetMarshal().Value) + { + if (item is ISocket socket + && socket.Id == id) + { + return socket; + } + } + } + + return null; + } + + public override void ReadFromJson(JsonObject json) + { + base.ReadFromJson(json); + + if (json.TryGetPropertyValue(nameof(Nodes), out JsonNode? nodesNode) + && nodesNode is JsonArray nodesArray) + { + foreach (JsonObject nodeJson in nodesArray.OfType()) + { + if (nodeJson.TryGetDiscriminator(out Type? type) + && Activator.CreateInstance(type) is Node node) + { + // Todo: 型が見つからない場合、SourceOperatorと同じようにする + node.ReadFromJson(nodeJson); + Nodes.Add(node); + } + } + } + } + + public override void WriteToJson(JsonObject json) + { + base.WriteToJson(json); + + Span nodes = _nodes.GetMarshal().Value; + if (nodes.Length > 0) + { + var array = new JsonArray(); + + foreach (Node item in nodes) + { + var itemJson = new JsonObject(); + item.WriteToJson(itemJson); + itemJson.WriteDiscriminator(item.GetType()); + + array.Add(itemJson); + } + + json[nameof(Nodes)] = array; + } + } +} diff --git a/src/Beutl.ProjectSystem/NodeTree/OutputSocket.cs b/src/Beutl.ProjectSystem/NodeTree/OutputSocket.cs index 260c162f7..e8f129fa3 100644 --- a/src/Beutl.ProjectSystem/NodeTree/OutputSocket.cs +++ b/src/Beutl.ProjectSystem/NodeTree/OutputSocket.cs @@ -201,13 +201,13 @@ private void TryRestoreConnection() } } - protected override void OnAttachedToNodeTree(NodeTreeSpace nodeTree) + protected override void OnAttachedToNodeTree(NodeTreeModel nodeTree) { base.OnAttachedToNodeTree(nodeTree); TryRestoreConnection(); } - protected override void OnDetachedFromNodeTree(NodeTreeSpace nodeTree) + protected override void OnDetachedFromNodeTree(NodeTreeModel nodeTree) { base.OnDetachedFromNodeTree(nodeTree); if (_inputIds != null) diff --git a/src/Beutl/ViewModels/NodeTree/NodeInputViewModel.cs b/src/Beutl/ViewModels/NodeTree/NodeInputViewModel.cs index 8e31ee798..76447a6e6 100644 --- a/src/Beutl/ViewModels/NodeTree/NodeInputViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/NodeInputViewModel.cs @@ -16,7 +16,7 @@ public sealed class NodeInputViewModel : IDisposable, IPropertyEditorContextVisi { private readonly CompositeDisposable _disposables = new(); private readonly string _defaultName; - private NodeTreeSpace _nodeTree; + private NodeTreeModel _nodeTree; private NodeTreeInputViewModel _parent; public NodeInputViewModel(LayerInputNode node, int originalIndex, NodeTreeInputViewModel parent) diff --git a/src/Beutl/ViewModels/NodeTree/NodeTreeInputViewModel.cs b/src/Beutl/ViewModels/NodeTree/NodeTreeInputViewModel.cs index dbf3c96f9..cc4f51b25 100644 --- a/src/Beutl/ViewModels/NodeTree/NodeTreeInputViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/NodeTreeInputViewModel.cs @@ -128,7 +128,7 @@ public void Dispose() public object? GetService(Type serviceType) { - if (serviceType.IsAssignableTo(typeof(NodeTreeSpace))) + if (serviceType.IsAssignableTo(typeof(NodeTreeModel))) return Model.NodeTree; return _parent.GetService(serviceType); diff --git a/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs b/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs index 09a99b571..e9724aecc 100644 --- a/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs @@ -15,14 +15,14 @@ public sealed class NodeTreeNavigationItem : IDisposable { internal Lazy _lazyViewModel; - public NodeTreeNavigationItem(NodeTreeViewModel viewModel, ReadOnlyReactivePropertySlim name, NodeTreeSpace nodeTree) + public NodeTreeNavigationItem(NodeTreeViewModel viewModel, ReadOnlyReactivePropertySlim name, NodeTreeModel nodeTree) { _lazyViewModel = new Lazy(viewModel); Name = name; NodeTree = nodeTree; } - public NodeTreeNavigationItem(ReadOnlyReactivePropertySlim name, NodeTreeSpace nodeTree) + public NodeTreeNavigationItem(ReadOnlyReactivePropertySlim name, NodeTreeModel nodeTree) { NodeTree = nodeTree; _lazyViewModel = new Lazy(() => new NodeTreeViewModel(NodeTree)); @@ -33,7 +33,7 @@ public NodeTreeNavigationItem(ReadOnlyReactivePropertySlim name, NodeTre public ReadOnlyReactivePropertySlim Name { get; } - public NodeTreeSpace NodeTree { get; private set; } + public NodeTreeModel NodeTree { get; private set; } public void Dispose() { @@ -134,7 +134,7 @@ public void NavigateTo(int index) } } - public NodeTreeNavigationItem? FindItem(NodeTreeSpace nodeTree) + public NodeTreeNavigationItem? FindItem(NodeTreeModel nodeTree) { foreach (NodeTreeNavigationItem navItem in Items) { @@ -147,15 +147,15 @@ public void NavigateTo(int index) return null; } - public void NavigateTo(NodeTreeSpace nodeTree) + public void NavigateTo(NodeTreeModel nodeTree) { - using var stack = new PooledList(); + using var stack = new PooledList(); IHierarchical? current = nodeTree; while (current != null) { - if (current is NodeTreeSpace curNodeTree) + if (current is NodeTreeModel curNodeTree) { stack.Insert(0, curNodeTree); } @@ -172,7 +172,7 @@ public void NavigateTo(NodeTreeSpace nodeTree) using var list = new PooledList(stack.Count); - foreach (NodeTreeSpace item in stack.Span) + foreach (NodeTreeModel item in stack.Span) { NodeTreeNavigationItem? foundItem = FindItem(item); diff --git a/src/Beutl/ViewModels/NodeTree/NodeTreeViewModel.cs b/src/Beutl/ViewModels/NodeTree/NodeTreeViewModel.cs index 050be1964..c6cf2b87d 100644 --- a/src/Beutl/ViewModels/NodeTree/NodeTreeViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/NodeTreeViewModel.cs @@ -9,7 +9,7 @@ public sealed class NodeTreeViewModel : IDisposable { private readonly CompositeDisposable _disposables = new(); - public NodeTreeViewModel(NodeTreeSpace nodeTree) + public NodeTreeViewModel(NodeTreeModel nodeTree) { NodeTree = nodeTree; @@ -38,7 +38,7 @@ public NodeTreeViewModel(NodeTreeSpace nodeTree) public CoreList Nodes { get; } = new(); - public NodeTreeSpace NodeTree { get; } + public NodeTreeModel NodeTree { get; } public SocketViewModel? FindSocketViewModel(ISocket socket) { diff --git a/src/Beutl/ViewModels/NodeTree/NodeViewModel.cs b/src/Beutl/ViewModels/NodeTree/NodeViewModel.cs index 4f6094e67..6946bf52b 100644 --- a/src/Beutl/ViewModels/NodeTree/NodeViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/NodeViewModel.cs @@ -65,7 +65,7 @@ public NodeViewModel(Node node) Delete.Subscribe(() => { - NodeTreeSpace? tree = Node.FindHierarchicalParent(); + NodeTreeModel? tree = Node.FindHierarchicalParent(); if (tree != null) { new RemoveCommand(tree.Nodes, Node) From d717f8a38ef9682524f9619f1d713b7bdcf7ae4c Mon Sep 17 00:00:00 2001 From: indigo-san Date: Tue, 11 Apr 2023 16:18:23 +0900 Subject: [PATCH 33/84] =?UTF-8?q?Layer=E3=81=AE=E5=90=8D=E5=89=8D=E3=82=92?= =?UTF-8?q?Element=E3=81=AB=E5=A4=89=E6=9B=B4=20-=20=E9=80=94=E4=B8=AD?= =?UTF-8?q?=E3=81=A7=E4=B8=80=E3=81=A4=E3=81=AE=E3=83=AC=E3=82=A4=E3=83=A4?= =?UTF-8?q?=E3=83=BC=E3=81=AB=E8=A4=87=E6=95=B0=E3=82=AA=E3=83=96=E3=82=B8?= =?UTF-8?q?=E3=82=A7=E3=82=AF=E3=83=88=E3=82=92=E9=85=8D=E7=BD=AE=E3=81=A7?= =?UTF-8?q?=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F?= =?UTF-8?q?=E3=81=8C=E5=90=8D=E5=89=8D=E3=81=AE=E4=BF=AE=E6=AD=A3=E3=81=8C?= =?UTF-8?q?=E3=81=BE=E3=81=A0=E3=81=A0=E3=81=A3=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build/props/Avalonia.props | 1 + src/Beutl.Language/Strings.Designer.cs | 30 +- src/Beutl.Language/Strings.ja.resx | 8 +- src/Beutl.Language/Strings.resx | 8 +- ...deTreeModel.cs => ElementNodeTreeModel.cs} | 6 +- .../Operation/SourceOperation.cs | 2 +- .../ProjectSystem/{Layer.cs => Element.cs} | 76 ++--- .../ProjectSystem/Elements.cs | 12 + .../ProjectSystem/Layers.cs | 12 - .../ProjectSystem/PreviewOptions.cs | 21 -- .../ProjectSystem/Scene.cs | 282 +++++++++--------- src/Beutl.ProjectSystem/SceneRenderer.cs | 26 +- src/Beutl/Beutl.csproj | 21 -- src/Beutl/Models/Constants.cs | 4 +- ...erDescription.cs => ElementDescription.cs} | 2 +- ...wModel.cs => AddElementDialogViewModel.cs} | 16 +- src/Beutl/ViewModels/EditViewModel.cs | 6 +- .../ViewModels/Editors/BaseEditorViewModel.cs | 6 +- ...eLayerViewModel.cs => ElementViewModel.cs} | 32 +- src/Beutl/ViewModels/GraphEditorViewModel.cs | 6 +- .../InlineAnimationLayerViewModel.cs | 6 +- src/Beutl/ViewModels/LayerHeaderViewModel.cs | 4 +- .../NodeTree/NodeTreeInputTabViewModel.cs | 8 +- .../NodeTree/NodeTreeInputViewModel.cs | 8 +- .../NodeTree/NodeTreeTabViewModel.cs | 4 +- src/Beutl/ViewModels/TimelineViewModel.cs | 26 +- .../Tools/SourceOperatorsTabViewModel.cs | 14 +- ...{AddLayer.axaml => AddElementDialog.axaml} | 18 +- ...yer.axaml.cs => AddElementDialog.axaml.cs} | 6 +- .../Views/Editors/PropertyEditorMenu.axaml.cs | 4 +- ...{TimelineLayer.axaml => ElementView.axaml} | 4 +- ...ineLayer.axaml.cs => ElementView.axaml.cs} | 98 +++--- src/Beutl/Views/LayerHeader.axaml.cs | 2 +- src/Beutl/Views/MainView.axaml | 4 +- src/Beutl/Views/MainView.axaml.cs | 18 +- src/Beutl/Views/Timeline.axaml | 2 +- src/Beutl/Views/Timeline.axaml.cs | 56 ++-- .../Views/Tools/SourceOperatorView.axaml.cs | 4 +- .../Views/Tools/SourceOperatorsTab.axaml.cs | 2 +- 39 files changed, 403 insertions(+), 462 deletions(-) rename src/Beutl.ProjectSystem/NodeTree/{LayerNodeTreeModel.cs => ElementNodeTreeModel.cs} (96%) rename src/Beutl.ProjectSystem/ProjectSystem/{Layer.cs => Element.cs} (83%) create mode 100644 src/Beutl.ProjectSystem/ProjectSystem/Elements.cs delete mode 100644 src/Beutl.ProjectSystem/ProjectSystem/Layers.cs delete mode 100644 src/Beutl.ProjectSystem/ProjectSystem/PreviewOptions.cs rename src/Beutl/Models/{LayerDescription.cs => ElementDescription.cs} (82%) rename src/Beutl/ViewModels/Dialogs/{AddLayerViewModel.cs => AddElementDialogViewModel.cs} (86%) rename src/Beutl/ViewModels/{TimelineLayerViewModel.cs => ElementViewModel.cs} (91%) rename src/Beutl/Views/Dialogs/{AddLayer.axaml => AddElementDialog.axaml} (80%) rename src/Beutl/Views/Dialogs/{AddLayer.axaml.cs => AddElementDialog.axaml.cs} (90%) rename src/Beutl/Views/{TimelineLayer.axaml => ElementView.axaml} (98%) rename src/Beutl/Views/{TimelineLayer.axaml.cs => ElementView.axaml.cs} (86%) diff --git a/build/props/Avalonia.props b/build/props/Avalonia.props index fc18011e4..c5e7fa805 100644 --- a/build/props/Avalonia.props +++ b/build/props/Avalonia.props @@ -2,6 +2,7 @@ + diff --git a/src/Beutl.Language/Strings.Designer.cs b/src/Beutl.Language/Strings.Designer.cs index 38c165e56..7f9ab69b3 100644 --- a/src/Beutl.Language/Strings.Designer.cs +++ b/src/Beutl.Language/Strings.Designer.cs @@ -79,20 +79,20 @@ public static string Add { } /// - /// Additional Options に類似しているローカライズされた文字列を検索します。 + /// Add Element に類似しているローカライズされた文字列を検索します。 /// - public static string AdditionalOptions { + public static string AddElement { get { - return ResourceManager.GetString("AdditionalOptions", resourceCulture); + return ResourceManager.GetString("AddElement", resourceCulture); } } /// - /// Add Layer に類似しているローカライズされた文字列を検索します。 + /// Additional Options に類似しているローカライズされた文字列を検索します。 /// - public static string AddLayer { + public static string AdditionalOptions { get { - return ResourceManager.GetString("AddLayer", resourceCulture); + return ResourceManager.GetString("AdditionalOptions", resourceCulture); } } @@ -664,6 +664,15 @@ public static string Editors { } } + /// + /// Element に類似しているローカライズされた文字列を検索します。 + /// + public static string Element { + get { + return ResourceManager.GetString("Element", resourceCulture); + } + } + /// /// Ellipse に類似しているローカライズされた文字列を検索します。 /// @@ -1015,15 +1024,6 @@ public static string Language { } } - /// - /// Layer に類似しているローカライズされた文字列を検索します。 - /// - public static string Layer { - get { - return ResourceManager.GetString("Layer", resourceCulture); - } - } - /// /// Left に類似しているローカライズされた文字列を検索します。 /// diff --git a/src/Beutl.Language/Strings.ja.resx b/src/Beutl.Language/Strings.ja.resx index 98eaf47d8..3cc57f2a7 100644 --- a/src/Beutl.Language/Strings.ja.resx +++ b/src/Beutl.Language/Strings.ja.resx @@ -129,8 +129,8 @@ 追加 - - レイヤーを追加 + + 要素を追加 適用 @@ -237,8 +237,8 @@ 情報 - - レイヤー + + 要素 diff --git a/src/Beutl.Language/Strings.resx b/src/Beutl.Language/Strings.resx index d95bd90cf..2f0e855bc 100644 --- a/src/Beutl.Language/Strings.resx +++ b/src/Beutl.Language/Strings.resx @@ -282,11 +282,11 @@ Color - - Layer + + Element - - Add Layer + + Add Element Location diff --git a/src/Beutl.ProjectSystem/NodeTree/LayerNodeTreeModel.cs b/src/Beutl.ProjectSystem/NodeTree/ElementNodeTreeModel.cs similarity index 96% rename from src/Beutl.ProjectSystem/NodeTree/LayerNodeTreeModel.cs rename to src/Beutl.ProjectSystem/NodeTree/ElementNodeTreeModel.cs index 6be69ea3f..b001faf40 100644 --- a/src/Beutl.ProjectSystem/NodeTree/LayerNodeTreeModel.cs +++ b/src/Beutl.ProjectSystem/NodeTree/ElementNodeTreeModel.cs @@ -9,13 +9,13 @@ namespace Beutl.NodeTree; -public class LayerNodeTreeModel : NodeTreeModel +public class ElementNodeTreeModel : NodeTreeModel { // 評価する順番 private readonly List _evalContexts = new(); private bool _isDirty = true; - public LayerNodeTreeModel() + public ElementNodeTreeModel() { Nodes.Attached += OnNodeAttached; Nodes.Detached += OnNodeDetached; @@ -46,7 +46,7 @@ private void OnNodeDetached(Node obj) obj.Invalidated -= OnNodeInvalidated; } - public void Evaluate(IRenderer renderer, Layer layer) + public void Evaluate(IRenderer renderer, Element layer) { Build(renderer, layer.Clock); using var list = new PooledList(); diff --git a/src/Beutl.ProjectSystem/Operation/SourceOperation.cs b/src/Beutl.ProjectSystem/Operation/SourceOperation.cs index 3c63a2167..96457bb4e 100644 --- a/src/Beutl.ProjectSystem/Operation/SourceOperation.cs +++ b/src/Beutl.ProjectSystem/Operation/SourceOperation.cs @@ -77,7 +77,7 @@ public override void WriteToJson(JsonObject json) } } - public void Evaluate(IRenderer renderer, Layer layer, IList unhandled) + public void Evaluate(IRenderer renderer, Element layer, IList unhandled) { void Detach(IList renderables) { diff --git a/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs b/src/Beutl.ProjectSystem/ProjectSystem/Element.cs similarity index 83% rename from src/Beutl.ProjectSystem/ProjectSystem/Layer.cs rename to src/Beutl.ProjectSystem/ProjectSystem/Element.cs index 27ea2c222..80010a6f9 100644 --- a/src/Beutl.ProjectSystem/ProjectSystem/Layer.cs +++ b/src/Beutl.ProjectSystem/ProjectSystem/Element.cs @@ -15,7 +15,7 @@ namespace Beutl.ProjectSystem; -public class Layer : ProjectItem +public class Element : ProjectItem { public static readonly CoreProperty StartProperty; public static readonly CoreProperty LengthProperty; @@ -25,7 +25,7 @@ public class Layer : ProjectItem public static readonly CoreProperty AllowOutflowProperty; public static readonly CoreProperty SpanProperty; public static readonly CoreProperty OperationProperty; - public static readonly CoreProperty NodeTreeProperty; + public static readonly CoreProperty NodeTreeProperty; public static readonly CoreProperty UseNodeProperty; private readonly InstanceClock _instanceClock = new(); private TimeSpan _start; @@ -36,54 +36,54 @@ public class Layer : ProjectItem private IDisposable? _disposable; private bool _useNode; - static Layer() + static Element() { - StartProperty = ConfigureProperty(nameof(Start)) + StartProperty = ConfigureProperty(nameof(Start)) .Accessor(o => o.Start, (o, v) => o.Start = v) .Register(); - LengthProperty = ConfigureProperty(nameof(Length)) + LengthProperty = ConfigureProperty(nameof(Length)) .Accessor(o => o.Length, (o, v) => o.Length = v) .Register(); - ZIndexProperty = ConfigureProperty(nameof(ZIndex)) + ZIndexProperty = ConfigureProperty(nameof(ZIndex)) .Accessor(o => o.ZIndex, (o, v) => o.ZIndex = v) .Register(); - AccentColorProperty = ConfigureProperty(nameof(AccentColor)) + AccentColorProperty = ConfigureProperty(nameof(AccentColor)) .DefaultValue(Colors.Teal) .Register(); - IsEnabledProperty = ConfigureProperty(nameof(IsEnabled)) + IsEnabledProperty = ConfigureProperty(nameof(IsEnabled)) .Accessor(o => o.IsEnabled, (o, v) => o.IsEnabled = v) .DefaultValue(true) .Register(); - AllowOutflowProperty = ConfigureProperty(nameof(AllowOutflow)) + AllowOutflowProperty = ConfigureProperty(nameof(AllowOutflow)) .Accessor(o => o.AllowOutflow, (o, v) => o.AllowOutflow = v) .DefaultValue(false) .Register(); - SpanProperty = ConfigureProperty(nameof(Span)) + SpanProperty = ConfigureProperty(nameof(Span)) .Accessor(o => o.Span, null) .Register(); - OperationProperty = ConfigureProperty(nameof(Operation)) + OperationProperty = ConfigureProperty(nameof(Operation)) .Accessor(o => o.Operation, null) .Register(); - NodeTreeProperty = ConfigureProperty(nameof(NodeTree)) + NodeTreeProperty = ConfigureProperty(nameof(NodeTree)) .Accessor(o => o.NodeTree, null) .Register(); - UseNodeProperty = ConfigureProperty(nameof(UseNode)) + UseNodeProperty = ConfigureProperty(nameof(UseNode)) .Accessor(o => o.UseNode, (o, v) => o.UseNode = v) .DefaultValue(false) .Register(); ZIndexProperty.Changed.Subscribe(args => { - if (args.Sender is Layer layer && layer.HierarchicalParent is Scene { Renderer: { IsDisposed: false } renderer }) + if (args.Sender is Element layer && layer.HierarchicalParent is Scene { Renderer: { IsDisposed: false } renderer }) { renderer[args.OldValue]?.RemoveSpan(layer.Span); if (args.NewValue >= 0) @@ -101,21 +101,21 @@ static Layer() IsEnabledProperty.Changed.Subscribe(args => { - if (args.Sender is Layer layer) + if (args.Sender is Element layer) { layer.ForceRender(); } }); AllowOutflowProperty.Changed.Subscribe(args => { - if (args.Sender is Layer layer) + if (args.Sender is Element layer) { layer.ForceRender(); } }); UseNodeProperty.Changed.Subscribe(args => { - if (args.Sender is Layer layer) + if (args.Sender is Element layer) { layer.ForceRender(); } @@ -123,7 +123,7 @@ static Layer() StartProperty.Changed.Subscribe(e => { - if (e.Sender is Layer layer) + if (e.Sender is Element layer) { layer.Span.Start = e.NewValue; } @@ -131,21 +131,21 @@ static Layer() LengthProperty.Changed.Subscribe(e => { - if (e.Sender is Layer layer) + if (e.Sender is Element layer) { layer.Span.Duration = e.NewValue; } }); } - public Layer() + public Element() { Operation = new SourceOperation(); Operation.Invalidated += (_, _) => ForceRender(); #if DEBUG Operation.Children.CollectionChanged += OnOperatorsCollectionChanged; #endif - NodeTree = new LayerNodeTreeModel(); + NodeTree = new ElementNodeTreeModel(); NodeTree.Invalidated += (_, _) => ForceRender(); HierarchicalChildren.Add(Operation); @@ -222,7 +222,7 @@ public bool AllowOutflow public SourceOperation Operation { get; } - public LayerNodeTreeModel NodeTree { get; } + public ElementNodeTreeModel NodeTree { get; } public bool UseNode { @@ -342,12 +342,12 @@ private IDisposable SubscribeToLayerNode() .Subscribe(_ => ForceRender()); } - internal Layer? GetBefore(int zindex, TimeSpan start) + internal Element? GetBefore(int zindex, TimeSpan start) { if (HierarchicalParent is Scene scene) { - Layer? tmp = null; - foreach (Layer? item in scene.Children.GetMarshal().Value) + Element? tmp = null; + foreach (Element? item in scene.Children.GetMarshal().Value) { if (item != this && item.ZIndex == zindex && item.Start < start) { @@ -363,12 +363,12 @@ private IDisposable SubscribeToLayerNode() return null; } - internal Layer? GetAfter(int zindex, TimeSpan end) + internal Element? GetAfter(int zindex, TimeSpan end) { if (HierarchicalParent is Scene scene) { - Layer? tmp = null; - foreach (Layer? item in scene.Children.GetMarshal().Value) + Element? tmp = null; + foreach (Element? item in scene.Children.GetMarshal().Value) { if (item != this && item.ZIndex == zindex && item.Range.End > end) { @@ -384,16 +384,16 @@ private IDisposable SubscribeToLayerNode() return null; } - internal (Layer? Before, Layer? After, Layer? Cover) GetBeforeAndAfterAndCover(int zindex, TimeSpan start, TimeSpan end) + internal (Element? Before, Element? After, Element? Cover) GetBeforeAndAfterAndCover(int zindex, TimeSpan start, TimeSpan end) { if (HierarchicalParent is Scene scene) { - Layer? beforeTmp = null; - Layer? afterTmp = null; - Layer? coverTmp = null; + Element? beforeTmp = null; + Element? afterTmp = null; + Element? coverTmp = null; var range = new TimeRange(start, end - start); - foreach (Layer? item in scene.Children.GetMarshal().Value) + foreach (Element? item in scene.Children.GetMarshal().Value) { if (item != this && item.ZIndex == zindex) { @@ -421,16 +421,16 @@ private IDisposable SubscribeToLayerNode() return (null, null, null); } - internal (Layer? Before, Layer? After, Layer? Cover) GetBeforeAndAfterAndCover(int zindex, TimeSpan start, Layer[] excludes) + internal (Element? Before, Element? After, Element? Cover) GetBeforeAndAfterAndCover(int zindex, TimeSpan start, Element[] excludes) { if (HierarchicalParent is Scene scene) { - Layer? beforeTmp = null; - Layer? afterTmp = null; - Layer? coverTmp = null; + Element? beforeTmp = null; + Element? afterTmp = null; + Element? coverTmp = null; var range = new TimeRange(start, Length); - foreach (Layer? item in scene.Children.Except(excludes)) + foreach (Element? item in scene.Children.Except(excludes)) { if (item != this && item.ZIndex == zindex) { diff --git a/src/Beutl.ProjectSystem/ProjectSystem/Elements.cs b/src/Beutl.ProjectSystem/ProjectSystem/Elements.cs new file mode 100644 index 000000000..d71da5751 --- /dev/null +++ b/src/Beutl.ProjectSystem/ProjectSystem/Elements.cs @@ -0,0 +1,12 @@ + +using Beutl.Collections; + +namespace Beutl.ProjectSystem; + +public sealed class Elements : HierarchicalList +{ + public Elements(IModifiableHierarchical parent) + : base(parent) + { + } +} diff --git a/src/Beutl.ProjectSystem/ProjectSystem/Layers.cs b/src/Beutl.ProjectSystem/ProjectSystem/Layers.cs deleted file mode 100644 index 8dbd1f98f..000000000 --- a/src/Beutl.ProjectSystem/ProjectSystem/Layers.cs +++ /dev/null @@ -1,12 +0,0 @@ - -using Beutl.Collections; - -namespace Beutl.ProjectSystem; - -public sealed class Layers : HierarchicalList -{ - public Layers(IModifiableHierarchical parent) - : base(parent) - { - } -} diff --git a/src/Beutl.ProjectSystem/ProjectSystem/PreviewOptions.cs b/src/Beutl.ProjectSystem/ProjectSystem/PreviewOptions.cs deleted file mode 100644 index 06c352d5f..000000000 --- a/src/Beutl.ProjectSystem/ProjectSystem/PreviewOptions.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Beutl.ProjectSystem; - -public enum PreviewMode -{ - Default = 0, - Memory = 1, - Storage = 2, -} - -public record PreviewOptions -{ - public PreviewMode PreviewMode { get; init; } - - public bool LowColor { get; init; } - - public bool LowResolution { get; init; } - - public int Start { get; init; } - - public int Length { get; init; } -} \ No newline at end of file diff --git a/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs b/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs index 007e8e025..9acb38915 100644 --- a/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs +++ b/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs @@ -17,20 +17,18 @@ public class Scene : ProjectItem { public static readonly CoreProperty WidthProperty; public static readonly CoreProperty HeightProperty; - public static readonly CoreProperty ChildrenProperty; + public static readonly CoreProperty ChildrenProperty; public static readonly CoreProperty DurationProperty; public static readonly CoreProperty CurrentFrameProperty; - public static readonly CoreProperty PreviewOptionsProperty; public static readonly CoreProperty RendererProperty; - private readonly List _includeLayers = new() + private readonly List _includeElements = new() { - "**/*.layer" + "**/*.belm" }; - private readonly List _excludeLayers = new(); - private readonly Layers _children; + private readonly List _excludeElements = new(); + private readonly Elements _children; private TimeSpan _duration = TimeSpan.FromMinutes(5); private TimeSpan _currentFrame; - private PreviewOptions? _previewOptions; private IRenderer _renderer; public Scene() @@ -40,7 +38,7 @@ public Scene() public Scene(int width, int height, string name) { - _children = new Layers(this); + _children = new Elements(this); _children.CollectionChanged += Children_CollectionChanged; Initialize(width, height); Name = name; @@ -56,7 +54,7 @@ static Scene() .Accessor(o => o.Height) .Register(); - ChildrenProperty = ConfigureProperty(nameof(Children)) + ChildrenProperty = ConfigureProperty(nameof(Children)) .Accessor(o => o.Children, (o, v) => o.Children = v) .Register(); @@ -68,10 +66,6 @@ static Scene() .Accessor(o => o.CurrentFrame, (o, v) => o.CurrentFrame = v) .Register(); - PreviewOptionsProperty = ConfigureProperty(nameof(PreviewOptions)) - .Accessor(o => o.PreviewOptions, (o, v) => o.PreviewOptions = v) - .Register(); - RendererProperty = ConfigureProperty(nameof(Renderer)) .Accessor(o => o.Renderer, (o, v) => o.Renderer = v) .Register(); @@ -117,19 +111,12 @@ public TimeSpan CurrentFrame } [NotAutoSerialized] - public Layers Children + public Elements Children { get => _children; set => _children.Replace(value); } - [NotAutoSerialized] - public PreviewOptions? PreviewOptions - { - get => _previewOptions; - set => SetAndRaise(PreviewOptionsProperty, ref _previewOptions, value); - } - [NotAutoSerialized] public IRenderer Renderer { @@ -137,7 +124,7 @@ public IRenderer Renderer private set => SetAndRaise(RendererProperty, ref _renderer, value); } - [MemberNotNull("_renderer")] + [MemberNotNull(nameof(_renderer))] public void Initialize(int width, int height) { PixelSize oldSize = _renderer?.Graphics?.Size ?? PixelSize.Empty; @@ -145,7 +132,7 @@ public void Initialize(int width, int height) Renderer = new SceneRenderer(this, width, height); _renderer = Renderer; - foreach (Layer item in _children.GetMarshal().Value) + foreach (Element item in _children.GetMarshal().Value) { IRenderLayer? context = _renderer[item.ZIndex]; if (context == null) @@ -171,27 +158,27 @@ public void Initialize(int width, int height) oldValue: oldSize.Height)); } - // layer.FileNameが既に設定されている状態 - public IRecordableCommand AddChild(Layer layer) + // element.FileNameが既に設定されている状態 + public IRecordableCommand AddChild(Element element) { - ArgumentNullException.ThrowIfNull(layer); - layer.ZIndex = NearestLayerNumber(layer); + ArgumentNullException.ThrowIfNull(element); + element.ZIndex = NearestLayerNumber(element); - return new AddCommand(this, layer); + return new AddCommand(this, element); } - public IRecordableCommand RemoveChild(Layer layer) + public IRecordableCommand RemoveChild(Element element) { - ArgumentNullException.ThrowIfNull(layer); + ArgumentNullException.ThrowIfNull(element); - return new RemoveCommand(this, layer); + return new RemoveCommand(this, element); } #pragma warning disable CA1822 - public IRecordableCommand MoveChild(int layerNum, TimeSpan start, TimeSpan length, Layer layer) + public IRecordableCommand MoveChild(int zIndex, TimeSpan start, TimeSpan length, Element element) #pragma warning restore CA1822 { - ArgumentNullException.ThrowIfNull(layer); + ArgumentNullException.ThrowIfNull(element); if (start < TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(start)); @@ -199,17 +186,17 @@ public IRecordableCommand MoveChild(int layerNum, TimeSpan start, TimeSpan lengt if (length <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(length)); - return new MoveCommand(layerNum, layer, start, layer.Start, length, layer.Length); + return new MoveCommand(zIndex, element, start, element.Start, length, element.Length); } - public IRecordableCommand MoveChildren(int deltaIndex, TimeSpan deltaStart, Layer[] layers) + public IRecordableCommand MoveChildren(int deltaIndex, TimeSpan deltaStart, Element[] elements) { - if (layers.Length < 2) + if (elements.Length < 2) { - throw new ArgumentOutOfRangeException(nameof(layers)); + throw new ArgumentOutOfRangeException(nameof(elements)); } - return new MultipleMoveCommand(this, layers, deltaIndex, deltaStart); + return new MultipleMoveCommand(this, elements, deltaIndex, deltaStart); } public override void ReadFromJson(JsonObject json) @@ -248,26 +235,26 @@ static void Process(Func add, JsonNode node, List list) Initialize(width, height); } - if (json.TryGetPropertyValue(nameof(Layers), out JsonNode? layersNode) - && layersNode is JsonObject layersJson) + if (json.TryGetPropertyValue(nameof(Elements), out JsonNode? elementsNode) + && elementsNode is JsonObject elementsJson) { var matcher = new Matcher(); var directory = new DirectoryInfoWrapper(new DirectoryInfo(Path.GetDirectoryName(FileName)!)); // 含めるクリップ - if (layersJson.TryGetPropertyValue("Include", out JsonNode? includeNode)) + if (elementsJson.TryGetPropertyValue("Include", out JsonNode? includeNode)) { - Process(matcher.AddInclude, includeNode!, _includeLayers); + Process(matcher.AddInclude, includeNode!, _includeElements); } // 除外するクリップ - if (layersJson.TryGetPropertyValue("Exclude", out JsonNode? excludeNode)) + if (elementsJson.TryGetPropertyValue("Exclude", out JsonNode? excludeNode)) { - Process(matcher.AddExclude, excludeNode!, _excludeLayers); + Process(matcher.AddExclude, excludeNode!, _excludeElements); } PatternMatchingResult result = matcher.Execute(directory); - SyncronizeLayers(result.Files.Select(x => x.Path)); + SyncronizeFiles(result.Files.Select(x => x.Path)); } else { @@ -306,14 +293,14 @@ static void Process(JsonObject jobject, string jsonName, List list) json[nameof(Height)] = _renderer.Graphics.Size.Height; } - var layersNode = new JsonObject(); + var elementsNode = new JsonObject(); UpdateInclude(); - Process(layersNode, "Include", _includeLayers); - Process(layersNode, "Exclude", _excludeLayers); + Process(elementsNode, "Include", _includeElements); + Process(elementsNode, "Exclude", _excludeElements); - json["Layers"] = layersNode; + json["Elements"] = elementsNode; } protected override void SaveCore(string filename) @@ -333,27 +320,27 @@ protected override void RestoreCore(string filename) this.JsonRestore(filename); } - private void SyncronizeLayers(IEnumerable pathToLayer) + private void SyncronizeFiles(IEnumerable pathToElement) { string baseDir = Path.GetDirectoryName(FileName)!; - pathToLayer = pathToLayer.Select(x => Path.GetFullPath(x, baseDir)).ToArray(); + pathToElement = pathToElement.Select(x => Path.GetFullPath(x, baseDir)).ToArray(); - // 削除するLayers - IEnumerable toRemoveLayers = Children.ExceptBy(pathToLayer, x => x.FileName); - // 追加するLayers - IEnumerable toAddLayers = pathToLayer.Except(Children.Select(x => x.FileName)); + // 削除するElements + IEnumerable toBeRemoved = Children.ExceptBy(pathToElement, x => x.FileName); + // 追加するElements + IEnumerable toBeAdded = pathToElement.Except(Children.Select(x => x.FileName)); - foreach (Layer item in toRemoveLayers) + foreach (Element item in toBeRemoved) { Children.Remove(item); } - foreach (string item in toAddLayers) + foreach (string item in toBeAdded) { - var layer = new Layer(); - layer.Restore(item); + var element = new Element(); + element.Restore(item); - Children.Add(layer); + Children.Add(element); } } @@ -363,18 +350,18 @@ private void UpdateInclude() var directory = new DirectoryInfoWrapper(new DirectoryInfo(dirPath)); var matcher = new Matcher(); - matcher.AddIncludePatterns(_includeLayers); - matcher.AddExcludePatterns(_excludeLayers); + matcher.AddIncludePatterns(_includeElements); + matcher.AddExcludePatterns(_excludeElements); string[] files = matcher.Execute(directory).Files.Select(x => x.Path).ToArray(); - foreach (Layer item in Children.GetMarshal().Value) + foreach (Element item in Children.GetMarshal().Value) { string rel = Path.GetRelativePath(dirPath, item.FileName).Replace('\\', '/'); // 含まれていない場合追加 if (!files.Contains(rel)) { - _includeLayers.Add(rel); + _includeElements.Add(rel); } } } @@ -385,24 +372,24 @@ private void Children_CollectionChanged(object? sender, NotifyCollectionChangedE e.OldItems != null) { string dirPath = Path.GetDirectoryName(FileName)!; - foreach (Layer item in e.OldItems.OfType()) + foreach (Element item in e.OldItems.OfType()) { string rel = Path.GetRelativePath(dirPath, item.FileName).Replace('\\', '/'); - if (!_excludeLayers.Contains(rel) && File.Exists(item.FileName)) + if (!_excludeElements.Contains(rel) && File.Exists(item.FileName)) { - _excludeLayers.Add(rel); + _excludeElements.Add(rel); } } } } - private int NearestLayerNumber(Layer layer) + private int NearestLayerNumber(Element element) { - if (Children.Any(i => !(i.ZIndex != layer.ZIndex - || i.Range.Intersects(layer.Range) - || i.Range.Contains(layer.Range) - || layer.Range.Contains(i.Range)))) + if (Children.Any(i => !(i.ZIndex != element.ZIndex + || i.Range.Intersects(element.Range) + || i.Range.Contains(element.Range) + || element.Range.Contains(i.Range)))) { int layerMax = Children.Max(i => i.ZIndex); @@ -411,10 +398,7 @@ private int NearestLayerNumber(Layer layer) for (int l = 0; l <= layerMax; l++) { - if (Children.Any(i => !(i.ZIndex != l - || i.Range.Intersects(layer.Range) - || i.Range.Contains(layer.Range) - || layer.Range.Contains(i.Range)))) + if (!Children.Any(i => i.ZIndex == l && i.Range.Intersects(element.Range))) { numbers.Add(l); } @@ -425,36 +409,36 @@ private int NearestLayerNumber(Layer layer) return layerMax + 1; } - return numbers.Nearest(layer.ZIndex); + return numbers.Nearest(element.ZIndex); } - return layer.ZIndex; + return element.ZIndex; } private sealed class AddCommand : IRecordableCommand { private readonly Scene _scene; - private readonly Layer _layer; - private readonly int _layerNum; + private readonly Element _element; + private readonly int _zIndex; - public AddCommand(Scene scene, Layer layer) + public AddCommand(Scene scene, Element element) { _scene = scene; - _layer = layer; - _layerNum = layer.ZIndex; + _element = element; + _zIndex = element.ZIndex; } - public AddCommand(Scene scene, Layer layer, int layerNum) + public AddCommand(Scene scene, Element element, int zIndex) { _scene = scene; - _layer = layer; - _layerNum = layerNum; + _element = element; + _zIndex = zIndex; } public void Do() { - _layer.ZIndex = _layerNum; - _scene.Children.Add(_layer); + _element.ZIndex = _zIndex; + _scene.Children.Add(_element); } public void Redo() @@ -464,28 +448,28 @@ public void Redo() public void Undo() { - _scene.Children.Remove(_layer); - _layer.ZIndex = -1; + _scene.Children.Remove(_element); + _element.ZIndex = -1; } } private sealed class RemoveCommand : IRecordableCommand { private readonly Scene _scene; - private readonly Layer _layer; - private int _layerNum; + private readonly Element _element; + private int _zIndex; - public RemoveCommand(Scene scene, Layer layer) + public RemoveCommand(Scene scene, Element element) { _scene = scene; - _layer = layer; + _element = element; } public void Do() { - _layerNum = _layer.ZIndex; - _scene.Children.Remove(_layer); - _layer.ZIndex = -1; + _zIndex = _element.ZIndex; + _scene.Children.Remove(_element); + _element.ZIndex = -1; } public void Redo() @@ -495,30 +479,30 @@ public void Redo() public void Undo() { - _layer.ZIndex = _layerNum; - _scene.Children.Add(_layer); + _element.ZIndex = _zIndex; + _scene.Children.Add(_element); } } private sealed class MoveCommand : IRecordableCommand { - private readonly Layer _layer; - private readonly int _layerNum; - private readonly int _oldLayerNum; + private readonly Element _element; + private readonly int _zIndex; + private readonly int _oldZIndex; private readonly TimeSpan _newStart; private readonly TimeSpan _oldStart; private readonly TimeSpan _newLength; private readonly TimeSpan _oldLength; public MoveCommand( - int layerNum, - Layer layer, + int zIndex, + Element element, TimeSpan newStart, TimeSpan oldStart, TimeSpan newLength, TimeSpan oldLength) { - _layer = layer; - _layerNum = layerNum; - _oldLayerNum = layer.ZIndex; + _element = element; + _zIndex = zIndex; + _oldZIndex = element.ZIndex; _newStart = newStart; _oldStart = oldStart; _newLength = newLength; @@ -528,15 +512,15 @@ public MoveCommand( public void Do() { TimeSpan newEnd = _newStart + _newLength; - (Layer? before, Layer? after, Layer? cover) = _layer.GetBeforeAndAfterAndCover(_layerNum, _newStart, newEnd); + (Element? before, Element? after, Element? cover) = _element.GetBeforeAndAfterAndCover(_zIndex, _newStart, newEnd); if (before != null && before.Range.End >= _newStart) { if ((after != null && (after.Start - before.Range.End) >= _newLength) || after == null) { - _layer.Start = before.Range.End; - _layer.Length = _newLength; - _layer.ZIndex = _layerNum; + _element.Start = before.Range.End; + _element.Length = _newLength; + _element.ZIndex = _zIndex; } else { @@ -548,9 +532,9 @@ public void Do() TimeSpan ns = after.Start - _newLength; if (((before != null && (after.Start - before.Range.End) >= _newLength) || before == null) && ns >= TimeSpan.Zero) { - _layer.Start = ns; - _layer.Length = _newLength; - _layer.ZIndex = _layerNum; + _element.Start = ns; + _element.Length = _newLength; + _element.ZIndex = _zIndex; } else { @@ -563,9 +547,9 @@ public void Do() } else { - _layer.Start = _newStart; - _layer.Length = _newLength; - _layer.ZIndex = _layerNum; + _element.Start = _newStart; + _element.Length = _newLength; + _element.ZIndex = _zIndex; } } @@ -576,32 +560,32 @@ public void Redo() public void Undo() { - _layer.ZIndex = _oldLayerNum; - _layer.Start = _oldStart; - _layer.Length = _oldLength; + _element.ZIndex = _oldZIndex; + _element.Start = _oldStart; + _element.Length = _oldLength; } } private sealed class MultipleMoveCommand : IRecordableCommand { - private readonly Layer[] _layers; - private readonly int _deltaLayer; + private readonly Element[] _element; + private readonly int _deltaZIndex; private readonly TimeSpan _deltaTime; private readonly bool _conflict; public MultipleMoveCommand( Scene scene, - Layer[] layers, - int deltaLayer, + Element[] elements, + int deltaZIndex, TimeSpan deltaTime) { - _layers = layers; - _deltaLayer = deltaLayer; + _element = elements; + _deltaZIndex = deltaZIndex; _deltaTime = deltaTime; - foreach (Layer item in layers) + foreach (Element item in elements) { - _conflict = HasConflict(scene, _deltaLayer, _deltaTime); + _conflict = HasConflict(scene, _deltaZIndex, _deltaTime); if (!_conflict) { break; @@ -616,20 +600,20 @@ public MultipleMoveCommand( } } - _conflict = HasConflict(scene, _deltaLayer, _deltaTime); + _conflict = HasConflict(scene, _deltaZIndex, _deltaTime); } - private bool HasConflict(Scene scene, int deltaLayer, TimeSpan deltaTime) + private bool HasConflict(Scene scene, int deltaZIndex, TimeSpan deltaTime) { - Layer[] others = scene.Children.Except(_layers).ToArray(); - foreach (Layer item in _layers) + Element[] others = scene.Children.Except(_element).ToArray(); + foreach (Element item in _element) { TimeRange newRange = item.Range.AddStart(deltaTime); - int newLayer = item.ZIndex + deltaLayer; + int newLayer = item.ZIndex + deltaZIndex; if (newLayer < 0 || newRange.Start.Ticks < 0) return true; - foreach (Layer other in others) + foreach (Element other in others) { if (other.ZIndex == newLayer && other.Range.Intersects(newRange)) { @@ -641,32 +625,32 @@ private bool HasConflict(Scene scene, int deltaLayer, TimeSpan deltaTime) return false; } - private TimeSpan? DeltaStart(Layer layer) + private TimeSpan? DeltaStart(Element element) { - TimeSpan newStart = layer.Start + _deltaTime; + TimeSpan newStart = element.Start + _deltaTime; - TimeSpan newEnd = newStart + layer.Length; - int newIndex = layer.ZIndex + _deltaLayer; - (Layer? before, Layer? after, Layer? _) = layer.GetBeforeAndAfterAndCover(newIndex, newStart, _layers); + TimeSpan newEnd = newStart + element.Length; + int newIndex = element.ZIndex + _deltaZIndex; + (Element? before, Element? after, Element? _) = element.GetBeforeAndAfterAndCover(newIndex, newStart, _element); if (before != null && before.Range.End >= newStart) { - if ((after != null && (after.Start - before.Range.End) >= layer.Length) || after == null) + if ((after != null && (after.Start - before.Range.End) >= element.Length) || after == null) { - return before.Range.End - layer.Start; + return before.Range.End - element.Start; } } else if (after != null && after.Start < newEnd) { - TimeSpan ns = after.Start - layer.Length; - if (((before != null && (after.Start - before.Range.End) >= layer.Length) || before == null) && ns >= TimeSpan.Zero) + TimeSpan ns = after.Start - element.Length; + if (((before != null && (after.Start - before.Range.End) >= element.Length) || before == null) && ns >= TimeSpan.Zero) { - return ns - layer.Start; + return ns - element.Start; } } else if (newStart.Ticks < 0) { - return -layer.Start; + return -element.Start; } return null; @@ -676,10 +660,10 @@ public void Do() { if (!_conflict) { - foreach (Layer item in _layers) + foreach (Element item in _element) { item.Start += _deltaTime; - item.ZIndex += _deltaLayer; + item.ZIndex += _deltaZIndex; } } } @@ -693,10 +677,10 @@ public void Undo() { if (!_conflict) { - foreach (Layer item in _layers) + foreach (Element item in _element) { item.Start -= _deltaTime; - item.ZIndex -= _deltaLayer; + item.ZIndex -= _deltaZIndex; } } } diff --git a/src/Beutl.ProjectSystem/SceneRenderer.cs b/src/Beutl.ProjectSystem/SceneRenderer.cs index ed11e18fc..6ae3d9469 100644 --- a/src/Beutl.ProjectSystem/SceneRenderer.cs +++ b/src/Beutl.ProjectSystem/SceneRenderer.cs @@ -12,9 +12,9 @@ internal sealed class SceneRenderer : //DeferredRenderer { private readonly Scene _scene; - private readonly List _entered = new(); - private readonly List _exited = new(); - private readonly List _layers = new(); + private readonly List _entered = new(); + private readonly List _exited = new(); + private readonly List _layers = new(); private readonly List _unhandleds = new(); private TimeSpan _recentTime = TimeSpan.MinValue; @@ -33,22 +33,22 @@ protected override void RenderGraphicsCore() var timeSpan = Clock.CurrentTime; CurrentTime = timeSpan; SortLayers(timeSpan, out _); - Span layers = CollectionsMarshal.AsSpan(_layers); - Span entered = CollectionsMarshal.AsSpan(_entered); - Span exited = CollectionsMarshal.AsSpan(_exited); + Span layers = CollectionsMarshal.AsSpan(_layers); + Span entered = CollectionsMarshal.AsSpan(_entered); + Span exited = CollectionsMarshal.AsSpan(_exited); _unhandleds.Clear(); - foreach (Layer item in exited) + foreach (Element item in exited) { ExitSourceOperators(item); } - foreach (Layer item in entered) + foreach (Element item in entered) { EnterSourceOperators(item); } - foreach (Layer layer in layers) + foreach (Element layer in layers) { layer.Evaluate(this, _unhandleds); } @@ -57,7 +57,7 @@ protected override void RenderGraphicsCore() _recentTime = timeSpan; } - private static void EnterSourceOperators(Layer layer) + private static void EnterSourceOperators(Element layer) { foreach (SourceOperator item in layer.Operation.Children.GetMarshal().Value) { @@ -65,7 +65,7 @@ private static void EnterSourceOperators(Layer layer) } } - private static void ExitSourceOperators(Layer layer) + private static void ExitSourceOperators(Element layer) { foreach (SourceOperator item in layer.Operation.Children.GetMarshal().Value) { @@ -82,7 +82,7 @@ private void SortLayers(TimeSpan timeSpan, out TimeRange enterAffectsRange) TimeSpan enterStart = TimeSpan.MaxValue; TimeSpan enterEnd = TimeSpan.Zero; - foreach (Layer? item in _scene.Children) + foreach (Element? item in _scene.Children) { bool recent = InRange(item, _recentTime); bool current = InRange(item, timeSpan); @@ -115,7 +115,7 @@ private void SortLayers(TimeSpan timeSpan, out TimeRange enterAffectsRange) // itemがtsの範囲内かを確かめます - private static bool InRange(Layer item, TimeSpan ts) + private static bool InRange(Element item, TimeSpan ts) { return item.Start <= ts && ts < item.Length + item.Start; } diff --git a/src/Beutl/Beutl.csproj b/src/Beutl/Beutl.csproj index bcd317182..745717c32 100644 --- a/src/Beutl/Beutl.csproj +++ b/src/Beutl/Beutl.csproj @@ -41,27 +41,6 @@ - - - True - True - VectorEditorViewModels.tt - - - True - True - GraphEditorViewViewModelFactory.Impl.tt - - - PropertyEditorMenu.axaml - - - SocketView.axaml - - - NodeTreeInputTab.axaml - - diff --git a/src/Beutl/Models/Constants.cs b/src/Beutl/Models/Constants.cs index d0f856911..d38f50b5f 100644 --- a/src/Beutl/Models/Constants.cs +++ b/src/Beutl/Models/Constants.cs @@ -2,8 +2,8 @@ public static class Constants { - public const string Layer = "BeutlLayerJson"; - public const string LayerFileExtension = "layer"; + public const string Element = "BeutlElementJson"; + public const string ElementFileExtension = "belm"; public const string SceneFileExtension = "scene"; public const string ProjectFileExtension = "bep"; public const string BeutlFolder = ".beutl"; diff --git a/src/Beutl/Models/LayerDescription.cs b/src/Beutl/Models/ElementDescription.cs similarity index 82% rename from src/Beutl/Models/LayerDescription.cs rename to src/Beutl/Models/ElementDescription.cs index b1a766ffa..45a336d5c 100644 --- a/src/Beutl/Models/LayerDescription.cs +++ b/src/Beutl/Models/ElementDescription.cs @@ -2,7 +2,7 @@ namespace Beutl.Models; -public record struct LayerDescription( +public record struct ElementDescription( TimeSpan Start, TimeSpan Length, int Layer, diff --git a/src/Beutl/ViewModels/Dialogs/AddLayerViewModel.cs b/src/Beutl/ViewModels/Dialogs/AddElementDialogViewModel.cs similarity index 86% rename from src/Beutl/ViewModels/Dialogs/AddLayerViewModel.cs rename to src/Beutl/ViewModels/Dialogs/AddElementDialogViewModel.cs index f0389c948..24fb3eb26 100644 --- a/src/Beutl/ViewModels/Dialogs/AddLayerViewModel.cs +++ b/src/Beutl/ViewModels/Dialogs/AddElementDialogViewModel.cs @@ -10,15 +10,15 @@ namespace Beutl.ViewModels.Dialogs; -public sealed class AddLayerViewModel +public sealed class AddElementDialogViewModel { private readonly Scene _scene; - private readonly LayerDescription _layerDescription; + private readonly ElementDescription _description; - public AddLayerViewModel(Scene scene, LayerDescription desc) + public AddElementDialogViewModel(Scene scene, ElementDescription desc) { _scene = scene; - _layerDescription = desc; + _description = desc; Color.Value = (desc.InitialOperator == null ? Colors.Teal : desc.InitialOperator.AccentColor).ToAvalonia(); Layer.Value = desc.Layer; @@ -72,19 +72,19 @@ public AddLayerViewModel(Scene scene, LayerDescription desc) Add.Subscribe(() => { - var sLayer = new Layer() + var sLayer = new Element() { Name = Name.Value, Start = Start.Value, Length = Duration.Value, ZIndex = Layer.Value, AccentColor = new(Color.Value.A, Color.Value.R, Color.Value.G, Color.Value.B), - FileName = Helper.RandomLayerFileName(Path.GetDirectoryName(_scene.FileName)!, Constants.LayerFileExtension) + FileName = Helper.RandomLayerFileName(Path.GetDirectoryName(_scene.FileName)!, Constants.ElementFileExtension) }; - if (_layerDescription.InitialOperator != null) + if (_description.InitialOperator != null) { - sLayer.Operation.AddChild((SourceOperator)Activator.CreateInstance(_layerDescription.InitialOperator.Type)!).Do(); + sLayer.Operation.AddChild((SourceOperator)Activator.CreateInstance(_description.InitialOperator.Type)!).Do(); } sLayer.Save(sLayer.FileName); diff --git a/src/Beutl/ViewModels/EditViewModel.cs b/src/Beutl/ViewModels/EditViewModel.cs index 9ab7c1be0..261cfad95 100644 --- a/src/Beutl/ViewModels/EditViewModel.cs +++ b/src/Beutl/ViewModels/EditViewModel.cs @@ -211,7 +211,7 @@ private void SaveState() string viewStateDir = ViewStateDirectory(); var json = new JsonObject { - ["selected-layer"] = (SelectedObject.Value as Layer)?.ZIndex ?? -1, + ["selected-layer"] = (SelectedObject.Value as Element)?.ZIndex ?? -1, ["max-layer-count"] = Options.Value.MaxLayerCount, ["scale"] = Options.Value.Scale, ["offset"] = new JsonObject @@ -265,7 +265,7 @@ private void RestoreState() int layer = (int?)json["selected-layer"] ?? -1; if (layer >= 0) { - foreach (Layer item in Scene.Children.GetMarshal().Value) + foreach (Element item in Scene.Children.GetMarshal().Value) { if (item.ZIndex == layer) { @@ -388,7 +388,7 @@ public KnownCommandsImpl(Scene scene) public ValueTask OnSave() { _scene.Save(_scene.FileName); - foreach (Layer layer in _scene.Children) + foreach (Element layer in _scene.Children) { layer.Save(layer.FileName); } diff --git a/src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs b/src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs index 45b484cfe..2dcf8ba6d 100644 --- a/src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs +++ b/src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs @@ -25,7 +25,7 @@ public abstract class BaseEditorViewModel : IPropertyEditorContext, IServiceProv private bool _disposedValue; private IDisposable? _currentFrameRevoker; private bool _skipKeyFrameIndexSubscription; - private Layer? _layer; + private Element? _layer; private EditViewModel? _editViewModel; private IServiceProvider? _parentServices; @@ -175,7 +175,7 @@ public virtual void Accept(IPropertyEditorContextVisitor visitor) if (visitor is IServiceProvider serviceProvider) { _parentServices = serviceProvider; - _layer = serviceProvider.GetService(); + _layer = serviceProvider.GetService(); _editViewModel = serviceProvider.GetService(); if (_editViewModel != null) @@ -317,7 +317,7 @@ public void SetValue(T? oldValue, T? newValue) private TimeSpan ConvertKeyTime(TimeSpan globalkeyTime, IAnimation animation) { - Layer? layer = this.GetService(); + Element? layer = this.GetService(); TimeSpan localKeyTime = layer != null ? globalkeyTime - layer.Start : globalkeyTime; TimeSpan keyTime = animation.UseGlobalClock ? globalkeyTime : localKeyTime; diff --git a/src/Beutl/ViewModels/TimelineLayerViewModel.cs b/src/Beutl/ViewModels/ElementViewModel.cs similarity index 91% rename from src/Beutl/ViewModels/TimelineLayerViewModel.cs rename to src/Beutl/ViewModels/ElementViewModel.cs index 9d602281f..3bff1b070 100644 --- a/src/Beutl/ViewModels/TimelineLayerViewModel.cs +++ b/src/Beutl/ViewModels/ElementViewModel.cs @@ -12,53 +12,53 @@ namespace Beutl.ViewModels; -public sealed class TimelineLayerViewModel : IDisposable +public sealed class ElementViewModel : IDisposable { private readonly CompositeDisposable _disposables = new(); - public TimelineLayerViewModel(Layer sceneLayer, TimelineViewModel timeline) + public ElementViewModel(Element element, TimelineViewModel timeline) { - Model = sceneLayer; + Model = element; Timeline = timeline; // プロパティを構成 - IsEnabled = sceneLayer.GetObservable(Layer.IsEnabledProperty) + IsEnabled = element.GetObservable(Element.IsEnabledProperty) .ToReadOnlyReactivePropertySlim() .AddTo(_disposables); - AllowOutflow = sceneLayer.GetObservable(Layer.AllowOutflowProperty) + AllowOutflow = element.GetObservable(Element.AllowOutflowProperty) .ToReadOnlyReactivePropertySlim() .AddTo(_disposables); - UseNode = sceneLayer.GetObservable(Layer.UseNodeProperty) + UseNode = element.GetObservable(Element.UseNodeProperty) .ToReadOnlyReactivePropertySlim() .AddTo(_disposables); - Name = sceneLayer.GetObservable(CoreObject.NameProperty) + Name = element.GetObservable(CoreObject.NameProperty) .ToReactiveProperty() .AddTo(_disposables)!; Name.Subscribe(v => Model.Name = v) .AddTo(_disposables); - IObservable zIndexSubject = sceneLayer.GetObservable(Layer.ZIndexProperty); + IObservable zIndexSubject = element.GetObservable(Element.ZIndexProperty); Margin = Timeline.GetTrackedLayerTopObservable(zIndexSubject) .Select(item => new Thickness(0, item, 0, 0)) .ToReactiveProperty() .AddTo(_disposables); - BorderMargin = sceneLayer.GetObservable(Layer.StartProperty) + BorderMargin = element.GetObservable(Element.StartProperty) .CombineLatest(timeline.EditorContext.Scale) .Select(item => new Thickness(item.First.ToPixel(item.Second), 0, 0, 0)) .ToReactiveProperty() .AddTo(_disposables); - Width = sceneLayer.GetObservable(Layer.LengthProperty) + Width = element.GetObservable(Element.LengthProperty) .CombineLatest(timeline.EditorContext.Scale) .Select(item => item.First.ToPixel(item.Second)) .ToReactiveProperty() .AddTo(_disposables); - Color = sceneLayer.GetObservable(Layer.AccentColorProperty) + Color = element.GetObservable(Element.AccentColorProperty) .Select(c => c.ToAvalonia()) .ToReactiveProperty() .AddTo(_disposables); @@ -104,7 +104,7 @@ public TimelineLayerViewModel(Layer sceneLayer, TimelineViewModel timeline) .AddTo(_disposables); } - ~TimelineLayerViewModel() + ~ElementViewModel() { _disposables.Dispose(); } @@ -113,7 +113,7 @@ public TimelineLayerViewModel(Layer sceneLayer, TimelineViewModel timeline) public TimelineViewModel Timeline { get; private set; } - public Layer Model { get; private set; } + public Element Model { get; private set; } public Scene Scene => (Scene)Model.HierarchicalParent!; @@ -230,7 +230,7 @@ private async ValueTask SetClipboard() string json = jsonNode.ToJsonString(JsonHelper.SerializerOptions); var data = new DataObject(); data.Set(DataFormats.Text, json); - data.Set(Constants.Layer, json); + data.Set(Constants.Element, json); await clipboard.SetDataObjectAsync(data); return true; @@ -308,14 +308,14 @@ private void OnSplit(TimeSpan timeSpan) var jsonNode = new JsonObject(); Model.WriteToJson(jsonNode); string json = jsonNode.ToJsonString(JsonHelper.SerializerOptions); - var backwardLayer = new Layer(); + var backwardLayer = new Element(); backwardLayer.ReadFromJson(JsonNode.Parse(json)!.AsObject()); Scene.MoveChild(Model.ZIndex, Model.Start, forwardLength, Model).DoAndRecord(CommandRecorder.Default); backwardLayer.Start = absTime; backwardLayer.Length = backwardLength; - backwardLayer.Save(Helper.RandomLayerFileName(Path.GetDirectoryName(Scene.FileName)!, Constants.LayerFileExtension)); + backwardLayer.Save(Helper.RandomLayerFileName(Path.GetDirectoryName(Scene.FileName)!, Constants.ElementFileExtension)); Scene.AddChild(backwardLayer).DoAndRecord(CommandRecorder.Default); } diff --git a/src/Beutl/ViewModels/GraphEditorViewModel.cs b/src/Beutl/ViewModels/GraphEditorViewModel.cs index 0c917d9d9..984ee49a0 100644 --- a/src/Beutl/ViewModels/GraphEditorViewModel.cs +++ b/src/Beutl/ViewModels/GraphEditorViewModel.cs @@ -17,10 +17,10 @@ public sealed class GraphEditorViewModel : IDisposable private readonly CompositeDisposable _disposables = new(); private readonly EditViewModel _editViewModel; private readonly GraphEditorViewViewModelFactory[] _factories; - private Layer? _layer; + private Element? _layer; private bool _editting; - public GraphEditorViewModel(EditViewModel editViewModel, IKeyFrameAnimation animation, Layer? layer) + public GraphEditorViewModel(EditViewModel editViewModel, IKeyFrameAnimation animation, Element? layer) { _editViewModel = editViewModel; _layer = layer; @@ -31,7 +31,7 @@ public GraphEditorViewModel(EditViewModel editViewModel, IKeyFrameAnimation anim .DisposeWith(_disposables); Margin = UseGlobalClock.Select(v => !v - ? _layer?.GetObservable(Layer.StartProperty) + ? _layer?.GetObservable(Element.StartProperty) .CombineLatest(Options) .Select(item => new Thickness(item.First.ToPixel(item.Second.Scale), 0, 0, 0)) : null) diff --git a/src/Beutl/ViewModels/InlineAnimationLayerViewModel.cs b/src/Beutl/ViewModels/InlineAnimationLayerViewModel.cs index ef574fad5..c2b55e39c 100644 --- a/src/Beutl/ViewModels/InlineAnimationLayerViewModel.cs +++ b/src/Beutl/ViewModels/InlineAnimationLayerViewModel.cs @@ -11,7 +11,7 @@ namespace Beutl.ViewModels; public sealed class InlineAnimationLayerViewModel : InlineAnimationLayerViewModel { - public InlineAnimationLayerViewModel(IAbstractAnimatableProperty property, TimelineViewModel timeline, TimelineLayerViewModel layer) + public InlineAnimationLayerViewModel(IAbstractAnimatableProperty property, TimelineViewModel timeline, ElementViewModel layer) : base(property, timeline, layer) { } @@ -84,7 +84,7 @@ public abstract class InlineAnimationLayerViewModel : IDisposable protected InlineAnimationLayerViewModel( IAbstractAnimatableProperty property, TimelineViewModel timeline, - TimelineLayerViewModel layer) + ElementViewModel layer) { Property = property; Timeline = timeline; @@ -155,7 +155,7 @@ protected InlineAnimationLayerViewModel( public TimelineViewModel Timeline { get; } - public TimelineLayerViewModel Layer { get; } + public ElementViewModel Layer { get; } public CoreList Items { get; } = new(); diff --git a/src/Beutl/ViewModels/LayerHeaderViewModel.cs b/src/Beutl/ViewModels/LayerHeaderViewModel.cs index 32afef327..832f13816 100644 --- a/src/Beutl/ViewModels/LayerHeaderViewModel.cs +++ b/src/Beutl/ViewModels/LayerHeaderViewModel.cs @@ -27,11 +27,11 @@ public LayerHeaderViewModel(int num, TimelineViewModel timeline) IsEnabled.Subscribe(b => { IRecordableCommand? command = null; - foreach (Layer? item in Timeline.Scene.Children.Where(i => i.ZIndex == Number.Value)) + foreach (Element? item in Timeline.Scene.Children.Where(i => i.ZIndex == Number.Value)) { if (item.IsEnabled != b) { - var command2 = new ChangePropertyCommand(item, Layer.IsEnabledProperty, b, item.IsEnabled); + var command2 = new ChangePropertyCommand(item, Element.IsEnabledProperty, b, item.IsEnabled); if (command == null) { command = command2; diff --git a/src/Beutl/ViewModels/NodeTree/NodeTreeInputTabViewModel.cs b/src/Beutl/ViewModels/NodeTree/NodeTreeInputTabViewModel.cs index c767825f7..162f962d0 100644 --- a/src/Beutl/ViewModels/NodeTree/NodeTreeInputTabViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/NodeTreeInputTabViewModel.cs @@ -17,7 +17,7 @@ public NodeTreeInputTabViewModel(EditViewModel editViewModel) { _editViewModel = editViewModel; Layer = editViewModel.SelectedObject - .Select(x => x as Layer) + .Select(x => x as Element) .ToReactiveProperty() .DisposeWith(_disposables); @@ -26,7 +26,7 @@ public NodeTreeInputTabViewModel(EditViewModel editViewModel) { InnerViewModel.Value?.Dispose(); InnerViewModel.Value = null; - if (obj.NewValue is Layer newValue) + if (obj.NewValue is Element newValue) { InnerViewModel.Value = new NodeTreeInputViewModel(newValue, this); } @@ -42,7 +42,7 @@ public NodeTreeInputTabViewModel(EditViewModel editViewModel) public ToolTabExtension.TabPlacement Placement => ToolTabExtension.TabPlacement.Right; - public ReactiveProperty Layer { get; } + public ReactiveProperty Layer { get; } public ReactivePropertySlim InnerViewModel { get; } = new(); @@ -58,7 +58,7 @@ public void Dispose() public object? GetService(Type serviceType) { - if (serviceType == typeof(Layer)) + if (serviceType == typeof(Element)) return Layer.Value; return _editViewModel.GetService(serviceType); diff --git a/src/Beutl/ViewModels/NodeTree/NodeTreeInputViewModel.cs b/src/Beutl/ViewModels/NodeTree/NodeTreeInputViewModel.cs index cc4f51b25..529f74ec1 100644 --- a/src/Beutl/ViewModels/NodeTree/NodeTreeInputViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/NodeTreeInputViewModel.cs @@ -12,18 +12,18 @@ public sealed class NodeTreeInputViewModel : IDisposable, IServiceProvider private readonly CompositeDisposable _disposables = new(); private NodeTreeInputTabViewModel _parent; - public NodeTreeInputViewModel(Layer layer, NodeTreeInputTabViewModel parent) + public NodeTreeInputViewModel(Element layer, NodeTreeInputTabViewModel parent) { Model = layer; _parent = parent; - UseNode = layer.GetObservable(Layer.UseNodeProperty) + UseNode = layer.GetObservable(Element.UseNodeProperty) .ToReactiveProperty() .DisposeWith(_disposables); UseNode.Skip(1) //.ObserveOnRendererThread() - .Subscribe(v => new ChangePropertyCommand(Model, Layer.UseNodeProperty, v, !v) + .Subscribe(v => new ChangePropertyCommand(Model, Element.UseNodeProperty, v, !v) .DoAndRecord(CommandRecorder.Default)) .DisposeWith(_disposables); @@ -68,7 +68,7 @@ public NodeTreeInputViewModel(Layer layer, NodeTreeInputTabViewModel parent) .DisposeWith(_disposables); } - public Layer Model { get; } + public Element Model { get; } public ReactiveProperty UseNode { get; } diff --git a/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs b/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs index e9724aecc..9a4ab48b1 100644 --- a/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs @@ -94,7 +94,7 @@ public NodeTreeTabViewModel(EditViewModel editViewModel) public ToolTabExtension.TabPlacement Placement => ToolTabExtension.TabPlacement.Bottom; - public ReactivePropertySlim Layer { get; } = new(); + public ReactivePropertySlim Layer { get; } = new(); public ReactivePropertySlim NodeTree { get; } = new(); @@ -218,7 +218,7 @@ public void WriteToJson(JsonObject json) public object? GetService(Type serviceType) { - if (serviceType == typeof(Layer)) + if (serviceType == typeof(Element)) return Layer.Value; return _editViewModel.GetService(serviceType); diff --git a/src/Beutl/ViewModels/TimelineViewModel.cs b/src/Beutl/ViewModels/TimelineViewModel.cs index 46d9c25ab..b2901ca07 100644 --- a/src/Beutl/ViewModels/TimelineViewModel.cs +++ b/src/Beutl/ViewModels/TimelineViewModel.cs @@ -56,12 +56,12 @@ public TimelineViewModel(EditViewModel editViewModel) AddLayer.Subscribe(item => { - var sLayer = new Layer() + var sLayer = new Element() { Start = item.Start, Length = item.Length, ZIndex = item.Layer, - FileName = Helper.RandomLayerFileName(Path.GetDirectoryName(Scene.FileName)!, Constants.LayerFileExtension) + FileName = Helper.RandomLayerFileName(Path.GetDirectoryName(Scene.FileName)!, Constants.ElementFileExtension) }; if (item.InitialOperator != null) @@ -76,10 +76,10 @@ public TimelineViewModel(EditViewModel editViewModel) LayerHeaders.AddRange(Enumerable.Range(0, 100).Select(num => new LayerHeaderViewModel(num, this))); Scene.Children.ForEachItem( - (idx, item) => Layers.Insert(idx, new TimelineLayerViewModel(item, this)), + (idx, item) => Layers.Insert(idx, new ElementViewModel(item, this)), (idx, _) => { - TimelineLayerViewModel layer = Layers[idx]; + ElementViewModel layer = Layers[idx]; foreach (InlineAnimationLayerViewModel item in Inlines.Where(x => x.Layer == layer).ToArray()) { DetachInline(item); @@ -90,7 +90,7 @@ public TimelineViewModel(EditViewModel editViewModel) }, () => { - foreach (TimelineLayerViewModel? item in Layers.GetMarshal().Value) + foreach (ElementViewModel? item in Layers.GetMarshal().Value) { item.Dispose(); } @@ -113,9 +113,9 @@ public TimelineViewModel(EditViewModel editViewModel) public ReadOnlyReactivePropertySlim EndingBarMargin { get; } - public ReactiveCommand AddLayer { get; } = new(); + public ReactiveCommand AddLayer { get; } = new(); - public CoreList Layers { get; } = new(); + public CoreList Layers { get; } = new(); public CoreList Inlines { get; } = new(); @@ -142,7 +142,7 @@ public TimelineViewModel(EditViewModel editViewModel) public void Dispose() { _disposables.Dispose(); - foreach (TimelineLayerViewModel? item in Layers.GetMarshal().Value) + foreach (ElementViewModel? item in Layers.GetMarshal().Value) { item.Dispose(); } @@ -173,7 +173,7 @@ public void WriteToJson(JsonObject json) { } - public void AttachInline(IAbstractAnimatableProperty property, Layer layer) + public void AttachInline(IAbstractAnimatableProperty property, Element layer) { if (!Inlines.Any(x => x.Layer.Model == layer && x.Property == property) && Layers.FirstOrDefault(x => x.Model == layer) is { } viewModel) @@ -248,9 +248,9 @@ public int ToLayerNumber(Thickness thickness) return -1; } - public bool AnySelected(TimelineLayerViewModel? exclude = null) + public bool AnySelected(ElementViewModel? exclude = null) { - foreach (TimelineLayerViewModel item in Layers) + foreach (ElementViewModel item in Layers) { if ((exclude == null || exclude != item) && item.IsSelected.Value) { @@ -261,9 +261,9 @@ public bool AnySelected(TimelineLayerViewModel? exclude = null) return false; } - public IEnumerable GetSelected(TimelineLayerViewModel? exclude = null) + public IEnumerable GetSelected(ElementViewModel? exclude = null) { - foreach (TimelineLayerViewModel item in Layers) + foreach (ElementViewModel item in Layers) { if ((exclude == null || exclude != item) && item.IsSelected.Value) { diff --git a/src/Beutl/ViewModels/Tools/SourceOperatorsTabViewModel.cs b/src/Beutl/ViewModels/Tools/SourceOperatorsTabViewModel.cs index c565ecaf9..876e86a39 100644 --- a/src/Beutl/ViewModels/Tools/SourceOperatorsTabViewModel.cs +++ b/src/Beutl/ViewModels/Tools/SourceOperatorsTabViewModel.cs @@ -18,13 +18,13 @@ public sealed class SourceOperatorsTabViewModel : IToolContext private readonly IDisposable _disposable0; private EditViewModel _editViewModel; private IDisposable? _disposable1; - private Layer? _oldLayer; + private Element? _oldLayer; public SourceOperatorsTabViewModel(EditViewModel editViewModel) { _editViewModel = editViewModel; Layer = editViewModel.SelectedObject - .Select(x => x as Layer) + .Select(x => x as Element) .ToReactiveProperty(); _disposable0 = Layer.Subscribe(layer => @@ -111,7 +111,7 @@ static void RemoveItems(CoreList items, int index, int public Action? RequestScroll { get; set; } - public ReactiveProperty Layer { get; } + public ReactiveProperty Layer { get; } public CoreList Items { get; } = new(); @@ -141,7 +141,7 @@ public void Dispose() RequestScroll = null; } - private static string ViewStateDirectory(Layer layer) + private static string ViewStateDirectory(Element layer) { string directory = Path.GetDirectoryName(layer.FileName)!; @@ -154,7 +154,7 @@ private static string ViewStateDirectory(Layer layer) return directory; } - private void SaveState(Layer layer) + private void SaveState(Element layer) { string viewStateDir = ViewStateDirectory(layer); var json = new JsonArray(); @@ -166,7 +166,7 @@ private void SaveState(Layer layer) json.JsonSave(Path.Combine(viewStateDir, $"{Path.GetFileNameWithoutExtension(layer.FileName)}.operators.config")); } - private void RestoreState(Layer layer) + private void RestoreState(Element layer) { string viewStateDir = ViewStateDirectory(layer); string viewStateFile = Path.Combine(viewStateDir, $"{Path.GetFileNameWithoutExtension(layer.FileName)}.operators.config"); @@ -207,7 +207,7 @@ public void WriteToJson(JsonObject json) public object? GetService(Type serviceType) { - if (serviceType == typeof(Layer)) + if (serviceType == typeof(Element)) return Layer.Value; return _editViewModel.GetService(serviceType); diff --git a/src/Beutl/Views/Dialogs/AddLayer.axaml b/src/Beutl/Views/Dialogs/AddElementDialog.axaml similarity index 80% rename from src/Beutl/Views/Dialogs/AddLayer.axaml rename to src/Beutl/Views/Dialogs/AddElementDialog.axaml index 64cb1e678..739ce24fc 100644 --- a/src/Beutl/Views/Dialogs/AddLayer.axaml +++ b/src/Beutl/Views/Dialogs/AddElementDialog.axaml @@ -1,4 +1,4 @@ - - - - + - - - + - + ()); + anmTimelineViewModel.SelectedAnimation.Value = new GraphEditorViewModel(editViewModel, kfAnimation, viewModel.GetService()); editViewModel.OpenToolTab(anmTimelineViewModel); } } @@ -64,7 +64,7 @@ private void EditInlineAnimation_Click(object? sender, RoutedEventArgs e) if (DataContext is BaseEditorViewModel viewModel && viewModel.WrappedProperty is IAbstractAnimatableProperty animatableProperty && viewModel.GetService() is { } editViewModel - && viewModel.GetService() is { } layer + && viewModel.GetService() is { } layer && editViewModel.FindToolTab() is { } timeline) { if (animatableProperty.Animation is not IKeyFrameAnimation) diff --git a/src/Beutl/Views/TimelineLayer.axaml b/src/Beutl/Views/ElementView.axaml similarity index 98% rename from src/Beutl/Views/TimelineLayer.axaml rename to src/Beutl/Views/ElementView.axaml index 37cc6e60e..0089f966a 100644 --- a/src/Beutl/Views/TimelineLayer.axaml +++ b/src/Beutl/Views/ElementView.axaml @@ -1,4 +1,4 @@ -(OnDataContextAttached, OnDataContextDetached); + this.SubscribeDataContextChange(OnDataContextAttached, OnDataContextDetached); } public Func GetClickedTime => () => _pointerPosition; - private TimelineLayerViewModel ViewModel => (TimelineLayerViewModel)DataContext!; + private ElementViewModel ViewModel => (ElementViewModel)DataContext!; - private void OnDataContextDetached(TimelineLayerViewModel obj) + private void OnDataContextDetached(ElementViewModel obj) { obj.AnimationRequested = (_, _) => Task.CompletedTask; _disposable1?.Dispose(); _disposable1 = null; } - private void OnDataContextAttached(TimelineLayerViewModel obj) + private void OnDataContextAttached(ElementViewModel obj) { obj.AnimationRequested = async (args, token) => { @@ -121,11 +121,11 @@ await Dispatcher.UIThread.InvokeAsync(async () => }); }; - _disposable1 = obj.Model.GetObservable(Layer.IsEnabledProperty) + _disposable1 = obj.Model.GetObservable(Element.IsEnabledProperty) .Subscribe(b => Dispatcher.UIThread.InvokeAsync(() => border.Opacity = b ? 1 : 0.5)); } - protected override void OnAttachedToLogicalTree(Avalonia.LogicalTree.LogicalTreeAttachmentEventArgs e) + protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { base.OnAttachedToLogicalTree(e); _timeline = this.FindLogicalAncestorOfType(); @@ -137,7 +137,7 @@ protected override void OnAttachedToLogicalTree(Avalonia.LogicalTree.LogicalTree behaviors.Add(new _MoveBehavior()); } - protected override void OnDetachedFromLogicalTree(Avalonia.LogicalTree.LogicalTreeAttachmentEventArgs e) + protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) { base.OnDetachedFromLogicalTree(e); _timeline = null; @@ -155,15 +155,15 @@ protected override void OnPointerMoved(PointerEventArgs e) private void UseNodeClick(object? sender, RoutedEventArgs e) { - var layer = ViewModel.Model; - var command = new ChangePropertyCommand(layer, Layer.UseNodeProperty, !layer.UseNode, layer.UseNode); + var model = ViewModel.Model; + var command = new ChangePropertyCommand(model, Element.UseNodeProperty, !model.UseNode, model.UseNode); command.DoAndRecord(CommandRecorder.Default); } private void AllowOutflowClick(object? sender, RoutedEventArgs e) { - var layer = ViewModel.Model; - var command = new ChangePropertyCommand(layer, Layer.AllowOutflowProperty, !layer.AllowOutflow, layer.AllowOutflow); + var model = ViewModel.Model; + var command = new ChangePropertyCommand(model, Element.AllowOutflowProperty, !model.AllowOutflow, model.AllowOutflow); command.DoAndRecord(CommandRecorder.Default); } @@ -175,25 +175,25 @@ private void OnTextBoxLostFocus(object? sender, RoutedEventArgs e) private void OpenNodeTree_Click(object? sender, RoutedEventArgs e) { - Layer layer = ViewModel.Model; + Element model = ViewModel.Model; EditViewModel context = ViewModel.Timeline.EditorContext; NodeTreeTabViewModel? nodeTree = context.FindToolTab( - v => v.Layer.Value == layer || v.Layer.Value == null); + v => v.Layer.Value == model || v.Layer.Value == null); nodeTree ??= new NodeTreeTabViewModel(context); - nodeTree.Layer.Value = layer; + nodeTree.Layer.Value = model; context.OpenToolTab(nodeTree); } private TimeSpan RoundStartTime(TimeSpan time, float scale, bool flag) { - Layer layer = ViewModel.Model; + Element model = ViewModel.Model; if (!flag) { - foreach (Layer item in ViewModel.Scene.Children.GetMarshal().Value) + foreach (Element item in ViewModel.Scene.Children.GetMarshal().Value) { - if (item != layer) + if (item != model) { const double ThreadholdPixel = 10; TimeSpan threadhold = ThreadholdPixel.ToTimeSpan(scale); @@ -217,10 +217,10 @@ private TimeSpan RoundStartTime(TimeSpan time, float scale, bool flag) return time; } - private sealed class _ResizeBehavior : Behavior + private sealed class _ResizeBehavior : Behavior { - private Layer? _before; - private Layer? _after; + private Element? _before; + private Element? _after; private bool _pressed; private AlignmentX _resizeType; @@ -249,18 +249,18 @@ protected override void OnDetaching() private void OnPointerMoved(object? sender, PointerEventArgs e) { - if (AssociatedObject is { ViewModel: { } viewModel } layer) + if (AssociatedObject is { ViewModel: { } viewModel } view) { - Point point = e.GetPosition(layer); + Point point = e.GetPosition(view); float scale = viewModel.Timeline.Options.Value.Scale; TimeSpan pointerFrame = point.X.ToTimeSpan(scale); - if (layer._timeline is { } timeline && _pressed) + if (view._timeline is { } timeline && _pressed) { - pointerFrame = layer.RoundStartTime(pointerFrame, scale, e.KeyModifiers.HasFlag(KeyModifiers.Alt)); + pointerFrame = view.RoundStartTime(pointerFrame, scale, e.KeyModifiers.HasFlag(KeyModifiers.Alt)); point = point.WithX(pointerFrame.ToPixel(scale)); - if (layer.Cursor != Cursors.Arrow && layer.Cursor is { }) + if (view.Cursor != Cursors.Arrow && view.Cursor is { }) { double left = viewModel.BorderMargin.Value.Left; @@ -287,11 +287,11 @@ private void OnPointerMoved(object? sender, PointerEventArgs e) private void OnBorderPointerPressed(object? sender, PointerPressedEventArgs e) { - if (AssociatedObject is { _timeline: { }, border: { } border, ViewModel: { } viewModel } layer) + if (AssociatedObject is { _timeline: { }, border: { } border, ViewModel: { } viewModel } view) { - PointerPoint point = e.GetCurrentPoint(layer.border); + PointerPoint point = e.GetCurrentPoint(view.border); if (point.Properties.IsLeftButtonPressed && e.KeyModifiers is KeyModifiers.None or KeyModifiers.Alt - && layer.Cursor != Cursors.Arrow && layer.Cursor is { }) + && view.Cursor != Cursors.Arrow && view.Cursor is { }) { _before = viewModel.Model.GetBefore(viewModel.Model.ZIndex, viewModel.Model.Start); _after = viewModel.Model.GetAfter(viewModel.Model.ZIndex, viewModel.Model.Range.End); @@ -320,11 +320,11 @@ private async void OnBorderPointerReleased(object? sender, PointerReleasedEventA private void OnBorderPointerMoved(object? sender, PointerEventArgs e) { - if (AssociatedObject is { border: { } border } layer) + if (AssociatedObject is { border: { } border } view) { if (e.KeyModifiers is not (KeyModifiers.None or KeyModifiers.Alt)) { - layer.Cursor = null; + view.Cursor = null; _resizeType = AlignmentX.Center; } else if (!_pressed) @@ -335,17 +335,17 @@ private void OnBorderPointerMoved(object? sender, PointerEventArgs e) // 左右 10px内 なら左右矢印 if (horizon < 10) { - layer.Cursor = Cursors.SizeWestEast; + view.Cursor = Cursors.SizeWestEast; _resizeType = AlignmentX.Left; } else if (horizon > border.Bounds.Width - 10) { - layer.Cursor = Cursors.SizeWestEast; + view.Cursor = Cursors.SizeWestEast; _resizeType = AlignmentX.Right; } else { - layer.Cursor = null; + view.Cursor = null; _resizeType = AlignmentX.Center; } } @@ -353,7 +353,7 @@ private void OnBorderPointerMoved(object? sender, PointerEventArgs e) } } - private sealed class _MoveBehavior : Behavior + private sealed class _MoveBehavior : Behavior { private bool _pressed; private Point _start; @@ -382,15 +382,15 @@ protected override void OnDetaching() private void OnPointerMoved(object? sender, PointerEventArgs e) { - if (AssociatedObject is { ViewModel: { } viewModel } layer - && layer._timeline is { } timeline && _pressed) + if (AssociatedObject is { ViewModel: { } viewModel } view + && view._timeline is { } timeline && _pressed) { Scene scene = viewModel.Scene; - Point point = e.GetPosition(layer); + Point point = e.GetPosition(view); float scale = viewModel.Timeline.Options.Value.Scale; TimeSpan pointerFrame = point.X.ToTimeSpan(scale); - pointerFrame = layer.RoundStartTime(pointerFrame, scale, e.KeyModifiers.HasFlag(KeyModifiers.Alt)); + pointerFrame = view.RoundStartTime(pointerFrame, scale, e.KeyModifiers.HasFlag(KeyModifiers.Alt)); TimeSpan newframe = pointerFrame - _start.X.ToTimeSpan(scale); @@ -404,7 +404,7 @@ private void OnPointerMoved(object? sender, PointerEventArgs e) viewModel.Margin.Value = new(0, newTop, 0, 0); viewModel.BorderMargin.Value = new Thickness(newLeft, 0, 0, 0); - foreach (TimelineLayerViewModel item in viewModel.Timeline.GetSelected(viewModel)) + foreach (ElementViewModel item in viewModel.Timeline.GetSelected(viewModel)) { item.Margin.Value = new(0, item.Margin.Value.Top + deltaTop, 0, 0); item.BorderMargin.Value = new(item.BorderMargin.Value.Left + deltaLeft, 0, 0, 0); @@ -416,11 +416,11 @@ private void OnPointerMoved(object? sender, PointerEventArgs e) private void OnBorderPointerPressed(object? sender, PointerPressedEventArgs e) { - if (AssociatedObject is { _timeline: { }, border: { } border } layer) + if (AssociatedObject is { _timeline: { }, border: { } border } view) { - PointerPoint point = e.GetCurrentPoint(layer.border); + PointerPoint point = e.GetCurrentPoint(view.border); if (point.Properties.IsLeftButtonPressed - && (layer.Cursor == Cursors.Arrow || layer.Cursor == null)) + && (view.Cursor == Cursors.Arrow || view.Cursor == null)) { _pressed = true; _start = point.Position; @@ -438,10 +438,10 @@ private async void OnBorderPointerReleased(object? sender, PointerReleasedEventA if (AssociatedObject is { ViewModel: { } viewModel }) { e.Handled = true; - var layers = new List() { viewModel.Model }; - layers.AddRange(viewModel.Timeline.GetSelected(viewModel).Select(x => x.Model)); + var elems = new List() { viewModel.Model }; + elems.AddRange(viewModel.Timeline.GetSelected(viewModel).Select(x => x.Model)); - if (layers.Count == 1) + if (elems.Count == 1) { await viewModel.SubmitViewModelChanges(); } @@ -458,7 +458,7 @@ private async void OnBorderPointerReleased(object? sender, PointerReleasedEventA .Select(x => (ViewModel: x, Context: x.PrepareAnimation())) .ToArray(); - viewModel.Scene.MoveChildren(deltaIndex, deltaStart, layers.ToArray()) + viewModel.Scene.MoveChildren(deltaIndex, deltaStart, elems.ToArray()) .DoAndRecord(CommandRecorder.Default); foreach (var (item, context) in animations) @@ -471,7 +471,7 @@ private async void OnBorderPointerReleased(object? sender, PointerReleasedEventA } } - private sealed class _SelectBehavior : Behavior + private sealed class _SelectBehavior : Behavior { private bool _pressedWithModifier; private Thickness _snapshot; diff --git a/src/Beutl/Views/LayerHeader.axaml.cs b/src/Beutl/Views/LayerHeader.axaml.cs index 4d5fa734c..b3849b850 100644 --- a/src/Beutl/Views/LayerHeader.axaml.cs +++ b/src/Beutl/Views/LayerHeader.axaml.cs @@ -10,7 +10,7 @@ using static Beutl.Views.Timeline; -using TLVM = Beutl.ViewModels.TimelineLayerViewModel; +using TLVM = Beutl.ViewModels.ElementViewModel; namespace Beutl.Views; diff --git a/src/Beutl/Views/MainView.axaml b/src/Beutl/Views/MainView.axaml index 410f72b0f..6843b17e6 100644 --- a/src/Beutl/Views/MainView.axaml +++ b/src/Beutl/Views/MainView.axaml @@ -159,8 +159,8 @@ - - + + diff --git a/src/Beutl/Views/MainView.axaml.cs b/src/Beutl/Views/MainView.axaml.cs index 3786b5100..1cef6d2c3 100644 --- a/src/Beutl/Views/MainView.axaml.cs +++ b/src/Beutl/Views/MainView.axaml.cs @@ -580,10 +580,10 @@ private void InitCommands(MainViewModel viewModel) if (TryGetSelectedEditViewModel(out EditViewModel? viewModel) && viewModel.FindToolTab() is TimelineViewModel timeline) { - var dialog = new AddLayer + var dialog = new AddElementDialog { - DataContext = new AddLayerViewModel(viewModel.Scene, - new LayerDescription(timeline.ClickedFrame, TimeSpan.FromSeconds(5), timeline.ClickedLayer)) + DataContext = new AddElementDialogViewModel(viewModel.Scene, + new ElementDescription(timeline.ClickedFrame, TimeSpan.FromSeconds(5), timeline.ClickedLayer)) }; await dialog.ShowAsync(); } @@ -593,7 +593,7 @@ private void InitCommands(MainViewModel viewModel) { if (TryGetSelectedEditViewModel(out EditViewModel? viewModel) && viewModel.Scene is Scene scene - && viewModel.SelectedObject.Value is Layer layer) + && viewModel.SelectedObject.Value is Element layer) { string name = Path.GetFileName(layer.FileName); var dialog = new ContentDialog @@ -619,7 +619,7 @@ private void InitCommands(MainViewModel viewModel) { if (TryGetSelectedEditViewModel(out EditViewModel? viewModel) && viewModel.Scene is Scene scene - && viewModel.SelectedObject.Value is Layer layer) + && viewModel.SelectedObject.Value is Element layer) { scene.RemoveChild(layer).DoAndRecord(CommandRecorder.Default); } @@ -629,7 +629,7 @@ private void InitCommands(MainViewModel viewModel) { if (TryGetSelectedEditViewModel(out EditViewModel? viewModel) && viewModel.Scene is Scene scene - && viewModel.SelectedObject.Value is Layer layer) + && viewModel.SelectedObject.Value is Element layer) { IClipboard? clipboard = Application.Current?.Clipboard; if (clipboard != null) @@ -639,7 +639,7 @@ private void InitCommands(MainViewModel viewModel) string json = jsonNode.ToJsonString(JsonHelper.SerializerOptions); var data = new DataObject(); data.Set(DataFormats.Text, json); - data.Set(Constants.Layer, json); + data.Set(Constants.Element, json); await clipboard.SetDataObjectAsync(data); scene.RemoveChild(layer).DoAndRecord(CommandRecorder.Default); @@ -651,7 +651,7 @@ private void InitCommands(MainViewModel viewModel) { if (TryGetSelectedEditViewModel(out EditViewModel? viewModel) && viewModel.Scene is Scene scene - && viewModel.SelectedObject.Value is Layer layer) + && viewModel.SelectedObject.Value is Element layer) { IClipboard? clipboard = Application.Current?.Clipboard; if (clipboard != null) @@ -661,7 +661,7 @@ private void InitCommands(MainViewModel viewModel) string json = jsonNode.ToJsonString(JsonHelper.SerializerOptions); var data = new DataObject(); data.Set(DataFormats.Text, json); - data.Set(Constants.Layer, json); + data.Set(Constants.Element, json); await clipboard.SetDataObjectAsync(data); } diff --git a/src/Beutl/Views/Timeline.axaml b/src/Beutl/Views/Timeline.axaml index 6077154fc..794f711d3 100644 --- a/src/Beutl/Views/Timeline.axaml +++ b/src/Beutl/Views/Timeline.axaml @@ -102,7 +102,7 @@ - + diff --git a/src/Beutl/Views/Timeline.axaml.cs b/src/Beutl/Views/Timeline.axaml.cs index 147939f27..78721877c 100644 --- a/src/Beutl/Views/Timeline.axaml.cs +++ b/src/Beutl/Views/Timeline.axaml.cs @@ -35,8 +35,8 @@ internal enum MouseFlags internal int _pointerLayer; private TimelineViewModel? _viewModel; private readonly CompositeDisposable _disposables = new(); - private TimelineLayer? _selectedLayer; - private List<(TimelineLayerViewModel Layer, bool IsSelectedOriginal)> _rangeSelection = new(); + private ElementView? _selectedLayer; + private readonly List<(ElementViewModel Layer, bool IsSelectedOriginal)> _rangeSelection = new(); public Timeline() { @@ -77,8 +77,8 @@ private void OnDataContextAttached(TimelineViewModel vm) LeftPanel[!MinHeightProperty] = minHeightBinding; vm.Layers.ForEachItem( - AddLayer, - RemoveLayer, + AddElement, + RemoveElement, () => { }) .DisposeWith(_disposables); @@ -94,15 +94,15 @@ private void OnDataContextAttached(TimelineViewModel vm) { string[] formats = await clipboard.GetFormatsAsync(); - if (formats.AsSpan().Contains(Constants.Layer)) + if (formats.AsSpan().Contains(Constants.Element)) { string json = await clipboard.GetTextAsync(); - var layer = new Layer(); + var layer = new Element(); layer.ReadFromJson(JsonNode.Parse(json)!.AsObject()); layer.Start = ViewModel.ClickedFrame; layer.ZIndex = ViewModel.ClickedLayer; - layer.Save(Helper.RandomLayerFileName(Path.GetDirectoryName(ViewModel.Scene.FileName)!, Constants.LayerFileExtension)); + layer.Save(Helper.RandomLayerFileName(Path.GetDirectoryName(ViewModel.Scene.FileName)!, Constants.ElementFileExtension)); ViewModel.Scene.AddChild(layer).DoAndRecord(CommandRecorder.Default); } @@ -114,7 +114,7 @@ private void OnDataContextAttached(TimelineViewModel vm) { if (_selectedLayer != null) { - foreach (TimelineLayerViewModel item in ViewModel.Layers.GetMarshal().Value) + foreach (ElementViewModel item in ViewModel.Layers.GetMarshal().Value) { item.IsSelected.Value = false; } @@ -122,7 +122,7 @@ private void OnDataContextAttached(TimelineViewModel vm) _selectedLayer = null; } - if (e is Layer layer && FindLayerView(layer) is TimelineLayer { DataContext: TimelineLayerViewModel viewModel } newView) + if (e is Element layer && FindLayerView(layer) is ElementView { DataContext: ElementViewModel viewModel } newView) { viewModel.IsSelected.Value = true; _selectedLayer = newView; @@ -250,7 +250,7 @@ private void TimelinePanel_PointerReleased(object? sender, PointerReleasedEventA private void UpdateRangeSelection() { TimelineViewModel viewModel = ViewModel; - foreach ((TimelineLayerViewModel layer, bool isSelectedOriginal) in _rangeSelection) + foreach ((ElementViewModel layer, bool isSelectedOriginal) in _rangeSelection) { layer.IsSelected.Value = isSelectedOriginal; } @@ -264,7 +264,7 @@ private void UpdateRangeSelection() int startLayer = viewModel.ToLayerNumber(rect.Top); int endLayer = viewModel.ToLayerNumber(rect.Bottom); - foreach (TimelineLayerViewModel item in viewModel.Layers) + foreach (ElementViewModel item in viewModel.Layers) { if (timeRange.Intersects(item.Model.Range) && startLayer <= item.Model.ZIndex && item.Model.ZIndex <= endLayer) @@ -327,15 +327,15 @@ private async void TimelinePanel_Drop(object? sender, DragEventArgs e) { if (e.KeyModifiers == KeyModifiers.Control) { - var dialog = new AddLayer + var dialog = new AddElementDialog { - DataContext = new AddLayerViewModel(scene, new LayerDescription(viewModel.ClickedFrame, TimeSpan.FromSeconds(5), viewModel.ClickedLayer, InitialOperator: item2)) + DataContext = new AddElementDialogViewModel(scene, new ElementDescription(viewModel.ClickedFrame, TimeSpan.FromSeconds(5), viewModel.ClickedLayer, InitialOperator: item2)) }; await dialog.ShowAsync(); } else { - viewModel.AddLayer.Execute(new LayerDescription( + viewModel.AddLayer.Execute(new ElementDescription( viewModel.ClickedFrame, TimeSpan.FromSeconds(5), viewModel.ClickedLayer, InitialOperator: item2)); } } @@ -354,13 +354,13 @@ private void TimelinePanel_DragOver(object? sender, DragEventArgs e) } } - // レイヤーを追加 - private async void AddLayerClick(object? sender, RoutedEventArgs e) + // 要素を追加 + private async void AddElementClick(object? sender, RoutedEventArgs e) { - var dialog = new AddLayer + var dialog = new AddElementDialog { - DataContext = new AddLayerViewModel(ViewModel.Scene, - new LayerDescription(ViewModel.ClickedFrame, TimeSpan.FromSeconds(5), ViewModel.ClickedLayer)) + DataContext = new AddElementDialogViewModel(ViewModel.Scene, + new ElementDescription(ViewModel.ClickedFrame, TimeSpan.FromSeconds(5), ViewModel.ClickedLayer)) }; await dialog.ShowAsync(); } @@ -374,10 +374,10 @@ private async void ShowSceneSettings(object? sender, RoutedEventArgs e) await dialog.ShowAsync(); } - // レイヤーを追加 - private void AddLayer(int index, TimelineLayerViewModel viewModel) + // 要素を追加 + private void AddElement(int index, ElementViewModel viewModel) { - var view = new TimelineLayer + var view = new ElementView { DataContext = viewModel }; @@ -385,15 +385,15 @@ private void AddLayer(int index, TimelineLayerViewModel viewModel) TimelinePanel.Children.Add(view); } - // レイヤーを削除 - private void RemoveLayer(int index, TimelineLayerViewModel viewModel) + // 要素を削除 + private void RemoveElement(int index, ElementViewModel viewModel) { - Layer layer = viewModel.Model; + Element elm = viewModel.Model; for (int i = 0; i < TimelinePanel.Children.Count; i++) { IControl item = TimelinePanel.Children[i]; - if (item.DataContext is TimelineLayerViewModel vm && vm.Model == layer) + if (item.DataContext is ElementViewModel vm && vm.Model == elm) { TimelinePanel.Children.RemoveAt(i); break; @@ -425,9 +425,9 @@ private void OnRemovedInline(InlineAnimationLayerViewModel viewModel) } } - private TimelineLayer? FindLayerView(Layer layer) + private ElementView? FindLayerView(Element layer) { - return TimelinePanel.Children.FirstOrDefault(ctr => ctr.DataContext is TimelineLayerViewModel vm && vm.Model == layer) as TimelineLayer; + return TimelinePanel.Children.FirstOrDefault(ctr => ctr.DataContext is ElementViewModel vm && vm.Model == layer) as ElementView; } private void ZoomClick(object? sender, RoutedEventArgs e) diff --git a/src/Beutl/Views/Tools/SourceOperatorView.axaml.cs b/src/Beutl/Views/Tools/SourceOperatorView.axaml.cs index f1a7122bc..60fa5326d 100644 --- a/src/Beutl/Views/Tools/SourceOperatorView.axaml.cs +++ b/src/Beutl/Views/Tools/SourceOperatorView.axaml.cs @@ -35,7 +35,7 @@ public void Remove_Click(object? sender, RoutedEventArgs e) if (DataContext is SourceOperatorViewModel viewModel2) { SourceOperator operation = viewModel2.Model; - Layer layer = operation.FindRequiredHierarchicalParent(); + Element layer = operation.FindRequiredHierarchicalParent(); layer.Operation.RemoveChild(operation) .DoAndRecord(CommandRecorder.Default); } @@ -47,7 +47,7 @@ private void Drop(object? sender, DragEventArgs e) && DataContext is SourceOperatorViewModel viewModel2) { SourceOperator operation = viewModel2.Model; - Layer layer = operation.FindRequiredHierarchicalParent(); + Element layer = operation.FindRequiredHierarchicalParent(); Rect bounds = Bounds; Point position = e.GetPosition(this); double half = bounds.Height / 2; diff --git a/src/Beutl/Views/Tools/SourceOperatorsTab.axaml.cs b/src/Beutl/Views/Tools/SourceOperatorsTab.axaml.cs index 6aa57edea..14d42ce32 100644 --- a/src/Beutl/Views/Tools/SourceOperatorsTab.axaml.cs +++ b/src/Beutl/Views/Tools/SourceOperatorsTab.axaml.cs @@ -55,7 +55,7 @@ private void Drop(object? sender, DragEventArgs e) { if (e.Data.Get("SourceOperator") is OperatorRegistry.RegistryItem item && DataContext is SourceOperatorsTabViewModel vm - && vm.Layer.Value is Layer layer) + && vm.Layer.Value is Element layer) { layer.Operation.AddChild((SourceOperator)Activator.CreateInstance(item.Type)!) .DoAndRecord(CommandRecorder.Default); From 828e112e7da60dbfc6957b735099449fc8137988 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Tue, 11 Apr 2023 17:29:53 +0900 Subject: [PATCH 34/84] =?UTF-8?q?CoreObject=E3=81=A7Id=E3=82=92=E9=87=8D?= =?UTF-8?q?=E8=A4=87=E3=81=97=E3=81=AA=E3=81=84=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Core/CoreObject.cs | 5 ++ src/Beutl.Core/CoreObjectExtensions.cs | 69 +++++++++++++++++++ src/Beutl.ProjectSystem/NodeTree/NodeGroup.cs | 1 - src/Beutl.ProjectSystem/NodeTree/NodeItem.cs | 5 -- 4 files changed, 74 insertions(+), 6 deletions(-) diff --git a/src/Beutl.Core/CoreObject.cs b/src/Beutl.Core/CoreObject.cs index ab498a92c..6d9f1196a 100644 --- a/src/Beutl.Core/CoreObject.cs +++ b/src/Beutl.Core/CoreObject.cs @@ -79,6 +79,11 @@ static CoreObject() .Register(); } + protected CoreObject() + { + Id = Guid.NewGuid(); + } + [Browsable(false)] public Guid Id { diff --git a/src/Beutl.Core/CoreObjectExtensions.cs b/src/Beutl.Core/CoreObjectExtensions.cs index bdb87c70e..419a8d8ca 100644 --- a/src/Beutl.Core/CoreObjectExtensions.cs +++ b/src/Beutl.Core/CoreObjectExtensions.cs @@ -22,6 +22,75 @@ public static IObservable GetObservable(this ICoreObject obj, CoreProperty return new CoreObjectSubject(obj, property); } + public static ICoreObject? FindById(this ICoreObject obj, Guid id, bool includeSelf = true) + { + return obj.Find(o => (o as ICoreObject)?.Id == id, includeSelf) as ICoreObject; + } + + public static object? Find(this ICoreObject obj, Predicate predicate, bool includeSelf = true) + { + return obj.Find(predicate, includeSelf, new HashSet()); + } + + public static object? Find(this ICoreObject obj, Predicate predicate, bool includeSelf, HashSet hashSet) + { + ArgumentNullException.ThrowIfNull(predicate); + + if (!hashSet.Add(obj)) + return null; + + if (includeSelf && predicate(obj)) + return obj; + + if (obj is IHierarchical hierarchical) + { + object? match = hierarchical.FindHierarchy(predicate, hashSet); + if (match != null) + return match; + } + + Type type = obj.GetType(); + + IReadOnlyList props = PropertyRegistry.GetRegistered(type); + foreach (CoreProperty prop in props) + { + object? inner = obj.GetValue(prop); + if (inner != null && hashSet.Add(inner)) + { + if (predicate(inner)) + { + return inner; + } + else if ((inner as ICoreObject)?.Find(predicate, true, hashSet) is { } match) + { + return match; + } + } + } + + return null; + } + + private static object? FindHierarchy(this IHierarchical hierarchical, Predicate predicate, HashSet hashSet) + { + foreach (IHierarchical item in hierarchical.HierarchicalChildren) + { + if (hashSet.Add(item)) + { + if (predicate(item)) + { + return item; + } + else if ((item as ICoreObject)?.Find(predicate, true, hashSet) is { } match) + { + return match; + } + } + } + + return null; + } + private sealed class CorePropertyChangedObservable : LightweightObservableBase> { private readonly CoreProperty _property; diff --git a/src/Beutl.ProjectSystem/NodeTree/NodeGroup.cs b/src/Beutl.ProjectSystem/NodeTree/NodeGroup.cs index 7569f1ac5..cd9c3e0e7 100644 --- a/src/Beutl.ProjectSystem/NodeTree/NodeGroup.cs +++ b/src/Beutl.ProjectSystem/NodeTree/NodeGroup.cs @@ -26,7 +26,6 @@ static NodeGroup() public NodeGroup() { - Id = Guid.NewGuid(); Nodes.Attached += OnNodeAttached; Nodes.Detached += OnNodeDetached; } diff --git a/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs b/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs index 49ff98ea0..831b34eea 100644 --- a/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs +++ b/src/Beutl.ProjectSystem/NodeTree/NodeItem.cs @@ -16,11 +16,6 @@ static NodeItem() .Register(); } - public NodeItem() - { - Id = Guid.NewGuid(); - } - public int LocalId { get => _localId; From fba04605979680caf2b109f02b360636be0f0a52 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Tue, 11 Apr 2023 17:40:28 +0900 Subject: [PATCH 35/84] =?UTF-8?q?NodeTreeBackground=E3=81=A7=E6=8B=A1?= =?UTF-8?q?=E5=A4=A7=E3=80=81=E7=A7=BB=E5=8B=95=E3=81=97=E3=81=9F=E3=81=A8?= =?UTF-8?q?=E3=81=8D=E3=81=AB=E6=A0=BC=E5=AD=90=E3=81=8C=E3=81=9A=E3=82=8C?= =?UTF-8?q?=E3=82=8B=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl/Views/NodeTree/NodeTreeBackground.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Beutl/Views/NodeTree/NodeTreeBackground.cs b/src/Beutl/Views/NodeTree/NodeTreeBackground.cs index 452a161cb..b37f80ba9 100644 --- a/src/Beutl/Views/NodeTree/NodeTreeBackground.cs +++ b/src/Beutl/Views/NodeTree/NodeTreeBackground.cs @@ -63,8 +63,8 @@ public override void Render(DrawingContext context) int hsplit = (int)Math.Ceiling(width / DotSize) + 1; int vsplit = (int)Math.Ceiling(height / DotSize) + 1; - double offsetX = _zoomBorder.OffsetX; - double offsetY = _zoomBorder.OffsetY; + double offsetX = _zoomBorder.OffsetX / _zoomBorder.ZoomX; + double offsetY = _zoomBorder.OffsetY / _zoomBorder.ZoomY; double amariX = offsetX % DotSize; double amariY = offsetY % DotSize; From 139d85e8092d04e512fda77699206a95a8612eeb Mon Sep 17 00:00:00 2001 From: indigo-san Date: Tue, 11 Apr 2023 17:58:17 +0900 Subject: [PATCH 36/84] =?UTF-8?q?NodeTree=E3=81=AE=E8=A1=A8=E7=A4=BA?= =?UTF-8?q?=E3=81=AE=E7=8A=B6=E6=85=8B=E3=82=92=E4=BF=9D=E5=AD=98=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NodeTree/NodeTreeTabViewModel.cs | 90 ++++++++++++++++++- .../ViewModels/NodeTree/NodeTreeViewModel.cs | 44 ++++++++- .../ViewModels/NodeTree/NodeViewModel.cs | 39 +++++++- .../Views/NodeTree/NodeTreeView.axaml.cs | 21 ++++- 4 files changed, 185 insertions(+), 9 deletions(-) diff --git a/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs b/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs index 9a4ab48b1..1123daf29 100644 --- a/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/NodeTreeTabViewModel.cs @@ -3,6 +3,7 @@ using Avalonia.Collections.Pooled; using Beutl.Framework; +using Beutl.Models; using Beutl.NodeTree; using Beutl.ProjectSystem; using Beutl.Services.PrimitiveImpls; @@ -11,7 +12,7 @@ namespace Beutl.ViewModels.NodeTree; -public sealed class NodeTreeNavigationItem : IDisposable +public sealed class NodeTreeNavigationItem : IDisposable, IJsonSerializable { internal Lazy _lazyViewModel; @@ -46,6 +47,16 @@ public void Dispose() _lazyViewModel = null!; NodeTree = null!; } + + public void WriteToJson(JsonObject json) + { + ViewModel.WriteToJson(json); + } + + public void ReadFromJson(JsonObject json) + { + ViewModel.ReadFromJson(json); + } } public sealed class NodeTreeTabViewModel : IToolContext @@ -53,6 +64,7 @@ public sealed class NodeTreeTabViewModel : IToolContext private readonly ReactiveProperty _isSelected = new(true); private readonly CompositeDisposable _disposables = new(); private EditViewModel _editViewModel; + private Element? _oldLayer; public NodeTreeTabViewModel(EditViewModel editViewModel) { @@ -60,6 +72,12 @@ public NodeTreeTabViewModel(EditViewModel editViewModel) Layer.Subscribe(v => { + if (_oldLayer != null) + { + SaveState(_oldLayer); + } + _oldLayer = v; + foreach (NodeTreeNavigationItem item in Items) { item.Dispose(); @@ -82,6 +100,8 @@ public NodeTreeTabViewModel(EditViewModel editViewModel) name: name.CombineLatest(fileName) .Select(x => string.IsNullOrWhiteSpace(x.First) ? x.Second : x.First) .ToReadOnlyReactivePropertySlim()!)); + + RestoreState(v); } }).DisposeWith(_disposables); } @@ -102,6 +122,8 @@ public NodeTreeTabViewModel(EditViewModel editViewModel) public void Dispose() { + Layer.Value = null; + _disposables.Dispose(); foreach (NodeTreeNavigationItem item in Items) { @@ -112,7 +134,6 @@ public void Dispose() NodeTree.Dispose(); NodeTree.Value?.Dispose(); NodeTree.Value = null; - Layer.Value = null; _editViewModel = null!; } @@ -197,6 +218,71 @@ public void NavigateTo(NodeTreeModel nodeTree) } } + private static string ViewStateDirectory(Element layer) + { + string directory = Path.GetDirectoryName(layer.FileName)!; + + directory = Path.Combine(directory, Constants.BeutlFolder, Constants.ViewStateFolder); + if (!Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + return directory; + } + + private void SaveState(Element layer) + { + string viewStateDir = ViewStateDirectory(layer); + + var itemsJson = new JsonObject(); + foreach (NodeTreeNavigationItem item in Items.GetMarshal().Value) + { + var itemJson = new JsonObject(); + item.WriteToJson(itemJson); + itemsJson[item.NodeTree.Id.ToString()] = itemJson; + } + + var json = new JsonObject + { + [nameof(Items)] = itemsJson + }; + if (NodeTree.Value != null) + { + json["Selected"] = NodeTree.Value.NodeTree.Id; + } + + json.JsonSave(Path.Combine(viewStateDir, $"{Path.GetFileNameWithoutExtension(layer.FileName)}.nodetree.config")); + } + + private void RestoreState(Element layer) + { + string viewStateDir = ViewStateDirectory(layer); + string viewStateFile = Path.Combine(viewStateDir, $"{Path.GetFileNameWithoutExtension(layer.FileName)}.nodetree.config"); + + if (File.Exists(viewStateFile)) + { + using var stream = new FileStream(viewStateFile, FileMode.Open, FileAccess.Read, FileShare.Read); + JsonObject json = JsonNode.Parse(stream)!.AsObject(); + Guid? selected = (Guid?)json["Selected"]; + if (selected.HasValue + && layer.FindById(selected.Value) is NodeTreeModel selectedModel) + { + NavigateTo(selectedModel); + } + + JsonObject itemsJson = json[nameof(Items)]!.AsObject(); + + foreach (NodeTreeNavigationItem item in Items) + { + if (itemsJson.TryGetPropertyValue(item.NodeTree.Id.ToString(), out JsonNode? itemJson)) + { + item.ReadFromJson(itemJson!.AsObject()); + } + } + } + } + public void ReadFromJson(JsonObject json) { if (Layer.Value == null diff --git a/src/Beutl/ViewModels/NodeTree/NodeTreeViewModel.cs b/src/Beutl/ViewModels/NodeTree/NodeTreeViewModel.cs index c6cf2b87d..9648fd935 100644 --- a/src/Beutl/ViewModels/NodeTree/NodeTreeViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/NodeTreeViewModel.cs @@ -1,11 +1,15 @@ -using Avalonia; +using System.Text.Json.Nodes; + +using Avalonia; using Beutl.NodeTree; using Beutl.NodeTree.Nodes.Group; +using Reactive.Bindings; + namespace Beutl.ViewModels.NodeTree; -public sealed class NodeTreeViewModel : IDisposable +public sealed class NodeTreeViewModel : IDisposable, IJsonSerializable { private readonly CompositeDisposable _disposables = new(); @@ -38,6 +42,8 @@ public NodeTreeViewModel(NodeTreeModel nodeTree) public CoreList Nodes { get; } = new(); + public ReactiveProperty Matrix { get; } = new(Avalonia.Matrix.Identity); + public NodeTreeModel NodeTree { get; } public SocketViewModel? FindSocketViewModel(ISocket socket) @@ -90,4 +96,38 @@ public void Dispose() _disposables.Dispose(); } + + public void WriteToJson(JsonObject json) + { + var nodesJson = new JsonObject(); + foreach (NodeViewModel item in Nodes) + { + var nodeJson = new JsonObject(); + item.WriteToJson(nodeJson); + nodesJson[item.Node.Id.ToString()] = nodeJson; + } + + json[nameof(Nodes)] = nodesJson; + + Matrix m = Matrix.Value; + json[nameof(Matrix)] = $"{m.M11},{m.M12},{m.M21},{m.M22},{m.M31},{m.M32}"; + } + + public void ReadFromJson(JsonObject json) + { + JsonObject nodesJson = json[nameof(Nodes)]!.AsObject(); + foreach (NodeViewModel item in Nodes) + { + if (nodesJson.TryGetPropertyValue(item.Node.Id.ToString(), out JsonNode? nodeJson)) + { + item.ReadFromJson(nodeJson!.AsObject()); + } + } + + if (json.TryGetPropertyValue(nameof(Matrix), out var mJson)) + { + string m = (string)mJson!; + Matrix.Value = Avalonia.Matrix.Parse(m); + } + } } diff --git a/src/Beutl/ViewModels/NodeTree/NodeViewModel.cs b/src/Beutl/ViewModels/NodeTree/NodeViewModel.cs index 6946bf52b..59e9bcbb0 100644 --- a/src/Beutl/ViewModels/NodeTree/NodeViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/NodeViewModel.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Specialized; +using System.Text.Json.Nodes; using Avalonia; using Avalonia.Media; @@ -17,7 +18,7 @@ namespace Beutl.ViewModels.NodeTree; -public sealed class NodeViewModel : IDisposable +public sealed class NodeViewModel : IDisposable, IJsonSerializable { private readonly CompositeDisposable _disposables = new(); private readonly string _defaultName; @@ -214,4 +215,40 @@ public void UpdateName(string? name) new ChangePropertyCommand(Node, CoreObject.NameProperty, name, Node.Name) .DoAndRecord(CommandRecorder.Default); } + + public void WriteToJson(JsonObject json) + { + json[nameof(IsExpanded)] = IsExpanded.Value; + + var itemsJson = new JsonObject(); + foreach (NodeItemViewModel item in Items) + { + if (item.Model != null + && item.PropertyEditorContext != null) + { + var itemJson = new JsonObject(); + item.PropertyEditorContext.WriteToJson(itemJson); + + itemsJson[item.Model.Id.ToString()] = itemJson; + } + } + + json[nameof(Items)] = itemsJson; + } + + public void ReadFromJson(JsonObject json) + { + IsExpanded.Value = (bool)json[nameof(IsExpanded)]!; + + JsonObject itemsJson = json[nameof(Items)]!.AsObject(); + foreach (NodeItemViewModel item in Items) + { + if (item.Model != null + && item.PropertyEditorContext != null + && itemsJson.TryGetPropertyValue(item.Model.Id.ToString(), out JsonNode? itemJson)) + { + item.PropertyEditorContext.ReadFromJson(itemJson!.AsObject()); + } + } + } } diff --git a/src/Beutl/Views/NodeTree/NodeTreeView.axaml.cs b/src/Beutl/Views/NodeTree/NodeTreeView.axaml.cs index 93a61bc37..715b568df 100644 --- a/src/Beutl/Views/NodeTree/NodeTreeView.axaml.cs +++ b/src/Beutl/Views/NodeTree/NodeTreeView.axaml.cs @@ -14,11 +14,12 @@ namespace Beutl.Views.NodeTree; public partial class NodeTreeView : UserControl { - private IDisposable? _disposable; + private readonly CompositeDisposable _disposables = new(); private Point _rightClickedPosition; internal Point _leftClickedPosition; private bool _rangeSelectionPressed; private List<(NodeView Node, bool IsSelectedOriginal)> _rangeSelection = new(); + private bool _matrixUpdating; public NodeTreeView() { @@ -60,6 +61,13 @@ private void OnDragEnter(object? sender, DragEventArgs e) private void OnZoomChanged(object sender, ZoomChangedEventArgs e) { + if (DataContext is NodeTreeViewModel viewModel) + { + _matrixUpdating = true; + viewModel.Matrix.Value = zoomBorder.Matrix; + _matrixUpdating = false; + } + if (_rangeSelectionPressed) { UpdateRangeSelection(); @@ -208,7 +216,7 @@ internal static ConnectionLine CreateLine(InputSocketViewModel input, OutputSock private void OnDataContextAttached(NodeTreeViewModel obj) { - _disposable = obj.Nodes.ForEachItem( + obj.Nodes.ForEachItem( node => { var control = new NodeView() @@ -266,12 +274,17 @@ private void OnDataContextAttached(NodeTreeViewModel obj) } } }, - canvas.Children.Clear); + canvas.Children.Clear) + .DisposeWith(_disposables); + + obj.Matrix.Where(_ => !_matrixUpdating) + .Subscribe(m => zoomBorder.SetMatrix(m, true)) + .DisposeWith(_disposables); } private void OnDataContextDetached(NodeTreeViewModel obj) { - _disposable?.Dispose(); + _disposables.Clear(); canvas.Children.Clear(); } } From 952f368aaf2bda1ad61fec7292416e7e07728948 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Wed, 12 Apr 2023 14:43:19 +0900 Subject: [PATCH 37/84] =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E3=82=92=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Graphics/Media/{ => Brushes}/Brush.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/Brushes.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/ConicGradientBrush.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/DrawableBrush.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/GradientBrush.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/GradientSpreadMethod.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/GradientStop.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/GradientStops.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/IBrush.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/IConicGradientBrush.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/IDrawableBrush.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/IGradientBrush.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/IGradientStop.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/IImageBrush.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/ILinearGradientBrush.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/IMutableBrush.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/IRadialGradientBrush.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/ISolidColorBrush.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/ITileBrush.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/ImageBrush.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/LinearGradientBrush.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/RadialGradientBrush.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/SolidColorBrush.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/TileBrush.cs | 0 src/Beutl.Graphics/Media/{ => Brushes}/TileMode.cs | 0 src/Beutl.Graphics/Media/{ => Font}/FontExtensions.cs | 0 src/Beutl.Graphics/Media/{ => Font}/FontFamily.cs | 0 src/Beutl.Graphics/Media/{ => Font}/FontManager.cs | 0 src/Beutl.Graphics/Media/{ => Font}/FontMetrics.cs | 0 src/Beutl.Graphics/Media/{ => Font}/FontStyle.cs | 0 src/Beutl.Graphics/Media/{ => Font}/FontWeight.cs | 0 src/Beutl.Graphics/Media/{ => Font}/Typeface.cs | 0 32 files changed, 0 insertions(+), 0 deletions(-) rename src/Beutl.Graphics/Media/{ => Brushes}/Brush.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/Brushes.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/ConicGradientBrush.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/DrawableBrush.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/GradientBrush.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/GradientSpreadMethod.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/GradientStop.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/GradientStops.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/IBrush.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/IConicGradientBrush.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/IDrawableBrush.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/IGradientBrush.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/IGradientStop.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/IImageBrush.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/ILinearGradientBrush.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/IMutableBrush.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/IRadialGradientBrush.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/ISolidColorBrush.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/ITileBrush.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/ImageBrush.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/LinearGradientBrush.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/RadialGradientBrush.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/SolidColorBrush.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/TileBrush.cs (100%) rename src/Beutl.Graphics/Media/{ => Brushes}/TileMode.cs (100%) rename src/Beutl.Graphics/Media/{ => Font}/FontExtensions.cs (100%) rename src/Beutl.Graphics/Media/{ => Font}/FontFamily.cs (100%) rename src/Beutl.Graphics/Media/{ => Font}/FontManager.cs (100%) rename src/Beutl.Graphics/Media/{ => Font}/FontMetrics.cs (100%) rename src/Beutl.Graphics/Media/{ => Font}/FontStyle.cs (100%) rename src/Beutl.Graphics/Media/{ => Font}/FontWeight.cs (100%) rename src/Beutl.Graphics/Media/{ => Font}/Typeface.cs (100%) diff --git a/src/Beutl.Graphics/Media/Brush.cs b/src/Beutl.Graphics/Media/Brushes/Brush.cs similarity index 100% rename from src/Beutl.Graphics/Media/Brush.cs rename to src/Beutl.Graphics/Media/Brushes/Brush.cs diff --git a/src/Beutl.Graphics/Media/Brushes.cs b/src/Beutl.Graphics/Media/Brushes/Brushes.cs similarity index 100% rename from src/Beutl.Graphics/Media/Brushes.cs rename to src/Beutl.Graphics/Media/Brushes/Brushes.cs diff --git a/src/Beutl.Graphics/Media/ConicGradientBrush.cs b/src/Beutl.Graphics/Media/Brushes/ConicGradientBrush.cs similarity index 100% rename from src/Beutl.Graphics/Media/ConicGradientBrush.cs rename to src/Beutl.Graphics/Media/Brushes/ConicGradientBrush.cs diff --git a/src/Beutl.Graphics/Media/DrawableBrush.cs b/src/Beutl.Graphics/Media/Brushes/DrawableBrush.cs similarity index 100% rename from src/Beutl.Graphics/Media/DrawableBrush.cs rename to src/Beutl.Graphics/Media/Brushes/DrawableBrush.cs diff --git a/src/Beutl.Graphics/Media/GradientBrush.cs b/src/Beutl.Graphics/Media/Brushes/GradientBrush.cs similarity index 100% rename from src/Beutl.Graphics/Media/GradientBrush.cs rename to src/Beutl.Graphics/Media/Brushes/GradientBrush.cs diff --git a/src/Beutl.Graphics/Media/GradientSpreadMethod.cs b/src/Beutl.Graphics/Media/Brushes/GradientSpreadMethod.cs similarity index 100% rename from src/Beutl.Graphics/Media/GradientSpreadMethod.cs rename to src/Beutl.Graphics/Media/Brushes/GradientSpreadMethod.cs diff --git a/src/Beutl.Graphics/Media/GradientStop.cs b/src/Beutl.Graphics/Media/Brushes/GradientStop.cs similarity index 100% rename from src/Beutl.Graphics/Media/GradientStop.cs rename to src/Beutl.Graphics/Media/Brushes/GradientStop.cs diff --git a/src/Beutl.Graphics/Media/GradientStops.cs b/src/Beutl.Graphics/Media/Brushes/GradientStops.cs similarity index 100% rename from src/Beutl.Graphics/Media/GradientStops.cs rename to src/Beutl.Graphics/Media/Brushes/GradientStops.cs diff --git a/src/Beutl.Graphics/Media/IBrush.cs b/src/Beutl.Graphics/Media/Brushes/IBrush.cs similarity index 100% rename from src/Beutl.Graphics/Media/IBrush.cs rename to src/Beutl.Graphics/Media/Brushes/IBrush.cs diff --git a/src/Beutl.Graphics/Media/IConicGradientBrush.cs b/src/Beutl.Graphics/Media/Brushes/IConicGradientBrush.cs similarity index 100% rename from src/Beutl.Graphics/Media/IConicGradientBrush.cs rename to src/Beutl.Graphics/Media/Brushes/IConicGradientBrush.cs diff --git a/src/Beutl.Graphics/Media/IDrawableBrush.cs b/src/Beutl.Graphics/Media/Brushes/IDrawableBrush.cs similarity index 100% rename from src/Beutl.Graphics/Media/IDrawableBrush.cs rename to src/Beutl.Graphics/Media/Brushes/IDrawableBrush.cs diff --git a/src/Beutl.Graphics/Media/IGradientBrush.cs b/src/Beutl.Graphics/Media/Brushes/IGradientBrush.cs similarity index 100% rename from src/Beutl.Graphics/Media/IGradientBrush.cs rename to src/Beutl.Graphics/Media/Brushes/IGradientBrush.cs diff --git a/src/Beutl.Graphics/Media/IGradientStop.cs b/src/Beutl.Graphics/Media/Brushes/IGradientStop.cs similarity index 100% rename from src/Beutl.Graphics/Media/IGradientStop.cs rename to src/Beutl.Graphics/Media/Brushes/IGradientStop.cs diff --git a/src/Beutl.Graphics/Media/IImageBrush.cs b/src/Beutl.Graphics/Media/Brushes/IImageBrush.cs similarity index 100% rename from src/Beutl.Graphics/Media/IImageBrush.cs rename to src/Beutl.Graphics/Media/Brushes/IImageBrush.cs diff --git a/src/Beutl.Graphics/Media/ILinearGradientBrush.cs b/src/Beutl.Graphics/Media/Brushes/ILinearGradientBrush.cs similarity index 100% rename from src/Beutl.Graphics/Media/ILinearGradientBrush.cs rename to src/Beutl.Graphics/Media/Brushes/ILinearGradientBrush.cs diff --git a/src/Beutl.Graphics/Media/IMutableBrush.cs b/src/Beutl.Graphics/Media/Brushes/IMutableBrush.cs similarity index 100% rename from src/Beutl.Graphics/Media/IMutableBrush.cs rename to src/Beutl.Graphics/Media/Brushes/IMutableBrush.cs diff --git a/src/Beutl.Graphics/Media/IRadialGradientBrush.cs b/src/Beutl.Graphics/Media/Brushes/IRadialGradientBrush.cs similarity index 100% rename from src/Beutl.Graphics/Media/IRadialGradientBrush.cs rename to src/Beutl.Graphics/Media/Brushes/IRadialGradientBrush.cs diff --git a/src/Beutl.Graphics/Media/ISolidColorBrush.cs b/src/Beutl.Graphics/Media/Brushes/ISolidColorBrush.cs similarity index 100% rename from src/Beutl.Graphics/Media/ISolidColorBrush.cs rename to src/Beutl.Graphics/Media/Brushes/ISolidColorBrush.cs diff --git a/src/Beutl.Graphics/Media/ITileBrush.cs b/src/Beutl.Graphics/Media/Brushes/ITileBrush.cs similarity index 100% rename from src/Beutl.Graphics/Media/ITileBrush.cs rename to src/Beutl.Graphics/Media/Brushes/ITileBrush.cs diff --git a/src/Beutl.Graphics/Media/ImageBrush.cs b/src/Beutl.Graphics/Media/Brushes/ImageBrush.cs similarity index 100% rename from src/Beutl.Graphics/Media/ImageBrush.cs rename to src/Beutl.Graphics/Media/Brushes/ImageBrush.cs diff --git a/src/Beutl.Graphics/Media/LinearGradientBrush.cs b/src/Beutl.Graphics/Media/Brushes/LinearGradientBrush.cs similarity index 100% rename from src/Beutl.Graphics/Media/LinearGradientBrush.cs rename to src/Beutl.Graphics/Media/Brushes/LinearGradientBrush.cs diff --git a/src/Beutl.Graphics/Media/RadialGradientBrush.cs b/src/Beutl.Graphics/Media/Brushes/RadialGradientBrush.cs similarity index 100% rename from src/Beutl.Graphics/Media/RadialGradientBrush.cs rename to src/Beutl.Graphics/Media/Brushes/RadialGradientBrush.cs diff --git a/src/Beutl.Graphics/Media/SolidColorBrush.cs b/src/Beutl.Graphics/Media/Brushes/SolidColorBrush.cs similarity index 100% rename from src/Beutl.Graphics/Media/SolidColorBrush.cs rename to src/Beutl.Graphics/Media/Brushes/SolidColorBrush.cs diff --git a/src/Beutl.Graphics/Media/TileBrush.cs b/src/Beutl.Graphics/Media/Brushes/TileBrush.cs similarity index 100% rename from src/Beutl.Graphics/Media/TileBrush.cs rename to src/Beutl.Graphics/Media/Brushes/TileBrush.cs diff --git a/src/Beutl.Graphics/Media/TileMode.cs b/src/Beutl.Graphics/Media/Brushes/TileMode.cs similarity index 100% rename from src/Beutl.Graphics/Media/TileMode.cs rename to src/Beutl.Graphics/Media/Brushes/TileMode.cs diff --git a/src/Beutl.Graphics/Media/FontExtensions.cs b/src/Beutl.Graphics/Media/Font/FontExtensions.cs similarity index 100% rename from src/Beutl.Graphics/Media/FontExtensions.cs rename to src/Beutl.Graphics/Media/Font/FontExtensions.cs diff --git a/src/Beutl.Graphics/Media/FontFamily.cs b/src/Beutl.Graphics/Media/Font/FontFamily.cs similarity index 100% rename from src/Beutl.Graphics/Media/FontFamily.cs rename to src/Beutl.Graphics/Media/Font/FontFamily.cs diff --git a/src/Beutl.Graphics/Media/FontManager.cs b/src/Beutl.Graphics/Media/Font/FontManager.cs similarity index 100% rename from src/Beutl.Graphics/Media/FontManager.cs rename to src/Beutl.Graphics/Media/Font/FontManager.cs diff --git a/src/Beutl.Graphics/Media/FontMetrics.cs b/src/Beutl.Graphics/Media/Font/FontMetrics.cs similarity index 100% rename from src/Beutl.Graphics/Media/FontMetrics.cs rename to src/Beutl.Graphics/Media/Font/FontMetrics.cs diff --git a/src/Beutl.Graphics/Media/FontStyle.cs b/src/Beutl.Graphics/Media/Font/FontStyle.cs similarity index 100% rename from src/Beutl.Graphics/Media/FontStyle.cs rename to src/Beutl.Graphics/Media/Font/FontStyle.cs diff --git a/src/Beutl.Graphics/Media/FontWeight.cs b/src/Beutl.Graphics/Media/Font/FontWeight.cs similarity index 100% rename from src/Beutl.Graphics/Media/FontWeight.cs rename to src/Beutl.Graphics/Media/Font/FontWeight.cs diff --git a/src/Beutl.Graphics/Media/Typeface.cs b/src/Beutl.Graphics/Media/Font/Typeface.cs similarity index 100% rename from src/Beutl.Graphics/Media/Typeface.cs rename to src/Beutl.Graphics/Media/Font/Typeface.cs From 44d5258697f9ad6f789ce739248df64cf3a88eb6 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Thu, 13 Apr 2023 21:27:43 +0900 Subject: [PATCH 38/84] =?UTF-8?q?Geometry=E3=82=AF=E3=83=A9=E3=82=B9?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Graphics/Media/Geometry/Geometry.cs | 131 ++++++++++++++++++ .../Media/Geometry/GeometryContext.cs | 88 ++++++++++++ .../Media/Geometry/IGeometryContext.cs | 30 ++++ .../Media/Geometry/Operations/ArcOperation.cs | 78 +++++++++++ .../Geometry/Operations/CloseOperation.cs | 9 ++ .../Geometry/Operations/ConicOperation.cs | 64 +++++++++ .../Operations/CubicBezierOperation.cs | 64 +++++++++ .../Geometry/Operations/LineOperation.cs | 39 ++++++ .../Geometry/Operations/MoveOperation.cs | 39 ++++++ .../Geometry/Operations/PathOperation.cs | 46 ++++++ .../Geometry/Operations/PathOperations.cs | 5 + .../Operations/QuadraticBezierOperation.cs | 51 +++++++ .../Media/Geometry/PathFillType.cs | 9 ++ .../Media/Geometry/PathGeometry.cs | 131 ++++++++++++++++++ 14 files changed, 784 insertions(+) create mode 100644 src/Beutl.Graphics/Media/Geometry/Geometry.cs create mode 100644 src/Beutl.Graphics/Media/Geometry/GeometryContext.cs create mode 100644 src/Beutl.Graphics/Media/Geometry/IGeometryContext.cs create mode 100644 src/Beutl.Graphics/Media/Geometry/Operations/ArcOperation.cs create mode 100644 src/Beutl.Graphics/Media/Geometry/Operations/CloseOperation.cs create mode 100644 src/Beutl.Graphics/Media/Geometry/Operations/ConicOperation.cs create mode 100644 src/Beutl.Graphics/Media/Geometry/Operations/CubicBezierOperation.cs create mode 100644 src/Beutl.Graphics/Media/Geometry/Operations/LineOperation.cs create mode 100644 src/Beutl.Graphics/Media/Geometry/Operations/MoveOperation.cs create mode 100644 src/Beutl.Graphics/Media/Geometry/Operations/PathOperation.cs create mode 100644 src/Beutl.Graphics/Media/Geometry/Operations/PathOperations.cs create mode 100644 src/Beutl.Graphics/Media/Geometry/Operations/QuadraticBezierOperation.cs create mode 100644 src/Beutl.Graphics/Media/Geometry/PathFillType.cs create mode 100644 src/Beutl.Graphics/Media/Geometry/PathGeometry.cs diff --git a/src/Beutl.Graphics/Media/Geometry/Geometry.cs b/src/Beutl.Graphics/Media/Geometry/Geometry.cs new file mode 100644 index 000000000..e04ea0e0d --- /dev/null +++ b/src/Beutl.Graphics/Media/Geometry/Geometry.cs @@ -0,0 +1,131 @@ +using Beutl.Animation; +using Beutl.Graphics; +using Beutl.Graphics.Transformation; + +using SkiaSharp; + +namespace Beutl.Media; + +public abstract class Geometry : Animatable, IAffectsRender +{ + public static readonly CoreProperty FillTypeProperty; + public static readonly CoreProperty TransformProperty; + private readonly GeometryContext _context = new(); + private PathFillType _fillType; + private ITransform? _transform; + private bool _isDirty = true; + + static Geometry() + { + FillTypeProperty = ConfigureProperty(nameof(FillType)) + .Accessor(o => o.FillType, (o, v) => o.FillType = v) + .Register(); + + TransformProperty = ConfigureProperty(nameof(Transform)) + .Accessor(o => o.Transform, (o, v) => o.Transform = v) + .Register(); + + AffectsRender(FillTypeProperty, TransformProperty); + } + + public Geometry() + { + Invalidated += OnInvalidated; + } + + ~Geometry() + { + _context.Dispose(); + } + + public event EventHandler? Invalidated; + + public PathFillType FillType + { + get => _fillType; + set => SetAndRaise(FillTypeProperty, ref _fillType, value); + } + + public ITransform? Transform + { + get => _transform; + set => SetAndRaise(TransformProperty, ref _transform, value); + } + + public Rect Bounds + { + get + { + if (_isDirty) + { + _context.Clear(); + ApplyTo(_context); + _isDirty = false; + } + + return _context.Bounds; + } + } + + protected static void AffectsRender(params CoreProperty[] properties) + where T : Geometry + { + foreach (CoreProperty item in properties) + { + item.Changed.Subscribe(e => + { + if (e.Sender is T s) + { + s.Invalidated?.Invoke(s, new RenderInvalidatedEventArgs(s, e.Property.Name)); + + if (e.OldValue is IAffectsRender oldAffectsRender) + { + oldAffectsRender.Invalidated -= s.OnAffectsRenderInvalidated; + } + + if (e.NewValue is IAffectsRender newAffectsRender) + { + newAffectsRender.Invalidated += s.OnAffectsRenderInvalidated; + } + } + }); + } + } + + private void OnAffectsRenderInvalidated(object? sender, RenderInvalidatedEventArgs e) + { + Invalidated?.Invoke(this, e); + } + + protected void RaiseInvalidated(RenderInvalidatedEventArgs args) + { + Invalidated?.Invoke(this, args); + } + + public virtual void ApplyTo(IGeometryContext context) + { + if (Transform?.IsEnabled == true) + { + context.Transform(Transform.Value); + } + + context.FillType = _fillType; + } + + private void OnInvalidated(object? sender, RenderInvalidatedEventArgs e) + { + _isDirty = true; + } + + internal SKPath GetNativeObject() + { + if (_isDirty) + { + _context.Clear(); + ApplyTo(_context); + _isDirty = false; + } + + return _context.NativeObject; + } +} diff --git a/src/Beutl.Graphics/Media/Geometry/GeometryContext.cs b/src/Beutl.Graphics/Media/Geometry/GeometryContext.cs new file mode 100644 index 000000000..957b47d8b --- /dev/null +++ b/src/Beutl.Graphics/Media/Geometry/GeometryContext.cs @@ -0,0 +1,88 @@ +using Beutl.Graphics; + +using SkiaSharp; + +namespace Beutl.Media; + +public sealed class GeometryContext : IGeometryContext, IDisposable +{ + ~GeometryContext() + { + NativeObject.Dispose(); + IsDisposed = true; + } + + public SKPath NativeObject { get; } = new(); + + public bool IsDisposed { get; private set; } + + public PathFillType FillType + { + get => (PathFillType)NativeObject.FillType; + set => NativeObject.FillType = (SKPathFillType)value; + } + + public Point LastPoint => NativeObject.LastPoint.ToGraphicsPoint(); + + public Rect Bounds => NativeObject.Bounds.ToGraphicsRect(); + + public void ArcTo(Size radius, float angle, bool isLargeArc, bool sweepClockwise, Point point) + { + NativeObject.ArcTo( + r: new SKPoint(radius.Width, radius.Height), + xAxisRotate: angle, + largeArc: isLargeArc ? SKPathArcSize.Large : SKPathArcSize.Small, + sweep: sweepClockwise ? SKPathDirection.Clockwise : SKPathDirection.CounterClockwise, + xy: point.ToSKPoint()); + } + + public void Clear() + { + NativeObject.Reset(); + } + + public void Close() + { + NativeObject.Close(); + } + + public void ConicTo(Point controlPoint, Point endPoint, float weight) + { + NativeObject.ConicTo(controlPoint.ToSKPoint(), endPoint.ToSKPoint(), weight); + } + + public void CubicTo(Point controlPoint1, Point controlPoint2, Point endPoint) + { + NativeObject.CubicTo(controlPoint1.ToSKPoint(), controlPoint2.ToSKPoint(), endPoint.ToSKPoint()); + } + + public void LineTo(Point point) + { + NativeObject.LineTo(point.ToSKPoint()); + } + + public void MoveTo(Point point) + { + NativeObject.MoveTo(point.ToSKPoint()); + } + + public void QuadraticTo(Point controlPoint, Point endPoint) + { + NativeObject.QuadTo(controlPoint.ToSKPoint(), endPoint.ToSKPoint()); + } + + public void Transform(Matrix matrix) + { + NativeObject.Transform(matrix.ToSKMatrix()); + } + + public void Dispose() + { + if (!IsDisposed) + { + NativeObject.Dispose(); + GC.SuppressFinalize(this); + IsDisposed = true; + } + } +} diff --git a/src/Beutl.Graphics/Media/Geometry/IGeometryContext.cs b/src/Beutl.Graphics/Media/Geometry/IGeometryContext.cs new file mode 100644 index 000000000..534718424 --- /dev/null +++ b/src/Beutl.Graphics/Media/Geometry/IGeometryContext.cs @@ -0,0 +1,30 @@ +using Beutl.Graphics; + +namespace Beutl.Media; + +public interface IGeometryContext +{ + PathFillType FillType { get; set; } + + Point LastPoint { get; } + + Rect Bounds { get; } + + void Clear(); + + void Transform(Matrix matrix); + + void MoveTo(Point point); + + void ArcTo(Size radius, float angle, bool isLargeArc, bool sweepClockwise, Point point); + + void ConicTo(Point controlPoint, Point endPoint, float weight); + + void CubicTo(Point controlPoint1, Point controlPoint2, Point endPoint); + + void LineTo(Point point); + + void QuadraticTo(Point controlPoint, Point endPoint); + + void Close(); +} diff --git a/src/Beutl.Graphics/Media/Geometry/Operations/ArcOperation.cs b/src/Beutl.Graphics/Media/Geometry/Operations/ArcOperation.cs new file mode 100644 index 000000000..8e10c28e1 --- /dev/null +++ b/src/Beutl.Graphics/Media/Geometry/Operations/ArcOperation.cs @@ -0,0 +1,78 @@ +using Beutl.Graphics; + +namespace Beutl.Media; + +public sealed class ArcOperation : PathOperation +{ + public static readonly CoreProperty RadiusProperty; + public static readonly CoreProperty RotationAngleProperty; + public static readonly CoreProperty IsLargeArcProperty; + public static readonly CoreProperty SweepClockwiseProperty; + public static readonly CoreProperty PointProperty; + private Size _radius; + private float _rotationAngle; + private bool _isLargeArc; + private bool _sweepClockwise = true; + private Point _point; + + static ArcOperation() + { + RadiusProperty = ConfigureProperty(nameof(Radius)) + .Accessor(o => o.Radius, (o, v) => o.Radius = v) + .Register(); + + RotationAngleProperty = ConfigureProperty(nameof(RotationAngle)) + .Accessor(o => o.RotationAngle, (o, v) => o.RotationAngle = v) + .Register(); + + IsLargeArcProperty = ConfigureProperty(nameof(IsLargeArc)) + .Accessor(o => o.IsLargeArc, (o, v) => o.IsLargeArc = v) + .Register(); + + SweepClockwiseProperty = ConfigureProperty(nameof(SweepClockwise)) + .Accessor(o => o.SweepClockwise, (o, v) => o.SweepClockwise = v) + .DefaultValue(true) + .Register(); + + PointProperty = ConfigureProperty(nameof(Point)) + .Accessor(o => o.Point, (o, v) => o.Point = v) + .Register(); + + AffectsRender(RadiusProperty, RotationAngleProperty, IsLargeArcProperty, SweepClockwiseProperty, PointProperty); + } + + public Size Radius + { + get => _radius; + set => SetAndRaise(RadiusProperty, ref _radius, value); + } + + public float RotationAngle + { + get => _rotationAngle; + set => SetAndRaise(RotationAngleProperty, ref _rotationAngle, value); + } + + public bool IsLargeArc + { + get => _isLargeArc; + set => SetAndRaise(IsLargeArcProperty, ref _isLargeArc, value); + } + + public bool SweepClockwise + { + get => _sweepClockwise; + set => SetAndRaise(SweepClockwiseProperty, ref _sweepClockwise, value); + } + + public Point Point + { + get => _point; + set => SetAndRaise(PointProperty, ref _point, value); + } + + public override void ApplyTo(IGeometryContext context) + { + context.ArcTo(Radius, RotationAngle, IsLargeArc, SweepClockwise, Point); + } +} diff --git a/src/Beutl.Graphics/Media/Geometry/Operations/CloseOperation.cs b/src/Beutl.Graphics/Media/Geometry/Operations/CloseOperation.cs new file mode 100644 index 000000000..09ccb1c5a --- /dev/null +++ b/src/Beutl.Graphics/Media/Geometry/Operations/CloseOperation.cs @@ -0,0 +1,9 @@ +namespace Beutl.Media; + +public sealed class CloseOperation : PathOperation +{ + public override void ApplyTo(IGeometryContext context) + { + context.Close(); + } +} diff --git a/src/Beutl.Graphics/Media/Geometry/Operations/ConicOperation.cs b/src/Beutl.Graphics/Media/Geometry/Operations/ConicOperation.cs new file mode 100644 index 000000000..849166435 --- /dev/null +++ b/src/Beutl.Graphics/Media/Geometry/Operations/ConicOperation.cs @@ -0,0 +1,64 @@ +using Beutl.Graphics; + +namespace Beutl.Media; + +public sealed class ConicOperation : PathOperation +{ + public static readonly CoreProperty ControlPointProperty; + public static readonly CoreProperty EndPointProperty; + public static readonly CoreProperty WeightProperty; + private Point _controlPoint; + private Point _endPoint; + private float _weight; + + static ConicOperation() + { + ControlPointProperty = ConfigureProperty(nameof(ControlPoint)) + .Accessor(o => o.ControlPoint, (o, v) => o.ControlPoint = v) + .Register(); + + EndPointProperty = ConfigureProperty(nameof(EndPoint)) + .Accessor(o => o.EndPoint, (o, v) => o.EndPoint = v) + .Register(); + + WeightProperty = ConfigureProperty(nameof(Weight)) + .Accessor(o => o.Weight, (o, v) => o.Weight = v) + .Register(); + + AffectsRender(ControlPointProperty, EndPointProperty, WeightProperty); + } + + public ConicOperation() + { + } + + public ConicOperation(Point controlPoint, Point endPoint, float weight) + { + ControlPoint = controlPoint; + EndPoint = endPoint; + Weight = weight; + } + + public Point ControlPoint + { + get => _controlPoint; + set => SetAndRaise(ControlPointProperty, ref _controlPoint, value); + } + + public Point EndPoint + { + get => _endPoint; + set => SetAndRaise(EndPointProperty, ref _endPoint, value); + } + + public float Weight + { + get => _weight; + set => SetAndRaise(WeightProperty, ref _weight, value); + } + + public override void ApplyTo(IGeometryContext context) + { + context.ConicTo(ControlPoint, EndPoint, Weight); + } +} diff --git a/src/Beutl.Graphics/Media/Geometry/Operations/CubicBezierOperation.cs b/src/Beutl.Graphics/Media/Geometry/Operations/CubicBezierOperation.cs new file mode 100644 index 000000000..df6c6058f --- /dev/null +++ b/src/Beutl.Graphics/Media/Geometry/Operations/CubicBezierOperation.cs @@ -0,0 +1,64 @@ +using Beutl.Graphics; + +namespace Beutl.Media; + +public sealed class CubicBezierOperation : PathOperation +{ + public static readonly CoreProperty ControlPoint1Property; + public static readonly CoreProperty ControlPoint2Property; + public static readonly CoreProperty EndPointProperty; + private Point _controlPoint1; + private Point _controlPoint2; + private Point _endPoint; + + static CubicBezierOperation() + { + ControlPoint1Property = ConfigureProperty(nameof(ControlPoint1)) + .Accessor(o => o.ControlPoint1, (o, v) => o.ControlPoint1 = v) + .Register(); + + ControlPoint2Property = ConfigureProperty(nameof(ControlPoint2)) + .Accessor(o => o.ControlPoint2, (o, v) => o.ControlPoint2 = v) + .Register(); + + EndPointProperty = ConfigureProperty(nameof(EndPoint)) + .Accessor(o => o.EndPoint, (o, v) => o.EndPoint = v) + .Register(); + + AffectsRender(ControlPoint1Property, ControlPoint2Property, EndPointProperty); + } + + public CubicBezierOperation() + { + } + + public CubicBezierOperation(Point controlPoint1, Point controlPoint2, Point endPoint) + { + ControlPoint1 = controlPoint1; + ControlPoint2 = controlPoint2; + EndPoint = endPoint; + } + + public Point ControlPoint1 + { + get => _controlPoint1; + set => SetAndRaise(ControlPoint1Property, ref _controlPoint1, value); + } + + public Point ControlPoint2 + { + get => _controlPoint2; + set => SetAndRaise(ControlPoint2Property, ref _controlPoint2, value); + } + + public Point EndPoint + { + get => _endPoint; + set => SetAndRaise(EndPointProperty, ref _endPoint, value); + } + + public override void ApplyTo(IGeometryContext context) + { + context.CubicTo(ControlPoint1, ControlPoint2,EndPoint); + } +} diff --git a/src/Beutl.Graphics/Media/Geometry/Operations/LineOperation.cs b/src/Beutl.Graphics/Media/Geometry/Operations/LineOperation.cs new file mode 100644 index 000000000..7f2816556 --- /dev/null +++ b/src/Beutl.Graphics/Media/Geometry/Operations/LineOperation.cs @@ -0,0 +1,39 @@ +using Beutl.Graphics; + +namespace Beutl.Media; + +public sealed class LineOperation : PathOperation +{ + public static readonly CoreProperty PointProperty; + private Point _point; + + static LineOperation() + { + PointProperty = ConfigureProperty(nameof(Point)) + .Accessor(o => o.Point, (o, v) => o.Point = v) + .Register(); + + AffectsRender(PointProperty); + } + + public LineOperation(Point point) + { + Point = point; + } + + public LineOperation(float x, float y) + : this(new Point(x, y)) + { + } + + public Point Point + { + get => _point; + set => SetAndRaise(PointProperty, ref _point, value); + } + + public override void ApplyTo(IGeometryContext context) + { + context.LineTo(Point); + } +} diff --git a/src/Beutl.Graphics/Media/Geometry/Operations/MoveOperation.cs b/src/Beutl.Graphics/Media/Geometry/Operations/MoveOperation.cs new file mode 100644 index 000000000..738c23fd6 --- /dev/null +++ b/src/Beutl.Graphics/Media/Geometry/Operations/MoveOperation.cs @@ -0,0 +1,39 @@ +using Beutl.Graphics; + +namespace Beutl.Media; + +public sealed class MoveOperation : PathOperation +{ + public static readonly CoreProperty PointProperty; + private Point _point; + + static MoveOperation() + { + PointProperty = ConfigureProperty(nameof(Point)) + .Accessor(o => o.Point, (o, v) => o.Point = v) + .Register(); + + AffectsRender(PointProperty); + } + + public MoveOperation(Point point) + { + Point = point; + } + + public MoveOperation(float x, float y) + : this(new Point(x, y)) + { + } + + public Point Point + { + get => _point; + set => SetAndRaise(PointProperty, ref _point, value); + } + + public override void ApplyTo(IGeometryContext context) + { + context.MoveTo(Point); + } +} diff --git a/src/Beutl.Graphics/Media/Geometry/Operations/PathOperation.cs b/src/Beutl.Graphics/Media/Geometry/Operations/PathOperation.cs new file mode 100644 index 000000000..7380884f2 --- /dev/null +++ b/src/Beutl.Graphics/Media/Geometry/Operations/PathOperation.cs @@ -0,0 +1,46 @@ +using Beutl.Animation; +using Beutl.Graphics; + +namespace Beutl.Media; + +public abstract class PathOperation : Animatable, IAffectsRender +{ + public event EventHandler? Invalidated; + + public abstract void ApplyTo(IGeometryContext context); + + protected static void AffectsRender(params CoreProperty[] properties) + where T : PathOperation + { + foreach (CoreProperty item in properties) + { + item.Changed.Subscribe(e => + { + if (e.Sender is T s) + { + s.Invalidated?.Invoke(s, new RenderInvalidatedEventArgs(s, e.Property.Name)); + + if (e.OldValue is IAffectsRender oldAffectsRender) + { + oldAffectsRender.Invalidated -= s.OnAffectsRenderInvalidated; + } + + if (e.NewValue is IAffectsRender newAffectsRender) + { + newAffectsRender.Invalidated += s.OnAffectsRenderInvalidated; + } + } + }); + } + } + + private void OnAffectsRenderInvalidated(object? sender, RenderInvalidatedEventArgs e) + { + Invalidated?.Invoke(this, e); + } + + protected void RaiseInvalidated(RenderInvalidatedEventArgs args) + { + Invalidated?.Invoke(this, args); + } +} diff --git a/src/Beutl.Graphics/Media/Geometry/Operations/PathOperations.cs b/src/Beutl.Graphics/Media/Geometry/Operations/PathOperations.cs new file mode 100644 index 000000000..192f590b4 --- /dev/null +++ b/src/Beutl.Graphics/Media/Geometry/Operations/PathOperations.cs @@ -0,0 +1,5 @@ +namespace Beutl.Media; + +public sealed class PathOperations : AffectsRenders +{ +} diff --git a/src/Beutl.Graphics/Media/Geometry/Operations/QuadraticBezierOperation.cs b/src/Beutl.Graphics/Media/Geometry/Operations/QuadraticBezierOperation.cs new file mode 100644 index 000000000..80f3eafba --- /dev/null +++ b/src/Beutl.Graphics/Media/Geometry/Operations/QuadraticBezierOperation.cs @@ -0,0 +1,51 @@ +using Beutl.Graphics; + +namespace Beutl.Media; + +public sealed class QuadraticBezierOperation : PathOperation +{ + public static readonly CoreProperty ControlPointProperty; + public static readonly CoreProperty EndPointProperty; + private Point _controlPoint; + private Point _endPoint; + + static QuadraticBezierOperation() + { + ControlPointProperty = ConfigureProperty(nameof(ControlPoint)) + .Accessor(o => o.ControlPoint, (o, v) => o.ControlPoint = v) + .Register(); + + EndPointProperty = ConfigureProperty(nameof(EndPoint)) + .Accessor(o => o.EndPoint, (o, v) => o.EndPoint = v) + .Register(); + + AffectsRender(ControlPointProperty, EndPointProperty); + } + + public QuadraticBezierOperation() + { + } + + public QuadraticBezierOperation(Point controlPoint, Point endPoint) + { + ControlPoint = controlPoint; + EndPoint = endPoint; + } + + public Point ControlPoint + { + get => _controlPoint; + set => SetAndRaise(ControlPointProperty, ref _controlPoint, value); + } + + public Point EndPoint + { + get => _endPoint; + set => SetAndRaise(EndPointProperty, ref _endPoint, value); + } + + public override void ApplyTo(IGeometryContext context) + { + context.QuadraticTo(ControlPoint, EndPoint); + } +} diff --git a/src/Beutl.Graphics/Media/Geometry/PathFillType.cs b/src/Beutl.Graphics/Media/Geometry/PathFillType.cs new file mode 100644 index 000000000..8744c9374 --- /dev/null +++ b/src/Beutl.Graphics/Media/Geometry/PathFillType.cs @@ -0,0 +1,9 @@ +namespace Beutl.Media; + +public enum PathFillType +{ + Winding = 0, + EvenOdd = 1, + InverseWinding = 2, + InverseEvenOdd = 3 +} diff --git a/src/Beutl.Graphics/Media/Geometry/PathGeometry.cs b/src/Beutl.Graphics/Media/Geometry/PathGeometry.cs new file mode 100644 index 000000000..a3730205b --- /dev/null +++ b/src/Beutl.Graphics/Media/Geometry/PathGeometry.cs @@ -0,0 +1,131 @@ +using Beutl.Graphics; + +using SkiaSharp; + +namespace Beutl.Media; + +public sealed class PathGeometry : Geometry +{ + public static readonly CoreProperty OperationsProperty; + private readonly PathOperations _operations = new(); + + static PathGeometry() + { + OperationsProperty = ConfigureProperty(nameof(Operations)) + .Accessor(o => o.Operations, (o, v) => o.Operations = v) + .Register(); + } + + public PathGeometry() + { + _operations.Invalidated += (_, e) => RaiseInvalidated(e); + } + + [NotAutoSerialized] + public PathOperations Operations + { + get => _operations; + set => _operations.Replace(value); + } + + public static PathGeometry Parse(string svg) + { + using var path = SKPath.ParseSvgPathData(svg); + using SKPath.RawIterator it = path.CreateRawIterator(); + + var result = new PathGeometry(); + Span points = stackalloc SKPoint[4]; + SKPathVerb pathVerb; + + do + { + pathVerb = it.Next(points); + PathOperation? operation; + + operation = pathVerb switch + { + SKPathVerb.Move => new MoveOperation(points[0].ToGraphicsPoint()), + SKPathVerb.Line => new LineOperation(points[1].ToGraphicsPoint()), + SKPathVerb.Quad => new QuadraticBezierOperation(points[1].ToGraphicsPoint(), points[2].ToGraphicsPoint()), + SKPathVerb.Conic => new ConicOperation(points[1].ToGraphicsPoint(), points[2].ToGraphicsPoint(), it.ConicWeight()), + SKPathVerb.Cubic => new CubicBezierOperation(points[1].ToGraphicsPoint(), points[2].ToGraphicsPoint(), points[3].ToGraphicsPoint()), + SKPathVerb.Close => new CloseOperation(), + SKPathVerb.Done or _ => null, + }; + + if (operation != null) + { + result.Operations.Add(operation); + } + + } while (pathVerb != SKPathVerb.Done); + + return result; + } + + public void MoveTo(Point point) + { + Operations.Add(new MoveOperation(point)); + } + + public void ArcTo(Size radius, float angle, bool isLargeArc, bool sweepClockwise, Point point) + { + Operations.Add(new ArcOperation() + { + Radius = radius, + RotationAngle = angle, + IsLargeArc = isLargeArc, + SweepClockwise = sweepClockwise, + Point = point + }); + } + + public void ConicTo(Point controlPoint, Point endPoint, float weight) + { + Operations.Add(new ConicOperation() + { + ControlPoint = controlPoint, + EndPoint = endPoint, + Weight = weight + }); + } + + public void CubicTo(Point controlPoint1, Point controlPoint2, Point endPoint) + { + Operations.Add(new CubicBezierOperation() + { + ControlPoint1 = controlPoint1, + ControlPoint2 = controlPoint2, + EndPoint = endPoint + }); + } + + public void LineTo(Point point) + { + Operations.Add(new LineOperation(point)); + } + + public void QuadraticTo(Point controlPoint, Point endPoint) + { + Operations.Add(new QuadraticBezierOperation() + { + ControlPoint = controlPoint, + EndPoint = endPoint + }); + } + + public void Close() + { + Operations.Add(new CloseOperation()); + } + + public override void ApplyTo(IGeometryContext context) + { + base.ApplyTo(context); + + foreach (PathOperation item in Operations.GetMarshal().Value) + { + item.ApplyTo(context); + } + } +} From 2142bd008d59db3754b7601a18ab8e1bfa9bfe8e Mon Sep 17 00:00:00 2001 From: indigo-san Date: Wed, 26 Apr 2023 21:38:07 +0900 Subject: [PATCH 39/84] =?UTF-8?q?Shape=E3=82=92Geometry=E3=83=99=E3=83=BC?= =?UTF-8?q?=E3=82=B9=E3=81=AE=E3=82=82=E3=81=AE=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Graphics/Graphics/Canvas.cs | 313 +++++++++--------- src/Beutl.Graphics/Graphics/Drawable.cs | 41 +-- src/Beutl.Graphics/Graphics/ICanvas.cs | 42 +-- src/Beutl.Graphics/Graphics/IDrawable.cs | 4 - src/Beutl.Graphics/Graphics/NullCanvas.cs | 17 +- src/Beutl.Graphics/Graphics/PushedState.cs | 12 +- .../Graphics/PushedStateType.cs | 6 +- src/Beutl.Graphics/Graphics/Rect.cs | 14 + src/Beutl.Graphics/Graphics/Shapes/Ellipse.cs | 43 --- .../Graphics/Shapes/EllipseShape.cs | 79 +++++ .../Graphics/Shapes/GeometryShape.cs | 29 ++ .../Graphics/Shapes/RectShape.cs | 75 +++++ .../Graphics/Shapes/Rectangle.cs | 43 --- .../Graphics/Shapes/RoundedRect.cs | 146 -------- .../Graphics/Shapes/RoundedRectShape.cs | 144 ++++++++ src/Beutl.Graphics/Graphics/Shapes/Shape.cs | 245 ++++++++++++++ .../Graphics/Shapes/TextBlock.cs | 4 +- .../Graphics/SkiaSharpExtensions.cs | 5 + src/Beutl.Graphics/Media/IPen.cs | 23 ++ .../Media/Immutable/ImmutablePen.cs | 62 ++++ src/Beutl.Graphics/Media/Pen.cs | 183 ++++++++++ src/Beutl.Graphics/Media/StrokeCap.cs | 8 + src/Beutl.Graphics/Media/StrokeJoin.cs | 8 + .../Source/EllipseOperation.cs | 4 +- src/Beutl.Operators/Source/RectOperator.cs | 4 +- .../Source/RoundedRectOperator.cs | 6 +- .../NodeTree/Nodes/RectNode.cs | 14 +- tests/Beutl.Graphics.UnitTests/ShapeTests.cs | 206 ++++++++++++ tests/Beutl.Graphics.UnitTests/StyleTests.cs | 14 +- 29 files changed, 1311 insertions(+), 483 deletions(-) delete mode 100644 src/Beutl.Graphics/Graphics/Shapes/Ellipse.cs create mode 100644 src/Beutl.Graphics/Graphics/Shapes/EllipseShape.cs create mode 100644 src/Beutl.Graphics/Graphics/Shapes/GeometryShape.cs create mode 100644 src/Beutl.Graphics/Graphics/Shapes/RectShape.cs delete mode 100644 src/Beutl.Graphics/Graphics/Shapes/Rectangle.cs delete mode 100644 src/Beutl.Graphics/Graphics/Shapes/RoundedRect.cs create mode 100644 src/Beutl.Graphics/Graphics/Shapes/RoundedRectShape.cs create mode 100644 src/Beutl.Graphics/Graphics/Shapes/Shape.cs create mode 100644 src/Beutl.Graphics/Media/IPen.cs create mode 100644 src/Beutl.Graphics/Media/Immutable/ImmutablePen.cs create mode 100644 src/Beutl.Graphics/Media/Pen.cs create mode 100644 src/Beutl.Graphics/Media/StrokeCap.cs create mode 100644 src/Beutl.Graphics/Media/StrokeJoin.cs create mode 100644 tests/Beutl.Graphics.UnitTests/ShapeTests.cs diff --git a/src/Beutl.Graphics/Graphics/Canvas.cs b/src/Beutl.Graphics/Graphics/Canvas.cs index bbf978f57..9f898922d 100644 --- a/src/Beutl.Graphics/Graphics/Canvas.cs +++ b/src/Beutl.Graphics/Graphics/Canvas.cs @@ -21,10 +21,12 @@ public class Canvas : ICanvas private readonly Dispatcher? _dispatcher; private readonly Stack _brushesStack = new(); private readonly Stack _maskStack = new(); - private readonly Stack _filterStack = new(); + private readonly Stack _filterStack = new(); + private readonly Stack _penStack = new(); private readonly Stack _strokeWidthStack = new(); private readonly Stack _blendModeStack = new(); - private readonly SKPaint _sharedPaint = new(); + private readonly SKPaint _sharedFillPaint = new(); + private readonly SKPaint _sharedStrokePaint = new(); private Matrix _currentTransform; public Canvas(int width, int height) @@ -46,11 +48,20 @@ public Canvas(int width, int height) public bool IsDisposed { get; private set; } - public IBrush Foreground { get; set; } = Brushes.White; + public IBrush FillBrush { get; set; } = Brushes.White; - public IImageFilter? Filter { get; set; } + public IPen? Pen { get; set; } - public float StrokeWidth { get; set; } + public IImageFilter? Filter + { + get + { + if (_filterStack.Count == 0) + return null; + + return _filterStack.PeekOrDefault(default).ImageFilter; + } + } public BlendMode BlendMode { get; set; } = BlendMode.SrcOver; @@ -87,10 +98,10 @@ public void ClipRect(Rect clip, ClipOperation operation = ClipOperation.Intersec _canvas.ClipRect(clip.ToSKRect(), operation.ToSKClipOperation()); } - public void ClipPath(SKPath path, ClipOperation operation = ClipOperation.Intersect) + public void ClipPath(Geometry geometry, ClipOperation operation = ClipOperation.Intersect) { VerifyAccess(); - _canvas.ClipPath(path, operation.ToSKClipOperation()); + _canvas.ClipPath(geometry.GetNativeObject(), operation.ToSKClipOperation()); } public void Dispose() @@ -98,7 +109,8 @@ public void Dispose() void DisposeCore() { _surface.Dispose(); - _sharedPaint.Dispose(); + _sharedFillPaint.Dispose(); + _sharedStrokePaint.Dispose(); GC.SuppressFinalize(this); IsDisposed = true; } @@ -122,74 +134,66 @@ public void DrawBitmap(IBitmap bmp) return; VerifyAccess(); - _sharedPaint.Reset(); - ConfigurePaint(_sharedPaint, new Size(bmp.Width, bmp.Height)); + var size = new Size(bmp.Width, bmp.Height); + ConfigureFillPaint(size); + ConfigureStrokePaint(size); if (bmp is Bitmap) { using var img = SKImage.FromPixels(new SKImageInfo(bmp.Width, bmp.Height, SKColorType.Bgra8888), bmp.Data); - _canvas.DrawImage(img, SKPoint.Empty, _sharedPaint); + _canvas.DrawImage(img, SKPoint.Empty, _sharedFillPaint); } else { using var skbmp = bmp.ToSKBitmap(); - _canvas.DrawBitmap(skbmp, SKPoint.Empty, _sharedPaint); + _canvas.DrawBitmap(skbmp, SKPoint.Empty, _sharedFillPaint); } } - public void DrawCircle(Size size) + public void DrawCircle(Rect rect) { VerifyAccess(); - _sharedPaint.Reset(); - ConfigurePaint(_sharedPaint, size); - float line = StrokeWidth; - - if (line >= MathF.Min(size.Width, size.Height) / 2) - line = MathF.Min(size.Width, size.Height) / 2; + ConfigureFillPaint(rect.Size); - float min = MathF.Min(size.Width, size.Height); - - if (line < min) min = line; - if (min < 0) min = 0; + _canvas.DrawOval(rect.ToSKRect(), _sharedFillPaint); + if (Pen != null && Pen.Thickness != 0) + { + rect = rect.Inflate(Pen.Thickness); - _sharedPaint.Style = SKPaintStyle.Stroke; - _sharedPaint.StrokeWidth = min; + ConfigureStrokePaint(rect.Size); - _canvas.DrawOval( - size.Width / 2, size.Height / 2, - (size.Width - min) / 2, (size.Height - min) / 2, - _sharedPaint); + _canvas.DrawOval(rect.ToSKRect(), _sharedStrokePaint); + } } - public void DrawRect(Size size) + public void DrawRect(Rect rect) { VerifyAccess(); - _sharedPaint.Reset(); - ConfigurePaint(_sharedPaint, size); - float stroke = Math.Min(StrokeWidth, Math.Min(size.Width, size.Height)) / 2; + ConfigureFillPaint(rect.Size); + + _canvas.DrawRect(rect.ToSKRect(), _sharedFillPaint); + + if (Pen != null && Pen.Thickness != 0) + { + rect = rect.Inflate(Pen.Thickness); - _sharedPaint.Style = SKPaintStyle.Stroke; - _sharedPaint.StrokeWidth = stroke; + ConfigureStrokePaint(rect.Size); - _canvas.DrawRect( - stroke / 2, stroke / 2, - size.Width - stroke, size.Height - stroke, - _sharedPaint); + _canvas.DrawRect(rect.ToSKRect(), _sharedStrokePaint); + } } public void DrawText(FormattedText text) { VerifyAccess(); - _sharedPaint.Reset(); - var typeface = new Typeface(text.Font, text.Style, text.Weight); SKTypeface sktypeface = typeface.ToSkia(); - ConfigurePaint(_sharedPaint, text.Bounds); - _sharedPaint.TextSize = text.Size; - _sharedPaint.Typeface = sktypeface; - _sharedPaint.Style = SKPaintStyle.Fill; + ConfigureFillPaint(text.Bounds); + _sharedFillPaint.TextSize = text.Size; + _sharedFillPaint.Typeface = sktypeface; + _sharedFillPaint.Style = SKPaintStyle.Fill; Span sc = stackalloc char[1]; float prevRight = 0; @@ -197,17 +201,17 @@ public void DrawText(FormattedText text) { sc[0] = item; var bounds = default(SKRect); - float w = _sharedPaint.MeasureText(sc, ref bounds); + float w = _sharedFillPaint.MeasureText(sc, ref bounds); _canvas.Save(); _canvas.Translate(prevRight + bounds.Left, 0); - SKPath path = _sharedPaint.GetTextPath( + SKPath path = _sharedFillPaint.GetTextPath( sc, (bounds.Width / 2) - bounds.MidX, 0/*-_paint.FontMetrics.Ascent*/); - _canvas.DrawPath(path, _sharedPaint); + _canvas.DrawPath(path, _sharedFillPaint); path.Dispose(); prevRight += text.Spacing; @@ -217,25 +221,23 @@ public void DrawText(FormattedText text) } } - public void FillCircle(Size size) + public void DrawGeometry(Geometry geometry) { VerifyAccess(); - _sharedPaint.Reset(); - ConfigurePaint(_sharedPaint, size); - _sharedPaint.Style = SKPaintStyle.Fill; + SKPath skPath = geometry.GetNativeObject(); + Rect rect = geometry.Bounds; + ConfigureFillPaint(rect.Size); - _canvas.DrawOval(SKPoint.Empty, size.ToSKSize(), _sharedPaint); - } + _canvas.DrawPath(skPath, _sharedFillPaint); - public void FillRect(Size size) - { - VerifyAccess(); - _sharedPaint.Reset(); - ConfigurePaint(_sharedPaint, size); + if (Pen != null && Pen.Thickness != 0) + { + rect = rect.Inflate(Pen.Thickness); - _sharedPaint.Style = SKPaintStyle.Fill; + ConfigureStrokePaint(rect.Size); - _canvas.DrawRect(0, 0, size.Width, size.Height, _sharedPaint); + _canvas.DrawPath(skPath, _sharedStrokePaint); + } } public unsafe Bitmap GetBitmap() @@ -248,6 +250,27 @@ public unsafe Bitmap GetBitmap() return result; } + public PushedState PushPen(IPen? pen) + { + VerifyAccess(); + int level = _brushesStack.Count; + _penStack.Push(Pen); + Pen = pen; + return new PushedState(this, level, PushedStateType.Pen); + } + + public void PopPen(int level = -1) + { + VerifyAccess(); + level = level < 0 ? _penStack.Count - 1 : level; + + while (_penStack.Count > level && + _penStack.TryPop(out IPen? state)) + { + Pen = state; + } + } + public PushedState PushClip(Rect clip, ClipOperation operation = ClipOperation.Intersect) { VerifyAccess(); @@ -283,7 +306,7 @@ public PushedState PushOpacityMask(IBrush mask, Rect bounds, bool invert = false var paint = new SKPaint(); int level = _canvas.SaveLayer(paint); - ConfigurePaint(paint, bounds.Size, mask, (BlendMode)paint.BlendMode, null, paint.StrokeWidth); + ConfigurePaint(paint, bounds.Size, mask, (BlendMode)paint.BlendMode); _maskStack.Push(new MaskInfo(invert, paint)); return new PushedState(this, level, PushedStateType.OpacityMask); } @@ -292,10 +315,10 @@ public void PopOpacityMask(int level = -1) { VerifyAccess(); MaskInfo maskInfo = _maskStack.Pop(); - _sharedPaint.Reset(); - _sharedPaint.BlendMode = maskInfo.Invert ? SKBlendMode.DstOut : SKBlendMode.DstIn; + _sharedFillPaint.Reset(); + _sharedFillPaint.BlendMode = maskInfo.Invert ? SKBlendMode.DstOut : SKBlendMode.DstIn; - _canvas.SaveLayer(_sharedPaint); + _canvas.SaveLayer(_sharedFillPaint); using (SKPaint maskPaint = maskInfo.Paint) { _canvas.DrawPaint(maskPaint); @@ -306,16 +329,16 @@ public void PopOpacityMask(int level = -1) _canvas.RestoreToCount(level); } - public PushedState PushForeground(IBrush brush) + public PushedState PushFillBrush(IBrush brush) { VerifyAccess(); int level = _brushesStack.Count; - _brushesStack.Push(Foreground); - Foreground = brush; - return new PushedState(this, level, PushedStateType.Foreground); + _brushesStack.Push(FillBrush); + FillBrush = brush; + return new PushedState(this, level, PushedStateType.FillBrush); } - public void PopForeground(int level = -1) + public void PopFillBrush(int level = -1) { VerifyAccess(); level = level < 0 ? _brushesStack.Count - 1 : level; @@ -323,50 +346,38 @@ public void PopForeground(int level = -1) while (_brushesStack.Count > level && _brushesStack.TryPop(out IBrush? state)) { - Foreground = state; + FillBrush = state; } } - public PushedState PushStrokeWidth(float strokeWidth) - { - VerifyAccess(); - int level = _strokeWidthStack.Count; - _strokeWidthStack.Push(StrokeWidth); - StrokeWidth = strokeWidth; - return new PushedState(this, level, PushedStateType.StrokeWidth); - } - - public void PopStrokeWidth(int level = -1) + public PushedState PushImageFilter(IImageFilter filter) { VerifyAccess(); - level = level < 0 ? _strokeWidthStack.Count - 1 : level; - - while (_strokeWidthStack.Count > level && - _strokeWidthStack.TryPop(out float state)) + SKPaint? paint; + using (var skFilter = filter.ToSKImageFilter()) { - StrokeWidth = state; + paint = new SKPaint + { + ImageFilter = skFilter + }; } - } - public PushedState PushFilters(IImageFilter? filter) - { - VerifyAccess(); - int level = _filterStack.Count; - _filterStack.Push(Filter); - Filter = filter; + int level = _canvas.SaveLayer(paint); + + _filterStack.Push(new ImageFilterInfo(filter, paint, level)); return new PushedState(this, level, PushedStateType.Filter); } - public void PopFilters(int level = -1) + public void PopImageFilter(int level = -1) { VerifyAccess(); - level = level < 0 ? _filterStack.Count - 1 : level; - - while (_filterStack.Count > level && - _filterStack.TryPop(out IImageFilter? state)) + while (_filterStack.TryPop(out ImageFilterInfo state) + && state.SaveCount >= level) { - Filter = state; + state.Paint.Dispose(); } + + _canvas.RestoreToCount(level); } public PushedState PushBlendMode(BlendMode blendMode) @@ -418,41 +429,6 @@ public void PopTransform(int level = -1) _currentTransform = _canvas.TotalMatrix.ToMatrix(); } - public void RotateDegrees(float degrees) - { - VerifyAccess(); - _canvas.RotateDegrees(degrees); - _currentTransform = _canvas.TotalMatrix.ToMatrix(); - } - - public void RotateRadians(float radians) - { - VerifyAccess(); - _canvas.RotateRadians(radians); - _currentTransform = _canvas.TotalMatrix.ToMatrix(); - } - - public void Scale(Vector vector) - { - VerifyAccess(); - _canvas.Scale(vector.X, vector.Y); - _currentTransform = _canvas.TotalMatrix.ToMatrix(); - } - - public void Skew(Vector vector) - { - VerifyAccess(); - _canvas.Skew(vector.X, vector.Y); - _currentTransform = _canvas.TotalMatrix.ToMatrix(); - } - - public void Translate(Vector vector) - { - VerifyAccess(); - _canvas.Translate(vector.X, vector.Y); - _currentTransform = _canvas.TotalMatrix.ToMatrix(); - } - private void VerifyAccess() { if (IsDisposed) @@ -663,40 +639,73 @@ private static void ConfigureTileBrush(SKPaint paint, Size targetSize, ITileBrus tileTransform = tileTransform.PreConcat(transform.ToSKMatrix()); } - SKShader shader = intermediate.ToShader(tileX, tileY, tileTransform); + using (SKShader shader = intermediate.ToShader(tileX, tileY, tileTransform)) + { + paint.Shader = shader; + } + } + + private void ConfigureStrokePaint(Size targetSize) + { + _sharedStrokePaint.Reset(); + IPen? pen = Pen; + + if (pen != null && pen.Thickness != 0) + { + float thickness = pen.Thickness; + + _sharedStrokePaint.IsStroke = true; + _sharedStrokePaint.StrokeWidth = thickness; + _sharedStrokePaint.StrokeCap = (SKStrokeCap)pen.StrokeCap; + _sharedStrokePaint.StrokeJoin = (SKStrokeJoin)pen.StrokeJoin; + _sharedStrokePaint.StrokeMiter = pen.MiterLimit; + if (pen.DashArray != null && pen.DashArray.Count > 0) + { + IReadOnlyList srcDashes = pen.DashArray; + + int count = srcDashes.Count % 2 == 0 ? srcDashes.Count : srcDashes.Count * 2; + + float[] dashesArray = new float[count]; - paint.Shader = shader; + for (int i = 0; i < count; ++i) + { + dashesArray[i] = (float)srcDashes[i % srcDashes.Count] * thickness; + } + + float offset = (float)(pen.DashOffset * thickness); + + var pe = SKPathEffect.CreateDash(dashesArray, offset); + + _sharedStrokePaint.PathEffect = pe; + } + + ConfigurePaint(_sharedStrokePaint, targetSize, pen.Brush, BlendMode); + } } - private void ConfigurePaint(SKPaint paint, Size targetSize) + private void ConfigureFillPaint(Size targetSize) { - ConfigurePaint(paint, targetSize, Foreground, BlendMode, Filter, StrokeWidth); + _sharedFillPaint.Reset(); + ConfigurePaint(_sharedFillPaint, targetSize, FillBrush, BlendMode); } - private static void ConfigurePaint(SKPaint paint, Size targetSize, IBrush foreground, BlendMode blendMode, IImageFilter? filters, float strokeWidth) + private static void ConfigurePaint(SKPaint paint, Size targetSize, IBrush? brush, BlendMode blendMode) { - double opacity = foreground.Opacity; - paint.StrokeWidth = strokeWidth; + float opacity = brush?.Opacity ?? 0; paint.IsAntialias = true; paint.BlendMode = (SKBlendMode)blendMode; - paint.ImageFilter?.Dispose(); - paint.ImageFilter = null; - if (filters != null) - { - paint.ImageFilter = filters.ToSKImageFilter(); - } paint.Color = new SKColor(255, 255, 255, (byte)(255 * opacity)); - if (foreground is ISolidColorBrush solid) + if (brush is ISolidColorBrush solid) { paint.Color = new SKColor(solid.Color.R, solid.Color.G, solid.Color.B, (byte)(solid.Color.A * opacity)); } - else if (foreground is IGradientBrush gradient) + else if (brush is IGradientBrush gradient) { ConfigureGradientBrush(paint, targetSize, gradient); } - else if (foreground is ITileBrush tileBrush) + else if (brush is ITileBrush tileBrush) { ConfigureTileBrush(paint, targetSize, tileBrush); } @@ -708,6 +717,10 @@ private static void ConfigurePaint(SKPaint paint, Size targetSize, IBrush foregr private readonly record struct MaskInfo(bool Invert, SKPaint Paint); + private readonly record struct ImageFilterInfo(IImageFilter ImageFilter, SKPaint Paint, int SaveCount); + + private readonly record struct PenInfo(IPen? Pen, SKPaint? Paint, int SaveCount); + private readonly struct TileBrushCalculator { private readonly Size _imageSize; diff --git a/src/Beutl.Graphics/Graphics/Drawable.cs b/src/Beutl.Graphics/Graphics/Drawable.cs index d5a7b3696..38fdec7b0 100644 --- a/src/Beutl.Graphics/Graphics/Drawable.cs +++ b/src/Beutl.Graphics/Graphics/Drawable.cs @@ -13,8 +13,6 @@ namespace Beutl.Graphics; public abstract class Drawable : Renderable, IDrawable, IHierarchical { - public static readonly CoreProperty WidthProperty; - public static readonly CoreProperty HeightProperty; public static readonly CoreProperty TransformProperty; public static readonly CoreProperty FilterProperty; public static readonly CoreProperty EffectProperty; @@ -24,8 +22,6 @@ public abstract class Drawable : Renderable, IDrawable, IHierarchical public static readonly CoreProperty ForegroundProperty; public static readonly CoreProperty OpacityMaskProperty; public static readonly CoreProperty BlendModeProperty; - private float _width = 0; - private float _height = 0; private ITransform? _transform; private IImageFilter? _filter; private IBitmapEffect? _effect; @@ -38,16 +34,6 @@ public abstract class Drawable : Renderable, IDrawable, IHierarchical static Drawable() { - WidthProperty = ConfigureProperty(nameof(Width)) - .Accessor(o => o.Width, (o, v) => o.Width = v) - .DefaultValue(0) - .Register(); - - HeightProperty = ConfigureProperty(nameof(Height)) - .Accessor(o => o.Height, (o, v) => o.Height = v) - .DefaultValue(0) - .Register(); - TransformProperty = ConfigureProperty(nameof(Transform)) .Accessor(o => o.Transform, (o, v) => o.Transform = v) .DefaultValue(null) @@ -92,7 +78,6 @@ static Drawable() .Register(); AffectsRender( - WidthProperty, HeightProperty, TransformProperty, FilterProperty, EffectProperty, AlignmentXProperty, AlignmentYProperty, TransformOriginProperty, @@ -100,22 +85,6 @@ static Drawable() BlendModeProperty); } - [Display(Name = nameof(Strings.Width), ResourceType = typeof(Strings))] - [Range(0, float.MaxValue)] - public float Width - { - get => _width; - set => SetAndRaise(WidthProperty, ref _width, value); - } - - [Display(Name = nameof(Strings.Height), ResourceType = typeof(Strings))] - [Range(0, float.MaxValue)] - public float Height - { - get => _height; - set => SetAndRaise(HeightProperty, ref _height, value); - } - public Rect Bounds { get; private set; } [Display(Name = nameof(Strings.Transform), ResourceType = typeof(Strings))] @@ -189,7 +158,7 @@ public IBitmap ToBitmap() if (width > 0 && height > 0) { using (var canvas = new Canvas(width, height)) - using (Foreground == null ? new() : canvas.PushForeground(Foreground)) + using (Foreground == null ? new() : canvas.PushFillBrush(Foreground)) { OnDraw(canvas); return canvas.GetBitmap(); @@ -256,10 +225,10 @@ private void HasBitmapEffect(ICanvas canvas) } rect = rect.TransformToAABB(transform); - using (Foreground == null ? new() : canvas.PushForeground(Foreground)) + using (Foreground == null ? new() : canvas.PushFillBrush(Foreground)) using (canvas.PushBlendMode(BlendMode)) using (canvas.PushTransform(transformFact)) - using (_filter == null ? new() : canvas.PushFilters(_filter)) + using (_filter == null ? new() : canvas.PushImageFilter(_filter)) using (OpacityMask == null ? new() : canvas.PushOpacityMask(OpacityMask, rect)) { IBitmap bitmap = ToBitmap(); @@ -315,10 +284,10 @@ public void Draw(ICanvas canvas) Matrix transform = GetTransformMatrix(availableSize, size); - using (Foreground == null ? new() : canvas.PushForeground(Foreground)) + using (Foreground == null ? new() : canvas.PushFillBrush(Foreground)) using (canvas.PushBlendMode(BlendMode)) using (canvas.PushTransform(transform)) - using (_filter == null ? new() : canvas.PushFilters(_filter)) + using (_filter == null ? new() : canvas.PushImageFilter(_filter)) using (OpacityMask == null ? new() : canvas.PushOpacityMask(OpacityMask, new Rect(size))) { OnDraw(canvas); diff --git a/src/Beutl.Graphics/Graphics/ICanvas.cs b/src/Beutl.Graphics/Graphics/ICanvas.cs index 9efceb9f6..75cebb8c5 100644 --- a/src/Beutl.Graphics/Graphics/ICanvas.cs +++ b/src/Beutl.Graphics/Graphics/ICanvas.cs @@ -13,11 +13,11 @@ public interface ICanvas : IDisposable bool IsDisposed { get; } - IBrush Foreground { get; set; } + IBrush FillBrush { get; set; } - IImageFilter? Filter { get; set; } + IPen? Pen { get; set; } - float StrokeWidth { get; set; } + IImageFilter? Filter { get; } BlendMode BlendMode { get; set; } @@ -29,20 +29,18 @@ public interface ICanvas : IDisposable void ClipRect(Rect clip, ClipOperation operation = ClipOperation.Intersect); - void ClipPath(SKPath path, ClipOperation operation = ClipOperation.Intersect); + void ClipPath(Geometry geometry, ClipOperation operation = ClipOperation.Intersect); void DrawBitmap(IBitmap bmp); - void DrawCircle(Size size); + void DrawCircle(Rect rect); - void DrawRect(Size size); + void DrawRect(Rect rect); + + void DrawGeometry(Geometry geometry); void DrawText(FormattedText text); - void FillCircle(Size size); - - void FillRect(Size size); - Bitmap GetBitmap(); PushedState PushClip(Rect clip, ClipOperation operation = ClipOperation.Intersect); @@ -57,17 +55,17 @@ public interface ICanvas : IDisposable void PopOpacityMask(int level = -1); - PushedState PushForeground(IBrush brush); + PushedState PushFillBrush(IBrush brush); - void PopForeground(int level = -1); - - PushedState PushStrokeWidth(float strokeWidth); + void PopFillBrush(int level = -1); + + PushedState PushPen(IPen? pen); - void PopStrokeWidth(int level = -1); + void PopPen(int level = -1); - PushedState PushFilters(IImageFilter? filter); + PushedState PushImageFilter(IImageFilter filter); - void PopFilters(int level = -1); + void PopImageFilter(int level = -1); PushedState PushBlendMode(BlendMode blendMode); @@ -76,16 +74,6 @@ public interface ICanvas : IDisposable PushedState PushTransform(Matrix matrix, TransformOperator transformOperator = TransformOperator.Prepend); void PopTransform(int level = -1); - - void RotateDegrees(float degrees); - - void RotateRadians(float radians); - - void Scale(Vector vector); - - void Skew(Vector vector); - - void Translate(Vector vector); } public enum TransformOperator diff --git a/src/Beutl.Graphics/Graphics/IDrawable.cs b/src/Beutl.Graphics/Graphics/IDrawable.cs index ea49ec610..8cd2af100 100644 --- a/src/Beutl.Graphics/Graphics/IDrawable.cs +++ b/src/Beutl.Graphics/Graphics/IDrawable.cs @@ -7,10 +7,6 @@ namespace Beutl.Graphics; public interface IDrawable : IRenderable { - float Width { get; set; } - - float Height { get; set; } - Rect Bounds { get; } IBrush? Foreground { get; set; } diff --git a/src/Beutl.Graphics/Graphics/NullCanvas.cs b/src/Beutl.Graphics/Graphics/NullCanvas.cs index e032b2f47..396d4221b 100644 --- a/src/Beutl.Graphics/Graphics/NullCanvas.cs +++ b/src/Beutl.Graphics/Graphics/NullCanvas.cs @@ -13,7 +13,7 @@ internal sealed class NullCanvas : ICanvas public bool IsDisposed => throw new NotImplementedException(); - public IBrush Foreground + public IBrush FillBrush { get => throw new NotImplementedException(); set => throw new NotImplementedException(); @@ -41,6 +41,7 @@ public Matrix Transform get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public IPen? Pen { get; set; } public void Clear() => throw new NotImplementedException(); @@ -72,9 +73,9 @@ public Matrix Transform public void PopClip(int level = -1) => throw new NotImplementedException(); - public void PopFilters(int level = -1) => throw new NotImplementedException(); + public void PopImageFilter(int level = -1) => throw new NotImplementedException(); - public void PopForeground(int level = -1) => throw new NotImplementedException(); + public void PopFillBrush(int level = -1) => throw new NotImplementedException(); public void PopOpacityMask(int level = -1) => throw new NotImplementedException(); @@ -88,9 +89,9 @@ public Matrix Transform public PushedState PushClip(Rect clip, ClipOperation operation = ClipOperation.Intersect) => throw new NotImplementedException(); - public PushedState PushFilters(IImageFilter? filter) => throw new NotImplementedException(); + public PushedState PushImageFilter(IImageFilter? filter) => throw new NotImplementedException(); - public PushedState PushForeground(IBrush brush) => throw new NotImplementedException(); + public PushedState PushFillBrush(IBrush brush) => throw new NotImplementedException(); public PushedState PushOpacityMask(IBrush mask, Rect bounds, bool invert = false) => throw new NotImplementedException(); @@ -107,4 +108,10 @@ public Matrix Transform public void Skew(Vector vector) => throw new NotImplementedException(); public void Translate(Vector vector) => throw new NotImplementedException(); + public PushedState PushPen(IPen? pen) => throw new NotImplementedException(); + public void PopPen(int level = -1) => throw new NotImplementedException(); + public void ClipPath(Geometry geometry, ClipOperation operation = ClipOperation.Intersect) => throw new NotImplementedException(); + public void DrawCircle(Rect rect) => throw new NotImplementedException(); + public void DrawRect(Rect rect) => throw new NotImplementedException(); + public void DrawGeometry(Geometry geometry) => throw new NotImplementedException(); } diff --git a/src/Beutl.Graphics/Graphics/PushedState.cs b/src/Beutl.Graphics/Graphics/PushedState.cs index 2986d76f5..2b3f788cc 100644 --- a/src/Beutl.Graphics/Graphics/PushedState.cs +++ b/src/Beutl.Graphics/Graphics/PushedState.cs @@ -33,14 +33,11 @@ public void Dispose() { case PushedStateType.None: break; - case PushedStateType.Foreground: - Canvas.PopForeground(Level); + case PushedStateType.FillBrush: + Canvas.PopFillBrush(Level); break; case PushedStateType.Filter: - Canvas.PopFilters(Level); - break; - case PushedStateType.StrokeWidth: - Canvas.PopStrokeWidth(Level); + Canvas.PopImageFilter(Level); break; case PushedStateType.BlendMode: Canvas.PopBlendMode(Level); @@ -57,6 +54,9 @@ public void Dispose() case PushedStateType.Canvas: Canvas.PopCanvas(Level); break; + case PushedStateType.Pen: + Canvas.PopPen(Level); + break; default: break; } diff --git a/src/Beutl.Graphics/Graphics/PushedStateType.cs b/src/Beutl.Graphics/Graphics/PushedStateType.cs index ed7b04265..ef8e09265 100644 --- a/src/Beutl.Graphics/Graphics/PushedStateType.cs +++ b/src/Beutl.Graphics/Graphics/PushedStateType.cs @@ -3,12 +3,12 @@ public enum PushedStateType { None, - Foreground, + FillBrush, Filter, - StrokeWidth, BlendMode, Transform, Clip, OpacityMask, - Canvas + Canvas, + Pen } diff --git a/src/Beutl.Graphics/Graphics/Rect.cs b/src/Beutl.Graphics/Graphics/Rect.cs index 3c57d234d..6294e1123 100644 --- a/src/Beutl.Graphics/Graphics/Rect.cs +++ b/src/Beutl.Graphics/Graphics/Rect.cs @@ -482,6 +482,20 @@ public Rect Union(Rect rect) } } + /// + /// Gets the union of this rectangle and the specified point. + /// + /// The point. + /// The union. + public Rect Union(Point point) + { + float x1 = MathF.Min(X, point.X); + float x2 = MathF.Max(Right, point.X); + float y1 = MathF.Min(Y, point.Y); + float y2 = MathF.Max(Bottom, point.Y); + return new Rect(new Point(x1, y1), new Point(x2, y2)); + } + /// /// Returns a new with the specified X position. /// diff --git a/src/Beutl.Graphics/Graphics/Shapes/Ellipse.cs b/src/Beutl.Graphics/Graphics/Shapes/Ellipse.cs deleted file mode 100644 index 92957443d..000000000 --- a/src/Beutl.Graphics/Graphics/Shapes/Ellipse.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -using Beutl.Language; - -namespace Beutl.Graphics.Shapes; - -public sealed class Ellipse : Drawable -{ - public static readonly CoreProperty StrokeWidthProperty; - private float _strokeWidth = 4000; - - static Ellipse() - { - StrokeWidthProperty = ConfigureProperty(nameof(StrokeWidth)) - .Accessor(o => o.StrokeWidth, (o, v) => o.StrokeWidth = v) - .DefaultValue(4000) - .Register(); - - AffectsRender(StrokeWidthProperty); - } - - [Display(Name = nameof(Strings.StrokeWidth), ResourceType = typeof(Strings))] - [Range(0, float.MaxValue)] - public float StrokeWidth - { - get => _strokeWidth; - set => SetAndRaise(StrokeWidthProperty, ref _strokeWidth, value); - } - - protected override Size MeasureCore(Size availableSize) - { - return new Size(Math.Max(Width, 0), Math.Max(Height, 0)); - } - - protected override void OnDraw(ICanvas canvas) - { - canvas.StrokeWidth = StrokeWidth; - if (Width > 0 && Height > 0) - { - canvas.DrawCircle(new Size(Width, Height)); - } - } -} diff --git a/src/Beutl.Graphics/Graphics/Shapes/EllipseShape.cs b/src/Beutl.Graphics/Graphics/Shapes/EllipseShape.cs new file mode 100644 index 000000000..6efd36df5 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Shapes/EllipseShape.cs @@ -0,0 +1,79 @@ +using Beutl.Media; + +namespace Beutl.Graphics.Shapes; + +public sealed class EllipseShape : Shape +{ + private EllipseGeometry? _geometry; + + static EllipseShape() + { + AffectsGeometry(WidthProperty, HeightProperty); + } + + protected override Geometry CreateGeometry() + { + _geometry ??= new EllipseGeometry(); + _geometry.Width = Width; + _geometry.Height = Height; + return _geometry; + } + + private sealed class EllipseGeometry : Geometry + { + public static readonly CoreProperty WidthProperty; + public static readonly CoreProperty HeightProperty; + private float _width = 0; + private float _height = 0; + + static EllipseGeometry() + { + WidthProperty = ConfigureProperty(nameof(Width)) + .Accessor(o => o.Width, (o, v) => o.Width = v) + .DefaultValue(0) + .Register(); + + HeightProperty = ConfigureProperty(nameof(Height)) + .Accessor(o => o.Height, (o, v) => o.Height = v) + .DefaultValue(0) + .Register(); + + AffectsRender(WidthProperty, HeightProperty); + } + + public float Width + { + get => _width; + set => SetAndRaise(WidthProperty, ref _width, value); + } + + public float Height + { + get => _height; + set => SetAndRaise(HeightProperty, ref _height, value); + } + + public override void ApplyTo(IGeometryContext context) + { + base.ApplyTo(context); + float width = Width; + float height = Height; + if (float.IsInfinity(width)) + width = 0; + + if (float.IsInfinity(height)) + height = 0; + + float radiusX = width / 2; + float radiusY = height / 2; + var radius = new Size(radiusX, radiusY); + + context.MoveTo(new Point(radiusX, 0)); + context.ArcTo(radius, 90, false, true, new Point(width, radiusY)); + context.ArcTo(radius, 90, false, true, new Point(radiusX, height)); + context.ArcTo(radius, 90, false, true, new Point(0, radiusY)); + context.ArcTo(radius, 90, false, true, new Point(radiusX, 0)); + context.Close(); + } + } +} diff --git a/src/Beutl.Graphics/Graphics/Shapes/GeometryShape.cs b/src/Beutl.Graphics/Graphics/Shapes/GeometryShape.cs new file mode 100644 index 000000000..9ae0da0e9 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Shapes/GeometryShape.cs @@ -0,0 +1,29 @@ +using Beutl.Media; + +namespace Beutl.Graphics.Shapes; + +public sealed class GeometryShape : Shape +{ + public static readonly CoreProperty DataProperty; + private Geometry? _data; + + static GeometryShape() + { + DataProperty = ConfigureProperty(nameof(Data)) + .Accessor(o => o.Data, (o, v) => o.Data = v) + .Register(); + + AffectsGeometry(DataProperty); + } + + public Geometry? Data + { + get => _data; + set => SetAndRaise(DataProperty, ref _data, value); + } + + protected override Geometry? CreateGeometry() + { + return _data; + } +} diff --git a/src/Beutl.Graphics/Graphics/Shapes/RectShape.cs b/src/Beutl.Graphics/Graphics/Shapes/RectShape.cs new file mode 100644 index 000000000..9dd416a46 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Shapes/RectShape.cs @@ -0,0 +1,75 @@ +using Beutl.Media; + +namespace Beutl.Graphics.Shapes; + +public sealed class RectShape : Shape +{ + private RectGeometry? _geometry; + + static RectShape() + { + AffectsGeometry(WidthProperty, HeightProperty); + } + + protected override Geometry CreateGeometry() + { + _geometry ??= new RectGeometry(); + _geometry.Width = Width; + _geometry.Height = Height; + return _geometry; + } + + private sealed class RectGeometry : Geometry + { + public static readonly CoreProperty WidthProperty; + public static readonly CoreProperty HeightProperty; + private float _width = 0; + private float _height = 0; + + static RectGeometry() + { + WidthProperty = ConfigureProperty(nameof(Width)) + .Accessor(o => o.Width, (o, v) => o.Width = v) + .DefaultValue(0) + .Register(); + + HeightProperty = ConfigureProperty(nameof(Height)) + .Accessor(o => o.Height, (o, v) => o.Height = v) + .DefaultValue(0) + .Register(); + + AffectsRender(WidthProperty, HeightProperty); + } + + public float Width + { + get => _width; + set => SetAndRaise(WidthProperty, ref _width, value); + } + + public float Height + { + get => _height; + set => SetAndRaise(HeightProperty, ref _height, value); + } + + public override void ApplyTo(IGeometryContext context) + { + base.ApplyTo(context); + float width = Width; + float height = Height; + if (float.IsInfinity(width)) + width = 0; + + if (float.IsInfinity(height)) + height = 0; + + context.MoveTo(new Point(0, 0)); + context.LineTo(new Point(width, 0)); + context.LineTo(new Point(width, height)); + context.LineTo(new Point(0, height)); + context.LineTo(new Point(0, 0)); + context.Close(); + } + } +} diff --git a/src/Beutl.Graphics/Graphics/Shapes/Rectangle.cs b/src/Beutl.Graphics/Graphics/Shapes/Rectangle.cs deleted file mode 100644 index 1f9333107..000000000 --- a/src/Beutl.Graphics/Graphics/Shapes/Rectangle.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -using Beutl.Language; - -namespace Beutl.Graphics.Shapes; - -public sealed class Rectangle : Drawable -{ - public static readonly CoreProperty StrokeWidthProperty; - private float _strokeWidth = 4000; - - static Rectangle() - { - StrokeWidthProperty = ConfigureProperty(nameof(StrokeWidth)) - .Accessor(o => o.StrokeWidth, (o, v) => o.StrokeWidth = v) - .DefaultValue(4000) - .Register(); - - AffectsRender(StrokeWidthProperty); - } - - [Display(Name = nameof(Strings.StrokeWidth), ResourceType = typeof(Strings))] - [Range(0, float.MaxValue)] - public float StrokeWidth - { - get => _strokeWidth; - set => SetAndRaise(StrokeWidthProperty, ref _strokeWidth, value); - } - - protected override Size MeasureCore(Size availableSize) - { - return new Size(Math.Max(Width, 0), Math.Max(Height, 0)); - } - - protected override void OnDraw(ICanvas canvas) - { - canvas.StrokeWidth = StrokeWidth; - if (Width > 0 && Height > 0) - { - canvas.DrawRect(new Size(Width, Height)); - } - } -} diff --git a/src/Beutl.Graphics/Graphics/Shapes/RoundedRect.cs b/src/Beutl.Graphics/Graphics/Shapes/RoundedRect.cs deleted file mode 100644 index ac8e09ef8..000000000 --- a/src/Beutl.Graphics/Graphics/Shapes/RoundedRect.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; - -using Beutl.Language; -using Beutl.Media; -using Beutl.Media.Pixel; - -namespace Beutl.Graphics.Shapes; - -public sealed class RoundedRect : Drawable -{ - public static readonly CoreProperty StrokeWidthProperty; - public static readonly CoreProperty CornerRadiusProperty; - private float _strokeWidth = 4000; - private CornerRadius _cornerRadius; - - static RoundedRect() - { - StrokeWidthProperty = ConfigureProperty(nameof(StrokeWidth)) - .Accessor(o => o.StrokeWidth, (o, v) => o.StrokeWidth = v) - .DefaultValue(4000) - .Register(); - - CornerRadiusProperty = ConfigureProperty(nameof(CornerRadius)) - .Accessor(o => o.CornerRadius, (o, v) => o.CornerRadius = v) - .DefaultValue(new CornerRadius()) - .Register(); - - AffectsRender(StrokeWidthProperty, CornerRadiusProperty); - } - - [Display(Name = nameof(Strings.StrokeWidth), ResourceType = typeof(Strings))] - [Range(0, float.MaxValue)] - public float StrokeWidth - { - get => _strokeWidth; - set => SetAndRaise(StrokeWidthProperty, ref _strokeWidth, value); - } - - [Display(Name = nameof(Strings.CornerRadius), ResourceType = typeof(Strings))] - [Range(typeof(CornerRadius), "0", "max")] - public CornerRadius CornerRadius - { - get => _cornerRadius; - set => SetAndRaise(CornerRadiusProperty, ref _cornerRadius, value); - } - - protected override Size MeasureCore(Size availableSize) - { - return new Size(Math.Max(Width, 0), Math.Max(Height, 0)); - } - - protected override void OnDraw(ICanvas canvas) - { - if (Width > 0 && Height > 0) - { - using Bitmap bmp = ToBitmapWithoutEffect(); - canvas.DrawBitmap(bmp); - } - } - - public Bitmap ToBitmapWithoutEffect() - { - using var g = new Canvas((int)Width, (int)Height); - - if (Foreground != null) - { - g.Foreground = Foreground; - } - g.StrokeWidth = StrokeWidth; - g.DrawRect(new Size(Width, Height)); - - Bitmap bmp = g.GetBitmap(); - ReplaceCorners(bmp); - - return bmp; - } - - private void ReplaceCorners(Bitmap bitmap) - { - CornerRadius cornerRadius = Clamp(CornerRadius, new CornerRadius(0), new CornerRadius(Math.Min(bitmap.Width, bitmap.Height) / 2)); - - // topleft - int topleftI = (int)cornerRadius.TopLeft; - using (Bitmap topleft = Corner(cornerRadius.TopLeft)) - using (Bitmap topleft1 = topleft[new PixelRect(0, 0, topleftI, topleftI)]) - { - bitmap[new PixelRect(0, 0, topleftI, topleftI)] = topleft1; - } - - // topright - int toprightI = (int)cornerRadius.TopRight; - using (Bitmap topright = Corner(cornerRadius.TopRight)) - using (Bitmap topright1 = topright[new PixelRect(toprightI, 0, toprightI, toprightI)]) - { - bitmap[new PixelRect(bitmap.Width - toprightI, 0, toprightI, toprightI)] = topright1; - } - - // bottomright - int bottomrightI = (int)cornerRadius.BottomRight; - using (Bitmap bottomright = Corner(cornerRadius.BottomRight)) - using (Bitmap bottomright1 = bottomright[new PixelRect(bottomrightI, bottomrightI, bottomrightI, bottomrightI)]) - { - bitmap[new PixelRect(bitmap.Width - bottomrightI, bitmap.Height - bottomrightI, bottomrightI, bottomrightI)] = bottomright1; - } - - // bottomleft - int bottomleftI = (int)cornerRadius.BottomLeft; - using (Bitmap bottomleft = Corner(cornerRadius.BottomLeft)) - using (Bitmap bottomleft1 = bottomleft[new PixelRect(0, bottomleftI, bottomleftI, bottomleftI)]) - { - bitmap[new PixelRect(0, bitmap.Height - bottomleftI, bottomleftI, bottomleftI)] = bottomleft1; - } - } - - private Bitmap Corner(float radius) - { - if ((int)radius <= 0) - { - return new Bitmap(0, 0); - } - else - { - float size = radius * 2; - using var c = new Canvas((int)size, (int)size); - - if (Foreground != null) - { - c.Foreground = Foreground; - } - c.StrokeWidth = StrokeWidth; - c.DrawCircle(new Size(size, size)); - - return c.GetBitmap(); - } - } - - private static CornerRadius Clamp(CornerRadius value, CornerRadius min, CornerRadius max) - { - return new CornerRadius( - Math.Clamp(value.TopLeft, min.TopLeft, max.TopLeft), - Math.Clamp(value.TopRight, min.TopRight, max.TopRight), - Math.Clamp(value.BottomRight, min.BottomRight, max.BottomRight), - Math.Clamp(value.BottomLeft, min.BottomLeft, max.BottomLeft)); - } -} diff --git a/src/Beutl.Graphics/Graphics/Shapes/RoundedRectShape.cs b/src/Beutl.Graphics/Graphics/Shapes/RoundedRectShape.cs new file mode 100644 index 000000000..f22170572 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Shapes/RoundedRectShape.cs @@ -0,0 +1,144 @@ +using System.ComponentModel.DataAnnotations; + +using Beutl.Language; +using Beutl.Media; + +namespace Beutl.Graphics.Shapes; + +public sealed class RoundedRectShape : Shape +{ + public static readonly CoreProperty CornerRadiusProperty; + private CornerRadius _cornerRadius; + private RoundedRectGeometry? _geometry; + + static RoundedRectShape() + { + CornerRadiusProperty = ConfigureProperty(nameof(CornerRadius)) + .Accessor(o => o.CornerRadius, (o, v) => o.CornerRadius = v) + .DefaultValue(new CornerRadius()) + .Register(); + + AffectsGeometry(WidthProperty, HeightProperty, CornerRadiusProperty); + } + + [Display(Name = nameof(Strings.CornerRadius), ResourceType = typeof(Strings))] + [Range(typeof(CornerRadius), "0", "max")] + public CornerRadius CornerRadius + { + get => _cornerRadius; + set => SetAndRaise(CornerRadiusProperty, ref _cornerRadius, value); + } + + protected override Geometry CreateGeometry() + { + _geometry ??= new RoundedRectGeometry(); + _geometry.Width = Width; + _geometry.Height = Height; + _geometry.CornerRadius = CornerRadius; + return _geometry; + } + + private sealed class RoundedRectGeometry : Geometry + { + public static readonly CoreProperty WidthProperty; + public static readonly CoreProperty HeightProperty; + public static readonly CoreProperty CornerRadiusProperty; + private float _width = 0; + private float _height = 0; + private CornerRadius _cornerRadius; + + static RoundedRectGeometry() + { + WidthProperty = ConfigureProperty(nameof(Width)) + .Accessor(o => o.Width, (o, v) => o.Width = v) + .DefaultValue(0) + .Register(); + + HeightProperty = ConfigureProperty(nameof(Height)) + .Accessor(o => o.Height, (o, v) => o.Height = v) + .DefaultValue(0) + .Register(); + + CornerRadiusProperty = ConfigureProperty(nameof(CornerRadius)) + .Accessor(o => o.CornerRadius, (o, v) => o.CornerRadius = v) + .DefaultValue(new CornerRadius()) + .Register(); + + AffectsRender(WidthProperty, HeightProperty, CornerRadiusProperty); + } + + public float Width + { + get => _width; + set => SetAndRaise(WidthProperty, ref _width, value); + } + + public float Height + { + get => _height; + set => SetAndRaise(HeightProperty, ref _height, value); + } + + public CornerRadius CornerRadius + { + get => _cornerRadius; + set => SetAndRaise(CornerRadiusProperty, ref _cornerRadius, value); + } + + public override void ApplyTo(IGeometryContext context) + { + base.ApplyTo(context); + float width = _width; + float height = _height; + if (float.IsInfinity(width)) + width = 0; + + if (float.IsInfinity(height)) + height = 0; + + (float radiusX, float radiusY) = (width / 2, height / 2); + float maxRadius = Math.Max(radiusX, radiusY); + CornerRadius cornerRadius = _cornerRadius; + float topLeft = Math.Clamp(cornerRadius.TopLeft, 0, maxRadius); + float topRight = Math.Clamp(cornerRadius.TopRight, 0, maxRadius); + float bottomRight = Math.Clamp(cornerRadius.BottomRight, 0, maxRadius); + float bottomLeft = Math.Clamp(cornerRadius.BottomLeft, 0, maxRadius); + + context.MoveTo(new Point(topLeft, 0)); + + context.LineTo(new Point(width - topRight, 0)); + context.ArcTo( + new Size(topRight, topRight), + 90, + false, + true, + new Point(width, topRight)); + + context.LineTo(new Point(width, height - bottomRight)); + context.ArcTo( + new Size(bottomRight, bottomRight), + 90, + false, + true, + new Point(width - bottomRight, height)); + + context.LineTo(new Point(bottomLeft, height)); + context.ArcTo( + new Size(bottomLeft, bottomLeft), + 90, + false, + true, + new Point(0, height - bottomLeft)); + + context.LineTo(new Point(0, topLeft)); + context.ArcTo( + new Size(topLeft, topLeft), + 90, + false, + true, + new Point(topLeft, 0)); + + context.Close(); + } + } +} diff --git a/src/Beutl.Graphics/Graphics/Shapes/Shape.cs b/src/Beutl.Graphics/Graphics/Shapes/Shape.cs new file mode 100644 index 000000000..f0439f479 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Shapes/Shape.cs @@ -0,0 +1,245 @@ +using System.ComponentModel.DataAnnotations; + +using Beutl.Language; +using Beutl.Media; + +namespace Beutl.Graphics.Shapes; + +public abstract class Shape : Drawable +{ + public static readonly CoreProperty WidthProperty; + public static readonly CoreProperty HeightProperty; + public static readonly CoreProperty StretchProperty; + public static readonly CoreProperty PenProperty; + public static readonly CoreProperty FillTypeProperty; + public static readonly CoreProperty CreatedGeometryProperty; + private float _width = 0; + private float _height = 0; + private Stretch _stretch = Stretch.None; + private IPen? _pen = null; + private PathFillType _fillType; + private Geometry? _createdGeometry; + + static Shape() + { + WidthProperty = ConfigureProperty(nameof(Width)) + .Accessor(o => o.Width, (o, v) => o.Width = v) + .DefaultValue(0) + .Register(); + + HeightProperty = ConfigureProperty(nameof(Height)) + .Accessor(o => o.Height, (o, v) => o.Height = v) + .DefaultValue(0) + .Register(); + + StretchProperty = ConfigureProperty(nameof(Stretch)) + .Accessor(o => o.Stretch, (o, v) => o.Stretch = v) + .Register(); + + PenProperty = ConfigureProperty(nameof(Pen)) + .Accessor(o => o.Pen, (o, v) => o.Pen = v) + .Register(); + + FillTypeProperty = ConfigureProperty(nameof(FillType)) + .Accessor(o => o.FillType, (o, v) => o.FillType = v) + .Register(); + + CreatedGeometryProperty = ConfigureProperty(nameof(CreatedGeometry)) + .Accessor(o => o.CreatedGeometry, (o, v) => o.CreatedGeometry = v) + .Register(); + + AffectsRender( + WidthProperty, HeightProperty, + StretchProperty, PenProperty, FillTypeProperty, CreatedGeometryProperty); + } + + [Display(Name = nameof(Strings.Width), ResourceType = typeof(Strings))] + [Range(0, float.MaxValue)] + public float Width + { + get => _width; + set => SetAndRaise(WidthProperty, ref _width, value); + } + + [Display(Name = nameof(Strings.Height), ResourceType = typeof(Strings))] + [Range(0, float.MaxValue)] + public float Height + { + get => _height; + set => SetAndRaise(HeightProperty, ref _height, value); + } + + public Stretch Stretch + { + get => _stretch; + set => SetAndRaise(StretchProperty, ref _stretch, value); + } + + public IPen? Pen + { + get => _pen; + set => SetAndRaise(PenProperty, ref _pen, value); + } + + public PathFillType FillType + { + get => _fillType; + set => SetAndRaise(FillTypeProperty, ref _fillType, value); + } + + public Geometry? CreatedGeometry + { + get => _createdGeometry; + private set => SetAndRaise(CreatedGeometryProperty, ref _createdGeometry, value); + } + + protected static void AffectsGeometry(params CoreProperty[] properties) + where T : Shape + { + foreach (CoreProperty item in properties) + { + item.Changed.Subscribe(e => + { + if (e.Sender is T s) + { + s.InvalidateGeometry(); + + if (e.OldValue is IAffectsRender oldAffectsRender) + { + oldAffectsRender.Invalidated -= s.OnAffectsRenderGeometryInvalidated; + } + + if (e.NewValue is IAffectsRender newAffectsRender) + { + newAffectsRender.Invalidated += s.OnAffectsRenderGeometryInvalidated; + } + } + }); + } + } + + private void OnAffectsRenderGeometryInvalidated(object? sender, RenderInvalidatedEventArgs e) + { + InvalidateGeometry(); + } + + private Geometry? GetOrCreateGeometry() + { + CreatedGeometry ??= CreateGeometry(); + if (CreatedGeometry != null) + { + CreatedGeometry.FillType = FillType; + } + + return CreatedGeometry; + } + + public void InvalidateGeometry() + { + CreatedGeometry = null; + } + + private static (Size size, Matrix transform) CalculateSizeAndTransform(Size requestedSize, Rect shapeBounds, Stretch stretch) + { + var shapeSize = new Size(shapeBounds.Right, shapeBounds.Bottom); + Matrix translate = Matrix.Identity; + float desiredX = requestedSize.Width; + float desiredY = requestedSize.Height; + float sx = 0.0f; + float sy = 0.0f; + + if (stretch != Stretch.None) + { + shapeSize = shapeBounds.Size; + translate = Matrix.CreateTranslation(-(Vector)shapeBounds.Position); + } + + if (float.IsInfinity(requestedSize.Width)) + { + desiredX = shapeSize.Width; + } + + if (float.IsInfinity(requestedSize.Height)) + { + desiredY = shapeSize.Height; + } + + if (shapeBounds.Width > 0) + { + sx = desiredX / shapeSize.Width; + } + + if (shapeBounds.Height > 0) + { + sy = desiredY / shapeSize.Height; + } + + if (float.IsInfinity(requestedSize.Width)) + { + sx = sy; + } + + if (float.IsInfinity(requestedSize.Height)) + { + sy = sx; + } + + switch (stretch) + { + case Stretch.Uniform: + sx = sy = Math.Min(sx, sy); + break; + case Stretch.UniformToFill: + sx = sy = Math.Max(sx, sy); + break; + case Stretch.Fill: + if (float.IsInfinity(requestedSize.Width)) + { + sx = 1.0f; + } + + if (float.IsInfinity(requestedSize.Height)) + { + sy = 1.0f; + } + + break; + default: + sx = sy = 1; + break; + } + + Matrix transform = translate * Matrix.CreateScale(sx, sy); + var size = new Size(shapeSize.Width * sx, shapeSize.Height * sy); + return (size, transform); + } + + protected override Size MeasureCore(Size availableSize) + { + Geometry? geometry = GetOrCreateGeometry(); + if (geometry == null) + { + return default; + } + + return CalculateSizeAndTransform(new Size(Width, Height), geometry.Bounds, Stretch).size; + } + + protected abstract Geometry? CreateGeometry(); + + protected override void OnDraw(ICanvas canvas) + { + Geometry? geometry = GetOrCreateGeometry(); + if (geometry == null) + return; + + var requestedSize = new Size(Width, Height); + (Size _, Matrix transform) = CalculateSizeAndTransform(requestedSize, geometry.Bounds, Stretch); + + using (canvas.PushTransform(transform)) + using (canvas.PushPen(Pen)) + { + canvas.DrawGeometry(geometry); + } + } +} diff --git a/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs b/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs index d5bea17de..ab74b6764 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs @@ -208,10 +208,10 @@ protected override void OnDraw(ICanvas canvas) { if (item.Text.Length > 0) { - canvas.Translate(new(prevRight, 0)); + canvas.Transform = Matrix.CreateTranslation(prevRight, 0) * canvas.Transform; Size elementBounds = item.Bounds; - using (item.Brush != null ? canvas.PushForeground(item.Brush) : default) + using (item.Brush != null ? canvas.PushFillBrush(item.Brush) : default) canvas.DrawText(item); prevRight = elementBounds.Width + item.Margin.Right; diff --git a/src/Beutl.Graphics/Graphics/SkiaSharpExtensions.cs b/src/Beutl.Graphics/Graphics/SkiaSharpExtensions.cs index 876666cda..d55c69902 100644 --- a/src/Beutl.Graphics/Graphics/SkiaSharpExtensions.cs +++ b/src/Beutl.Graphics/Graphics/SkiaSharpExtensions.cs @@ -23,6 +23,11 @@ public static SKPoint ToSKPoint(this Point p) { return new SKPoint(p.X, p.Y); } + + public static Point ToGraphicsPoint(this in SKPoint p) + { + return new Point(p.X, p.Y); + } public static SKPoint ToSKPoint(this Vector p) { diff --git a/src/Beutl.Graphics/Media/IPen.cs b/src/Beutl.Graphics/Media/IPen.cs new file mode 100644 index 000000000..5d7730f2f --- /dev/null +++ b/src/Beutl.Graphics/Media/IPen.cs @@ -0,0 +1,23 @@ +namespace Beutl.Media; + +public interface IPen +{ + IBrush? Brush { get; } + + IReadOnlyList? DashArray { get; } + + float DashOffset { get; } + + float Thickness { get; } + + float MiterLimit { get; } + + StrokeCap StrokeCap { get; } + + StrokeJoin StrokeJoin { get; } +} + +public interface IMutablePen : IPen, IAffectsRender +{ + IPen ToImmutable(); +} diff --git a/src/Beutl.Graphics/Media/Immutable/ImmutablePen.cs b/src/Beutl.Graphics/Media/Immutable/ImmutablePen.cs new file mode 100644 index 000000000..28418a2b0 --- /dev/null +++ b/src/Beutl.Graphics/Media/Immutable/ImmutablePen.cs @@ -0,0 +1,62 @@ +namespace Beutl.Media.Immutable; + +public sealed class ImmutablePen : IPen, IEquatable +{ + public ImmutablePen( + IBrush? brush, + IReadOnlyList? dashArray, + float dashOffset, + float thickness, + float miterLimit, + StrokeCap strokeCap, + StrokeJoin strokeJoin) + { + Brush = brush; + DashArray = dashArray; + DashOffset = dashOffset; + Thickness = thickness; + MiterLimit = miterLimit; + StrokeCap = strokeCap; + StrokeJoin = strokeJoin; + } + + public IBrush? Brush { get; } + + public IReadOnlyList? DashArray { get; } + + public float DashOffset { get; } + + public float Thickness { get; } + + public float MiterLimit { get; } + + public StrokeCap StrokeCap { get; } + + public StrokeJoin StrokeJoin { get; } + + public override bool Equals(object? obj) + { + return Equals(obj as IPen); + } + + public bool Equals(IPen? other) + { + return other is not null + && EqualityComparer.Default.Equals(Brush, other.Brush) + && EqualityComparer?>.Default.Equals(DashArray, other.DashArray) + && DashOffset == other.DashOffset + && Thickness == other.Thickness + && MiterLimit == other.MiterLimit + && StrokeCap == other.StrokeCap + && StrokeJoin == other.StrokeJoin; + } + + public override int GetHashCode() + { + return HashCode.Combine(Brush, DashArray, DashOffset, Thickness, MiterLimit, StrokeCap, StrokeJoin); + } + + public static bool operator ==(ImmutablePen? left, ImmutablePen? right) => EqualityComparer.Default.Equals(left, right); + + public static bool operator !=(ImmutablePen? left, ImmutablePen? right) => !(left == right); +} diff --git a/src/Beutl.Graphics/Media/Pen.cs b/src/Beutl.Graphics/Media/Pen.cs new file mode 100644 index 000000000..17e172f6c --- /dev/null +++ b/src/Beutl.Graphics/Media/Pen.cs @@ -0,0 +1,183 @@ +using System.Collections.Specialized; +using System.ComponentModel; + +using Beutl.Animation; +using Beutl.Collections; +using Beutl.Media.Immutable; + +namespace Beutl.Media; + +public sealed class Pen : Animatable, IMutablePen +{ + public static readonly CoreProperty BrushProperty; + public static readonly CoreProperty?> DashArrayProperty; + public static readonly CoreProperty DashOffsetProperty; + public static readonly CoreProperty ThicknessProperty; + public static readonly CoreProperty MiterLimitProperty; + public static readonly CoreProperty StrokeCapProperty; + public static readonly CoreProperty StrokeJoinProperty; + private IBrush? _brush; + private CoreList? _dashArray; + private float _dashOffset; + private float _thickness = 1; + private float _miterLimit = 10; + private StrokeCap _strokeCap = StrokeCap.Flat; + private StrokeJoin _strokeJoin = StrokeJoin.Miter; + + static Pen() + { + BrushProperty = ConfigureProperty(nameof(Brush)) + .Accessor(o => o.Brush, (o, v) => o.Brush = v) + .Register(); + + DashArrayProperty = ConfigureProperty?, Pen>(nameof(DashArray)) + .Accessor(o => o.DashArray, (o, v) => o.DashArray = v) + .Register(); + + DashOffsetProperty = ConfigureProperty(nameof(DashOffset)) + .Accessor(o => o.DashOffset, (o, v) => o.DashOffset = v) + .Register(); + + ThicknessProperty = ConfigureProperty(nameof(Thickness)) + .Accessor(o => o.Thickness, (o, v) => o.Thickness = v) + .DefaultValue(1) + .Register(); + + MiterLimitProperty = ConfigureProperty(nameof(MiterLimit)) + .Accessor(o => o.MiterLimit, (o, v) => o.MiterLimit = v) + .DefaultValue(10) + .Register(); + + StrokeCapProperty = ConfigureProperty(nameof(StrokeCap)) + .Accessor(o => o.StrokeCap, (o, v) => o.StrokeCap = v) + .DefaultValue(StrokeCap.Flat) + .Register(); + + StrokeJoinProperty = ConfigureProperty(nameof(StrokeJoin)) + .Accessor(o => o.StrokeJoin, (o, v) => o.StrokeJoin = v) + .DefaultValue(StrokeJoin.Miter) + .Register(); + } + + public IBrush? Brush + { + get => _brush; + set => SetAndRaise(BrushProperty, ref _brush, value); + } + + public CoreList? DashArray + { + get => _dashArray; + set => SetAndRaise(DashArrayProperty, ref _dashArray, value); + } + + public float DashOffset + { + get => _dashOffset; + set => SetAndRaise(DashOffsetProperty, ref _dashOffset, value); + } + + public float Thickness + { + get => _thickness; + set => SetAndRaise(ThicknessProperty, ref _thickness, value); + } + + public float MiterLimit + { + get => _miterLimit; + set => SetAndRaise(MiterLimitProperty, ref _miterLimit, value); + } + + public StrokeCap StrokeCap + { + get => _strokeCap; + set => SetAndRaise(StrokeCapProperty, ref _strokeCap, value); + } + + public StrokeJoin StrokeJoin + { + get => _strokeJoin; + set => SetAndRaise(StrokeJoinProperty, ref _strokeJoin, value); + } + + IReadOnlyList? IPen.DashArray => _dashArray; + + public event EventHandler? Invalidated; + + public override void ApplyAnimations(IClock clock) + { + base.ApplyAnimations(clock); + if (_brush is IAnimatable animatable) + { + animatable.ApplyAnimations(clock); + } + } + + protected override void OnPropertyChanged(PropertyChangedEventArgs args) + { + base.OnPropertyChanged(args); + switch (args) + { + case CorePropertyChangedEventArgs { PropertyName: nameof(Brush) } args1: + if (args1.OldValue is IAffectsRender oldAffectRender) + { + oldAffectRender.Invalidated -= OnAffectRenderInvalidated; + } + + if (args1.NewValue is IAffectsRender newAffectRender) + { + newAffectRender.Invalidated += OnAffectRenderInvalidated; + } + + goto RaiseInvalidated; + + case CorePropertyChangedEventArgs?> { PropertyName: nameof(DashArray) } args2: + if (args2.OldValue is { }) + { + args2.OldValue.CollectionChanged -= OnDashArrayCollectionChanged; + } + + if (args2.NewValue is { }) + { + args2.NewValue.CollectionChanged += OnDashArrayCollectionChanged; + } + + goto RaiseInvalidated; + + case CorePropertyChangedEventArgs { PropertyName: nameof(DashOffset) }: + case CorePropertyChangedEventArgs { PropertyName: nameof(Thickness) }: + case CorePropertyChangedEventArgs { PropertyName: nameof(MiterLimit) }: + case CorePropertyChangedEventArgs { PropertyName: nameof(StrokeCap) }: + case CorePropertyChangedEventArgs { PropertyName: nameof(StrokeJoin) }: + RaiseInvalidated: + Invalidated?.Invoke(this, new RenderInvalidatedEventArgs(this, args.PropertyName)); + break; + + default: + break; + } + } + + private void OnDashArrayCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + Invalidated?.Invoke(this, new RenderInvalidatedEventArgs(DashArray!)); + } + + private void OnAffectRenderInvalidated(object? sender, RenderInvalidatedEventArgs e) + { + Invalidated?.Invoke(this, e); + } + + public IPen ToImmutable() + { + return new ImmutablePen( + (Brush as IMutableBrush)?.ToImmutable() ?? Brush, + DashArray?.ToArray(), + DashOffset, + Thickness, + MiterLimit, + StrokeCap, + StrokeJoin); + } +} diff --git a/src/Beutl.Graphics/Media/StrokeCap.cs b/src/Beutl.Graphics/Media/StrokeCap.cs new file mode 100644 index 000000000..deaaeb10b --- /dev/null +++ b/src/Beutl.Graphics/Media/StrokeCap.cs @@ -0,0 +1,8 @@ +namespace Beutl.Media; + +public enum StrokeCap +{ + Flat = 0, + Round = 1, + Square = 2 +} diff --git a/src/Beutl.Graphics/Media/StrokeJoin.cs b/src/Beutl.Graphics/Media/StrokeJoin.cs new file mode 100644 index 000000000..e322a6470 --- /dev/null +++ b/src/Beutl.Graphics/Media/StrokeJoin.cs @@ -0,0 +1,8 @@ +namespace Beutl.Media; + +public enum StrokeJoin +{ + Miter = 0, + Round = 1, + Bevel = 2 +} diff --git a/src/Beutl.Operators/Source/EllipseOperation.cs b/src/Beutl.Operators/Source/EllipseOperation.cs index a215d2bc4..fdaf6a31a 100644 --- a/src/Beutl.Operators/Source/EllipseOperation.cs +++ b/src/Beutl.Operators/Source/EllipseOperation.cs @@ -9,13 +9,13 @@ namespace Beutl.Operators.Source; -public sealed class EllipseOperator : DrawablePublishOperator +public sealed class EllipseOperator : DrawablePublishOperator { protected override void OnInitializeSetters(IList initializing) { initializing.Add(new Setter(Drawable.WidthProperty, 100)); initializing.Add(new Setter(Drawable.HeightProperty, 100)); - initializing.Add(new Setter(Ellipse.StrokeWidthProperty, 4000)); + initializing.Add(new Setter(EllipseShape.StrokeWidthProperty, 4000)); initializing.Add(new Setter(Drawable.ForegroundProperty, new SolidColorBrush(Colors.White))); } } diff --git a/src/Beutl.Operators/Source/RectOperator.cs b/src/Beutl.Operators/Source/RectOperator.cs index 71094276a..b5277164b 100644 --- a/src/Beutl.Operators/Source/RectOperator.cs +++ b/src/Beutl.Operators/Source/RectOperator.cs @@ -6,13 +6,13 @@ namespace Beutl.Operators.Source; -public sealed class RectOperator : DrawablePublishOperator +public sealed class RectOperator : DrawablePublishOperator { protected override void OnInitializeSetters(IList initializing) { initializing.Add(new Setter(Drawable.WidthProperty, 100)); initializing.Add(new Setter(Drawable.HeightProperty, 100)); - initializing.Add(new Setter(Rectangle.StrokeWidthProperty, 4000)); + initializing.Add(new Setter(RectShape.StrokeWidthProperty, 4000)); initializing.Add(new Setter(Drawable.ForegroundProperty, new SolidColorBrush(Colors.White))); } } diff --git a/src/Beutl.Operators/Source/RoundedRectOperator.cs b/src/Beutl.Operators/Source/RoundedRectOperator.cs index 6936dc3ca..b2abac9c7 100644 --- a/src/Beutl.Operators/Source/RoundedRectOperator.cs +++ b/src/Beutl.Operators/Source/RoundedRectOperator.cs @@ -6,14 +6,14 @@ namespace Beutl.Operators.Source; -public sealed class RoundedRectOperator : DrawablePublishOperator +public sealed class RoundedRectOperator : DrawablePublishOperator { protected override void OnInitializeSetters(IList initializing) { initializing.Add(new Setter(Drawable.WidthProperty, 100)); initializing.Add(new Setter(Drawable.HeightProperty, 100)); - initializing.Add(new Setter(RoundedRect.StrokeWidthProperty, 4000)); + initializing.Add(new Setter(RoundedRectShape.StrokeWidthProperty, 4000)); initializing.Add(new Setter(Drawable.ForegroundProperty, new SolidColorBrush(Colors.White))); - initializing.Add(new Setter(RoundedRect.CornerRadiusProperty, new CornerRadius(25))); + initializing.Add(new Setter(RoundedRectShape.CornerRadiusProperty, new CornerRadius(25))); } } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/RectNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/RectNode.cs index 1dcfc2459..2603076f4 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/RectNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/RectNode.cs @@ -8,24 +8,24 @@ namespace Beutl.NodeTree.Nodes; public class RectNode : Node { - private readonly OutputSocket _outputSocket; + private readonly OutputSocket _outputSocket; private readonly InputSocket _widthSocket; private readonly InputSocket _heightSocket; private readonly InputSocket _strokeSocket; public RectNode() { - _outputSocket = AsOutput("Rectangle"); + _outputSocket = AsOutput("Rectangle"); - _widthSocket = AsInput(Drawable.WidthProperty, value: 100).AcceptNumber(); - _heightSocket = AsInput(Drawable.HeightProperty, value: 100).AcceptNumber(); - _strokeSocket = AsInput(Rectangle.StrokeWidthProperty, value: 4000).AcceptNumber(); + _widthSocket = AsInput(Drawable.WidthProperty, value: 100).AcceptNumber(); + _heightSocket = AsInput(Drawable.HeightProperty, value: 100).AcceptNumber(); + _strokeSocket = AsInput(RectShape.StrokeWidthProperty, value: 4000).AcceptNumber(); } public override void InitializeForContext(NodeEvaluationContext context) { base.InitializeForContext(context); - context.State = new Rectangle(); + context.State = new RectShape(); } public override void UninitializeForContext(NodeEvaluationContext context) @@ -36,7 +36,7 @@ public override void UninitializeForContext(NodeEvaluationContext context) public override void Evaluate(NodeEvaluationContext context) { - Rectangle rectangle = context.GetOrSetState(); + RectShape rectangle = context.GetOrSetState(); while (rectangle.BatchUpdate) { rectangle.EndBatchUpdate(); diff --git a/tests/Beutl.Graphics.UnitTests/ShapeTests.cs b/tests/Beutl.Graphics.UnitTests/ShapeTests.cs new file mode 100644 index 000000000..7a2a31bf9 --- /dev/null +++ b/tests/Beutl.Graphics.UnitTests/ShapeTests.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Beutl.Collections; +using Beutl.Graphics.Shapes; +using Beutl.Media; +using Beutl.Media.Pixel; + +using NUnit.Framework; + +namespace Beutl.Graphics.UnitTests; + +public class ShapeTests +{ + [Test] + public void DrawRectangle() + { + var shape = new RectShape + { + AlignmentX = AlignmentX.Center, + AlignmentY = AlignmentY.Center, + TransformOrigin = RelativePoint.Center, + + Width = 100, + Height = 100, + Foreground = Brushes.White + }; + + using var canvas = new Canvas(250, 250); + + canvas.Clear(Colors.Black); + shape.Draw(canvas); + + using Bitmap bmp = canvas.GetBitmap(); + + Assert.IsTrue(bmp.Save(Path.Combine(ArtifactProvider.GetArtifactDirectory(), $"0.png"), EncodedImageFormat.Png)); + } + + [Test] + public void DrawRectangleWithPen() + { + var shape = new RectShape + { + AlignmentX = AlignmentX.Center, + AlignmentY = AlignmentY.Center, + TransformOrigin = RelativePoint.Center, + + Width = 100, + Height = 100, + + Foreground = Brushes.Gray, + Pen = new Pen() + { + Brush = Brushes.White, + Thickness = 10, + StrokeCap = StrokeCap.Round, + DashArray = new CoreList() + { + 2 + }, + } + }; + + using var canvas = new Canvas(250, 250); + + canvas.Clear(Colors.Black); + + shape.Draw(canvas); + + using Bitmap bmp = canvas.GetBitmap(); + + Assert.IsTrue(bmp.Save(Path.Combine(ArtifactProvider.GetArtifactDirectory(), $"0.png"), EncodedImageFormat.Png)); + } + + [Test] + public void DrawEllipse() + { + var shape = new EllipseShape + { + AlignmentX = AlignmentX.Center, + AlignmentY = AlignmentY.Center, + TransformOrigin = RelativePoint.Center, + + Width = 100, + Height = 100, + Foreground = Brushes.White + }; + + using var canvas = new Canvas(250, 250); + + canvas.Clear(Colors.Black); + shape.Draw(canvas); + + using Bitmap bmp = canvas.GetBitmap(); + + Assert.IsTrue(bmp.Save(Path.Combine(ArtifactProvider.GetArtifactDirectory(), $"0.png"), EncodedImageFormat.Png)); + } + + [Test] + public void DrawRoundedRect() + { + var shape = new RoundedRectShape + { + AlignmentX = AlignmentX.Center, + AlignmentY = AlignmentY.Center, + TransformOrigin = RelativePoint.Center, + + Width = 100, + Height = 100, + CornerRadius = new CornerRadius(25), + + Foreground = Brushes.White + }; + + using var canvas = new Canvas(250, 250); + + canvas.Clear(Colors.Black); + shape.Draw(canvas); + + using Bitmap bmp = canvas.GetBitmap(); + + Assert.IsTrue(bmp.Save(Path.Combine(ArtifactProvider.GetArtifactDirectory(), $"0.png"), EncodedImageFormat.Png)); + } + + [Test] + public void DrawGeometry() + { + var geometry = new PathGeometry(); + var center = new Point(50, 50); + float radius = 0.45f * Math.Min(100, 100); + + geometry.MoveTo(new Point(50, 50 - radius)); + + for (int i = 1; i < 5; i++) + { + float angle = i * 4 * MathF.PI / 5; + geometry.LineTo(center + new Point(radius * MathF.Sin(angle), -radius * MathF.Cos(angle))); + } + geometry.Close(); + + var shape = new GeometryShape + { + AlignmentX = AlignmentX.Center, + AlignmentY = AlignmentY.Center, + TransformOrigin = RelativePoint.Center, + + Data = geometry, + Foreground = Brushes.White + }; + + using var canvas = new Canvas(250, 250); + + canvas.Clear(Colors.Black); + shape.Draw(canvas); + + using Bitmap bmp = canvas.GetBitmap(); + + Assert.IsTrue(bmp.Save(Path.Combine(ArtifactProvider.GetArtifactDirectory(), $"0.png"), EncodedImageFormat.Png)); + } + + [Test] + public void DrawGeometryWithPen() + { + var geometry = new PathGeometry(); + var center = new Point(50, 50); + float radius = 0.45f * Math.Min(100, 100); + + geometry.MoveTo(new Point(50, 50 - radius)); + + for (int i = 1; i < 5; i++) + { + float angle = i * 4 * MathF.PI / 5; + geometry.LineTo(center + new Point(radius * MathF.Sin(angle), -radius * MathF.Cos(angle))); + } + geometry.Close(); + + var shape = new GeometryShape + { + AlignmentX = AlignmentX.Center, + AlignmentY = AlignmentY.Center, + TransformOrigin = RelativePoint.Center, + + Data = geometry, + + Foreground = Brushes.Gray, + Pen = new Pen() + { + Brush = Brushes.White, + Thickness = 10, + StrokeCap = StrokeCap.Round, + } + }; + + using var canvas = new Canvas(250, 250); + + canvas.Clear(Colors.Black); + shape.Draw(canvas); + + using Bitmap bmp = canvas.GetBitmap(); + + Assert.IsTrue(bmp.Save(Path.Combine(ArtifactProvider.GetArtifactDirectory(), $"0.png"), EncodedImageFormat.Png)); + } +} diff --git a/tests/Beutl.Graphics.UnitTests/StyleTests.cs b/tests/Beutl.Graphics.UnitTests/StyleTests.cs index 9ac3498e5..b4f536c6a 100644 --- a/tests/Beutl.Graphics.UnitTests/StyleTests.cs +++ b/tests/Beutl.Graphics.UnitTests/StyleTests.cs @@ -12,7 +12,7 @@ namespace Beutl.Graphics.UnitTests; public class StyleTests { private StyleableObject _obj; - private Rectangle _obj2; + private RectShape _obj2; private Style[] _styles1; private Style[] _styles2; @@ -52,11 +52,11 @@ public void Setup() style3 }; - _obj2 = new Rectangle + _obj2 = new RectShape { Foreground = Brushes.White }; - style1 = new Style + style1 = new Style { Setters = { @@ -67,7 +67,7 @@ public void Setup() }) } }; - style2 = new Style + style2 = new Style { Setters = { @@ -149,6 +149,12 @@ public class Clock : IClock public TimeSpan CurrentTime { get; } public TimeSpan AudioStartTime { get; } + + public TimeSpan BeginTime { get; } + + public TimeSpan DurationTime { get; } + + public IClock GlobalClock => this; } public class InheritStyleableObject : StyleableObject From 302be1538fa8f5641b636ee8697f6a03ae4e401e Mon Sep 17 00:00:00 2001 From: indigo-san Date: Sun, 30 Apr 2023 22:45:56 +0900 Subject: [PATCH 40/84] =?UTF-8?q?Geometry=E3=81=AE=E3=83=8E=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Graphics/Shapes/EllipseShape.cs | 66 +--------- .../Graphics/Shapes/RectShape.cs | 62 +--------- .../Graphics/Shapes/RoundedRectShape.cs | 113 +----------------- src/Beutl.Graphics/Graphics/Shapes/Shape.cs | 23 ++-- .../Media/Geometry/EllipseGeometry.cs | 61 ++++++++++ .../Media/Geometry/RectGeometry.cs | 57 +++++++++ .../Media/Geometry/RoundedRectGeometry.cs | 107 +++++++++++++++++ .../NodeTree/NodeRegistry.cs | 24 +++- .../Nodes/Geometry/EllipseGeometryNode.cs | 44 +++++++ .../Nodes/Geometry/RectGeometryNode.cs | 44 +++++++ .../Nodes/Geometry/RoundedRectGeometryNode.cs | 47 ++++++++ .../NodeTree/Nodes/GeometryShapeNode.cs | 80 +++++++++++++ .../NodeTree/Nodes/NodesRegistrar.cs | 2 +- .../NodeTree/Nodes/RectNode.cs | 70 ----------- 14 files changed, 488 insertions(+), 312 deletions(-) create mode 100644 src/Beutl.Graphics/Media/Geometry/EllipseGeometry.cs create mode 100644 src/Beutl.Graphics/Media/Geometry/RectGeometry.cs create mode 100644 src/Beutl.Graphics/Media/Geometry/RoundedRectGeometry.cs create mode 100644 src/Beutl.ProjectSystem/NodeTree/Nodes/Geometry/EllipseGeometryNode.cs create mode 100644 src/Beutl.ProjectSystem/NodeTree/Nodes/Geometry/RectGeometryNode.cs create mode 100644 src/Beutl.ProjectSystem/NodeTree/Nodes/Geometry/RoundedRectGeometryNode.cs create mode 100644 src/Beutl.ProjectSystem/NodeTree/Nodes/GeometryShapeNode.cs delete mode 100644 src/Beutl.ProjectSystem/NodeTree/Nodes/RectNode.cs diff --git a/src/Beutl.Graphics/Graphics/Shapes/EllipseShape.cs b/src/Beutl.Graphics/Graphics/Shapes/EllipseShape.cs index 6efd36df5..56264fc3d 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/EllipseShape.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/EllipseShape.cs @@ -2,78 +2,22 @@ namespace Beutl.Graphics.Shapes; -public sealed class EllipseShape : Shape +public sealed partial class EllipseShape : Shape { private EllipseGeometry? _geometry; static EllipseShape() { + WidthProperty.OverrideDefaultValue(0f); + HeightProperty.OverrideDefaultValue(0f); AffectsGeometry(WidthProperty, HeightProperty); } protected override Geometry CreateGeometry() { _geometry ??= new EllipseGeometry(); - _geometry.Width = Width; - _geometry.Height = Height; + _geometry.Width = Math.Max(Width, 0); + _geometry.Height = Math.Max(Height, 0); return _geometry; } - - private sealed class EllipseGeometry : Geometry - { - public static readonly CoreProperty WidthProperty; - public static readonly CoreProperty HeightProperty; - private float _width = 0; - private float _height = 0; - - static EllipseGeometry() - { - WidthProperty = ConfigureProperty(nameof(Width)) - .Accessor(o => o.Width, (o, v) => o.Width = v) - .DefaultValue(0) - .Register(); - - HeightProperty = ConfigureProperty(nameof(Height)) - .Accessor(o => o.Height, (o, v) => o.Height = v) - .DefaultValue(0) - .Register(); - - AffectsRender(WidthProperty, HeightProperty); - } - - public float Width - { - get => _width; - set => SetAndRaise(WidthProperty, ref _width, value); - } - - public float Height - { - get => _height; - set => SetAndRaise(HeightProperty, ref _height, value); - } - - public override void ApplyTo(IGeometryContext context) - { - base.ApplyTo(context); - float width = Width; - float height = Height; - if (float.IsInfinity(width)) - width = 0; - - if (float.IsInfinity(height)) - height = 0; - - float radiusX = width / 2; - float radiusY = height / 2; - var radius = new Size(radiusX, radiusY); - - context.MoveTo(new Point(radiusX, 0)); - context.ArcTo(radius, 90, false, true, new Point(width, radiusY)); - context.ArcTo(radius, 90, false, true, new Point(radiusX, height)); - context.ArcTo(radius, 90, false, true, new Point(0, radiusY)); - context.ArcTo(radius, 90, false, true, new Point(radiusX, 0)); - context.Close(); - } - } } diff --git a/src/Beutl.Graphics/Graphics/Shapes/RectShape.cs b/src/Beutl.Graphics/Graphics/Shapes/RectShape.cs index 9dd416a46..3ad67322c 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/RectShape.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/RectShape.cs @@ -2,74 +2,22 @@ namespace Beutl.Graphics.Shapes; -public sealed class RectShape : Shape +public sealed partial class RectShape : Shape { private RectGeometry? _geometry; static RectShape() { + WidthProperty.OverrideDefaultValue(0f); + HeightProperty.OverrideDefaultValue(0f); AffectsGeometry(WidthProperty, HeightProperty); } protected override Geometry CreateGeometry() { _geometry ??= new RectGeometry(); - _geometry.Width = Width; - _geometry.Height = Height; + _geometry.Width = Math.Max(Width, 0); + _geometry.Height = Math.Max(Height, 0); return _geometry; } - - private sealed class RectGeometry : Geometry - { - public static readonly CoreProperty WidthProperty; - public static readonly CoreProperty HeightProperty; - private float _width = 0; - private float _height = 0; - - static RectGeometry() - { - WidthProperty = ConfigureProperty(nameof(Width)) - .Accessor(o => o.Width, (o, v) => o.Width = v) - .DefaultValue(0) - .Register(); - - HeightProperty = ConfigureProperty(nameof(Height)) - .Accessor(o => o.Height, (o, v) => o.Height = v) - .DefaultValue(0) - .Register(); - - AffectsRender(WidthProperty, HeightProperty); - } - - public float Width - { - get => _width; - set => SetAndRaise(WidthProperty, ref _width, value); - } - - public float Height - { - get => _height; - set => SetAndRaise(HeightProperty, ref _height, value); - } - - public override void ApplyTo(IGeometryContext context) - { - base.ApplyTo(context); - float width = Width; - float height = Height; - if (float.IsInfinity(width)) - width = 0; - - if (float.IsInfinity(height)) - height = 0; - - context.MoveTo(new Point(0, 0)); - context.LineTo(new Point(width, 0)); - context.LineTo(new Point(width, height)); - context.LineTo(new Point(0, height)); - context.LineTo(new Point(0, 0)); - context.Close(); - } - } } diff --git a/src/Beutl.Graphics/Graphics/Shapes/RoundedRectShape.cs b/src/Beutl.Graphics/Graphics/Shapes/RoundedRectShape.cs index f22170572..2e09be8ce 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/RoundedRectShape.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/RoundedRectShape.cs @@ -5,7 +5,7 @@ namespace Beutl.Graphics.Shapes; -public sealed class RoundedRectShape : Shape +public sealed partial class RoundedRectShape : Shape { public static readonly CoreProperty CornerRadiusProperty; private CornerRadius _cornerRadius; @@ -13,6 +13,9 @@ public sealed class RoundedRectShape : Shape static RoundedRectShape() { + WidthProperty.OverrideDefaultValue(0f); + HeightProperty.OverrideDefaultValue(0f); + CornerRadiusProperty = ConfigureProperty(nameof(CornerRadius)) .Accessor(o => o.CornerRadius, (o, v) => o.CornerRadius = v) .DefaultValue(new CornerRadius()) @@ -32,113 +35,9 @@ public CornerRadius CornerRadius protected override Geometry CreateGeometry() { _geometry ??= new RoundedRectGeometry(); - _geometry.Width = Width; - _geometry.Height = Height; + _geometry.Width = Math.Max(Width, 0); + _geometry.Height = Math.Max(Height, 0); _geometry.CornerRadius = CornerRadius; return _geometry; } - - private sealed class RoundedRectGeometry : Geometry - { - public static readonly CoreProperty WidthProperty; - public static readonly CoreProperty HeightProperty; - public static readonly CoreProperty CornerRadiusProperty; - private float _width = 0; - private float _height = 0; - private CornerRadius _cornerRadius; - - static RoundedRectGeometry() - { - WidthProperty = ConfigureProperty(nameof(Width)) - .Accessor(o => o.Width, (o, v) => o.Width = v) - .DefaultValue(0) - .Register(); - - HeightProperty = ConfigureProperty(nameof(Height)) - .Accessor(o => o.Height, (o, v) => o.Height = v) - .DefaultValue(0) - .Register(); - - CornerRadiusProperty = ConfigureProperty(nameof(CornerRadius)) - .Accessor(o => o.CornerRadius, (o, v) => o.CornerRadius = v) - .DefaultValue(new CornerRadius()) - .Register(); - - AffectsRender(WidthProperty, HeightProperty, CornerRadiusProperty); - } - - public float Width - { - get => _width; - set => SetAndRaise(WidthProperty, ref _width, value); - } - - public float Height - { - get => _height; - set => SetAndRaise(HeightProperty, ref _height, value); - } - - public CornerRadius CornerRadius - { - get => _cornerRadius; - set => SetAndRaise(CornerRadiusProperty, ref _cornerRadius, value); - } - - public override void ApplyTo(IGeometryContext context) - { - base.ApplyTo(context); - float width = _width; - float height = _height; - if (float.IsInfinity(width)) - width = 0; - - if (float.IsInfinity(height)) - height = 0; - - (float radiusX, float radiusY) = (width / 2, height / 2); - float maxRadius = Math.Max(radiusX, radiusY); - CornerRadius cornerRadius = _cornerRadius; - float topLeft = Math.Clamp(cornerRadius.TopLeft, 0, maxRadius); - float topRight = Math.Clamp(cornerRadius.TopRight, 0, maxRadius); - float bottomRight = Math.Clamp(cornerRadius.BottomRight, 0, maxRadius); - float bottomLeft = Math.Clamp(cornerRadius.BottomLeft, 0, maxRadius); - - context.MoveTo(new Point(topLeft, 0)); - - context.LineTo(new Point(width - topRight, 0)); - context.ArcTo( - new Size(topRight, topRight), - 90, - false, - true, - new Point(width, topRight)); - - context.LineTo(new Point(width, height - bottomRight)); - context.ArcTo( - new Size(bottomRight, bottomRight), - 90, - false, - true, - new Point(width - bottomRight, height)); - - context.LineTo(new Point(bottomLeft, height)); - context.ArcTo( - new Size(bottomLeft, bottomLeft), - 90, - false, - true, - new Point(0, height - bottomLeft)); - - context.LineTo(new Point(0, topLeft)); - context.ArcTo( - new Size(topLeft, topLeft), - 90, - false, - true, - new Point(topLeft, 0)); - - context.Close(); - } - } } diff --git a/src/Beutl.Graphics/Graphics/Shapes/Shape.cs b/src/Beutl.Graphics/Graphics/Shapes/Shape.cs index f0439f479..a4add081f 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/Shape.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/Shape.cs @@ -13,8 +13,8 @@ public abstract class Shape : Drawable public static readonly CoreProperty PenProperty; public static readonly CoreProperty FillTypeProperty; public static readonly CoreProperty CreatedGeometryProperty; - private float _width = 0; - private float _height = 0; + private float _width = -1; + private float _height = -1; private Stretch _stretch = Stretch.None; private IPen? _pen = null; private PathFillType _fillType; @@ -24,12 +24,12 @@ static Shape() { WidthProperty = ConfigureProperty(nameof(Width)) .Accessor(o => o.Width, (o, v) => o.Width = v) - .DefaultValue(0) + .DefaultValue(float.PositiveInfinity) .Register(); HeightProperty = ConfigureProperty(nameof(Height)) .Accessor(o => o.Height, (o, v) => o.Height = v) - .DefaultValue(0) + .DefaultValue(float.PositiveInfinity) .Register(); StretchProperty = ConfigureProperty(nameof(Stretch)) @@ -145,6 +145,9 @@ private static (Size size, Matrix transform) CalculateSizeAndTransform(Size requ Matrix translate = Matrix.Identity; float desiredX = requestedSize.Width; float desiredY = requestedSize.Height; + bool widthInfinityOrNegative = float.IsInfinity(requestedSize.Width) || requestedSize.Width < 0; + bool heightInfinityOrNegative = float.IsInfinity(requestedSize.Height) || requestedSize.Height < 0; + float sx = 0.0f; float sy = 0.0f; @@ -154,12 +157,12 @@ private static (Size size, Matrix transform) CalculateSizeAndTransform(Size requ translate = Matrix.CreateTranslation(-(Vector)shapeBounds.Position); } - if (float.IsInfinity(requestedSize.Width)) + if (widthInfinityOrNegative) { desiredX = shapeSize.Width; } - if (float.IsInfinity(requestedSize.Height)) + if (heightInfinityOrNegative) { desiredY = shapeSize.Height; } @@ -174,12 +177,12 @@ private static (Size size, Matrix transform) CalculateSizeAndTransform(Size requ sy = desiredY / shapeSize.Height; } - if (float.IsInfinity(requestedSize.Width)) + if (widthInfinityOrNegative) { sx = sy; } - if (float.IsInfinity(requestedSize.Height)) + if (heightInfinityOrNegative) { sy = sx; } @@ -193,12 +196,12 @@ private static (Size size, Matrix transform) CalculateSizeAndTransform(Size requ sx = sy = Math.Max(sx, sy); break; case Stretch.Fill: - if (float.IsInfinity(requestedSize.Width)) + if (widthInfinityOrNegative) { sx = 1.0f; } - if (float.IsInfinity(requestedSize.Height)) + if (heightInfinityOrNegative) { sy = 1.0f; } diff --git a/src/Beutl.Graphics/Media/Geometry/EllipseGeometry.cs b/src/Beutl.Graphics/Media/Geometry/EllipseGeometry.cs new file mode 100644 index 000000000..c31aebe3d --- /dev/null +++ b/src/Beutl.Graphics/Media/Geometry/EllipseGeometry.cs @@ -0,0 +1,61 @@ +using Beutl.Graphics; + +namespace Beutl.Media; + +public sealed class EllipseGeometry : Geometry +{ + public static readonly CoreProperty WidthProperty; + public static readonly CoreProperty HeightProperty; + private float _width = 0; + private float _height = 0; + + static EllipseGeometry() + { + WidthProperty = ConfigureProperty(nameof(Width)) + .Accessor(o => o.Width, (o, v) => o.Width = v) + .DefaultValue(0) + .Register(); + + HeightProperty = ConfigureProperty(nameof(Height)) + .Accessor(o => o.Height, (o, v) => o.Height = v) + .DefaultValue(0) + .Register(); + + AffectsRender(WidthProperty, HeightProperty); + } + + public float Width + { + get => _width; + set => SetAndRaise(WidthProperty, ref _width, value); + } + + public float Height + { + get => _height; + set => SetAndRaise(HeightProperty, ref _height, value); + } + + public override void ApplyTo(IGeometryContext context) + { + base.ApplyTo(context); + float width = Width; + float height = Height; + if (float.IsInfinity(width)) + width = 0; + + if (float.IsInfinity(height)) + height = 0; + + float radiusX = width / 2; + float radiusY = height / 2; + var radius = new Size(radiusX, radiusY); + + context.MoveTo(new Point(radiusX, 0)); + context.ArcTo(radius, 90, false, true, new Point(width, radiusY)); + context.ArcTo(radius, 90, false, true, new Point(radiusX, height)); + context.ArcTo(radius, 90, false, true, new Point(0, radiusY)); + context.ArcTo(radius, 90, false, true, new Point(radiusX, 0)); + context.Close(); + } +} diff --git a/src/Beutl.Graphics/Media/Geometry/RectGeometry.cs b/src/Beutl.Graphics/Media/Geometry/RectGeometry.cs new file mode 100644 index 000000000..42f1dcf89 --- /dev/null +++ b/src/Beutl.Graphics/Media/Geometry/RectGeometry.cs @@ -0,0 +1,57 @@ +using Beutl.Graphics; + +namespace Beutl.Media; + +public sealed class RectGeometry : Geometry +{ + public static readonly CoreProperty WidthProperty; + public static readonly CoreProperty HeightProperty; + private float _width = 0; + private float _height = 0; + + static RectGeometry() + { + WidthProperty = ConfigureProperty(nameof(Width)) + .Accessor(o => o.Width, (o, v) => o.Width = v) + .DefaultValue(0) + .Register(); + + HeightProperty = ConfigureProperty(nameof(Height)) + .Accessor(o => o.Height, (o, v) => o.Height = v) + .DefaultValue(0) + .Register(); + + AffectsRender(WidthProperty, HeightProperty); + } + + public float Width + { + get => _width; + set => SetAndRaise(WidthProperty, ref _width, value); + } + + public float Height + { + get => _height; + set => SetAndRaise(HeightProperty, ref _height, value); + } + + public override void ApplyTo(IGeometryContext context) + { + base.ApplyTo(context); + float width = Width; + float height = Height; + if (float.IsInfinity(width)) + width = 0; + + if (float.IsInfinity(height)) + height = 0; + + context.MoveTo(new Point(0, 0)); + context.LineTo(new Point(width, 0)); + context.LineTo(new Point(width, height)); + context.LineTo(new Point(0, height)); + context.LineTo(new Point(0, 0)); + context.Close(); + } +} diff --git a/src/Beutl.Graphics/Media/Geometry/RoundedRectGeometry.cs b/src/Beutl.Graphics/Media/Geometry/RoundedRectGeometry.cs new file mode 100644 index 000000000..6b1cb10f6 --- /dev/null +++ b/src/Beutl.Graphics/Media/Geometry/RoundedRectGeometry.cs @@ -0,0 +1,107 @@ +using Beutl.Graphics; + +namespace Beutl.Media; + +public sealed class RoundedRectGeometry : Geometry +{ + public static readonly CoreProperty WidthProperty; + public static readonly CoreProperty HeightProperty; + public static readonly CoreProperty CornerRadiusProperty; + private float _width = 0; + private float _height = 0; + private CornerRadius _cornerRadius; + + static RoundedRectGeometry() + { + WidthProperty = ConfigureProperty(nameof(Width)) + .Accessor(o => o.Width, (o, v) => o.Width = v) + .DefaultValue(0) + .Register(); + + HeightProperty = ConfigureProperty(nameof(Height)) + .Accessor(o => o.Height, (o, v) => o.Height = v) + .DefaultValue(0) + .Register(); + + CornerRadiusProperty = ConfigureProperty(nameof(CornerRadius)) + .Accessor(o => o.CornerRadius, (o, v) => o.CornerRadius = v) + .DefaultValue(new CornerRadius()) + .Register(); + + AffectsRender(WidthProperty, HeightProperty, CornerRadiusProperty); + } + + public float Width + { + get => _width; + set => SetAndRaise(WidthProperty, ref _width, value); + } + + public float Height + { + get => _height; + set => SetAndRaise(HeightProperty, ref _height, value); + } + + public CornerRadius CornerRadius + { + get => _cornerRadius; + set => SetAndRaise(CornerRadiusProperty, ref _cornerRadius, value); + } + + public override void ApplyTo(IGeometryContext context) + { + base.ApplyTo(context); + float width = _width; + float height = _height; + if (float.IsInfinity(width)) + width = 0; + + if (float.IsInfinity(height)) + height = 0; + + (float radiusX, float radiusY) = (width / 2, height / 2); + float maxRadius = Math.Max(radiusX, radiusY); + CornerRadius cornerRadius = _cornerRadius; + float topLeft = Math.Clamp(cornerRadius.TopLeft, 0, maxRadius); + float topRight = Math.Clamp(cornerRadius.TopRight, 0, maxRadius); + float bottomRight = Math.Clamp(cornerRadius.BottomRight, 0, maxRadius); + float bottomLeft = Math.Clamp(cornerRadius.BottomLeft, 0, maxRadius); + + context.MoveTo(new Point(topLeft, 0)); + + context.LineTo(new Point(width - topRight, 0)); + context.ArcTo( + new Size(topRight, topRight), + 90, + false, + true, + new Point(width, topRight)); + + context.LineTo(new Point(width, height - bottomRight)); + context.ArcTo( + new Size(bottomRight, bottomRight), + 90, + false, + true, + new Point(width - bottomRight, height)); + + context.LineTo(new Point(bottomLeft, height)); + context.ArcTo( + new Size(bottomLeft, bottomLeft), + 90, + false, + true, + new Point(0, height - bottomLeft)); + + context.LineTo(new Point(0, topLeft)); + context.ArcTo( + new Size(topLeft, topLeft), + 90, + false, + true, + new Point(topLeft, 0)); + + context.Close(); + } +} diff --git a/src/Beutl.ProjectSystem/NodeTree/NodeRegistry.cs b/src/Beutl.ProjectSystem/NodeTree/NodeRegistry.cs index 8bfe24615..11e15da5b 100644 --- a/src/Beutl.ProjectSystem/NodeTree/NodeRegistry.cs +++ b/src/Beutl.ProjectSystem/NodeTree/NodeRegistry.cs @@ -1,4 +1,5 @@ -using System.Reactive.Linq; +using System.Diagnostics.CodeAnalysis; +using System.Reactive.Linq; using Beutl.Media; @@ -9,13 +10,17 @@ public class NodeRegistry private static readonly List s_nodes = new(); internal static int s_totalCount; - public static void RegisterNode(string displayName) + public static void RegisterNode< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>( + string displayName) where T : Node, new() { Register(new RegistryItem(displayName, Colors.Teal, typeof(T))); } - public static void RegisterNode(string displayName, Color accentColor) + public static void RegisterNode< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>( + string displayName, Color accentColor) where T : Node, new() { Register(new RegistryItem(displayName, accentColor, typeof(T))); @@ -109,7 +114,11 @@ private static void Register(BaseRegistryItem item) public record BaseRegistryItem(string DisplayName, Color AccentColor); - public record RegistryItem(string DisplayName, Color AccentColor, Type Type) + public record RegistryItem( + string DisplayName, + Color AccentColor, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type Type) : BaseRegistryItem(DisplayName, AccentColor); public record GroupableRegistryItem(string DisplayName, Color AccentColor) @@ -163,7 +172,9 @@ internal RegistrationHelper(GroupableRegistryItem item, Action NodeRegistry.Register(item)); } - public RegistrationHelper Add(string displayName) + public RegistrationHelper Add< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>( + string displayName) where T : Node, new() { _item.Items!.Add(new RegistryItem(displayName, Colors.Teal, typeof(T))); @@ -171,7 +182,8 @@ public RegistrationHelper Add(string displayName) return this; } - public RegistrationHelper Add(string displayName, Color accentColor) + public RegistrationHelper Add<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>( + string displayName, Color accentColor) where T : Node, new() { _item.Items.Add(new RegistryItem(displayName, accentColor, typeof(T))); diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Geometry/EllipseGeometryNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Geometry/EllipseGeometryNode.cs new file mode 100644 index 000000000..45a73efbe --- /dev/null +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Geometry/EllipseGeometryNode.cs @@ -0,0 +1,44 @@ +using Beutl.Media; + +namespace Beutl.NodeTree.Nodes.Geometry; + +public sealed class EllipseGeometryNode : Node +{ + private readonly OutputSocket _outputSocket; + private readonly InputSocket _widthSocket; + private readonly InputSocket _heightSocket; + + public EllipseGeometryNode() + { + _outputSocket = AsOutput("Geometry"); + + _widthSocket = AsInput(EllipseGeometry.WidthProperty, value: 100).AcceptNumber(); + _heightSocket = AsInput(EllipseGeometry.HeightProperty, value: 100).AcceptNumber(); + } + + public override void InitializeForContext(NodeEvaluationContext context) + { + base.InitializeForContext(context); + context.State = new EllipseGeometry(); + } + + public override void UninitializeForContext(NodeEvaluationContext context) + { + base.UninitializeForContext(context); + context.State = null; + } + + public override void Evaluate(NodeEvaluationContext context) + { + EllipseGeometry ellipse = context.GetOrSetState(); + while (ellipse.BatchUpdate) + { + ellipse.EndBatchUpdate(); + } + + ellipse.BeginBatchUpdate(); + ellipse.Width = _widthSocket.Value; + ellipse.Height = _heightSocket.Value; + _outputSocket.Value = ellipse; + } +} diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Geometry/RectGeometryNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Geometry/RectGeometryNode.cs new file mode 100644 index 000000000..eb5c961bc --- /dev/null +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Geometry/RectGeometryNode.cs @@ -0,0 +1,44 @@ +using Beutl.Media; + +namespace Beutl.NodeTree.Nodes.Geometry; + +public sealed class RectGeometryNode : Node +{ + private readonly OutputSocket _outputSocket; + private readonly InputSocket _widthSocket; + private readonly InputSocket _heightSocket; + + public RectGeometryNode() + { + _outputSocket = AsOutput("Geometry"); + + _widthSocket = AsInput(RectGeometry.WidthProperty, value: 100).AcceptNumber(); + _heightSocket = AsInput(RectGeometry.HeightProperty, value: 100).AcceptNumber(); + } + + public override void InitializeForContext(NodeEvaluationContext context) + { + base.InitializeForContext(context); + context.State = new RectGeometry(); + } + + public override void UninitializeForContext(NodeEvaluationContext context) + { + base.UninitializeForContext(context); + context.State = null; + } + + public override void Evaluate(NodeEvaluationContext context) + { + RectGeometry rectangle = context.GetOrSetState(); + while (rectangle.BatchUpdate) + { + rectangle.EndBatchUpdate(); + } + + rectangle.BeginBatchUpdate(); + rectangle.Width = _widthSocket.Value; + rectangle.Height = _heightSocket.Value; + _outputSocket.Value = rectangle; + } +} diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Geometry/RoundedRectGeometryNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Geometry/RoundedRectGeometryNode.cs new file mode 100644 index 000000000..9967251a2 --- /dev/null +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Geometry/RoundedRectGeometryNode.cs @@ -0,0 +1,47 @@ +using Beutl.Media; + +namespace Beutl.NodeTree.Nodes.Geometry; + +public sealed class RoundedRectGeometryNode : Node +{ + private readonly OutputSocket _outputSocket; + private readonly InputSocket _widthSocket; + private readonly InputSocket _heightSocket; + private readonly InputSocket _radiusSocket; + + public RoundedRectGeometryNode() + { + _outputSocket = AsOutput("Geometry"); + + _widthSocket = AsInput(RoundedRectGeometry.WidthProperty, value: 100).AcceptNumber(); + _heightSocket = AsInput(RoundedRectGeometry.HeightProperty, value: 100).AcceptNumber(); + _radiusSocket = AsInput(RoundedRectGeometry.CornerRadiusProperty, value: new(25)); + } + + public override void InitializeForContext(NodeEvaluationContext context) + { + base.InitializeForContext(context); + context.State = new RoundedRectGeometry(); + } + + public override void UninitializeForContext(NodeEvaluationContext context) + { + base.UninitializeForContext(context); + context.State = null; + } + + public override void Evaluate(NodeEvaluationContext context) + { + RoundedRectGeometry rectangle = context.GetOrSetState(); + while (rectangle.BatchUpdate) + { + rectangle.EndBatchUpdate(); + } + + rectangle.BeginBatchUpdate(); + rectangle.Width = _widthSocket.Value; + rectangle.Height = _heightSocket.Value; + rectangle.CornerRadius = _radiusSocket.Value; + _outputSocket.Value = rectangle; + } +} diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/GeometryShapeNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/GeometryShapeNode.cs new file mode 100644 index 000000000..d1f54e079 --- /dev/null +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/GeometryShapeNode.cs @@ -0,0 +1,80 @@ +using Beutl.Graphics; +using Beutl.Graphics.Effects; +using Beutl.Graphics.Filters; +using Beutl.Graphics.Shapes; +using Beutl.Graphics.Transformation; +using Beutl.Media; + +namespace Beutl.NodeTree.Nodes; + +public sealed class GeometryShapeNode : Node +{ + private readonly OutputSocket _outputSocket; + private readonly InputSocket _geometrySocket; + private readonly InputSocket _widthSocket; + private readonly InputSocket _heightSocket; + private readonly InputSocket _stretchSocket; + private readonly InputSocket _fillTypeSocket; + private readonly InputSocket _penSocket; + private readonly InputSocket _transformSocket; + + public GeometryShapeNode() + { + _outputSocket = AsOutput("GeometryShape"); + + _geometrySocket = AsInput(GeometryShape.DataProperty, value: null); + _widthSocket = AsInput(Shape.WidthProperty, value: -1).AcceptNumber(); + _heightSocket = AsInput(Shape.HeightProperty, value: -1).AcceptNumber(); + _stretchSocket = AsInput(Shape.StretchProperty); + _fillTypeSocket = AsInput(Shape.FillTypeProperty); + _penSocket = AsInput(Shape.PenProperty); + _transformSocket = AsInput(Drawable.TransformProperty); + } + + public override void InitializeForContext(NodeEvaluationContext context) + { + base.InitializeForContext(context); + context.State = new GeometryShape(); + } + + public override void UninitializeForContext(NodeEvaluationContext context) + { + base.UninitializeForContext(context); + context.State = null; + } + + public override void Evaluate(NodeEvaluationContext context) + { + GeometryShape shape = context.GetOrSetState(); + while (shape.BatchUpdate) + { + shape.EndBatchUpdate(); + } + + shape.BeginBatchUpdate(); + shape.Data = _geometrySocket.Value; + shape.Width = _widthSocket.Value; + shape.Height = _heightSocket.Value; + shape.Stretch = _stretchSocket.Value; + shape.FillType = _fillTypeSocket.Value; + shape.Pen = _penSocket.Value; + shape.Transform = _transformSocket.Value; + + shape.BlendMode = BlendMode.SrcOver; + shape.AlignmentX = AlignmentX.Left; + shape.AlignmentY = AlignmentY.Top; + shape.TransformOrigin = RelativePoint.TopLeft; + + if (shape.Filter is ImageFilterGroup filterGroup) + filterGroup.Children.Clear(); + else + shape.Filter = new ImageFilterGroup(); + + if (shape.Effect is BitmapEffectGroup effectGroup) + effectGroup.Children.Clear(); + else + shape.Effect = new BitmapEffectGroup(); + + _outputSocket.Value = shape; + } +} diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/NodesRegistrar.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/NodesRegistrar.cs index 38814b2d6..274f34a36 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/NodesRegistrar.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/NodesRegistrar.cs @@ -12,7 +12,7 @@ public static void RegisterAll() { NodeRegistry.RegisterNode("Layer input"); NodeRegistry.RegisterNode("Layer output"); - NodeRegistry.RegisterNode(Strings.Rectangle); + NodeRegistry.RegisterNode(Strings.Rectangle); NodeRegistry.RegisterNodes("Group") .Add("Group Input") diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/RectNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/RectNode.cs deleted file mode 100644 index 2603076f4..000000000 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/RectNode.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Beutl.Graphics; -using Beutl.Graphics.Effects; -using Beutl.Graphics.Filters; -using Beutl.Graphics.Shapes; -using Beutl.Graphics.Transformation; - -namespace Beutl.NodeTree.Nodes; - -public class RectNode : Node -{ - private readonly OutputSocket _outputSocket; - private readonly InputSocket _widthSocket; - private readonly InputSocket _heightSocket; - private readonly InputSocket _strokeSocket; - - public RectNode() - { - _outputSocket = AsOutput("Rectangle"); - - _widthSocket = AsInput(Drawable.WidthProperty, value: 100).AcceptNumber(); - _heightSocket = AsInput(Drawable.HeightProperty, value: 100).AcceptNumber(); - _strokeSocket = AsInput(RectShape.StrokeWidthProperty, value: 4000).AcceptNumber(); - } - - public override void InitializeForContext(NodeEvaluationContext context) - { - base.InitializeForContext(context); - context.State = new RectShape(); - } - - public override void UninitializeForContext(NodeEvaluationContext context) - { - base.UninitializeForContext(context); - context.State = null; - } - - public override void Evaluate(NodeEvaluationContext context) - { - RectShape rectangle = context.GetOrSetState(); - while (rectangle.BatchUpdate) - { - rectangle.EndBatchUpdate(); - } - - rectangle.BeginBatchUpdate(); - rectangle.Width = _widthSocket.Value; - rectangle.Height = _heightSocket.Value; - rectangle.StrokeWidth = _strokeSocket.Value; - rectangle.BlendMode = BlendMode.SrcOver; - rectangle.AlignmentX = Media.AlignmentX.Left; - rectangle.AlignmentY = Media.AlignmentY.Top; - rectangle.TransformOrigin = RelativePoint.TopLeft; - if (rectangle.Transform is TransformGroup transformGroup) - transformGroup.Children.Clear(); - else - rectangle.Transform = new TransformGroup(); - - if (rectangle.Filter is ImageFilterGroup filterGroup) - filterGroup.Children.Clear(); - else - rectangle.Filter = new ImageFilterGroup(); - - if (rectangle.Effect is BitmapEffectGroup effectGroup) - effectGroup.Children.Clear(); - else - rectangle.Effect = new BitmapEffectGroup(); - - _outputSocket.Value = rectangle; - } -} From 699d4b23dca80d06ffcd9e97163f84da3e823205 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Sun, 30 Apr 2023 22:46:18 +0900 Subject: [PATCH 41/84] =?UTF-8?q?ITransform=E3=82=92=E6=B5=81=E3=81=99?= =?UTF-8?q?=E3=83=8E=E3=83=BC=E3=83=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NodeTree/Nodes/NodesRegistrar.cs | 12 ++-- .../Nodes/Transform/MatrixTransformNode.cs | 37 ++++++++++++ ...on3DNode.cs => Rotation3DTransformNode.cs} | 14 ++--- ...tationNode.cs => RotationTransformNode.cs} | 14 ++--- .../{ScaleNode.cs => ScaleTransformNode.cs} | 14 ++--- .../{SkewNode.cs => SkewTransformNode.cs} | 14 ++--- .../NodeTree/Nodes/Transform/TransformNode.cs | 58 +++++++++++-------- ...slateNode.cs => TranslateTransformNode.cs} | 14 ++--- 8 files changed, 108 insertions(+), 69 deletions(-) create mode 100644 src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/MatrixTransformNode.cs rename src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/{Rotation3DNode.cs => Rotation3DTransformNode.cs} (81%) rename src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/{RotationNode.cs => RotationTransformNode.cs} (56%) rename src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/{ScaleNode.cs => ScaleTransformNode.cs} (69%) rename src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/{SkewNode.cs => SkewTransformNode.cs} (63%) rename src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/{TranslateNode.cs => TranslateTransformNode.cs} (62%) diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/NodesRegistrar.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/NodesRegistrar.cs index 274f34a36..8231b0e53 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/NodesRegistrar.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/NodesRegistrar.cs @@ -34,12 +34,12 @@ public static void RegisterAll() .Register(); NodeRegistry.RegisterNodes(Strings.Transform) - .Add(Strings.Transform) - .Add(Strings.Translate) - .Add(Strings.Rotation) - .Add(Strings.Rotation3D) - .Add(Strings.Scale) - .Add(Strings.Skew) + .Add(Strings.Transform) + .Add(Strings.Translate) + .Add(Strings.Rotation) + .Add(Strings.Rotation3D) + .Add(Strings.Scale) + .Add(Strings.Skew) .Register(); NodeRegistry.RegisterNodes("Utilities") diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/MatrixTransformNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/MatrixTransformNode.cs new file mode 100644 index 000000000..25c09a609 --- /dev/null +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/MatrixTransformNode.cs @@ -0,0 +1,37 @@ +using Beutl.Graphics; +using Beutl.Graphics.Transformation; + +namespace Beutl.NodeTree.Nodes.Transform; + +public sealed class MatrixTransformNode : TransformNode +{ + private readonly InputSocket _matrixSocket; + + public MatrixTransformNode() + { + _matrixSocket = AsInput("Matrix"); + } + + public override void InitializeForContext(NodeEvaluationContext context) + { + base.InitializeForContext(context); + context.State = new TransformNodeEvaluationState(null, new MatrixTransform()); + } + + protected override void EvaluateCore(TransformGroup group, object? state) + { + if (state is MatrixTransform model) + { + if (_matrixSocket.Connection != null) + { + model.Matrix = _matrixSocket.Value; + } + else + { + model.Matrix = Matrix.Identity; + } + + group.Children.Add(model); + } + } +} diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/Rotation3DNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/Rotation3DTransformNode.cs similarity index 81% rename from src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/Rotation3DNode.cs rename to src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/Rotation3DTransformNode.cs index dbe7fd3e5..66070ebf5 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/Rotation3DNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/Rotation3DTransformNode.cs @@ -1,9 +1,8 @@ -using Beutl.Graphics; -using Beutl.Graphics.Transformation; +using Beutl.Graphics.Transformation; namespace Beutl.NodeTree.Nodes.Transform; -public class Rotation3DNode : ConfigureNode +public sealed class Rotation3DTransformNode : TransformNode { private readonly InputSocket _rotationXSocket; private readonly InputSocket _rotationYSocket; @@ -13,7 +12,7 @@ public class Rotation3DNode : ConfigureNode private readonly InputSocket _centerZSocket; private readonly InputSocket _depthSocket; - public Rotation3DNode() + public Rotation3DTransformNode() { _rotationXSocket = AsInput(Rotation3DTransform.RotationXProperty).AcceptNumber(); _rotationYSocket = AsInput(Rotation3DTransform.RotationYProperty).AcceptNumber(); @@ -27,13 +26,12 @@ public Rotation3DNode() public override void InitializeForContext(NodeEvaluationContext context) { base.InitializeForContext(context); - context.State = new ConfigureNodeEvaluationState(null, new Rotation3DTransform()); + context.State = new TransformNodeEvaluationState(null, new Rotation3DTransform()); } - protected override void EvaluateCore(Drawable drawable, object? state) + protected override void EvaluateCore(TransformGroup group, object? state) { - if (state is Rotation3DTransform model - && drawable.Transform is TransformGroup group) + if (state is Rotation3DTransform model) { model.RotationX = _rotationXSocket.Value; model.RotationY = _rotationYSocket.Value; diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/RotationNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/RotationTransformNode.cs similarity index 56% rename from src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/RotationNode.cs rename to src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/RotationTransformNode.cs index 90134f4c2..d67f60b22 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/RotationNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/RotationTransformNode.cs @@ -1,13 +1,12 @@ -using Beutl.Graphics; -using Beutl.Graphics.Transformation; +using Beutl.Graphics.Transformation; namespace Beutl.NodeTree.Nodes.Transform; -public class RotationNode : ConfigureNode +public sealed class RotationTransformNode : TransformNode { private readonly InputSocket _rotationSocket; - public RotationNode() + public RotationTransformNode() { _rotationSocket = AsInput(RotationTransform.RotationProperty).AcceptNumber(); } @@ -15,13 +14,12 @@ public RotationNode() public override void InitializeForContext(NodeEvaluationContext context) { base.InitializeForContext(context); - context.State = new ConfigureNodeEvaluationState(null, new RotationTransform()); + context.State = new TransformNodeEvaluationState(null, new RotationTransform()); } - protected override void EvaluateCore(Drawable drawable, object? state) + protected override void EvaluateCore(TransformGroup group, object? state) { - if (state is RotationTransform model - && drawable.Transform is TransformGroup group) + if (state is RotationTransform model) { model.Rotation = _rotationSocket.Value; diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/ScaleNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/ScaleTransformNode.cs similarity index 69% rename from src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/ScaleNode.cs rename to src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/ScaleTransformNode.cs index 0c576af88..be295d89a 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/ScaleNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/ScaleTransformNode.cs @@ -1,15 +1,14 @@ -using Beutl.Graphics; -using Beutl.Graphics.Transformation; +using Beutl.Graphics.Transformation; namespace Beutl.NodeTree.Nodes.Transform; -public class ScaleNode : ConfigureNode +public sealed class ScaleTransformNode : TransformNode { private readonly InputSocket _scaleSocket; private readonly InputSocket _scaleXSocket; private readonly InputSocket _scaleYSocket; - public ScaleNode() + public ScaleTransformNode() { _scaleSocket = AsInput(ScaleTransform.ScaleProperty).AcceptNumber(); _scaleXSocket = AsInput(ScaleTransform.ScaleXProperty).AcceptNumber(); @@ -19,13 +18,12 @@ public ScaleNode() public override void InitializeForContext(NodeEvaluationContext context) { base.InitializeForContext(context); - context.State = new ConfigureNodeEvaluationState(null, new ScaleTransform()); + context.State = new TransformNodeEvaluationState(null, new ScaleTransform()); } - protected override void EvaluateCore(Drawable drawable, object? state) + protected override void EvaluateCore(TransformGroup group, object? state) { - if (state is ScaleTransform model - && drawable.Transform is TransformGroup group) + if (state is ScaleTransform model) { model.Scale = _scaleSocket.Value; model.ScaleX = _scaleXSocket.Value; diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/SkewNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/SkewTransformNode.cs similarity index 63% rename from src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/SkewNode.cs rename to src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/SkewTransformNode.cs index b2fdf8e2f..575408314 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/SkewNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/SkewTransformNode.cs @@ -1,14 +1,13 @@ -using Beutl.Graphics; -using Beutl.Graphics.Transformation; +using Beutl.Graphics.Transformation; namespace Beutl.NodeTree.Nodes.Transform; -public class SkewNode : ConfigureNode +public sealed class SkewTransformNode : TransformNode { private readonly InputSocket _skewXSocket; private readonly InputSocket _skewYSocket; - public SkewNode() + public SkewTransformNode() { _skewXSocket = AsInput(SkewTransform.SkewXProperty).AcceptNumber(); _skewYSocket = AsInput(SkewTransform.SkewYProperty).AcceptNumber(); @@ -17,13 +16,12 @@ public SkewNode() public override void InitializeForContext(NodeEvaluationContext context) { base.InitializeForContext(context); - context.State = new ConfigureNodeEvaluationState(null, new SkewTransform()); + context.State = new TransformNodeEvaluationState(null, new SkewTransform()); } - protected override void EvaluateCore(Drawable drawable, object? state) + protected override void EvaluateCore(TransformGroup group, object? state) { - if (state is SkewTransform model - && drawable.Transform is TransformGroup group) + if (state is SkewTransform model) { model.SkewY = _skewXSocket.Value; model.SkewY = _skewYSocket.Value; diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/TransformNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/TransformNode.cs index ffe5725bd..f3cf1d876 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/TransformNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/TransformNode.cs @@ -1,38 +1,50 @@ -using Beutl.Graphics; -using Beutl.Graphics.Transformation; +using Beutl.Graphics.Transformation; namespace Beutl.NodeTree.Nodes.Transform; -public class TransformNode : ConfigureNode +public sealed class TransformNodeEvaluationState { - private readonly InputSocket _matrixSocket; - - public TransformNode() + public TransformNodeEvaluationState(TransformGroup? created, object? addtionalState) { - _matrixSocket = AsInput("Matrix"); + Created = created; + AddtionalState = addtionalState; } - public override void InitializeForContext(NodeEvaluationContext context) + public TransformGroup? Created { get; set; } + + public object? AddtionalState { get; set; } +} + +public abstract class TransformNode : Node +{ + public TransformNode() { - base.InitializeForContext(context); - context.State = new ConfigureNodeEvaluationState(null, new MatrixTransform()); + OutputSocket = AsOutput("TransformGroup"); + InputSocket = AsInput("TransformGroup"); } - protected override void EvaluateCore(Drawable drawable, object? state) + protected OutputSocket OutputSocket { get; } + + protected InputSocket InputSocket { get; } + + public override void Evaluate(NodeEvaluationContext context) { - if (state is MatrixTransform model - && drawable.Transform is TransformGroup group) + TransformGroup? value = InputSocket.Value; + var state = context.State as TransformNodeEvaluationState; + if (state == null) { - if (_matrixSocket.Connection != null) - { - model.Matrix = _matrixSocket.Value; - } - else - { - model.Matrix = Matrix.Identity; - } - - group.Children.Add(model); + context.State = state = new TransformNodeEvaluationState(null, null); } + + if (value == null) + { + state.Created ??= new TransformGroup(); + value = state.Created; + } + + EvaluateCore(value, state.AddtionalState); + OutputSocket.Value = value; } + + protected abstract void EvaluateCore(TransformGroup group, object? state); } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/TranslateNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/TranslateTransformNode.cs similarity index 62% rename from src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/TranslateNode.cs rename to src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/TranslateTransformNode.cs index 4360c95c5..a8230d87c 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/TranslateNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/TranslateTransformNode.cs @@ -1,14 +1,13 @@ -using Beutl.Graphics; -using Beutl.Graphics.Transformation; +using Beutl.Graphics.Transformation; namespace Beutl.NodeTree.Nodes.Transform; -public class TranslateNode : ConfigureNode +public sealed class TranslateTransformNode : TransformNode { private readonly InputSocket _xSocket; private readonly InputSocket _ySocket; - public TranslateNode() + public TranslateTransformNode() { _xSocket = AsInput(TranslateTransform.XProperty).AcceptNumber(); _ySocket = AsInput(TranslateTransform.YProperty).AcceptNumber(); @@ -17,13 +16,12 @@ public TranslateNode() public override void InitializeForContext(NodeEvaluationContext context) { base.InitializeForContext(context); - context.State = new ConfigureNodeEvaluationState(null, new TranslateTransform()); + context.State = new TransformNodeEvaluationState(null, new TranslateTransform()); } - protected override void EvaluateCore(Drawable drawable, object? state) + protected override void EvaluateCore(TransformGroup group, object? state) { - if (state is TranslateTransform model - && drawable.Transform is TransformGroup group) + if (state is TranslateTransform model) { model.X = _xSocket.Value; model.Y = _ySocket.Value; From c29a4d458c05597d911aa41a51b6bae3b12f5654 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Tue, 2 May 2023 19:29:14 +0900 Subject: [PATCH 42/84] =?UTF-8?q?Pen=E3=81=ABStrokeAlignment=E3=83=97?= =?UTF-8?q?=E3=83=AD=E3=83=91=E3=83=86=E3=82=A3=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Core/CoreProperty.cs | 11 ++ src/Beutl.Graphics/Graphics/Canvas.cs | 107 +++++++++++++----- .../Graphics/SkiaSharpExtensions.cs | 7 +- src/Beutl.Graphics/Media/IPen.cs | 4 +- .../Media/Immutable/ImmutablePen.cs | 11 +- src/Beutl.Graphics/Media/Pen.cs | 29 +++-- src/Beutl.Graphics/Media/StrokeAlignment.cs | 8 ++ tests/Beutl.Graphics.UnitTests/ShapeTests.cs | 58 ++++++++-- 8 files changed, 188 insertions(+), 47 deletions(-) create mode 100644 src/Beutl.Graphics/Media/StrokeAlignment.cs diff --git a/src/Beutl.Core/CoreProperty.cs b/src/Beutl.Core/CoreProperty.cs index 9110fd88b..2cc819e3c 100644 --- a/src/Beutl.Core/CoreProperty.cs +++ b/src/Beutl.Core/CoreProperty.cs @@ -202,6 +202,17 @@ public CoreProperty( public new IObservable> Changed => _changed; + public void OverrideDefaultValue(Optional defaultValue) + where TOverride : ICoreObject + { + OverrideMetadata(new CorePropertyMetadata(defaultValue)); + } + + public void OverrideMetadata(Type type, Optional defaultValue) + { + OverrideMetadata(type, new CorePropertyMetadata(defaultValue)); + } + internal override bool HasObservers => _changed.HasObservers; internal override void NotifyChanged(CorePropertyChangedEventArgs e) diff --git a/src/Beutl.Graphics/Graphics/Canvas.cs b/src/Beutl.Graphics/Graphics/Canvas.cs index 9f898922d..4ee0f839d 100644 --- a/src/Beutl.Graphics/Graphics/Canvas.cs +++ b/src/Beutl.Graphics/Graphics/Canvas.cs @@ -136,7 +136,7 @@ public void DrawBitmap(IBitmap bmp) VerifyAccess(); var size = new Size(bmp.Width, bmp.Height); ConfigureFillPaint(size); - ConfigureStrokePaint(size); + ConfigureStrokePaint(new Rect(size)); if (bmp is Bitmap) { @@ -155,16 +155,24 @@ public void DrawCircle(Rect rect) { VerifyAccess(); ConfigureFillPaint(rect.Size); - _canvas.DrawOval(rect.ToSKRect(), _sharedFillPaint); - if (Pen != null && Pen.Thickness != 0) + IPen? pen = Pen; + if (pen != null && pen.Thickness != 0) { - rect = rect.Inflate(Pen.Thickness); - - ConfigureStrokePaint(rect.Size); - - _canvas.DrawOval(rect.ToSKRect(), _sharedStrokePaint); + if (pen.StrokeAlignment == StrokeAlignment.Center) + { + ConfigureStrokePaint(rect); + _canvas.DrawOval(rect.ToSKRect(), _sharedStrokePaint); + } + else + { + using (var path = new SKPath()) + { + path.AddOval(rect.ToSKRect()); + DrawSKPath(path, true); + } + } } } @@ -172,16 +180,24 @@ public void DrawRect(Rect rect) { VerifyAccess(); ConfigureFillPaint(rect.Size); - _canvas.DrawRect(rect.ToSKRect(), _sharedFillPaint); - if (Pen != null && Pen.Thickness != 0) + IPen? pen = Pen; + if (pen != null && pen.Thickness != 0) { - rect = rect.Inflate(Pen.Thickness); - - ConfigureStrokePaint(rect.Size); - - _canvas.DrawRect(rect.ToSKRect(), _sharedStrokePaint); + if (pen.StrokeAlignment == StrokeAlignment.Center) + { + ConfigureStrokePaint(rect); + _canvas.DrawRect(rect.ToSKRect(), _sharedStrokePaint); + } + else + { + using (var path = new SKPath()) + { + path.AddRect(rect.ToSKRect()); + DrawSKPath(path, true); + } + } } } @@ -221,25 +237,50 @@ public void DrawText(FormattedText text) } } - public void DrawGeometry(Geometry geometry) + private void DrawSKPath(SKPath skPath, bool strokeOnly) { - VerifyAccess(); - SKPath skPath = geometry.GetNativeObject(); - Rect rect = geometry.Bounds; - ConfigureFillPaint(rect.Size); + Rect rect = skPath.Bounds.ToGraphicsRect(); - _canvas.DrawPath(skPath, _sharedFillPaint); + if (!strokeOnly) + { + ConfigureFillPaint(rect.Size); + _canvas.DrawPath(skPath, _sharedFillPaint); + } - if (Pen != null && Pen.Thickness != 0) + IPen? pen = Pen; + if (pen != null && pen.Thickness != 0) { - rect = rect.Inflate(Pen.Thickness); + ConfigureStrokePaint(rect); + switch (pen.StrokeAlignment) + { + case StrokeAlignment.Center: + _canvas.DrawPath(skPath, _sharedStrokePaint); + break; - ConfigureStrokePaint(rect.Size); + case StrokeAlignment.Inside: + _canvas.Save(); + _canvas.ClipPath(skPath, SKClipOperation.Intersect, true); + _canvas.DrawPath(skPath, _sharedStrokePaint); + _canvas.Restore(); + break; - _canvas.DrawPath(skPath, _sharedStrokePaint); + case StrokeAlignment.Outside: + _canvas.Save(); + _canvas.ClipPath(skPath, SKClipOperation.Difference, true); + _canvas.DrawPath(skPath, _sharedStrokePaint); + _canvas.Restore(); + break; + } } } + public void DrawGeometry(Geometry geometry) + { + VerifyAccess(); + SKPath skPath = geometry.GetNativeObject(); + DrawSKPath(skPath, false); + } + public unsafe Bitmap GetBitmap() { VerifyAccess(); @@ -645,7 +686,7 @@ private static void ConfigureTileBrush(SKPaint paint, Size targetSize, ITileBrus } } - private void ConfigureStrokePaint(Size targetSize) + private void ConfigureStrokePaint(Rect rect) { _sharedStrokePaint.Reset(); IPen? pen = Pen; @@ -653,7 +694,19 @@ private void ConfigureStrokePaint(Size targetSize) if (pen != null && pen.Thickness != 0) { float thickness = pen.Thickness; + switch (pen.StrokeAlignment) + { + case StrokeAlignment.Center: + break; + case StrokeAlignment.Inside: + case StrokeAlignment.Outside: + thickness *= 2; + break; + default: + break; + } + rect = rect.Inflate(thickness); _sharedStrokePaint.IsStroke = true; _sharedStrokePaint.StrokeWidth = thickness; _sharedStrokePaint.StrokeCap = (SKStrokeCap)pen.StrokeCap; @@ -679,7 +732,7 @@ private void ConfigureStrokePaint(Size targetSize) _sharedStrokePaint.PathEffect = pe; } - ConfigurePaint(_sharedStrokePaint, targetSize, pen.Brush, BlendMode); + ConfigurePaint(_sharedStrokePaint, rect.Size, pen.Brush, BlendMode); } } diff --git a/src/Beutl.Graphics/Graphics/SkiaSharpExtensions.cs b/src/Beutl.Graphics/Graphics/SkiaSharpExtensions.cs index d55c69902..766532312 100644 --- a/src/Beutl.Graphics/Graphics/SkiaSharpExtensions.cs +++ b/src/Beutl.Graphics/Graphics/SkiaSharpExtensions.cs @@ -23,7 +23,7 @@ public static SKPoint ToSKPoint(this Point p) { return new SKPoint(p.X, p.Y); } - + public static Point ToGraphicsPoint(this in SKPoint p) { return new Point(p.X, p.Y); @@ -49,6 +49,11 @@ public static Rect ToGraphicsRect(this in SKRect r) return new Rect(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top); } + public static Size ToGraphicsSize(this in SKSize s) + { + return new Size(s.Width, s.Height); + } + public static SKMatrix ToSKMatrix(this in Matrix m) { var sm = new SKMatrix diff --git a/src/Beutl.Graphics/Media/IPen.cs b/src/Beutl.Graphics/Media/IPen.cs index 5d7730f2f..962e941e9 100644 --- a/src/Beutl.Graphics/Media/IPen.cs +++ b/src/Beutl.Graphics/Media/IPen.cs @@ -9,12 +9,14 @@ public interface IPen float DashOffset { get; } float Thickness { get; } - + float MiterLimit { get; } StrokeCap StrokeCap { get; } StrokeJoin StrokeJoin { get; } + + StrokeAlignment StrokeAlignment { get; } } public interface IMutablePen : IPen, IAffectsRender diff --git a/src/Beutl.Graphics/Media/Immutable/ImmutablePen.cs b/src/Beutl.Graphics/Media/Immutable/ImmutablePen.cs index 28418a2b0..b251957be 100644 --- a/src/Beutl.Graphics/Media/Immutable/ImmutablePen.cs +++ b/src/Beutl.Graphics/Media/Immutable/ImmutablePen.cs @@ -9,7 +9,8 @@ public ImmutablePen( float thickness, float miterLimit, StrokeCap strokeCap, - StrokeJoin strokeJoin) + StrokeJoin strokeJoin, + StrokeAlignment strokeAlignment) { Brush = brush; DashArray = dashArray; @@ -18,6 +19,7 @@ public ImmutablePen( MiterLimit = miterLimit; StrokeCap = strokeCap; StrokeJoin = strokeJoin; + StrokeAlignment = strokeAlignment; } public IBrush? Brush { get; } @@ -34,6 +36,8 @@ public ImmutablePen( public StrokeJoin StrokeJoin { get; } + public StrokeAlignment StrokeAlignment { get; } + public override bool Equals(object? obj) { return Equals(obj as IPen); @@ -48,12 +52,13 @@ public bool Equals(IPen? other) && Thickness == other.Thickness && MiterLimit == other.MiterLimit && StrokeCap == other.StrokeCap - && StrokeJoin == other.StrokeJoin; + && StrokeJoin == other.StrokeJoin + && StrokeAlignment == other.StrokeAlignment; } public override int GetHashCode() { - return HashCode.Combine(Brush, DashArray, DashOffset, Thickness, MiterLimit, StrokeCap, StrokeJoin); + return HashCode.Combine(Brush, DashArray, DashOffset, Thickness, MiterLimit, StrokeCap, StrokeJoin, StrokeAlignment); } public static bool operator ==(ImmutablePen? left, ImmutablePen? right) => EqualityComparer.Default.Equals(left, right); diff --git a/src/Beutl.Graphics/Media/Pen.cs b/src/Beutl.Graphics/Media/Pen.cs index 17e172f6c..b3f7c6ab4 100644 --- a/src/Beutl.Graphics/Media/Pen.cs +++ b/src/Beutl.Graphics/Media/Pen.cs @@ -16,6 +16,7 @@ public sealed class Pen : Animatable, IMutablePen public static readonly CoreProperty MiterLimitProperty; public static readonly CoreProperty StrokeCapProperty; public static readonly CoreProperty StrokeJoinProperty; + public static readonly CoreProperty StrokeAlignmentProperty; private IBrush? _brush; private CoreList? _dashArray; private float _dashOffset; @@ -23,6 +24,7 @@ public sealed class Pen : Animatable, IMutablePen private float _miterLimit = 10; private StrokeCap _strokeCap = StrokeCap.Flat; private StrokeJoin _strokeJoin = StrokeJoin.Miter; + private StrokeAlignment _strokeAlignment = StrokeAlignment.Center; static Pen() { @@ -57,6 +59,11 @@ static Pen() .Accessor(o => o.StrokeJoin, (o, v) => o.StrokeJoin = v) .DefaultValue(StrokeJoin.Miter) .Register(); + + StrokeAlignmentProperty = ConfigureProperty(nameof(StrokeAlignment)) + .Accessor(o => o.StrokeAlignment, (o, v) => o.StrokeAlignment = v) + .DefaultValue(StrokeAlignment.Center) + .Register(); } public IBrush? Brush @@ -82,7 +89,7 @@ public float Thickness get => _thickness; set => SetAndRaise(ThicknessProperty, ref _thickness, value); } - + public float MiterLimit { get => _miterLimit; @@ -101,6 +108,12 @@ public StrokeJoin StrokeJoin set => SetAndRaise(StrokeJoinProperty, ref _strokeJoin, value); } + public StrokeAlignment StrokeAlignment + { + get => _strokeAlignment; + set => SetAndRaise(StrokeAlignmentProperty, ref _strokeAlignment, value); + } + IReadOnlyList? IPen.DashArray => _dashArray; public event EventHandler? Invalidated; @@ -145,11 +158,12 @@ protected override void OnPropertyChanged(PropertyChangedEventArgs args) goto RaiseInvalidated; - case CorePropertyChangedEventArgs { PropertyName: nameof(DashOffset) }: - case CorePropertyChangedEventArgs { PropertyName: nameof(Thickness) }: - case CorePropertyChangedEventArgs { PropertyName: nameof(MiterLimit) }: - case CorePropertyChangedEventArgs { PropertyName: nameof(StrokeCap) }: - case CorePropertyChangedEventArgs { PropertyName: nameof(StrokeJoin) }: + case { PropertyName: nameof(DashOffset) }: + case { PropertyName: nameof(Thickness) }: + case { PropertyName: nameof(MiterLimit) }: + case { PropertyName: nameof(StrokeCap) }: + case { PropertyName: nameof(StrokeJoin) }: + case { PropertyName: nameof(StrokeAlignment) }: RaiseInvalidated: Invalidated?.Invoke(this, new RenderInvalidatedEventArgs(this, args.PropertyName)); break; @@ -178,6 +192,7 @@ public IPen ToImmutable() Thickness, MiterLimit, StrokeCap, - StrokeJoin); + StrokeJoin, + StrokeAlignment); } } diff --git a/src/Beutl.Graphics/Media/StrokeAlignment.cs b/src/Beutl.Graphics/Media/StrokeAlignment.cs new file mode 100644 index 000000000..fabff4916 --- /dev/null +++ b/src/Beutl.Graphics/Media/StrokeAlignment.cs @@ -0,0 +1,8 @@ +namespace Beutl.Media; + +public enum StrokeAlignment +{ + Center = 0, + Inside = 1, + Outside = 2 +} diff --git a/tests/Beutl.Graphics.UnitTests/ShapeTests.cs b/tests/Beutl.Graphics.UnitTests/ShapeTests.cs index 7a2a31bf9..9e6038b80 100644 --- a/tests/Beutl.Graphics.UnitTests/ShapeTests.cs +++ b/tests/Beutl.Graphics.UnitTests/ShapeTests.cs @@ -11,6 +11,8 @@ using NUnit.Framework; +using SkiaSharp; + namespace Beutl.Graphics.UnitTests; public class ShapeTests @@ -124,15 +126,51 @@ public void DrawRoundedRect() Assert.IsTrue(bmp.Save(Path.Combine(ArtifactProvider.GetArtifactDirectory(), $"0.png"), EncodedImageFormat.Png)); } + + [Test] + [TestCase(StrokeAlignment.Center)] + [TestCase(StrokeAlignment.Inside)] + [TestCase(StrokeAlignment.Outside)] + public void DrawRoundedRectWithStroke(StrokeAlignment alignment) + { + var shape = new RoundedRectShape + { + AlignmentX = AlignmentX.Center, + AlignmentY = AlignmentY.Center, + TransformOrigin = RelativePoint.Center, + + Width = 100, + Height = 100, + CornerRadius = new CornerRadius(25), + + Foreground = Brushes.Gray, + Pen = new Pen() + { + Brush = Brushes.White, + Thickness = 10, + StrokeCap = StrokeCap.Round, + StrokeAlignment = alignment, + } + }; + + using var canvas = new Canvas(250, 250); + + canvas.Clear(Colors.Black); + shape.Draw(canvas); + + using Bitmap bmp = canvas.GetBitmap(); + + Assert.IsTrue(bmp.Save(Path.Combine(ArtifactProvider.GetArtifactDirectory(), $"{alignment}.png"), EncodedImageFormat.Png)); + } [Test] public void DrawGeometry() { var geometry = new PathGeometry(); - var center = new Point(50, 50); - float radius = 0.45f * Math.Min(100, 100); + var center = new Point(100, 100); + float radius = 0.45f * Math.Min(200, 200); - geometry.MoveTo(new Point(50, 50 - radius)); + geometry.MoveTo(new Point(100, 100 - radius)); for (int i = 1; i < 5; i++) { @@ -162,13 +200,16 @@ public void DrawGeometry() } [Test] - public void DrawGeometryWithPen() + [TestCase(StrokeAlignment.Center)] + [TestCase(StrokeAlignment.Inside)] + [TestCase(StrokeAlignment.Outside)] + public void DrawGeometryWithPen(StrokeAlignment alignment) { var geometry = new PathGeometry(); - var center = new Point(50, 50); - float radius = 0.45f * Math.Min(100, 100); + var center = new Point(100, 100); + float radius = 0.45f * Math.Min(200, 200); - geometry.MoveTo(new Point(50, 50 - radius)); + geometry.MoveTo(new Point(100, 100 - radius)); for (int i = 1; i < 5; i++) { @@ -191,6 +232,7 @@ public void DrawGeometryWithPen() Brush = Brushes.White, Thickness = 10, StrokeCap = StrokeCap.Round, + StrokeAlignment = alignment, } }; @@ -201,6 +243,6 @@ public void DrawGeometryWithPen() using Bitmap bmp = canvas.GetBitmap(); - Assert.IsTrue(bmp.Save(Path.Combine(ArtifactProvider.GetArtifactDirectory(), $"0.png"), EncodedImageFormat.Png)); + Assert.IsTrue(bmp.Save(Path.Combine(ArtifactProvider.GetArtifactDirectory(), $"{alignment}.png"), EncodedImageFormat.Png)); } } From de7f39a465febe65b151f20df91e1ea819131d28 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Wed, 3 May 2023 09:54:49 +0900 Subject: [PATCH 43/84] =?UTF-8?q?Beutl.Styling.Setter=E3=81=8B=E3=82=89Cor?= =?UTF-8?q?ePropertyMetadata=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Graphics/Styling/ISetter.cs | 2 -- src/Beutl.Graphics/Styling/Setter.cs | 8 -------- 2 files changed, 10 deletions(-) diff --git a/src/Beutl.Graphics/Styling/ISetter.cs b/src/Beutl.Graphics/Styling/ISetter.cs index bc6b6b620..c2e7d98ad 100644 --- a/src/Beutl.Graphics/Styling/ISetter.cs +++ b/src/Beutl.Graphics/Styling/ISetter.cs @@ -8,8 +8,6 @@ namespace Beutl.Styling; public interface ISetter { CoreProperty Property { get; } - - CorePropertyMetadata Metadata { get; } object? Value { get; } diff --git a/src/Beutl.Graphics/Styling/Setter.cs b/src/Beutl.Graphics/Styling/Setter.cs index 116beee57..62114751a 100644 --- a/src/Beutl.Graphics/Styling/Setter.cs +++ b/src/Beutl.Graphics/Styling/Setter.cs @@ -40,12 +40,6 @@ public Setter(CoreProperty property, T? value) _isDefault = false; } - public CorePropertyMetadata Metadata - { - get => _metadata ?? throw new InvalidOperationException(); - set => _metadata = value; - } - public CoreProperty Property { get => _property ?? throw new InvalidOperationException(); @@ -119,8 +113,6 @@ public IAnimation? Animation } } - CorePropertyMetadata ISetter.Metadata => Metadata; - CoreProperty ISetter.Property => Property; object? ISetter.Value => Value; From 2ced3b97938bcf8f32158e37d14d1a76dead10ef Mon Sep 17 00:00:00 2001 From: indigo-san Date: Wed, 3 May 2023 09:55:59 +0900 Subject: [PATCH 44/84] =?UTF-8?q?ComposedBitmapEffect,=20ComposedImageFilt?= =?UTF-8?q?er,=20MultiTransform=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Graphics/Effects/BitmapEffect.cs | 11 ++ .../Graphics/Effects/ComposedBitmapEffect.cs | 120 ++++++++++++++++++ .../Graphics/Filters/ComposedImageFilter.cs | 73 +++++++++++ .../Graphics/Filters/ImageFilter.cs | 11 ++ .../Graphics/Transformation/MultiTransform.cs | 71 +++++++++++ .../Graphics/Transformation/Transform.cs | 17 +++ 6 files changed, 303 insertions(+) create mode 100644 src/Beutl.Graphics/Graphics/Effects/ComposedBitmapEffect.cs create mode 100644 src/Beutl.Graphics/Graphics/Filters/ComposedImageFilter.cs create mode 100644 src/Beutl.Graphics/Graphics/Transformation/MultiTransform.cs diff --git a/src/Beutl.Graphics/Graphics/Effects/BitmapEffect.cs b/src/Beutl.Graphics/Graphics/Effects/BitmapEffect.cs index 52e03f32f..87c576c7b 100644 --- a/src/Beutl.Graphics/Graphics/Effects/BitmapEffect.cs +++ b/src/Beutl.Graphics/Graphics/Effects/BitmapEffect.cs @@ -48,11 +48,22 @@ protected static void AffectsRender(params CoreProperty[] properties) if (e.Sender is T s) { s.RaiseInvalidated(new RenderInvalidatedEventArgs(s, e.Property.Name)); + + if (e.OldValue is IAffectsRender oldAffectsRender) + oldAffectsRender.Invalidated -= s.OnAffectsRenderInvalidated; + + if (e.NewValue is IAffectsRender newAffectsRender) + newAffectsRender.Invalidated += s.OnAffectsRenderInvalidated; } }); } } + private void OnAffectsRenderInvalidated(object? sender, RenderInvalidatedEventArgs e) + { + Invalidated?.Invoke(this, e); + } + protected void RaiseInvalidated(RenderInvalidatedEventArgs args) { Invalidated?.Invoke(this, args); diff --git a/src/Beutl.Graphics/Graphics/Effects/ComposedBitmapEffect.cs b/src/Beutl.Graphics/Graphics/Effects/ComposedBitmapEffect.cs new file mode 100644 index 000000000..d9904e95c --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Effects/ComposedBitmapEffect.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Beutl.Animation; +using Beutl.Media; +using Beutl.Media.Pixel; + +namespace Beutl.Graphics.Effects; + +public class ComposedBitmapEffect : BitmapEffect +{ + public static readonly CoreProperty FirstProperty; + public static readonly CoreProperty SecondProperty; + private IBitmapEffect? _first; + private IBitmapEffect? _second; + + static ComposedBitmapEffect() + { + FirstProperty = ConfigureProperty(nameof(First)) + .Accessor(o => o.First, (o, v) => o.First = v) + .Register(); + + SecondProperty = ConfigureProperty(nameof(Second)) + .Accessor(o => o.Second, (o, v) => o.Second = v) + .Register(); + + AffectsRender(FirstProperty, SecondProperty); + } + + public ComposedBitmapEffect() + { + Processor = new _Processor(this); + } + + public ComposedBitmapEffect(IBitmapEffect? first, IBitmapEffect? second) + : this() + { + First = first; + Second = second; + } + + public IBitmapEffect? First + { + get => _first; + set => SetAndRaise(FirstProperty, ref _first, value); + } + + public IBitmapEffect? Second + { + get => _second; + set => SetAndRaise(SecondProperty, ref _second, value); + } + + public override IBitmapProcessor Processor { get; } + + public override void ApplyAnimations(IClock clock) + { + base.ApplyAnimations(clock); + (First as IAnimatable)?.ApplyAnimations(clock); + (Second as IAnimatable)?.ApplyAnimations(clock); + } + + public override Rect TransformBounds(Rect rect) + { + switch ((First, Second)) + { + case (null or { IsEnabled: false }, { IsEnabled: true }): + return Second.TransformBounds(rect); + + case ({ IsEnabled: true }, null or { IsEnabled: false }): + return First.TransformBounds(rect); + + case ({ IsEnabled: true }, { IsEnabled: true }): + return Second.TransformBounds(First.TransformBounds(rect)); + + default: + return rect; + } + } + + private sealed class _Processor : IBitmapProcessor + { + private readonly ComposedBitmapEffect _obj; + + public _Processor(ComposedBitmapEffect obj) => _obj = obj; + + public void Process(in Bitmap src, out Bitmap dst) + { + IBitmapEffect? first = _obj.First; + IBitmapEffect? second = _obj.Second; + + switch ((first, second)) + { + case (null or { IsEnabled: false }, { IsEnabled: true }): + second.Processor.Process(in src, out dst); + break; + case ({ IsEnabled: true }, null or { IsEnabled: false }): + first.Processor.Process(in src, out dst); + break; + + case ({ IsEnabled: true }, { IsEnabled: true }): + first.Processor.Process(src, out Bitmap? tmpDst); + second.Processor.Process(tmpDst, out dst); + + if (tmpDst != dst) + { + tmpDst.Dispose(); + } + break; + + default: + dst = src; + break; + } + } + } +} diff --git a/src/Beutl.Graphics/Graphics/Filters/ComposedImageFilter.cs b/src/Beutl.Graphics/Graphics/Filters/ComposedImageFilter.cs new file mode 100644 index 000000000..b511fb735 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Filters/ComposedImageFilter.cs @@ -0,0 +1,73 @@ +using Beutl.Animation; + +using SkiaSharp; + +namespace Beutl.Graphics.Filters; + +public sealed class ComposedImageFilter : ImageFilter +{ + public static readonly CoreProperty OuterProperty; + public static readonly CoreProperty InnerProperty; + private IImageFilter? _outer; + private IImageFilter? _inner; + + static ComposedImageFilter() + { + OuterProperty = ConfigureProperty(nameof(Outer)) + .Accessor(o => o.Outer, (o, v) => o.Outer = v) + .Register(); + + InnerProperty = ConfigureProperty(nameof(Inner)) + .Accessor(o => o.Inner, (o, v) => o.Inner = v) + .Register(); + + AffectsRender(OuterProperty, InnerProperty); + } + + public ComposedImageFilter() + { + } + + public ComposedImageFilter(IImageFilter? outer, IImageFilter? inner) + { + Outer = outer; + Inner = inner; + } + + public IImageFilter? Outer + { + get => _outer; + set => SetAndRaise(OuterProperty, ref _outer, value); + } + + public IImageFilter? Inner + { + get => _inner; + set => SetAndRaise(InnerProperty, ref _inner, value); + } + + protected internal override SKImageFilter ToSKImageFilter() + { + switch ((Outer, Inner)) + { + case (null or { IsEnabled: false }, { IsEnabled: true }): + return Inner.ToSKImageFilter(); + + case ({ IsEnabled: true }, null or { IsEnabled: false }): + return Outer.ToSKImageFilter(); + + case ({ IsEnabled: true }, { IsEnabled: true }): + return SKImageFilter.CreateCompose(Outer.ToSKImageFilter(), Inner.ToSKImageFilter()); + + default: + return SKImageFilter.CreateOffset(0, 0); + } + } + + public override void ApplyAnimations(IClock clock) + { + base.ApplyAnimations(clock); + (Outer as IAnimatable)?.ApplyAnimations(clock); + (Inner as IAnimatable)?.ApplyAnimations(clock); + } +} diff --git a/src/Beutl.Graphics/Graphics/Filters/ImageFilter.cs b/src/Beutl.Graphics/Graphics/Filters/ImageFilter.cs index 567d9f853..a4b5e2719 100644 --- a/src/Beutl.Graphics/Graphics/Filters/ImageFilter.cs +++ b/src/Beutl.Graphics/Graphics/Filters/ImageFilter.cs @@ -50,11 +50,22 @@ protected static void AffectsRender(params CoreProperty[] properties) if (e.Sender is T s) { s.RaiseInvalidated(new RenderInvalidatedEventArgs(s, e.Property.Name)); + + if (e.OldValue is IAffectsRender oldAffectsRender) + oldAffectsRender.Invalidated -= s.OnAffectsRenderInvalidated; + + if (e.NewValue is IAffectsRender newAffectsRender) + newAffectsRender.Invalidated += s.OnAffectsRenderInvalidated; } }); } } + private void OnAffectsRenderInvalidated(object? sender, RenderInvalidatedEventArgs e) + { + Invalidated?.Invoke(this, e); + } + protected void RaiseInvalidated(RenderInvalidatedEventArgs args) { Invalidated?.Invoke(this, args); diff --git a/src/Beutl.Graphics/Graphics/Transformation/MultiTransform.cs b/src/Beutl.Graphics/Graphics/Transformation/MultiTransform.cs new file mode 100644 index 000000000..5b3ba55a5 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Transformation/MultiTransform.cs @@ -0,0 +1,71 @@ +using Beutl.Animation; + +namespace Beutl.Graphics.Transformation; + +public sealed class MultiTransform : Transform +{ + public static readonly CoreProperty LeftProperty; + public static readonly CoreProperty RightProperty; + private ITransform? _left; + private ITransform? _right; + + static MultiTransform() + { + LeftProperty = ConfigureProperty(nameof(Left)) + .Accessor(o => o.Left, (o, v) => o.Left = v) + .Register(); + + RightProperty = ConfigureProperty(nameof(Right)) + .Accessor(o => o.Right, (o, v) => o.Right = v) + .Register(); + + AffectsRender(LeftProperty, RightProperty); + } + + public MultiTransform() + { + } + + public MultiTransform(ITransform? left, ITransform? right) + { + Left = left; + Right = right; + } + + public ITransform? Left + { + get => _left; + set => SetAndRaise(LeftProperty, ref _left, value); + } + + // 元のTransform、Inner + public ITransform? Right + { + get => _right; + set => SetAndRaise(RightProperty, ref _right, value); + } + + public override Matrix Value + { + get + { + return (Left, Right) switch + { + (null or { IsEnabled: false }, { IsEnabled: true }) + => Right.Value, + ({ IsEnabled: true }, null or { IsEnabled: false}) + => Left.Value, + ({ IsEnabled: true }, { IsEnabled: true }) + => Left.Value * Right.Value, + _ => Matrix.Identity, + }; + } + } + + public override void ApplyAnimations(IClock clock) + { + base.ApplyAnimations(clock); + (Left as IAnimatable)?.ApplyAnimations(clock); + (Right as IAnimatable)?.ApplyAnimations(clock); + } +} diff --git a/src/Beutl.Graphics/Graphics/Transformation/Transform.cs b/src/Beutl.Graphics/Graphics/Transformation/Transform.cs index b057cfbe0..d71724e9f 100644 --- a/src/Beutl.Graphics/Graphics/Transformation/Transform.cs +++ b/src/Beutl.Graphics/Graphics/Transformation/Transform.cs @@ -84,6 +84,12 @@ static void onNext(CorePropertyChangedEventArgs e) if (e.Sender is T s) { s.RaiseInvalidated(new RenderInvalidatedEventArgs(s, e.Property.Name)); + + if (e.OldValue is IAffectsRender oldAffectsRender) + oldAffectsRender.Invalidated -= s.OnAffectsRenderInvalidated; + + if (e.NewValue is IAffectsRender newAffectsRender) + newAffectsRender.Invalidated += s.OnAffectsRenderInvalidated; } } @@ -93,6 +99,11 @@ static void onNext(CorePropertyChangedEventArgs e) property4?.Changed.Subscribe(onNext); } + private void OnAffectsRenderInvalidated(object? sender, RenderInvalidatedEventArgs e) + { + Invalidated?.Invoke(this, e); + } + protected static void AffectsRender(params CoreProperty[] properties) where T : Transform { @@ -103,6 +114,12 @@ protected static void AffectsRender(params CoreProperty[] properties) if (e.Sender is T s) { s.RaiseInvalidated(new RenderInvalidatedEventArgs(s, e.Property.Name)); + + if (e.OldValue is IAffectsRender oldAffectsRender) + oldAffectsRender.Invalidated -= s.OnAffectsRenderInvalidated; + + if (e.NewValue is IAffectsRender newAffectsRender) + newAffectsRender.Invalidated += s.OnAffectsRenderInvalidated; } }); } From 677b8c68a1d00c2986d9ae967c1535089dab2aaa Mon Sep 17 00:00:00 2001 From: indigo-san Date: Wed, 3 May 2023 09:58:59 +0900 Subject: [PATCH 45/84] =?UTF-8?q?ComposedXXX,=20MultiTransform=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E3=81=86=E3=82=88=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BitmapEffect/BitmapEffectOperator.cs | 20 ++++++- .../ImageFilter/ImageFilterOperator.cs | 20 ++++++- .../Configure/Transform/TransformOperator.cs | 20 ++++++- .../Source/DrawablePublishOperator.cs | 31 +++++----- .../NodeTree/Nodes/Filters/DropShadowNode.cs | 10 ++-- .../NodeTree/Nodes/Filters/ImageFilterNode.cs | 58 +++++++++++++++++++ .../Nodes/Transform/MatrixTransformNode.cs | 6 +- .../Transform/Rotation3DTransformNode.cs | 5 +- .../Nodes/Transform/RotationTransformNode.cs | 6 +- .../Nodes/Transform/ScaleTransformNode.cs | 6 +- .../Nodes/Transform/SkewTransformNode.cs | 6 +- .../NodeTree/Nodes/Transform/TransformNode.cs | 40 +++++++------ .../Nodes/Transform/TranslateTransformNode.cs | 5 +- .../Operation/StyledSourcePublisher.cs | 8 +-- 14 files changed, 167 insertions(+), 74 deletions(-) create mode 100644 src/Beutl.ProjectSystem/NodeTree/Nodes/Filters/ImageFilterNode.cs diff --git a/src/Beutl.Operators/Configure/BitmapEffect/BitmapEffectOperator.cs b/src/Beutl.Operators/Configure/BitmapEffect/BitmapEffectOperator.cs index b6759d3ce..61a371862 100644 --- a/src/Beutl.Operators/Configure/BitmapEffect/BitmapEffectOperator.cs +++ b/src/Beutl.Operators/Configure/BitmapEffect/BitmapEffectOperator.cs @@ -1,4 +1,6 @@ -using Beutl.Graphics; +using System.Runtime.CompilerServices; + +using Beutl.Graphics; using Beutl.Graphics.Effects; using Beutl.Operation; @@ -10,6 +12,8 @@ namespace Beutl.Operators.Configure.BitmapEffect; public abstract class BitmapEffectOperator : ConfigureOperator, ISourceTransformer where T : BitmapEffect, new() { + private readonly ConditionalWeakTable _table = new(); + protected override void PreProcess(Drawable target, T value) { value.IsEnabled = IsEnabled; @@ -17,6 +21,18 @@ protected override void PreProcess(Drawable target, T value) protected override void Process(Drawable target, T value) { - (target.Effect as BitmapEffectGroup)?.Children.Add(value); + ComposedBitmapEffect composed = _table.GetValue(target, _ => new ComposedBitmapEffect()); + if (target.Effect != composed) + { + composed.Second = value; + composed.First = target.Effect; + target.Effect = composed; + } + } + + protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) + { + base.OnDetachedFromHierarchy(args); + _table.Clear(); } } diff --git a/src/Beutl.Operators/Configure/ImageFilter/ImageFilterOperator.cs b/src/Beutl.Operators/Configure/ImageFilter/ImageFilterOperator.cs index d0a7c0f17..c91bbcf9a 100644 --- a/src/Beutl.Operators/Configure/ImageFilter/ImageFilterOperator.cs +++ b/src/Beutl.Operators/Configure/ImageFilter/ImageFilterOperator.cs @@ -1,4 +1,6 @@ -using Beutl.Graphics; +using System.Runtime.CompilerServices; + +using Beutl.Graphics; using Beutl.Graphics.Filters; namespace Beutl.Operators.Configure.ImageFilter; @@ -9,6 +11,8 @@ namespace Beutl.Operators.Configure.ImageFilter; public abstract class ImageFilterOperator : ConfigureOperator where T : ImageFilter, new() { + private readonly ConditionalWeakTable _table = new(); + protected override void PreProcess(Drawable target, T value) { value.IsEnabled = IsEnabled; @@ -16,6 +20,18 @@ protected override void PreProcess(Drawable target, T value) protected override void Process(Drawable target, T value) { - (target.Filter as ImageFilterGroup)?.Children.Add(value); + ComposedImageFilter composed = _table.GetValue(target, _ => new ComposedImageFilter()); + if (target.Filter != composed) + { + composed.Outer = value; + composed.Inner = target.Filter; + target.Filter = composed; + } + } + + protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) + { + base.OnDetachedFromHierarchy(args); + _table.Clear(); } } diff --git a/src/Beutl.Operators/Configure/Transform/TransformOperator.cs b/src/Beutl.Operators/Configure/Transform/TransformOperator.cs index a08f6d059..0c453d65e 100644 --- a/src/Beutl.Operators/Configure/Transform/TransformOperator.cs +++ b/src/Beutl.Operators/Configure/Transform/TransformOperator.cs @@ -1,4 +1,6 @@ -using Beutl.Graphics; +using System.Runtime.CompilerServices; + +using Beutl.Graphics; using Beutl.Graphics.Transformation; namespace Beutl.Operators.Configure.Transform; @@ -9,6 +11,8 @@ namespace Beutl.Operators.Configure.Transform; public abstract class TransformOperator : ConfigureOperator where T : Transform, new() { + private readonly ConditionalWeakTable _table = new(); + protected override void PreProcess(Drawable target, T value) { value.IsEnabled = IsEnabled; @@ -16,6 +20,18 @@ protected override void PreProcess(Drawable target, T value) protected override void Process(Drawable target, T value) { - (target.Transform as TransformGroup)?.Children.Add(value); + MultiTransform multi = _table.GetValue(target, _ => new MultiTransform()); + if (target.Transform != multi) + { + multi.Left = value; + multi.Right = target.Transform; + target.Transform = multi; + } + } + + protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) + { + base.OnDetachedFromHierarchy(args); + _table.Clear(); } } diff --git a/src/Beutl.Operators/Source/DrawablePublishOperator.cs b/src/Beutl.Operators/Source/DrawablePublishOperator.cs index e03338920..1bddc5218 100644 --- a/src/Beutl.Operators/Source/DrawablePublishOperator.cs +++ b/src/Beutl.Operators/Source/DrawablePublishOperator.cs @@ -1,7 +1,6 @@ using Beutl.Graphics; using Beutl.Graphics.Effects; using Beutl.Graphics.Filters; -using Beutl.Graphics.Transformation; using Beutl.Media; using Beutl.Operation; using Beutl.Styling; @@ -18,29 +17,27 @@ protected override Style OnInitializeStyle(Func> setters) return style; } - protected override void OnPostPublish() + protected override void OnBeforeApplying() { - base.OnPostPublish(); + base.OnBeforeApplying(); + if (Instance?.Target is T drawable) + { + drawable.BeginBatchUpdate(); + } + } + + protected override void OnAfterApplying() + { + base.OnAfterApplying(); if (Instance?.Target is T drawable) { drawable.BlendMode = BlendMode.SrcOver; drawable.AlignmentX = AlignmentX.Left; drawable.AlignmentY = AlignmentY.Top; drawable.TransformOrigin = RelativePoint.TopLeft; - if (drawable.Transform is TransformGroup transformGroup) - transformGroup.Children.Clear(); - else - drawable.Transform = new TransformGroup(); - - if (drawable.Filter is ImageFilterGroup filterGroup) - filterGroup.Children.Clear(); - else - drawable.Filter = new ImageFilterGroup(); - - if (drawable.Effect is BitmapEffectGroup effectGroup) - effectGroup.Children.Clear(); - else - drawable.Effect = new BitmapEffectGroup(); + drawable.Transform = null; + drawable.Filter = null; + drawable.Effect = null; } } } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Filters/DropShadowNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Filters/DropShadowNode.cs index a6da2d578..de7c7f32a 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Filters/DropShadowNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Filters/DropShadowNode.cs @@ -4,7 +4,7 @@ namespace Beutl.NodeTree.Nodes.Filters; -public class DropShadowNode : ConfigureNode +public class DropShadowNode : ImageFilterNode { private readonly InputSocket _posSocket; private readonly InputSocket _sigmaSocket; @@ -22,19 +22,17 @@ public DropShadowNode() public override void InitializeForContext(NodeEvaluationContext context) { base.InitializeForContext(context); - context.State = new ConfigureNodeEvaluationState(null, new DropShadow()); + context.State = new ImageFilterNodeEvaluationState(new DropShadow()); } - protected override void EvaluateCore(Drawable drawable, object? state) + protected override void EvaluateCore(IImageFilter? state) { - if (state is DropShadow model - && drawable.Filter is ImageFilterGroup group) + if (state is DropShadow model) { model.Position = _posSocket.Value; model.Sigma = _sigmaSocket.Value; model.Color = _colorSocket.Value; model.ShadowOnly = _shadowOnlySocket.Value; - group.Children.Add(model); } } } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Filters/ImageFilterNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Filters/ImageFilterNode.cs new file mode 100644 index 000000000..531508eff --- /dev/null +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Filters/ImageFilterNode.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Beutl.Graphics.Filters; + +namespace Beutl.NodeTree.Nodes.Filters; + +public sealed class ImageFilterNodeEvaluationState +{ + public ImageFilterNodeEvaluationState(IImageFilter? created) + { + Created = created; + } + + public IImageFilter? Created { get; set; } + + public ComposedImageFilter? AddtionalState { get; set; } +} + +public abstract class ImageFilterNode : Node +{ + public ImageFilterNode() + { + OutputSocket = AsOutput("ImageFilter"); + InputSocket = AsInput("ImageFilter"); + } + + protected OutputSocket OutputSocket { get; } + + protected InputSocket InputSocket { get; } + + public override void Evaluate(NodeEvaluationContext context) + { + IImageFilter? input = InputSocket.Value; + if (context.State is not ImageFilterNodeEvaluationState state) + { + context.State = state = new ImageFilterNodeEvaluationState(null); + } + + EvaluateCore(state.Created); + if (input != null) + { + state.AddtionalState ??= new ComposedImageFilter(); + state.AddtionalState.Outer = state.Created; + state.AddtionalState.Inner = input; + OutputSocket.Value = state.AddtionalState; + } + else + { + OutputSocket.Value = state.Created; + } + } + + protected abstract void EvaluateCore(IImageFilter? state); +} diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/MatrixTransformNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/MatrixTransformNode.cs index 25c09a609..06fd46f77 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/MatrixTransformNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/MatrixTransformNode.cs @@ -15,10 +15,10 @@ public MatrixTransformNode() public override void InitializeForContext(NodeEvaluationContext context) { base.InitializeForContext(context); - context.State = new TransformNodeEvaluationState(null, new MatrixTransform()); + context.State = new TransformNodeEvaluationState(new MatrixTransform()); } - protected override void EvaluateCore(TransformGroup group, object? state) + protected override void EvaluateCore(ITransform? state) { if (state is MatrixTransform model) { @@ -30,8 +30,6 @@ protected override void EvaluateCore(TransformGroup group, object? state) { model.Matrix = Matrix.Identity; } - - group.Children.Add(model); } } } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/Rotation3DTransformNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/Rotation3DTransformNode.cs index 66070ebf5..9fb8fd5b2 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/Rotation3DTransformNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/Rotation3DTransformNode.cs @@ -26,10 +26,10 @@ public Rotation3DTransformNode() public override void InitializeForContext(NodeEvaluationContext context) { base.InitializeForContext(context); - context.State = new TransformNodeEvaluationState(null, new Rotation3DTransform()); + context.State = new TransformNodeEvaluationState(new Rotation3DTransform()); } - protected override void EvaluateCore(TransformGroup group, object? state) + protected override void EvaluateCore(ITransform? state) { if (state is Rotation3DTransform model) { @@ -40,7 +40,6 @@ protected override void EvaluateCore(TransformGroup group, object? state) model.CenterY = _centerYSocket.Value; model.CenterZ = _centerZSocket.Value; model.Depth = _depthSocket.Value; - group.Children.Add(model); } } } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/RotationTransformNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/RotationTransformNode.cs index d67f60b22..09fdec7ff 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/RotationTransformNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/RotationTransformNode.cs @@ -14,16 +14,14 @@ public RotationTransformNode() public override void InitializeForContext(NodeEvaluationContext context) { base.InitializeForContext(context); - context.State = new TransformNodeEvaluationState(null, new RotationTransform()); + context.State = new TransformNodeEvaluationState(new RotationTransform()); } - protected override void EvaluateCore(TransformGroup group, object? state) + protected override void EvaluateCore(ITransform? state) { if (state is RotationTransform model) { model.Rotation = _rotationSocket.Value; - - group.Children.Add(model); } } } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/ScaleTransformNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/ScaleTransformNode.cs index be295d89a..ce96cf93b 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/ScaleTransformNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/ScaleTransformNode.cs @@ -18,18 +18,16 @@ public ScaleTransformNode() public override void InitializeForContext(NodeEvaluationContext context) { base.InitializeForContext(context); - context.State = new TransformNodeEvaluationState(null, new ScaleTransform()); + context.State = new TransformNodeEvaluationState(new ScaleTransform()); } - protected override void EvaluateCore(TransformGroup group, object? state) + protected override void EvaluateCore(ITransform? state) { if (state is ScaleTransform model) { model.Scale = _scaleSocket.Value; model.ScaleX = _scaleXSocket.Value; model.ScaleY = _scaleYSocket.Value; - - group.Children.Add(model); } } } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/SkewTransformNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/SkewTransformNode.cs index 575408314..5dd8eab40 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/SkewTransformNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/SkewTransformNode.cs @@ -16,17 +16,15 @@ public SkewTransformNode() public override void InitializeForContext(NodeEvaluationContext context) { base.InitializeForContext(context); - context.State = new TransformNodeEvaluationState(null, new SkewTransform()); + context.State = new TransformNodeEvaluationState(new SkewTransform()); } - protected override void EvaluateCore(TransformGroup group, object? state) + protected override void EvaluateCore(ITransform? state) { if (state is SkewTransform model) { model.SkewY = _skewXSocket.Value; model.SkewY = _skewYSocket.Value; - - group.Children.Add(model); } } } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/TransformNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/TransformNode.cs index f3cf1d876..39613cda9 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/TransformNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/TransformNode.cs @@ -4,47 +4,49 @@ namespace Beutl.NodeTree.Nodes.Transform; public sealed class TransformNodeEvaluationState { - public TransformNodeEvaluationState(TransformGroup? created, object? addtionalState) + public TransformNodeEvaluationState(ITransform? created) { Created = created; - AddtionalState = addtionalState; } - public TransformGroup? Created { get; set; } + public ITransform? Created { get; set; } - public object? AddtionalState { get; set; } + public MultiTransform? AddtionalState { get; set; } } public abstract class TransformNode : Node { public TransformNode() { - OutputSocket = AsOutput("TransformGroup"); - InputSocket = AsInput("TransformGroup"); + OutputSocket = AsOutput("Transform"); + InputSocket = AsInput("Transform"); } - protected OutputSocket OutputSocket { get; } + protected OutputSocket OutputSocket { get; } - protected InputSocket InputSocket { get; } + protected InputSocket InputSocket { get; } public override void Evaluate(NodeEvaluationContext context) { - TransformGroup? value = InputSocket.Value; - var state = context.State as TransformNodeEvaluationState; - if (state == null) + ITransform? value = InputSocket.Value; + if (context.State is not TransformNodeEvaluationState state) { - context.State = state = new TransformNodeEvaluationState(null, null); + context.State = state = new TransformNodeEvaluationState(null); } - if (value == null) + EvaluateCore(state.Created); + if (value != null) { - state.Created ??= new TransformGroup(); - value = state.Created; + state.AddtionalState ??= new MultiTransform(); + state.AddtionalState.Left = state.Created; + state.AddtionalState.Right = value; + OutputSocket.Value = state.AddtionalState; + } + else + { + OutputSocket.Value = state.Created; } - - EvaluateCore(value, state.AddtionalState); - OutputSocket.Value = value; } - protected abstract void EvaluateCore(TransformGroup group, object? state); + protected abstract void EvaluateCore(ITransform? state); } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/TranslateTransformNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/TranslateTransformNode.cs index a8230d87c..f30d59180 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/TranslateTransformNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Transform/TranslateTransformNode.cs @@ -16,16 +16,15 @@ public TranslateTransformNode() public override void InitializeForContext(NodeEvaluationContext context) { base.InitializeForContext(context); - context.State = new TransformNodeEvaluationState(null, new TranslateTransform()); + context.State = new TransformNodeEvaluationState(new TranslateTransform()); } - protected override void EvaluateCore(TransformGroup group, object? state) + protected override void EvaluateCore(ITransform? state) { if (state is TranslateTransform model) { model.X = _xSocket.Value; model.Y = _ySocket.Value; - group.Children.Add(model); } } } diff --git a/src/Beutl.ProjectSystem/Operation/StyledSourcePublisher.cs b/src/Beutl.ProjectSystem/Operation/StyledSourcePublisher.cs index 5e8d80493..11ed04866 100644 --- a/src/Beutl.ProjectSystem/Operation/StyledSourcePublisher.cs +++ b/src/Beutl.ProjectSystem/Operation/StyledSourcePublisher.cs @@ -10,7 +10,6 @@ public abstract class StyledSourcePublisher : StylingOperator, ISourcePublisher public virtual IRenderable? Publish(IClock clock) { - OnPrePublish(); IRenderable? renderable = Instance?.Target as IRenderable; if (!ReferenceEquals(Style, Instance?.Source) || Instance?.Target == null) @@ -27,6 +26,7 @@ public abstract class StyledSourcePublisher : StylingOperator, ISourcePublisher } } + OnBeforeApplying(); if (Instance != null && IsEnabled) { Instance.Begin(); @@ -34,7 +34,7 @@ public abstract class StyledSourcePublisher : StylingOperator, ISourcePublisher Instance.End(); } - OnPostPublish(); + OnAfterApplying(); return IsEnabled ? renderable : null; } @@ -47,11 +47,11 @@ protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs tmp?.Dispose(); } - protected virtual void OnPrePublish() + protected virtual void OnBeforeApplying() { } - protected virtual void OnPostPublish() + protected virtual void OnAfterApplying() { } } From 0ab6c875f2109b741a71aef834e87afb50551e15 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Wed, 3 May 2023 10:00:40 +0900 Subject: [PATCH 46/84] =?UTF-8?q?SourceOperator=E3=81=AESetter=E3=81=AE?= =?UTF-8?q?=E5=88=9D=E6=9C=9F=E5=8C=96=E3=82=92=E3=83=97=E3=83=AD=E3=83=91?= =?UTF-8?q?=E3=83=86=E3=82=A3=E3=81=A7=E6=8C=87=E5=AE=9A=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=81=93=E3=81=A8=E3=81=A7=E7=9C=81=E3=81=91=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Configure/ConfigureOperator.cs | 2 + .../Source/EllipseOperation.cs | 14 +- src/Beutl.Operators/Source/RectOperator.cs | 14 +- .../Source/RoundedRectOperator.cs | 17 +- .../Operation/OperatorRegistry.cs | 31 +++- .../Operation/StylingOperator.cs | 158 +++++++++++++++++- 6 files changed, 199 insertions(+), 37 deletions(-) diff --git a/src/Beutl.Operators/Configure/ConfigureOperator.cs b/src/Beutl.Operators/Configure/ConfigureOperator.cs index 5f1997063..d3596ad1a 100644 --- a/src/Beutl.Operators/Configure/ConfigureOperator.cs +++ b/src/Beutl.Operators/Configure/ConfigureOperator.cs @@ -34,6 +34,7 @@ protected override Style OnInitializeStyle(Func> setters) return style; } + [Obsolete] protected override void OnInitializeSetters(IList initializing) { base.OnInitializeSetters(initializing); @@ -94,6 +95,7 @@ protected virtual void PostProcess(TTarget target, TValue value) protected abstract void Process(TTarget target, TValue value); + [Obsolete] protected virtual IEnumerable GetProperties() { yield break; diff --git a/src/Beutl.Operators/Source/EllipseOperation.cs b/src/Beutl.Operators/Source/EllipseOperation.cs index fdaf6a31a..00515b85c 100644 --- a/src/Beutl.Operators/Source/EllipseOperation.cs +++ b/src/Beutl.Operators/Source/EllipseOperation.cs @@ -11,11 +11,11 @@ namespace Beutl.Operators.Source; public sealed class EllipseOperator : DrawablePublishOperator { - protected override void OnInitializeSetters(IList initializing) - { - initializing.Add(new Setter(Drawable.WidthProperty, 100)); - initializing.Add(new Setter(Drawable.HeightProperty, 100)); - initializing.Add(new Setter(EllipseShape.StrokeWidthProperty, 4000)); - initializing.Add(new Setter(Drawable.ForegroundProperty, new SolidColorBrush(Colors.White))); - } + public Setter Width { get; set; } = new(Shape.WidthProperty, 100); + + public Setter Height { get; set; } = new(Shape.HeightProperty, 100); + + public Setter Pen { get; set; } = new(Shape.PenProperty); + + public Setter Fill { get; set; } = new(Drawable.ForegroundProperty, new SolidColorBrush(Colors.White)); } diff --git a/src/Beutl.Operators/Source/RectOperator.cs b/src/Beutl.Operators/Source/RectOperator.cs index b5277164b..188e21bee 100644 --- a/src/Beutl.Operators/Source/RectOperator.cs +++ b/src/Beutl.Operators/Source/RectOperator.cs @@ -8,11 +8,11 @@ namespace Beutl.Operators.Source; public sealed class RectOperator : DrawablePublishOperator { - protected override void OnInitializeSetters(IList initializing) - { - initializing.Add(new Setter(Drawable.WidthProperty, 100)); - initializing.Add(new Setter(Drawable.HeightProperty, 100)); - initializing.Add(new Setter(RectShape.StrokeWidthProperty, 4000)); - initializing.Add(new Setter(Drawable.ForegroundProperty, new SolidColorBrush(Colors.White))); - } + public Setter Width { get; set; } = new(Shape.WidthProperty, 100); + + public Setter Height { get; set; } = new(Shape.HeightProperty, 100); + + public Setter Pen { get; set; } = new(Shape.PenProperty); + + public Setter Fill { get; set; } = new(Drawable.ForegroundProperty, new SolidColorBrush(Colors.White)); } diff --git a/src/Beutl.Operators/Source/RoundedRectOperator.cs b/src/Beutl.Operators/Source/RoundedRectOperator.cs index b2abac9c7..f5f731e51 100644 --- a/src/Beutl.Operators/Source/RoundedRectOperator.cs +++ b/src/Beutl.Operators/Source/RoundedRectOperator.cs @@ -8,12 +8,13 @@ namespace Beutl.Operators.Source; public sealed class RoundedRectOperator : DrawablePublishOperator { - protected override void OnInitializeSetters(IList initializing) - { - initializing.Add(new Setter(Drawable.WidthProperty, 100)); - initializing.Add(new Setter(Drawable.HeightProperty, 100)); - initializing.Add(new Setter(RoundedRectShape.StrokeWidthProperty, 4000)); - initializing.Add(new Setter(Drawable.ForegroundProperty, new SolidColorBrush(Colors.White))); - initializing.Add(new Setter(RoundedRectShape.CornerRadiusProperty, new CornerRadius(25))); - } + public Setter Width { get; set; } = new(Shape.WidthProperty, 100); + + public Setter Height { get; set; } = new(Shape.HeightProperty, 100); + + public Setter CornerRadius { get; set; } = new(RoundedRectShape.CornerRadiusProperty, new(25)); + + public Setter Pen { get; set; } = new(Shape.PenProperty); + + public Setter Fill { get; set; } = new(Drawable.ForegroundProperty, new SolidColorBrush(Colors.White)); } diff --git a/src/Beutl.ProjectSystem/Operation/OperatorRegistry.cs b/src/Beutl.ProjectSystem/Operation/OperatorRegistry.cs index 9eed7ed4a..c879ff4bb 100644 --- a/src/Beutl.ProjectSystem/Operation/OperatorRegistry.cs +++ b/src/Beutl.ProjectSystem/Operation/OperatorRegistry.cs @@ -1,4 +1,5 @@ -using System.Reactive.Linq; +using System.Diagnostics.CodeAnalysis; +using System.Reactive.Linq; using Beutl.Media; @@ -9,19 +10,24 @@ public class OperatorRegistry private static readonly List s_operations = new(); internal static int s_totalCount; - public static void RegisterOperation(string displayName) + public static void RegisterOperation< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>( + string displayName) where T : SourceOperator, new() { Register(new RegistryItem(displayName, Colors.Teal, typeof(T))); } - public static void RegisterOperation(string displayName, Color accentColor) + public static void RegisterOperation< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>( + string displayName, Color accentColor) where T : SourceOperator, new() { Register(new RegistryItem(displayName, accentColor, typeof(T))); } - public static void RegisterOperation( + public static void RegisterOperation< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>( string displayName, Color accentColor, Func canOpen, @@ -126,7 +132,11 @@ private static void Register(BaseRegistryItem item) public record BaseRegistryItem(string DisplayName, Color AccentColor); - public record RegistryItem(string DisplayName, Color AccentColor, Type Type) + public record RegistryItem( + string DisplayName, + Color AccentColor, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + Type Type) : BaseRegistryItem(DisplayName, AccentColor) { public Func? OpenFile { get; init; } @@ -185,7 +195,9 @@ internal RegistrationHelper(GroupableRegistryItem item, Action OperatorRegistry.Register(item)); } - public RegistrationHelper Add(string displayName) + public RegistrationHelper Add< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>( + string displayName) where T : SourceOperator, new() { _item.Items!.Add(new RegistryItem(displayName, Colors.Teal, typeof(T))); @@ -193,7 +205,9 @@ public RegistrationHelper Add(string displayName) return this; } - public RegistrationHelper Add(string displayName, Color accentColor) + public RegistrationHelper Add< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>( + string displayName, Color accentColor) where T : SourceOperator, new() { _item.Items.Add(new RegistryItem(displayName, accentColor, typeof(T))); @@ -201,7 +215,8 @@ public RegistrationHelper Add(string displayName, Color accentColor) return this; } - public RegistrationHelper Add( + public RegistrationHelper Add< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>( string displayName, Color accentColor, Func canOpen, diff --git a/src/Beutl.ProjectSystem/Operation/StylingOperator.cs b/src/Beutl.ProjectSystem/Operation/StylingOperator.cs index ad3a3f79f..33923ead7 100644 --- a/src/Beutl.ProjectSystem/Operation/StylingOperator.cs +++ b/src/Beutl.ProjectSystem/Operation/StylingOperator.cs @@ -1,7 +1,10 @@ using System.Collections; using System.Collections.Specialized; +using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; using System.Reactive.Linq; +using System.Reflection; using System.Text.Json.Nodes; using Beutl.Animation; @@ -10,6 +13,8 @@ using Beutl.Reactive; using Beutl.Styling; +using static Beutl.Operation.StylingOperatorPropertyDefinition; + namespace Beutl.Operation; public interface IStylingSetterPropertyImpl : IAbstractProperty @@ -101,6 +106,96 @@ public void SetValue(T? value) } } +internal static class StylingOperatorPropertyDefinition +{ + internal record struct Definition(PropertyInfo Property, Func Getter, Action Setter); + + private static readonly Dictionary s_defines = new(); + + public static ISetter[] GetSetters(object obj) + { + Type type = obj.GetType(); + if (!s_defines.TryGetValue(type, out Definition[]? def)) + { + def = CreateDefinitions(type); + } + + var array = new ISetter[def.Length]; + for (int i = 0; i < def.Length; i++) + { + array[i] = def[i].Getter(obj); + } + + return array; + } + + public static Definition[] GetDefintions(Type type) + { + if (!s_defines.TryGetValue(type, out Definition[]? def)) + { + def = CreateDefinitions(type); + } + + return def; + } + + private static Definition[] CreateDefinitions(Type type) + { + PropertyInfo[] props = type.GetProperties(BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.Instance); + var list = new List<(Definition, DisplayAttribute?)>(); + + foreach (PropertyInfo item in props) + { + if (item.PropertyType.IsAssignableTo(typeof(ISetter)) + && item.CanWrite + && item.CanRead) + { + DisplayAttribute? att = item.GetCustomAttribute(); + ParameterExpression target = Expression.Parameter(typeof(object), "target"); + ParameterExpression value = Expression.Parameter(typeof(ISetter), "value"); + + // (object target) => (target as Type).Property; + var getExpr = Expression.Lambda>( + Expression.Property(Expression.TypeAs(target, type), item), + target); + // (object target, ISetter value) => (target as Type).Property = value; + var setExpr = Expression.Lambda>( + Expression.Assign(Expression.Property(Expression.TypeAs(target, type), item), Expression.TypeAs(value, item.PropertyType)), + target, value); + + list.Add((new Definition(item, getExpr.Compile(), setExpr.Compile()), att)); + } + } + + list.Sort((x, y) => + { + int? xOrder = x.Item2?.GetOrder(); + int? yOrder = y.Item2?.GetOrder(); + if (xOrder == yOrder) + { + return 0; + } + + if (!xOrder.HasValue) + { + return 1; + } + else if (!yOrder.HasValue) + { + return -1; + } + else + { + return xOrder.Value - yOrder.Value; + } + }); + + Definition[] array = list.Select(x => x.Item1).ToArray(); + s_defines[type] = array; + return array; + } +} + public abstract class StylingOperator : SourceOperator { private bool _isSettersChanging; @@ -110,9 +205,22 @@ protected StylingOperator() { Style = OnInitializeStyle(() => { - var list = new List(); - OnInitializeSetters(list); - return list; + ISetter[] setters = GetSetters(this); + if (setters.Length > 0) + { + return setters; + } + else + { + // 互換性のため + var list = new List(); + +#pragma warning disable CS0612 + OnInitializeSetters(list); +#pragma warning restore CS0612 + + return list; + } }); } @@ -120,7 +228,7 @@ public Style Style { get => _style; - [MemberNotNull("_style")] + [MemberNotNull(nameof(_style))] private set { if (!ReferenceEquals(value, _style)) @@ -154,10 +262,9 @@ private set } } - //public IStyleInstance? Instance { get; protected set; } - protected abstract Style OnInitializeStyle(Func> setters); + [Obsolete] protected virtual void OnInitializeSetters(IList initializing) { } @@ -170,6 +277,8 @@ private void OnInvalidated(object? s, EventArgs e) public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); + + // 互換性のため if (json.TryGetPropertyValue(nameof(Style), out JsonNode? styleNode) && styleNode is JsonObject styleObj) { @@ -181,12 +290,47 @@ public override void ReadFromJson(JsonObject json) RaiseInvalidated(new RenderInvalidatedEventArgs(this)); } } + else + { + Definition[] defs = GetDefintions(GetType()); + foreach (Definition item in defs.AsSpan()) + { + string name = item.Property.Name; + if (json.TryGetPropertyValue(name, out JsonNode? propNode) + && propNode != null) + { + ISetter knownSetter = item.Getter.Invoke(this); + + if (propNode.ToSetter(knownSetter.Property.Name, _style.TargetType) is ISetter setter) + { + item.Setter.Invoke(this, setter); + } + } + } + + Style = OnInitializeStyle(() => GetSetters(this)); + RaiseInvalidated(new RenderInvalidatedEventArgs(this)); + } } public override void WriteToJson(JsonObject json) { base.WriteToJson(json); - json[nameof(Style)] = StyleSerializer.ToJson(Style); + Definition[] defs = GetDefintions(GetType()); + if (defs.Length == 0) + { + // 互換性のため + json[nameof(Style)] = StyleSerializer.ToJson(Style); + } + else + { + foreach (Definition item in defs.AsSpan()) + { + string name = item.Property.Name; + ISetter setter = item.Getter.Invoke(this); + json[name] = setter.ToJson(_style.TargetType).Item2; + } + } } private void Properties_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) From a0d1e3251447c52e3afbd693fbb1c746731acf96 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Wed, 3 May 2023 10:01:08 +0900 Subject: [PATCH 47/84] =?UTF-8?q?UI=E5=81=B4=E3=81=AEPixelSizeConverter?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl/App.axaml | 2 +- .../{PixelSizeConverter.cs => AvaloniaPixelSizeConverter.cs} | 4 +++- src/Beutl/Views/Dialogs/CreateNewProject.axaml | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) rename src/Beutl/Converters/{PixelSizeConverter.cs => AvaloniaPixelSizeConverter.cs} (86%) diff --git a/src/Beutl/App.axaml b/src/Beutl/App.axaml index a2bb883ea..4e50c5113 100644 --- a/src/Beutl/App.axaml +++ b/src/Beutl/App.axaml @@ -13,7 +13,7 @@ - + diff --git a/src/Beutl/Converters/PixelSizeConverter.cs b/src/Beutl/Converters/AvaloniaPixelSizeConverter.cs similarity index 86% rename from src/Beutl/Converters/PixelSizeConverter.cs rename to src/Beutl/Converters/AvaloniaPixelSizeConverter.cs index 3543bd01f..c7d560b1b 100644 --- a/src/Beutl/Converters/PixelSizeConverter.cs +++ b/src/Beutl/Converters/AvaloniaPixelSizeConverter.cs @@ -4,8 +4,10 @@ namespace Beutl.Converters; -public sealed class PixelSizeConverter : IValueConverter +public sealed class AvaloniaPixelSizeConverter : IValueConverter { + public static readonly AvaloniaPixelSizeConverter Instance = new(); + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { if (value is PixelSize size) diff --git a/src/Beutl/Views/Dialogs/CreateNewProject.axaml b/src/Beutl/Views/Dialogs/CreateNewProject.axaml index f87ec6e0c..d515e2bb0 100644 --- a/src/Beutl/Views/Dialogs/CreateNewProject.axaml +++ b/src/Beutl/Views/Dialogs/CreateNewProject.axaml @@ -1,6 +1,7 @@ - + From 1a4d2bcaa8f80c7824a94ea0b7f17b5ead4657cc Mon Sep 17 00:00:00 2001 From: indigo-san Date: Sat, 6 May 2023 18:37:02 +0900 Subject: [PATCH 48/84] =?UTF-8?q?Shape.FillType=E3=82=92=E5=89=8A=E9=99=A4?= =?UTF-8?q?=E3=80=81=E8=A8=88=E6=B8=AC=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Graphics/Graphics/Canvas.cs | 11 ++- src/Beutl.Graphics/Graphics/Shapes/Shape.cs | 68 ++++++++++--------- src/Beutl.Graphics/Graphics/Size.cs | 25 +++++++ .../NodeTree/Nodes/GeometryShapeNode.cs | 8 ++- tests/Beutl.Graphics.UnitTests/ShapeTests.cs | 31 ++++----- 5 files changed, 90 insertions(+), 53 deletions(-) diff --git a/src/Beutl.Graphics/Graphics/Canvas.cs b/src/Beutl.Graphics/Graphics/Canvas.cs index 4ee0f839d..fc8173afa 100644 --- a/src/Beutl.Graphics/Graphics/Canvas.cs +++ b/src/Beutl.Graphics/Graphics/Canvas.cs @@ -101,7 +101,7 @@ public void ClipRect(Rect clip, ClipOperation operation = ClipOperation.Intersec public void ClipPath(Geometry geometry, ClipOperation operation = ClipOperation.Intersect) { VerifyAccess(); - _canvas.ClipPath(geometry.GetNativeObject(), operation.ToSKClipOperation()); + _canvas.ClipPath(geometry.GetNativeObject(), operation.ToSKClipOperation(), true); } public void Dispose() @@ -697,16 +697,21 @@ private void ConfigureStrokePaint(Rect rect) switch (pen.StrokeAlignment) { case StrokeAlignment.Center: + rect = rect.Inflate(thickness / 2); break; - case StrokeAlignment.Inside: + case StrokeAlignment.Outside: + rect = rect.Inflate(thickness); + goto case StrokeAlignment.Inside; + + case StrokeAlignment.Inside: thickness *= 2; break; + default: break; } - rect = rect.Inflate(thickness); _sharedStrokePaint.IsStroke = true; _sharedStrokePaint.StrokeWidth = thickness; _sharedStrokePaint.StrokeCap = (SKStrokeCap)pen.StrokeCap; diff --git a/src/Beutl.Graphics/Graphics/Shapes/Shape.cs b/src/Beutl.Graphics/Graphics/Shapes/Shape.cs index a4add081f..cc2f5cd78 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/Shape.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/Shape.cs @@ -11,13 +11,11 @@ public abstract class Shape : Drawable public static readonly CoreProperty HeightProperty; public static readonly CoreProperty StretchProperty; public static readonly CoreProperty PenProperty; - public static readonly CoreProperty FillTypeProperty; public static readonly CoreProperty CreatedGeometryProperty; private float _width = -1; private float _height = -1; private Stretch _stretch = Stretch.None; private IPen? _pen = null; - private PathFillType _fillType; private Geometry? _createdGeometry; static Shape() @@ -40,17 +38,13 @@ static Shape() .Accessor(o => o.Pen, (o, v) => o.Pen = v) .Register(); - FillTypeProperty = ConfigureProperty(nameof(FillType)) - .Accessor(o => o.FillType, (o, v) => o.FillType = v) - .Register(); - CreatedGeometryProperty = ConfigureProperty(nameof(CreatedGeometry)) .Accessor(o => o.CreatedGeometry, (o, v) => o.CreatedGeometry = v) .Register(); AffectsRender( WidthProperty, HeightProperty, - StretchProperty, PenProperty, FillTypeProperty, CreatedGeometryProperty); + StretchProperty, PenProperty, CreatedGeometryProperty); } [Display(Name = nameof(Strings.Width), ResourceType = typeof(Strings))] @@ -81,12 +75,6 @@ public IPen? Pen set => SetAndRaise(PenProperty, ref _pen, value); } - public PathFillType FillType - { - get => _fillType; - set => SetAndRaise(FillTypeProperty, ref _fillType, value); - } - public Geometry? CreatedGeometry { get => _createdGeometry; @@ -126,10 +114,6 @@ private void OnAffectsRenderGeometryInvalidated(object? sender, RenderInvalidate private Geometry? GetOrCreateGeometry() { CreatedGeometry ??= CreateGeometry(); - if (CreatedGeometry != null) - { - CreatedGeometry.FillType = FillType; - } return CreatedGeometry; } @@ -139,10 +123,9 @@ public void InvalidateGeometry() CreatedGeometry = null; } - private static (Size size, Matrix transform) CalculateSizeAndTransform(Size requestedSize, Rect shapeBounds, Stretch stretch) + private static Vector CalculateScale(Size requestedSize, Rect shapeBounds, Stretch stretch) { - var shapeSize = new Size(shapeBounds.Right, shapeBounds.Bottom); - Matrix translate = Matrix.Identity; + var shapeSize = shapeBounds.Size; float desiredX = requestedSize.Width; float desiredY = requestedSize.Height; bool widthInfinityOrNegative = float.IsInfinity(requestedSize.Width) || requestedSize.Width < 0; @@ -151,12 +134,6 @@ private static (Size size, Matrix transform) CalculateSizeAndTransform(Size requ float sx = 0.0f; float sy = 0.0f; - if (stretch != Stretch.None) - { - shapeSize = shapeBounds.Size; - translate = Matrix.CreateTranslation(-(Vector)shapeBounds.Position); - } - if (widthInfinityOrNegative) { desiredX = shapeSize.Width; @@ -212,9 +189,7 @@ private static (Size size, Matrix transform) CalculateSizeAndTransform(Size requ break; } - Matrix transform = translate * Matrix.CreateScale(sx, sy); - var size = new Size(shapeSize.Width * sx, shapeSize.Height * sy); - return (size, transform); + return new Vector(sx, sy); } protected override Size MeasureCore(Size availableSize) @@ -225,7 +200,25 @@ protected override Size MeasureCore(Size availableSize) return default; } - return CalculateSizeAndTransform(new Size(Width, Height), geometry.Bounds, Stretch).size; + Vector scale = CalculateScale(new Size(Width, Height), geometry.Bounds, Stretch); + Size size = geometry.Bounds.Size * scale; + if (Pen != null) + { + size = size.Inflate(ActualThickness(Pen)); + } + + return size; + } + + private static float ActualThickness(IPen pen) + { + return pen.StrokeAlignment switch + { + StrokeAlignment.Center => pen.Thickness / 2, + StrokeAlignment.Inside => 0, + StrokeAlignment.Outside => pen.Thickness, + _ => 0, + }; } protected abstract Geometry? CreateGeometry(); @@ -237,9 +230,20 @@ protected override void OnDraw(ICanvas canvas) return; var requestedSize = new Size(Width, Height); - (Size _, Matrix transform) = CalculateSizeAndTransform(requestedSize, geometry.Bounds, Stretch); + Rect shapeBounds = geometry.Bounds; + Vector scale = CalculateScale(requestedSize, shapeBounds, Stretch); + Matrix matrix = Matrix.CreateTranslation(-(Vector)shapeBounds.Position); + + if (Pen != null) + { + float thickness = ActualThickness(Pen); + + matrix *= Matrix.CreateTranslation(thickness, thickness); + } + + matrix *= Matrix.CreateScale(scale); - using (canvas.PushTransform(transform)) + using (canvas.PushTransform(matrix)) using (canvas.PushPen(Pen)) { canvas.DrawGeometry(geometry); diff --git a/src/Beutl.Graphics/Graphics/Size.cs b/src/Beutl.Graphics/Graphics/Size.cs index a76a0636c..b57d96b02 100644 --- a/src/Beutl.Graphics/Graphics/Size.cs +++ b/src/Beutl.Graphics/Graphics/Size.cs @@ -265,6 +265,19 @@ public Size Deflate(Thickness thickness) MathF.Max(0, Height - thickness.Top - thickness.Bottom)); } + /// + /// Deflates the size by a . + /// + /// The thickness. + /// The deflated size. + /// The deflated size cannot be less than 0. + public Size Deflate(float thickness) + { + return new Size( + MathF.Max(0, Width - (thickness * 2)), + MathF.Max(0, Height - (thickness * 2))); + } + /// /// Returns a boolean indicating whether the size is equal to the other given size (bitwise). /// @@ -320,6 +333,18 @@ public Size Inflate(Thickness thickness) Height + thickness.Top + thickness.Bottom); } + /// + /// Inflates the size by a . + /// + /// The thickness. + /// The inflated size. + public Size Inflate(float thickness) + { + return new Size( + Width + (thickness * 2), + Height + (thickness * 2)); + } + /// /// Returns a new with the same height and the specified width. /// diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/GeometryShapeNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/GeometryShapeNode.cs index d1f54e079..dcdd6f57f 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/GeometryShapeNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/GeometryShapeNode.cs @@ -26,7 +26,7 @@ public GeometryShapeNode() _widthSocket = AsInput(Shape.WidthProperty, value: -1).AcceptNumber(); _heightSocket = AsInput(Shape.HeightProperty, value: -1).AcceptNumber(); _stretchSocket = AsInput(Shape.StretchProperty); - _fillTypeSocket = AsInput(Shape.FillTypeProperty); + _fillTypeSocket = AsInput(Media.Geometry.FillTypeProperty); _penSocket = AsInput(Shape.PenProperty); _transformSocket = AsInput(Drawable.TransformProperty); } @@ -53,10 +53,14 @@ public override void Evaluate(NodeEvaluationContext context) shape.BeginBatchUpdate(); shape.Data = _geometrySocket.Value; + if (shape.Data != null) + { + shape.Data.FillType = _fillTypeSocket.Value; + } + shape.Width = _widthSocket.Value; shape.Height = _heightSocket.Value; shape.Stretch = _stretchSocket.Value; - shape.FillType = _fillTypeSocket.Value; shape.Pen = _penSocket.Value; shape.Transform = _transformSocket.Value; diff --git a/tests/Beutl.Graphics.UnitTests/ShapeTests.cs b/tests/Beutl.Graphics.UnitTests/ShapeTests.cs index 9e6038b80..5b5176295 100644 --- a/tests/Beutl.Graphics.UnitTests/ShapeTests.cs +++ b/tests/Beutl.Graphics.UnitTests/ShapeTests.cs @@ -1,18 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Beutl.Collections; +using Beutl.Collections; using Beutl.Graphics.Shapes; using Beutl.Media; +using Beutl.Media.Immutable; using Beutl.Media.Pixel; using NUnit.Framework; -using SkiaSharp; - namespace Beutl.Graphics.UnitTests; public class ShapeTests @@ -126,7 +119,7 @@ public void DrawRoundedRect() Assert.IsTrue(bmp.Save(Path.Combine(ArtifactProvider.GetArtifactDirectory(), $"0.png"), EncodedImageFormat.Png)); } - + [Test] [TestCase(StrokeAlignment.Center)] [TestCase(StrokeAlignment.Inside)] @@ -200,12 +193,18 @@ public void DrawGeometry() } [Test] - [TestCase(StrokeAlignment.Center)] - [TestCase(StrokeAlignment.Inside)] - [TestCase(StrokeAlignment.Outside)] - public void DrawGeometryWithPen(StrokeAlignment alignment) + [TestCase(StrokeAlignment.Center, PathFillType.Winding)] + [TestCase(StrokeAlignment.Inside, PathFillType.Winding)] + [TestCase(StrokeAlignment.Outside, PathFillType.Winding)] + [TestCase(StrokeAlignment.Center, PathFillType.EvenOdd)] + [TestCase(StrokeAlignment.Inside, PathFillType.EvenOdd)] + [TestCase(StrokeAlignment.Outside, PathFillType.EvenOdd)] + public void DrawGeometryWithPen(StrokeAlignment alignment, PathFillType fillType) { - var geometry = new PathGeometry(); + var geometry = new PathGeometry() + { + FillType = fillType + }; var center = new Point(100, 100); float radius = 0.45f * Math.Min(200, 200); @@ -243,6 +242,6 @@ public void DrawGeometryWithPen(StrokeAlignment alignment) using Bitmap bmp = canvas.GetBitmap(); - Assert.IsTrue(bmp.Save(Path.Combine(ArtifactProvider.GetArtifactDirectory(), $"{alignment}.png"), EncodedImageFormat.Png)); + Assert.IsTrue(bmp.Save(Path.Combine(ArtifactProvider.GetArtifactDirectory(), $"{alignment}_{fillType}.png"), EncodedImageFormat.Png)); } } From c3680c79754da13bc722e90bb10028d4dbe5a860 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Sat, 6 May 2023 22:13:54 +0900 Subject: [PATCH 49/84] =?UTF-8?q?=E3=83=86=E3=82=AD=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=81=A7=E7=B8=81=E5=8F=96=E3=82=8A=E3=82=92=E6=8C=87=E5=AE=9A?= =?UTF-8?q?=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=97?= =?UTF-8?q?=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Graphics/Graphics/Canvas.cs | 47 +++++- .../Graphics/Shapes/TextBlock.cs | 75 +++++++--- .../Graphics/Shapes/TextElement.cs | 94 +++++++++--- .../Graphics/Shapes/TextElements.cs | 15 +- .../Graphics/Shapes/TextElementsBuilder.cs | 23 ++- .../Media/TextFormatting/FormattedText.cs | 32 ++++ .../Media/TextFormatting/FormattedTextInfo.cs | 8 +- .../TextFormatting/FormattedTextParser.cs | 139 ++++++++++++++++++ src/Beutl.Language/Strings.Designer.cs | 9 ++ src/Beutl.Language/Strings.ja.resx | 3 + src/Beutl.Language/Strings.resx | 3 + tests/Beutl.Graphics.UnitTests/ShapeTests.cs | 1 - .../TextBlockTests.cs | 40 +++++ .../TextElementsTests.cs | 2 +- 14 files changed, 439 insertions(+), 52 deletions(-) diff --git a/src/Beutl.Graphics/Graphics/Canvas.cs b/src/Beutl.Graphics/Graphics/Canvas.cs index fc8173afa..6dcacad7d 100644 --- a/src/Beutl.Graphics/Graphics/Canvas.cs +++ b/src/Beutl.Graphics/Graphics/Canvas.cs @@ -205,11 +205,22 @@ public void DrawText(FormattedText text) { VerifyAccess(); var typeface = new Typeface(text.Font, text.Style, text.Weight); + Size size = text.Bounds; SKTypeface sktypeface = typeface.ToSkia(); - ConfigureFillPaint(text.Bounds); + ConfigureFillPaint(size); _sharedFillPaint.TextSize = text.Size; _sharedFillPaint.Typeface = sktypeface; - _sharedFillPaint.Style = SKPaintStyle.Fill; + + IPen? pen = Pen; + bool enableStroke = pen != null && pen.Thickness != 0; + + if (enableStroke) + { + ConfigureStrokePaint(new Rect(size)); + _sharedStrokePaint.TextSize = text.Size; + _sharedStrokePaint.Typeface = sktypeface; + } + Span sc = stackalloc char[1]; float prevRight = 0; @@ -222,13 +233,37 @@ public void DrawText(FormattedText text) _canvas.Save(); _canvas.Translate(prevRight + bounds.Left, 0); - SKPath path = _sharedFillPaint.GetTextPath( + SKPath skPath = _sharedFillPaint.GetTextPath( sc, (bounds.Width / 2) - bounds.MidX, 0/*-_paint.FontMetrics.Ascent*/); - _canvas.DrawPath(path, _sharedFillPaint); - path.Dispose(); + _canvas.DrawPath(skPath, _sharedFillPaint); + if (enableStroke) + { + switch (pen!.StrokeAlignment) + { + case StrokeAlignment.Center: + _canvas.DrawPath(skPath, _sharedStrokePaint); + break; + + case StrokeAlignment.Inside: + _canvas.Save(); + _canvas.ClipPath(skPath, SKClipOperation.Intersect, true); + _canvas.DrawPath(skPath, _sharedStrokePaint); + _canvas.Restore(); + break; + + case StrokeAlignment.Outside: + _canvas.Save(); + _canvas.ClipPath(skPath, SKClipOperation.Difference, true); + _canvas.DrawPath(skPath, _sharedStrokePaint); + _canvas.Restore(); + break; + } + } + + skPath.Dispose(); prevRight += text.Spacing; prevRight += w; @@ -237,7 +272,7 @@ public void DrawText(FormattedText text) } } - private void DrawSKPath(SKPath skPath, bool strokeOnly) + internal void DrawSKPath(SKPath skPath, bool strokeOnly) { Rect rect = skPath.Bounds.ToGraphicsRect(); diff --git a/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs b/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs index ab74b6764..3465bf773 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs @@ -7,7 +7,7 @@ using Beutl.Media; using Beutl.Media.TextFormatting; -using DynamicData; +using SkiaSharp; namespace Beutl.Graphics.Shapes; @@ -20,6 +20,7 @@ public class TextBlock : Drawable public static readonly CoreProperty SpacingProperty; public static readonly CoreProperty TextProperty; public static readonly CoreProperty MarginProperty; + public static readonly CoreProperty PenProperty; public static readonly CoreProperty ElementsProperty; private FontFamily _fontFamily = FontFamily.Default; private FontWeight _fontWeight = FontWeight.Regular; @@ -28,6 +29,7 @@ public class TextBlock : Drawable private float _spacing; private string _text = string.Empty; private Thickness _margin; + private IPen? _pen = null; private TextElements? _elements; static TextBlock() @@ -67,6 +69,10 @@ static TextBlock() .DefaultValue(new Thickness()) .Register(); + PenProperty = ConfigureProperty(nameof(Pen)) + .Accessor(o => o.Pen, (o, v) => o.Pen = v) + .Register(); + ElementsProperty = ConfigureProperty(nameof(Elements)) .Accessor(o => o.Elements, (o, v) => o.Elements = v) .Register(); @@ -128,6 +134,12 @@ public Thickness Margin set => SetAndRaise(MarginProperty, ref _margin, value); } + public IPen? Pen + { + get => _pen; + set => SetAndRaise(PenProperty, ref _pen, value); + } + [NotAutoSerialized] public TextElements? Elements { @@ -191,6 +203,37 @@ protected override Size MeasureCore(Size availableSize) return new Size(width, height); } + internal static SKPath ToSKPath(TextElements elements) + { + var skpath = new SKPath(); + + float prevBottom = 0; + foreach (Span line in elements.Lines) + { + Size lineBounds = MeasureLine(line); + float ascent = MinAscent(line); + var point = new Point(0, prevBottom - ascent); + + float prevRight = 0; + foreach (FormattedText item in line) + { + if (item.Text.Length > 0) + { + point += new Point(prevRight, 0); + Size elementBounds = item.Bounds; + + item.AddToSKPath(skpath, point); + + prevRight = elementBounds.Width + item.Margin.Right; + } + } + + prevBottom += lineBounds.Height; + } + + return skpath; + } + protected override void OnDraw(ICanvas canvas) { if (_elements != null) @@ -212,6 +255,7 @@ protected override void OnDraw(ICanvas canvas) Size elementBounds = item.Bounds; using (item.Brush != null ? canvas.PushFillBrush(item.Brush) : default) + using (canvas.PushPen(item.Pen)) canvas.DrawText(item); prevRight = elementBounds.Width + item.Margin.Right; @@ -227,23 +271,15 @@ protected override void OnDraw(ICanvas canvas) protected override void OnPropertyChanged(PropertyChangedEventArgs args) { base.OnPropertyChanged(args); - if (args.PropertyName is nameof(Elements)) - { - if (args is CorePropertyChangedEventArgs typedargs) - { - if (typedargs.OldValue != null) - { - HierarchicalChildren.RemoveMany(typedargs.OldValue); - } - - if (typedargs.NewValue != null) - { - HierarchicalChildren.AddRange(typedargs.NewValue); - } - } - } - - if (args.PropertyName is nameof(Text) or nameof(Size) or nameof(FontFamily) or nameof(FontStyle) or nameof(FontWeight) or nameof(Foreground) or nameof(Spacing) or nameof(Margin)) + if (args.PropertyName is nameof(Text) + or nameof(Size) + or nameof(FontFamily) + or nameof(FontStyle) + or nameof(FontWeight) + or nameof(Foreground) + or nameof(Spacing) + or nameof(Margin) + or nameof(Pen)) { OnUpdateText(); } @@ -258,7 +294,8 @@ private void OnUpdateText() Size: _size, Brush: (Foreground as IMutableBrush)?.ToImmutable(), Space: _spacing, - Margin: _margin); + Margin: _margin, + Pen: _pen); var builder = new TextElementsBuilder(options); builder.AppendTokens(CollectionsMarshal.AsSpan(tokenizer.Result)); diff --git a/src/Beutl.Graphics/Graphics/Shapes/TextElement.cs b/src/Beutl.Graphics/Graphics/Shapes/TextElement.cs index 4549ab735..e930d3380 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/TextElement.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/TextElement.cs @@ -1,6 +1,8 @@ -using System.ComponentModel.DataAnnotations; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; using System.Diagnostics; +using Beutl.Animation; using Beutl.Language; using Beutl.Media; using Beutl.Media.TextFormatting; @@ -8,7 +10,7 @@ namespace Beutl.Graphics.Shapes; [DebuggerDisplay("{Text}")] -public class TextElement : Drawable +public class TextElement : Animatable, IAffectsRender { public static readonly CoreProperty FontWeightProperty; public static readonly CoreProperty FontStyleProperty; @@ -17,6 +19,8 @@ public class TextElement : Drawable public static readonly CoreProperty SpacingProperty; public static readonly CoreProperty TextProperty; public static readonly CoreProperty MarginProperty; + public static readonly CoreProperty BrushProperty; + public static readonly CoreProperty PenProperty; public static readonly CoreProperty IgnoreLineBreaksProperty; private FontWeight _fontWeight; private FontStyle _fontStyle; @@ -25,6 +29,8 @@ public class TextElement : Drawable private float _spacing; private string _text = string.Empty; private Thickness _margin; + private IBrush? _brush; + private IPen? _pen = null; private bool _ignoreLineBreaks; private FormattedText _formattedText; @@ -65,22 +71,22 @@ static TextElement() .DefaultValue(new Thickness()) .Register(); + BrushProperty = ConfigureProperty(nameof(Brush)) + .Accessor(o => o.Brush, (o, v) => o.Brush = v) + .Register(); + + PenProperty = ConfigureProperty(nameof(Pen)) + .Accessor(o => o.Pen, (o, v) => o.Pen = v) + .Register(); + IgnoreLineBreaksProperty = ConfigureProperty(nameof(IgnoreLineBreaks)) .Accessor(o => o.IgnoreLineBreaks, (o, v) => o.IgnoreLineBreaks = v) .DefaultValue(false) .Register(); - - AffectsRender( - FontWeightProperty, - FontStyleProperty, - FontFamilyProperty, - SizeProperty, - SpacingProperty, - TextProperty, - MarginProperty, - IgnoreLineBreaksProperty); } + public event EventHandler? Invalidated; + [Display(Name = nameof(Strings.FontWeight), ResourceType = typeof(Strings))] public FontWeight FontWeight { @@ -131,12 +137,69 @@ public Thickness Margin set => SetAndRaise(MarginProperty, ref _margin, value); } + public IBrush? Brush + { + get => _brush; + set => SetAndRaise(BrushProperty, ref _brush, value); + } + + public IPen? Pen + { + get => _pen; + set => SetAndRaise(PenProperty, ref _pen, value); + } + public bool IgnoreLineBreaks { get => _ignoreLineBreaks; set => SetAndRaise(IgnoreLineBreaksProperty, ref _ignoreLineBreaks, value); } + protected override void OnPropertyChanged(PropertyChangedEventArgs args) + { + base.OnPropertyChanged(args); + switch (args.PropertyName) + { + case nameof(Brush) when args is CorePropertyChangedEventArgs args1: + if (args1.OldValue is IAffectsRender oldBrush) + oldBrush.Invalidated -= OnAffectsRenderInvalidated; + + if (args1.NewValue is IAffectsRender newBrush) + newBrush.Invalidated += OnAffectsRenderInvalidated; + + goto RaiseInvalidated; + + case nameof(Pen) when args is CorePropertyChangedEventArgs args2: + if (args2.OldValue is IAffectsRender oldPen) + oldPen.Invalidated -= OnAffectsRenderInvalidated; + + if (args2.NewValue is IAffectsRender newPen) + newPen.Invalidated += OnAffectsRenderInvalidated; + + goto RaiseInvalidated; + + case nameof(FontWeight): + case nameof(FontStyle): + case nameof(FontFamily): + case nameof(Size): + case nameof(Spacing): + case nameof(Text): + case nameof(Margin): + case nameof(IgnoreLineBreaks): + RaiseInvalidated: + Invalidated?.Invoke(this, new RenderInvalidatedEventArgs(args.PropertyName)); + break; + + default: + break; + } + } + + private void OnAffectsRenderInvalidated(object? sender, RenderInvalidatedEventArgs e) + { + Invalidated?.Invoke(this, e); + } + internal int GetFormattedTexts(Span span, bool startWithNewLine, out bool endWithNewLine) { int prevIdx = 0; @@ -213,13 +276,10 @@ private void SetFields(ref FormattedText text, StringSpan s) _formattedText.Size = _size; _formattedText.Spacing = _spacing; _formattedText.Text = s; - _formattedText.Brush = Foreground; + _formattedText.Brush = Brush; + _formattedText.Pen = Pen; text = _formattedText; } - - protected override Size MeasureCore(Size availableSize) => throw new NotImplementedException(); - - protected override void OnDraw(ICanvas canvas) => throw new NotImplementedException(); } diff --git a/src/Beutl.Graphics/Graphics/Shapes/TextElements.cs b/src/Beutl.Graphics/Graphics/Shapes/TextElements.cs index 9f4a594e3..b507b8cd2 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/TextElements.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/TextElements.cs @@ -4,11 +4,12 @@ using System.Collections; using System.Text.Json.Nodes; +using Beutl.Media; using Beutl.Media.TextFormatting; namespace Beutl.Graphics.Shapes; -public class TextElements : IReadOnlyList +public class TextElements : IReadOnlyList, IAffectsRender { private readonly TextElement[] _array; @@ -21,6 +22,16 @@ internal TextElements(TextElement[] array) { _array = array; Lines = new LineEnumerable(array); + + foreach (TextElement item in array) + { + item.Invalidated += OnItemInvalidated; + } + } + + private void OnItemInvalidated(object? sender, RenderInvalidatedEventArgs e) + { + Invalidated?.Invoke(sender, e); } public TextElement this[int index] => ((IReadOnlyList)_array)[index]; @@ -29,6 +40,8 @@ internal TextElements(TextElement[] array) public LineEnumerable Lines { get; } + public event EventHandler? Invalidated; + public IEnumerator GetEnumerator() { return ((IEnumerable)_array).GetEnumerator(); diff --git a/src/Beutl.Graphics/Graphics/Shapes/TextElementsBuilder.cs b/src/Beutl.Graphics/Graphics/Shapes/TextElementsBuilder.cs index 08f923bb0..003f3dd0f 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/TextElementsBuilder.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/TextElementsBuilder.cs @@ -16,6 +16,7 @@ public class TextElementsBuilder private readonly Stack _fontStyle = new(); private readonly Stack _size = new(); private readonly Stack _brush = new(); + private readonly Stack _pen = new(); private readonly Stack _spacing = new(); private readonly Stack _margin = new(); private readonly FormattedTextInfo _initialOptions; @@ -24,6 +25,7 @@ public class TextElementsBuilder private FontStyle _curFontStyle; private float _curSize; private IBrush? _curBrush; + private IPen? _curPen; private float _curSpacing; private Thickness _curMargin; private bool _singleLine; @@ -36,6 +38,7 @@ public TextElementsBuilder(FormattedTextInfo initialOptions) _curFontStyle = initialOptions.Typeface.Style; _curSize = initialOptions.Size; _curBrush = initialOptions.Brush; + _curPen = initialOptions.Pen; _curSpacing = initialOptions.Space; _curMargin = initialOptions.Margin; } @@ -72,6 +75,12 @@ public void PushBrush(IBrush brush) _curBrush = brush; } + public void PushPen(IPen pen) + { + _pen.Push(_curPen); + _curPen = pen; + } + public void PushSpacing(float value) { _spacing.Push(_curSpacing); @@ -83,7 +92,7 @@ public void PushMargin(Thickness margin) _margin.Push(_curMargin); _curMargin = margin; } - + public void PushSingleLine() { _singleLine = true; @@ -108,6 +117,9 @@ public void Pop(Options options) case Options.Brush: _curBrush = _brush.PopOrDefault(_initialOptions.Brush); break; + case Options.Pen: + _curPen = _pen.PopOrDefault(_initialOptions.Pen); + break; case Options.Spacing: _curSpacing = _spacing.PopOrDefault(_initialOptions.Space); break; @@ -131,10 +143,11 @@ public void Append(string text) FontStyle = _curFontStyle, FontWeight = _curFontWeight, Size = _curSize, - Foreground = _curBrush, + Brush = _curBrush, Spacing = _curSpacing, Margin = _curMargin, - IgnoreLineBreaks = _singleLine + IgnoreLineBreaks = _singleLine, + Pen = _curPen, }); } @@ -154,6 +167,8 @@ public void AppendTokens(Span tokens) PushSize(size1); else if (tag.TryGetColor(out Color color1)) PushBrush(color1.ToImmutableBrush()); + else if (tag.TryGetStroke(out IPen? pen1)) + PushPen(pen1); else if (tag.TryGetCharSpace(out float space1)) PushSpacing(space1); else if (tag.TryGetMargin(out Thickness margin1)) @@ -199,6 +214,7 @@ private static Options ToOptions(TagType tagType, ReadOnlySpan text) TagType.Font => Options.FontFamily, TagType.Size => Options.Size, TagType.Color or TagType.ColorHash => Options.Brush, + TagType.Stroke => Options.Pen, TagType.CharSpace => Options.Spacing, TagType.FontWeightBold or TagType.FontWeight => Options.FontWeight, TagType.FontStyle or TagType.FontStyleItalic => Options.FontStyle, @@ -216,6 +232,7 @@ public enum Options FontStyle, Size, Brush, + Pen, Spacing, Margin, SingleLine, diff --git a/src/Beutl.Graphics/Media/TextFormatting/FormattedText.cs b/src/Beutl.Graphics/Media/TextFormatting/FormattedText.cs index effccf986..68f6dba32 100644 --- a/src/Beutl.Graphics/Media/TextFormatting/FormattedText.cs +++ b/src/Beutl.Graphics/Media/TextFormatting/FormattedText.cs @@ -73,6 +73,8 @@ public StringSpan Text public bool BeginOnNewLine { get; set; } = false; public IBrush? Brush { get; set; } + + public IPen? Pen { get; set; } public Thickness Margin { get; set; } = new(); @@ -80,6 +82,36 @@ public StringSpan Text public Size Bounds => MeasureAndSetField().Bounds; + internal Point AddToSKPath(SKPath path, Point point) + { + SKTypeface typeface = new Typeface(Font, Style, Weight).ToSkia(); + using SKPaint paint = new() + { + TextSize = Size, + Typeface = typeface + }; + + Size size = Bounds; + Span buffer = stackalloc char[1]; + + foreach (char item in Text.AsSpan()) + { + buffer[0] = item; + var bounds = default(SKRect); + float width = paint.MeasureText(buffer, ref bounds); + + SKPath skPath = paint.GetTextPath(buffer, (bounds.Width / 2) - bounds.MidX, 0); + + path.AddPath(skPath, point.X + bounds.Left, point.Y); + + skPath.Dispose(); + + point += new Point(Spacing + width, 0); + } + + return point; + } + private (FontMetrics, Size) Measure() { SKTypeface typeface = new Typeface(Font, Style, Weight).ToSkia(); diff --git a/src/Beutl.Graphics/Media/TextFormatting/FormattedTextInfo.cs b/src/Beutl.Graphics/Media/TextFormatting/FormattedTextInfo.cs index 6dfa985ac..a426b0ade 100644 --- a/src/Beutl.Graphics/Media/TextFormatting/FormattedTextInfo.cs +++ b/src/Beutl.Graphics/Media/TextFormatting/FormattedTextInfo.cs @@ -2,13 +2,13 @@ namespace Beutl.Media.TextFormatting; -public record struct FormattedTextInfo(Typeface Typeface, float Size, IBrush? Brush, float Space, Thickness Margin) +public record struct FormattedTextInfo(Typeface Typeface, float Size, IBrush? Brush, float Space, Thickness Margin, IPen? Pen) { public static readonly FormattedTextInfo Default - = new(new Typeface(FontFamily.Default, FontStyle.Normal, FontWeight.Regular), 24, Brushes.White, 0, default); + = new(new Typeface(FontFamily.Default, FontStyle.Normal, FontWeight.Regular), 24, Brushes.White, 0, default, null); - public FormattedTextInfo(Typeface Typeface, float Size, Color Color, float Space, Thickness Margin) - : this(Typeface, Size, Color.ToImmutableBrush(), Space, Margin) + public FormattedTextInfo(Typeface Typeface, float Size, Color Color, float Space, Thickness Margin, IPen? Pen) + : this(Typeface, Size, Color.ToImmutableBrush(), Space, Margin, Pen) { } } diff --git a/src/Beutl.Graphics/Media/TextFormatting/FormattedTextParser.cs b/src/Beutl.Graphics/Media/TextFormatting/FormattedTextParser.cs index 16d1420cb..78fa4d55e 100644 --- a/src/Beutl.Graphics/Media/TextFormatting/FormattedTextParser.cs +++ b/src/Beutl.Graphics/Media/TextFormatting/FormattedTextParser.cs @@ -2,6 +2,8 @@ using System.Globalization; using Beutl.Graphics; +using Beutl.Media.Immutable; +using Beutl.Utilities; namespace Beutl.Media.TextFormatting; @@ -71,6 +73,10 @@ public static TagType GetCloseTagType(StringSpan tag) { return TagType.Color; } + else if (span.SequenceEqual("/stroke")) + { + return TagType.Stroke; + } else if (span.SequenceEqual("/cspace")) { return TagType.CharSpace; @@ -137,6 +143,10 @@ public static TagType GetTagType(StringSpan tag) { return TagType.Color; } + else if (span.SequenceEqual("stroke")) + { + return TagType.Stroke; + } else if (span.SequenceEqual("cspace")) { return TagType.CharSpace; @@ -193,6 +203,134 @@ public bool TryGetColor(out Color color) return false; } + public bool TryGetStroke([NotNullWhen(true)] out IPen? pen) + { + static bool TryReadStrokeCap(ReadOnlySpan s, ref StrokeCap cap) + { + if (s.StartsWith("Join:", StringComparison.OrdinalIgnoreCase)) + return false; + + if (s.Equals(nameof(StrokeCap.Flat), StringComparison.OrdinalIgnoreCase)) + { + cap = StrokeCap.Flat; + return true; + } + else if (s.Equals(nameof(StrokeCap.Round), StringComparison.OrdinalIgnoreCase)) + { + cap = StrokeCap.Round; + return true; + } + else if (s.Equals(nameof(StrokeCap.Square), StringComparison.OrdinalIgnoreCase)) + { + cap = StrokeCap.Square; + return true; + } + else + { + return false; + } + } + + static bool TryReadStrokeJoin(ReadOnlySpan s, ref StrokeJoin r) + { + if (s.StartsWith("Cap:", StringComparison.OrdinalIgnoreCase)) + return false; + + if (s.Equals(nameof(StrokeJoin.Miter), StringComparison.OrdinalIgnoreCase)) + { + r = StrokeJoin.Miter; + return true; + } + else if (s.Equals(nameof(StrokeJoin.Round), StringComparison.OrdinalIgnoreCase)) + { + r = StrokeJoin.Round; + return true; + } + else if (s.Equals(nameof(StrokeJoin.Bevel), StringComparison.OrdinalIgnoreCase)) + { + r = StrokeJoin.Bevel; + return true; + } + else + { + return false; + } + } + + static bool TryReadStrokeAlignment(ReadOnlySpan s, ref StrokeAlignment r) + { + if (s.Equals(nameof(StrokeAlignment.Center), StringComparison.OrdinalIgnoreCase)) + { + r = StrokeAlignment.Center; + return true; + } + else if (s.Equals(nameof(StrokeAlignment.Inside), StringComparison.OrdinalIgnoreCase)) + { + r = StrokeAlignment.Inside; + return true; + } + else if (s.Equals(nameof(StrokeAlignment.Outside), StringComparison.OrdinalIgnoreCase)) + { + r = StrokeAlignment.Outside; + return true; + } + else + { + return false; + } + } + + if (Type is TagType.Stroke) + { + ReadOnlySpan str = RemoveQuotation(Value); + var tokenizer = new RefStringTokenizer(str); + if (tokenizer.TryReadString(out ReadOnlySpan colorStr) + && Color.TryParse(colorStr, out Color color)) + { + if (tokenizer.TryReadSingle(out float thickness)) + { + StrokeCap cap = StrokeCap.Flat; + StrokeJoin join = StrokeJoin.Miter; + StrokeAlignment align = StrokeAlignment.Center; + + ReadOnlySpan str1 = tokenizer.TryReadString(out ReadOnlySpan _str1) ? _str1 : default; + ReadOnlySpan str2 = tokenizer.TryReadString(out ReadOnlySpan _str2) ? _str2 : default; + ReadOnlySpan str3 = tokenizer.TryReadString(out ReadOnlySpan _str3) ? _str3 : default; + + if (TryReadStrokeCap(str1, ref cap) + || TryReadStrokeJoin(str1, ref join) + || TryReadStrokeAlignment(str1, ref align)) + { } + if (TryReadStrokeCap(str2, ref cap) + || TryReadStrokeJoin(str2, ref join) + || TryReadStrokeAlignment(str2, ref align)) + { } + if (TryReadStrokeCap(str3, ref cap) + || TryReadStrokeJoin(str3, ref join) + || TryReadStrokeAlignment(str3, ref align)) + { } + + float miterLimit = tokenizer.TryReadSingle(out float _miterLimit) ? _miterLimit : 10; + + pen = new ImmutablePen( + new ImmutableSolidColorBrush(color), + null, + 0, + thickness, + miterLimit, + cap, + join, + align); + + return true; + } + } + } + + pen = default; + return false; + } + public bool TryGetFont([NotNullWhen(true)] out FontFamily? font) { if (Type == TagType.Font) @@ -341,6 +479,7 @@ public enum TagType Font, Size, Color, + Stroke, ColorHash, CharSpace, FontWeightBold, diff --git a/src/Beutl.Language/Strings.Designer.cs b/src/Beutl.Language/Strings.Designer.cs index 7f9ab69b3..3ae95ae78 100644 --- a/src/Beutl.Language/Strings.Designer.cs +++ b/src/Beutl.Language/Strings.Designer.cs @@ -781,6 +781,15 @@ public static string Files { } } + /// + /// Fill に類似しているローカライズされた文字列を検索します。 + /// + public static string Fill { + get { + return ResourceManager.GetString("Fill", resourceCulture); + } + } + /// /// Finish editing に類似しているローカライズされた文字列を検索します。 /// diff --git a/src/Beutl.Language/Strings.ja.resx b/src/Beutl.Language/Strings.ja.resx index 3cc57f2a7..462d61dbd 100644 --- a/src/Beutl.Language/Strings.ja.resx +++ b/src/Beutl.Language/Strings.ja.resx @@ -763,4 +763,7 @@ b-editorがダウンロードURLを管理します。 ノードツリー + + 塗りつぶし + \ No newline at end of file diff --git a/src/Beutl.Language/Strings.resx b/src/Beutl.Language/Strings.resx index 2f0e855bc..35a086e3e 100644 --- a/src/Beutl.Language/Strings.resx +++ b/src/Beutl.Language/Strings.resx @@ -763,4 +763,7 @@ and b-editor maintains the download URL. Node Tree + + Fill + \ No newline at end of file diff --git a/tests/Beutl.Graphics.UnitTests/ShapeTests.cs b/tests/Beutl.Graphics.UnitTests/ShapeTests.cs index 5b5176295..1a0c54b0f 100644 --- a/tests/Beutl.Graphics.UnitTests/ShapeTests.cs +++ b/tests/Beutl.Graphics.UnitTests/ShapeTests.cs @@ -1,7 +1,6 @@ using Beutl.Collections; using Beutl.Graphics.Shapes; using Beutl.Media; -using Beutl.Media.Immutable; using Beutl.Media.Pixel; using NUnit.Framework; diff --git a/tests/Beutl.Graphics.UnitTests/TextBlockTests.cs b/tests/Beutl.Graphics.UnitTests/TextBlockTests.cs index fe030985e..c44fcca91 100644 --- a/tests/Beutl.Graphics.UnitTests/TextBlockTests.cs +++ b/tests/Beutl.Graphics.UnitTests/TextBlockTests.cs @@ -18,6 +18,7 @@ public class TextBlockTests ます。 +縁取り Roboto Noto Sans"; private const string Case2 = "吾輩は猫である"; @@ -57,4 +58,43 @@ public void ParseAndDraw(string str, int id) Assert.IsTrue(bmp.Save(Path.Combine(ArtifactProvider.GetArtifactDirectory(), $"{id}.png"), EncodedImageFormat.Png)); } + + [Test] + public void ToSKPath() + { + Typeface typeface = TypefaceProvider.Typeface(); + var tb = new TextBlock() + { + FontFamily = typeface.FontFamily, + FontStyle = typeface.Style, + FontWeight = typeface.Weight, + Size = 100, + Foreground = Brushes.White, + Spacing = 0, + Text = Case1 + }; + + tb.Measure(Size.Infinity); + Rect bounds = tb.Bounds; + using var skpath = TextBlock.ToSKPath(tb.Elements!); + + using var graphics = new Canvas((int)bounds.Width, (int)bounds.Height); + + graphics.Clear(Colors.White); + + var pen = new Pen + { + Brush = Brushes.Black, + Thickness = 5, + StrokeAlignment = StrokeAlignment.Outside + }; + using (graphics.PushPen(pen)) + { + graphics.DrawSKPath(skpath, false); + } + + using Bitmap bmp = graphics.GetBitmap(); + + Assert.IsTrue(bmp.Save(Path.Combine(ArtifactProvider.GetArtifactDirectory(), $"0.png"), EncodedImageFormat.Png)); + } } diff --git a/tests/Beutl.Graphics.UnitTests/TextElementsTests.cs b/tests/Beutl.Graphics.UnitTests/TextElementsTests.cs index 761b46ae8..3bbe6d773 100644 --- a/tests/Beutl.Graphics.UnitTests/TextElementsTests.cs +++ b/tests/Beutl.Graphics.UnitTests/TextElementsTests.cs @@ -32,7 +32,7 @@ public void Parse() var tokenizer = new FormattedTextTokenizer(str); tokenizer.Tokenize(); - var options = new FormattedTextInfo(typeface, 100, Colors.Black, 0, default); + var options = new FormattedTextInfo(typeface, 100, Colors.Black, 0, default, null); var builder = new TextElementsBuilder(options); builder.AppendTokens(CollectionsMarshal.AsSpan(tokenizer.Result)); From cb82c30631e78719a1fd7f4c7c484efb55185983 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Sun, 28 May 2023 18:44:37 +0900 Subject: [PATCH 50/84] =?UTF-8?q?BrushEditor=E3=82=92=E4=BD=BF=E3=81=84?= =?UTF-8?q?=E3=82=84=E3=81=99=E3=81=8F=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Styling/PropertyEditors/EnumEditor.axaml | 15 ++- .../PropertyEditors/FontFamilyEditor.axaml | 15 ++- .../PropertyEditors/StringEditor.axaml | 15 ++- .../Editors/BrushEditorViewModel.cs | 42 ++++--- src/Beutl/Views/Editors/BrushEditor.axaml | 102 +++++++++------- src/Beutl/Views/Editors/BrushEditor.axaml.cs | 112 ++++++------------ src/Beutl/Views/Editors/OpacityEditor.axaml | 4 +- .../Views/Editors/PropertyEditorMenu.axaml | 4 +- .../Views/Editors/PropertyEditorMenu.axaml.cs | 18 --- .../Views/Tools/SourceOperatorView.axaml | 2 +- 10 files changed, 155 insertions(+), 174 deletions(-) diff --git a/src/Beutl.Controls/Styling/PropertyEditors/EnumEditor.axaml b/src/Beutl.Controls/Styling/PropertyEditors/EnumEditor.axaml index 8fdc0a90b..b33f7d822 100644 --- a/src/Beutl.Controls/Styling/PropertyEditors/EnumEditor.axaml +++ b/src/Beutl.Controls/Styling/PropertyEditors/EnumEditor.axaml @@ -22,7 +22,14 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{TemplateBinding CornerRadius}"> - + + + + + + + + - + + @@ -55,7 +64,7 @@ diff --git a/src/Beutl.Controls/Styling/PropertyEditors/FontFamilyEditor.axaml b/src/Beutl.Controls/Styling/PropertyEditors/FontFamilyEditor.axaml index 7273f2442..754dbfa21 100644 --- a/src/Beutl.Controls/Styling/PropertyEditors/FontFamilyEditor.axaml +++ b/src/Beutl.Controls/Styling/PropertyEditors/FontFamilyEditor.axaml @@ -22,7 +22,14 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{TemplateBinding CornerRadius}"> - + + + + + + + + - + + @@ -54,7 +63,7 @@ diff --git a/src/Beutl.Controls/Styling/PropertyEditors/StringEditor.axaml b/src/Beutl.Controls/Styling/PropertyEditors/StringEditor.axaml index a5689bb81..2e1b53b01 100644 --- a/src/Beutl.Controls/Styling/PropertyEditors/StringEditor.axaml +++ b/src/Beutl.Controls/Styling/PropertyEditors/StringEditor.axaml @@ -22,7 +22,14 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{TemplateBinding CornerRadius}"> - + + + + + + + + - + + @@ -53,7 +62,7 @@ diff --git a/src/Beutl/ViewModels/Editors/BrushEditorViewModel.cs b/src/Beutl/ViewModels/Editors/BrushEditorViewModel.cs index 56f34a1ad..87294c050 100644 --- a/src/Beutl/ViewModels/Editors/BrushEditorViewModel.cs +++ b/src/Beutl/ViewModels/Editors/BrushEditorViewModel.cs @@ -2,6 +2,7 @@ using Beutl.Framework; using Beutl.Media; +using Beutl.ViewModels.Tools; using Reactive.Bindings; @@ -9,7 +10,6 @@ namespace Beutl.ViewModels.Editors; public sealed class BrushEditorViewModel : BaseEditorViewModel { - private static readonly NullabilityInfoContext s_context = new(); private bool _accepted; public BrushEditorViewModel(IAbstractProperty property) @@ -17,29 +17,33 @@ public BrushEditorViewModel(IAbstractProperty property) { CoreProperty coreProperty = property.Property; PropertyInfo propertyInfo = coreProperty.OwnerType.GetProperty(coreProperty.Name)!; - NullabilityInfo? nullabilityInfo = s_context.Create(propertyInfo); CanWrite = propertyInfo.SetMethod?.IsPublic == true; - CanDelete = (CanWrite && nullabilityInfo.WriteState == NullabilityState.Nullable) - || IsStylingSetter; - IsSet = property.GetObservable() - .Select(x => x != null) + Value = property.GetObservable() + .Select(x => x as IBrush) .ToReadOnlyReactivePropertySlim() .DisposeWith(Disposables); - IsNotSetAndCanWrite = IsSet.Select(x => !x && CanWrite) + ChildContext = Value.Select(v => v as ICoreObject) + .Select(x => x != null ? new PropertiesEditorViewModel(x, m => m.Browsable) : null) + .Do(AcceptChildren) .ToReadOnlyReactivePropertySlim() .DisposeWith(Disposables); - Value = property.GetObservable() - .Select(x => x as IBrush) + IsSolid = Value.Select(v => v is ISolidColorBrush) .ToReadOnlyReactivePropertySlim() .DisposeWith(Disposables); - ChildContext = Value.Select(v => v as ICoreObject) - .Select(x => x != null ? new PropertiesEditorViewModel(x, m => m.Browsable) : null) - .Do(AcceptChildren) + IsLinearGradient = Value.Select(v => v is ILinearGradientBrush) + .ToReadOnlyReactivePropertySlim() + .DisposeWith(Disposables); + + IsConicGradient = Value.Select(v => v is IConicGradientBrush) + .ToReadOnlyReactivePropertySlim() + .DisposeWith(Disposables); + + IsRadialGradient = Value.Select(v => v is IRadialGradientBrush) .ToReadOnlyReactivePropertySlim() .DisposeWith(Disposables); } @@ -64,13 +68,17 @@ private void AcceptChildren(PropertiesEditorViewModel? obj) public ReadOnlyReactivePropertySlim ChildContext { get; } - public ReadOnlyReactivePropertySlim IsSet { get; } + public ReadOnlyReactivePropertySlim IsSolid { get; } - public ReadOnlyReactivePropertySlim IsNotSetAndCanWrite { get; } + public ReadOnlyReactivePropertySlim IsLinearGradient { get; } - public bool CanWrite { get; } + public ReadOnlyReactivePropertySlim IsConicGradient { get; } + + public ReadOnlyReactivePropertySlim IsRadialGradient { get; } - public bool CanDelete { get; } + public ReactivePropertySlim IsSeparatorVisible { get; } = new(); + + public bool CanWrite { get; } public override void Reset() { @@ -95,6 +103,8 @@ public override void Accept(IPropertyEditorContextVisitor visitor) { AcceptChildren(ChildContext.Value); } + + IsSeparatorVisible.Value = visitor is SourceOperatorViewModel; } private sealed record Visitor(BrushEditorViewModel Obj) : IServiceProvider, IPropertyEditorContextVisitor diff --git a/src/Beutl/Views/Editors/BrushEditor.axaml b/src/Beutl/Views/Editors/BrushEditor.axaml index 61ac16378..70a0cf642 100644 --- a/src/Beutl/Views/Editors/BrushEditor.axaml +++ b/src/Beutl/Views/Editors/BrushEditor.axaml @@ -10,61 +10,71 @@ xmlns:ui="using:FluentAvalonia.UI.Controls" xmlns:vm="using:Beutl.ViewModels.Editors" x:Name="root" + d:DesignHeight="300" d:DesignWidth="300" x:CompileBindings="True" x:DataType="vm:BrushEditorViewModel" mc:Ignorable="d"> - - + + - + + - + + - + diff --git a/src/Beutl/Views/Editors/BrushEditor.axaml.cs b/src/Beutl/Views/Editors/BrushEditor.axaml.cs index 8d43117dc..94d16008e 100644 --- a/src/Beutl/Views/Editors/BrushEditor.axaml.cs +++ b/src/Beutl/Views/Editors/BrushEditor.axaml.cs @@ -1,17 +1,11 @@ -using System.Reflection; - +using Avalonia; +using Avalonia.Animation; using Avalonia.Controls; -using Avalonia.Controls.Templates; +using Avalonia.Controls.Primitives; using Avalonia.Interactivity; -using Avalonia.LogicalTree; using Beutl.Media; -using Beutl.Operation; -using Beutl.Styling; -using Beutl.ViewModels; using Beutl.ViewModels.Editors; -using Beutl.ViewModels.Tools; -using Beutl.Views.Tools; using FluentAvalonia.UI.Controls; @@ -19,94 +13,54 @@ namespace Beutl.Views.Editors; public sealed partial class BrushEditor : UserControl { + private static readonly CrossFade s_transition = new(TimeSpan.FromMilliseconds(250)); + + private CancellationTokenSource? _lastTransitionCts; + public BrushEditor() { InitializeComponent(); - } - - private void Navigate_Click(object? sender, RoutedEventArgs e) - { - if (this.FindLogicalAncestorOfType()?.DataContext is EditViewModel editViewModel - && DataContext is BrushEditorViewModel viewModel - && viewModel.Value.Value is ICoreObject obj) - { - ObjectPropertyEditorViewModel objViewModel - = editViewModel.FindToolTab() - ?? new ObjectPropertyEditorViewModel(editViewModel); + expandToggle.GetObservable(ToggleButton.IsCheckedProperty) + .Subscribe(async v => + { + _lastTransitionCts?.Cancel(); + _lastTransitionCts = new CancellationTokenSource(); + CancellationToken localToken = _lastTransitionCts.Token; - objViewModel.NavigateCore(obj, false); - editViewModel.OpenToolTab(objViewModel); - } + if (v == true) + { + await s_transition.Start(null, content, localToken); + } + else + { + await s_transition.Start(content, null, localToken); + } + }); } private void Menu_Click(object? sender, RoutedEventArgs e) { if (sender is Button button) { - button.ContextMenu?.Open(); + button.ContextFlyout?.ShowAt(button); } } - private async void New_Click(object? sender, RoutedEventArgs e) + private void ChangeBrushType(object? sender, RoutedEventArgs e) { - if (DataContext is BrushEditorViewModel viewModel) + if (DataContext is BrushEditorViewModel viewModel + && sender is RadioMenuFlyoutItem { Tag: string tag }) { - (Type, string)[] items = new (Type, string)[] - { - (typeof(SolidColorBrush), "SolidColorBrush"), - (typeof(ImageBrush), "ImageBrush"), - (typeof(ConicGradientBrush), "ConicGradientBrush"), - (typeof(LinearGradientBrush), "LinearGradientBrush"), - (typeof(RadialGradientBrush), "RadialGradientBrush"), - }; - - var combobox = new ComboBox - { - Items = items, - SelectedIndex = 0, - ItemTemplate = new FuncDataTemplate<(Type, string)>( - match: _ => true, - build: (x, _) => new TextBlock { Text = x.Item2 }) - }; - - var dialog = new ContentDialog + IBrush? newBrush = tag switch { - Content = combobox, - Title = Message.MultipleTypesAreAvailable, - PrimaryButtonText = Strings.OK, - CloseButtonText = Strings.Cancel + "Solid" => new SolidColorBrush(), + "LinearGradient" => new LinearGradientBrush(), + "ConicGradient" => new ConicGradientBrush(), + "RadialGradient" => new RadialGradientBrush(), + _ => null }; - if (await dialog.ShowAsync() == ContentDialogResult.Primary - && combobox.SelectedItem is (Type type, string _)) - { - ConstructorInfo? constructorInfo = type.GetConstructor(Array.Empty()); - - if (constructorInfo?.Invoke(null) is IBrush typed) - { - viewModel.SetValue(viewModel.Value.Value, typed); - } - } - } - } - - private void Delete_Click(object? sender, RoutedEventArgs e) - { - if (DataContext is BrushEditorViewModel viewModel) - { - if (this.FindLogicalAncestorOfType()?.DataContext is StyleEditorViewModel parentViewModel - && viewModel.WrappedProperty is IStylingSetterPropertyImpl wrapper - && parentViewModel.Style.Value is Style style) - { - style.Setters.BeginRecord() - .Remove(wrapper.Setter) - .ToCommand() - .DoAndRecord(CommandRecorder.Default); - } - else - { - viewModel.SetValue(viewModel.Value.Value, default); - } + viewModel.SetValue(viewModel.Value.Value, newBrush); } } } diff --git a/src/Beutl/Views/Editors/OpacityEditor.axaml b/src/Beutl/Views/Editors/OpacityEditor.axaml index f22db4b91..6d455a2f5 100644 --- a/src/Beutl/Views/Editors/OpacityEditor.axaml +++ b/src/Beutl/Views/Editors/OpacityEditor.axaml @@ -11,7 +11,7 @@ x:DataType="vm:OpacityEditorViewModel" mc:Ignorable="d"> - @@ -124,7 +124,7 @@ + Margin="0,0,4,8" /> diff --git a/src/Beutl/Views/Editors/PropertyEditorMenu.axaml b/src/Beutl/Views/Editors/PropertyEditorMenu.axaml index b5342c7a2..8a6ccd86a 100644 --- a/src/Beutl/Views/Editors/PropertyEditorMenu.axaml +++ b/src/Beutl/Views/Editors/PropertyEditorMenu.axaml @@ -6,6 +6,7 @@ xmlns:lang="using:Beutl.Language" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="using:Beutl.ViewModels.Editors" + Margin="0,0,4,0" VerticalAlignment="Center" d:DesignHeight="500" d:DesignWidth="500" @@ -26,9 +27,6 @@ IsEnabled="{Binding CanReset}" /> - ()?.DataContext is StyleEditorViewModel parentViewModel - && viewModel.WrappedProperty is IStylingSetterPropertyImpl wrapper - && parentViewModel.Style.Value is Style style) - { - style.Setters.BeginRecord() - .Remove(wrapper.Setter) - .ToCommand() - .DoAndRecord(CommandRecorder.Default); - } - } } diff --git a/src/Beutl/Views/Tools/SourceOperatorView.axaml b/src/Beutl/Views/Tools/SourceOperatorView.axaml index 74bc6d0ac..b881dc122 100644 --- a/src/Beutl/Views/Tools/SourceOperatorView.axaml +++ b/src/Beutl/Views/Tools/SourceOperatorView.axaml @@ -44,6 +44,6 @@ - + From 4c4b4bf83b32f388540d4dcf36391bd5c382aaa5 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Sun, 28 May 2023 21:51:27 +0900 Subject: [PATCH 51/84] PenEditor --- .../PropertyEditors/AlignmentXEditor.axaml | 1 + .../Styling/PropertyEditors/ColorEditor.axaml | 1 + .../Styling/PropertyEditors/EnumEditor.axaml | 3 +- .../PropertyEditors/FontFamilyEditor.axaml | 1 + .../PropertyEditors/StorageFileEditor.axaml | 1 + .../PropertyEditors/StringEditor.axaml | 3 +- src/Beutl.Core/StaticProperty.cs | 2 +- src/Beutl/Services/PropertyEditorService.cs | 1 + .../Editors/BrushEditorViewModel.cs | 69 ++++---- .../ViewModels/Editors/PenEditorViewModel.cs | 153 ++++++++++++++++++ src/Beutl/Views/Editors/BrushEditor.axaml | 29 +++- src/Beutl/Views/Editors/BrushEditor.axaml.cs | 1 + src/Beutl/Views/Editors/PenEditor.axaml | 97 +++++++++++ src/Beutl/Views/Editors/PenEditor.axaml.cs | 83 ++++++++++ .../Views/Editors/PropertiesEditor.axaml.cs | 2 +- 15 files changed, 396 insertions(+), 51 deletions(-) create mode 100644 src/Beutl/ViewModels/Editors/PenEditorViewModel.cs create mode 100644 src/Beutl/Views/Editors/PenEditor.axaml create mode 100644 src/Beutl/Views/Editors/PenEditor.axaml.cs diff --git a/src/Beutl.Controls/Styling/PropertyEditors/AlignmentXEditor.axaml b/src/Beutl.Controls/Styling/PropertyEditors/AlignmentXEditor.axaml index 8c581b669..5c0a91f45 100644 --- a/src/Beutl.Controls/Styling/PropertyEditors/AlignmentXEditor.axaml +++ b/src/Beutl.Controls/Styling/PropertyEditors/AlignmentXEditor.axaml @@ -25,6 +25,7 @@ diff --git a/src/Beutl.Controls/Styling/PropertyEditors/ColorEditor.axaml b/src/Beutl.Controls/Styling/PropertyEditors/ColorEditor.axaml index 89f6cf513..017728c17 100644 --- a/src/Beutl.Controls/Styling/PropertyEditors/ColorEditor.axaml +++ b/src/Beutl.Controls/Styling/PropertyEditors/ColorEditor.axaml @@ -21,6 +21,7 @@ + Text="{TemplateBinding Header}" + TextTrimming="CharacterEllipsis" /> diff --git a/src/Beutl.Controls/Styling/PropertyEditors/FontFamilyEditor.axaml b/src/Beutl.Controls/Styling/PropertyEditors/FontFamilyEditor.axaml index 754dbfa21..df09701f8 100644 --- a/src/Beutl.Controls/Styling/PropertyEditors/FontFamilyEditor.axaml +++ b/src/Beutl.Controls/Styling/PropertyEditors/FontFamilyEditor.axaml @@ -32,6 +32,7 @@ + Text="{TemplateBinding Header}" + TextTrimming="CharacterEllipsis" /> diff --git a/src/Beutl.Core/StaticProperty.cs b/src/Beutl.Core/StaticProperty.cs index e75094485..e330bf1c7 100644 --- a/src/Beutl.Core/StaticProperty.cs +++ b/src/Beutl.Core/StaticProperty.cs @@ -35,7 +35,7 @@ internal override void RouteSetTypedValue(ICoreObject o, T? value) { if (Setter == null) { - throw new Exception("This property is read-only."); + throw new InvalidOperationException("This property is read-only."); } else { diff --git a/src/Beutl/Services/PropertyEditorService.cs b/src/Beutl/Services/PropertyEditorService.cs index babf72dda..f4bae32a0 100644 --- a/src/Beutl/Services/PropertyEditorService.cs +++ b/src/Beutl/Services/PropertyEditorService.cs @@ -147,6 +147,7 @@ private record struct Editor(Func CreateEditor, Fun { typeof(ISoundSource), new(_ => new SoundSourceEditor(), s=>new SoundSourceEditorViewModel(s.ToTyped())) }, { typeof(IBrush), new(_ => new BrushEditor(), s => new BrushEditorViewModel(s)) }, + { typeof(IPen), new(_ => new PenEditor(), s => new PenEditorViewModel(s)) }, { typeof(GradientStops), new(_ => new GradientStopsEditor(), s => new GradientStopsEditorViewModel(s.ToTyped())) }, { typeof(IList), new(_ => new ListEditor(), s => new ListEditorViewModel(s)) }, { typeof(ICoreObject), new(CreateNavigationButton, CreateNavigationButtonViewModel) }, diff --git a/src/Beutl/ViewModels/Editors/BrushEditorViewModel.cs b/src/Beutl/ViewModels/Editors/BrushEditorViewModel.cs index 87294c050..1bcb67226 100644 --- a/src/Beutl/ViewModels/Editors/BrushEditorViewModel.cs +++ b/src/Beutl/ViewModels/Editors/BrushEditorViewModel.cs @@ -1,6 +1,4 @@ -using System.Reflection; - -using Beutl.Framework; +using Beutl.Framework; using Beutl.Media; using Beutl.ViewModels.Tools; @@ -8,6 +6,35 @@ namespace Beutl.ViewModels.Editors; +public sealed class SetCommand : IRecordableCommand +{ + private readonly IAbstractProperty _setter; + private readonly object? _oldValue; + private readonly object? _newValue; + + public SetCommand(IAbstractProperty setter, object? oldValue, object? newValue) + { + _setter = setter; + _oldValue = oldValue; + _newValue = newValue; + } + + public void Do() + { + _setter.SetValue(_newValue); + } + + public void Redo() + { + Do(); + } + + public void Undo() + { + _setter.SetValue(_oldValue); + } +} + public sealed class BrushEditorViewModel : BaseEditorViewModel { private bool _accepted; @@ -15,11 +42,6 @@ public sealed class BrushEditorViewModel : BaseEditorViewModel public BrushEditorViewModel(IAbstractProperty property) : base(property) { - CoreProperty coreProperty = property.Property; - PropertyInfo propertyInfo = coreProperty.OwnerType.GetProperty(coreProperty.Name)!; - - CanWrite = propertyInfo.SetMethod?.IsPublic == true; - Value = property.GetObservable() .Select(x => x as IBrush) .ToReadOnlyReactivePropertySlim() @@ -78,8 +100,6 @@ private void AcceptChildren(PropertiesEditorViewModel? obj) public ReactivePropertySlim IsSeparatorVisible { get; } = new(); - public bool CanWrite { get; } - public override void Reset() { if (GetDefaultValue() is { } defaultValue) @@ -118,33 +138,4 @@ public void Visit(IPropertyEditorContext context) { } } - - private sealed class SetCommand : IRecordableCommand - { - private readonly IAbstractProperty _setter; - private readonly object? _oldValue; - private readonly object? _newValue; - - public SetCommand(IAbstractProperty setter, object? oldValue, object? newValue) - { - _setter = setter; - _oldValue = oldValue; - _newValue = newValue; - } - - public void Do() - { - _setter.SetValue(_newValue); - } - - public void Redo() - { - Do(); - } - - public void Undo() - { - _setter.SetValue(_oldValue); - } - } } diff --git a/src/Beutl/ViewModels/Editors/PenEditorViewModel.cs b/src/Beutl/ViewModels/Editors/PenEditorViewModel.cs new file mode 100644 index 000000000..c4d55797d --- /dev/null +++ b/src/Beutl/ViewModels/Editors/PenEditorViewModel.cs @@ -0,0 +1,153 @@ +using Avalonia.Collections.Pooled; + +using Beutl.Framework; +using Beutl.Media; +using Beutl.Operators.Configure; +using Beutl.Services; +using Beutl.ViewModels.Tools; + +using DynamicData; + +using Reactive.Bindings; + +namespace Beutl.ViewModels.Editors; + +public sealed class PenEditorViewModel : BaseEditorViewModel +{ + private bool _accepted; + + public PenEditorViewModel(IAbstractProperty property) + : base(property) + { + Value = property.GetObservable() + .Select(x => x as IPen) + .ToReadOnlyReactivePropertySlim() + .DisposeWith(Disposables); + + Value.Subscribe(Update) + .DisposeWith(Disposables); + } + + private void Update(IPen? pen) + { + void CreateContexts(PooledList props, CoreList dst) + { + CoreProperty[]? foundItems; + PropertyEditorExtension? extension; + + do + { + (foundItems, extension) = PropertyEditorService.MatchProperty(props); + if (foundItems != null && extension != null) + { + var tmp = new IAbstractProperty[foundItems.Length]; + for (int i = 0; i < foundItems.Length; i++) + { + CoreProperty item = foundItems[i]; + Type wrapperGType = typeof(AnimatableCorePropertyImpl<>).MakeGenericType(item.PropertyType); + tmp[i] = (IAbstractProperty)Activator.CreateInstance(wrapperGType, item, pen)!; + } + + if (extension.TryCreateContext(tmp, out IPropertyEditorContext? context)) + { + dst.Add(context); + } + + props.RemoveMany(foundItems); + } + } while (foundItems != null && extension != null); + } + + MajorProperties.Clear(); + MinorProperties.Clear(); + if (pen is Pen) + { + using var props = new PooledList(); + Span span = props.AddSpan(4); + span[0] = Pen.ThicknessProperty; + span[1] = Pen.StrokeCapProperty; + span[2] = Pen.StrokeAlignmentProperty; + span[3] = Pen.BrushProperty; + + CreateContexts(props, MajorProperties); + + props.Clear(); + span = props.AddSpan(4); + span[0] = Pen.MiterLimitProperty; + span[1] = Pen.StrokeJoinProperty; + span[2] = Pen.DashArrayProperty; + span[3] = Pen.DashOffsetProperty; + + CreateContexts(props, MinorProperties); + } + + AcceptChildren(); + } + + private void AcceptChildren() + { + _accepted = false; + + if (Value.Value is Pen) + { + var visitor = new Visitor(this); + foreach (IPropertyEditorContext item in MajorProperties) + { + item.Accept(visitor); + } + foreach (IPropertyEditorContext item in MinorProperties) + { + item.Accept(visitor); + } + + _accepted = true; + } + } + + public ReadOnlyReactivePropertySlim Value { get; } + + public CoreList MajorProperties { get; } = new(); + + public CoreList MinorProperties { get; } = new(); + + public ReactivePropertySlim IsSeparatorVisible { get; } = new(); + + public override void Reset() + { + if (GetDefaultValue() is { } defaultValue) + { + SetValue(Value.Value, (IPen?)defaultValue); + } + } + + public void SetValue(IPen? oldValue, IPen? newValue) + { + if (!EqualityComparer.Default.Equals(oldValue, newValue)) + { + CommandRecorder.Default.DoAndPush(new SetCommand(WrappedProperty, oldValue, newValue)); + } + } + + public override void Accept(IPropertyEditorContextVisitor visitor) + { + base.Accept(visitor); + if (visitor is IServiceProvider && !_accepted) + { + AcceptChildren(); + } + + IsSeparatorVisible.Value = visitor is SourceOperatorViewModel; + } + + private sealed record Visitor(PenEditorViewModel Obj) : IServiceProvider, IPropertyEditorContextVisitor + { + public object? GetService(Type serviceType) + { + return Obj.GetService(serviceType); + } + + public void Visit(IPropertyEditorContext context) + { + } + } +} diff --git a/src/Beutl/Views/Editors/BrushEditor.axaml b/src/Beutl/Views/Editors/BrushEditor.axaml index 70a0cf642..8ac113e21 100644 --- a/src/Beutl/Views/Editors/BrushEditor.axaml +++ b/src/Beutl/Views/Editors/BrushEditor.axaml @@ -17,8 +17,8 @@ mc:Ignorable="d"> + Margin="0,4" + IsVisible="{Binding IsSeparatorVisible.Value}" /> + @@ -70,11 +74,20 @@ Theme="{StaticResource ExpandCollapseToggleButtonStyle}" /> - + + + + + + + + + diff --git a/src/Beutl/Views/Editors/BrushEditor.axaml.cs b/src/Beutl/Views/Editors/BrushEditor.axaml.cs index 94d16008e..6005aa307 100644 --- a/src/Beutl/Views/Editors/BrushEditor.axaml.cs +++ b/src/Beutl/Views/Editors/BrushEditor.axaml.cs @@ -61,6 +61,7 @@ private void ChangeBrushType(object? sender, RoutedEventArgs e) }; viewModel.SetValue(viewModel.Value.Value, newBrush); + expandToggle.IsChecked = true; } } } diff --git a/src/Beutl/Views/Editors/PenEditor.axaml b/src/Beutl/Views/Editors/PenEditor.axaml new file mode 100644 index 000000000..1438f0c9f --- /dev/null +++ b/src/Beutl/Views/Editors/PenEditor.axaml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Beutl/Views/Editors/PenEditor.axaml.cs b/src/Beutl/Views/Editors/PenEditor.axaml.cs new file mode 100644 index 000000000..b060eb8da --- /dev/null +++ b/src/Beutl/Views/Editors/PenEditor.axaml.cs @@ -0,0 +1,83 @@ +using Avalonia; +using Avalonia.Animation; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Interactivity; + +using Beutl.Media; +using Beutl.ViewModels.Editors; + +using static Beutl.Views.Editors.PropertiesEditor; + +namespace Beutl.Views.Editors; + +public sealed partial class PenEditor : UserControl +{ + private static readonly CrossFade s_transition = new(TimeSpan.FromMilliseconds(250)); + + private CancellationTokenSource? _lastTransitionCts; + + public PenEditor() + { + Resources["ViewModelToViewConverter"] = ViewModelToViewConverter.Instance; + InitializeComponent(); + expandToggle.GetObservable(ToggleButton.IsCheckedProperty) + .Subscribe(async v => + { + _lastTransitionCts?.Cancel(); + _lastTransitionCts = new CancellationTokenSource(); + CancellationToken localToken = _lastTransitionCts.Token; + + if (v == true) + { + await s_transition.Start(null, content, localToken); + } + else + { + await s_transition.Start(content, null, localToken); + } + }); + + expandMinorProps.GetObservable(ToggleButton.IsCheckedProperty) + .Subscribe(async v => + { + _lastTransitionCts?.Cancel(); + _lastTransitionCts = new CancellationTokenSource(); + CancellationToken localToken = _lastTransitionCts.Token; + + if (v == true) + { + await s_transition.Start(null, minorProps, localToken); + } + else + { + await s_transition.Start(minorProps, null, localToken); + } + }); + } + + private void InitializeClick(object? sender, RoutedEventArgs e) + { + if (DataContext is PenEditorViewModel viewModel) + { + viewModel.SetValue(viewModel.Value.Value, new Pen()); + expandToggle.IsChecked = true; + } + } + + private void DeleteClick(object? sender, RoutedEventArgs e) + { + if (DataContext is PenEditorViewModel viewModel) + { + viewModel.SetValue(viewModel.Value.Value, null); + } + } + + private void Menu_Click(object? sender, RoutedEventArgs e) + { + if (sender is Button button) + { + button.ContextFlyout?.ShowAt(button); + } + } +} diff --git a/src/Beutl/Views/Editors/PropertiesEditor.axaml.cs b/src/Beutl/Views/Editors/PropertiesEditor.axaml.cs index 152e1dd97..9d4e64a5f 100644 --- a/src/Beutl/Views/Editors/PropertiesEditor.axaml.cs +++ b/src/Beutl/Views/Editors/PropertiesEditor.axaml.cs @@ -15,7 +15,7 @@ public PropertiesEditor() InitializeComponent(); } - private sealed class ViewModelToViewConverter : IValueConverter + public sealed class ViewModelToViewConverter : IValueConverter { public static readonly ViewModelToViewConverter Instance = new(); From fd38d3bdee9613a391d2f82f30981619a93878b0 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Sat, 3 Jun 2023 11:52:38 +0900 Subject: [PATCH 52/84] =?UTF-8?q?IAbstractProperty=E3=81=8B=E3=82=89CorePr?= =?UTF-8?q?operty=E3=81=AB=E3=81=A7=E3=81=8D=E3=82=8B=E3=81=A0=E3=81=91?= =?UTF-8?q?=E7=9B=B4=E6=8E=A5=E4=BE=9D=E5=AD=98=E3=81=97=E3=81=AA=E3=81=84?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Framework/IAbstractProperty.cs | 31 ++++++++--- .../PropertyEditorExtension.cs | 5 +- .../Configure/CorePropertyImpl.cs | 20 ++++++++ .../NodeTree/InputSocket.cs | 2 +- .../NodeTree/NodeDisplayNameHelper.cs | 4 +- .../NodeTree/Nodes/Group/GroupInput.cs | 7 +-- .../NodeTree/Nodes/Group/GroupOutput.cs | 5 +- .../NodeTree/Nodes/LayerInputNode.cs | 3 +- .../NodeTree/SetterPropertyImpl.cs | 20 ++++++++ .../Operation/StylingOperator.cs | 20 ++++++++ src/Beutl/Services/PropertyEditorService.cs | 51 ++++++++++--------- .../ViewModels/Editors/BaseEditorViewModel.cs | 10 ++-- .../Editors/NavigationButtonViewModel.cs | 19 +------ .../ViewModels/Editors/PenEditorViewModel.cs | 36 +++++-------- .../Editors/PropertiesEditorViewModel.cs | 25 ++++----- .../InlineAnimationLayerViewModel.cs | 3 +- .../ViewModels/NodeTree/NodeInputViewModel.cs | 11 ++-- .../ViewModels/NodeTree/NodeViewModel.cs | 11 ++-- src/Beutl/ViewModels/TimelineViewModel.cs | 2 +- .../Tools/SourceOperatorViewModel.cs | 13 ++--- .../ViewModels/Tools/StyleEditorViewModel.cs | 13 ++--- src/Beutl/Views/Editors/NavigateButton.axaml | 3 -- .../Views/Editors/NavigateButton.axaml.cs | 27 +--------- .../Views/Editors/PropertyEditorMenu.axaml.cs | 14 +++-- 24 files changed, 183 insertions(+), 172 deletions(-) diff --git a/src/Beutl.Framework/IAbstractProperty.cs b/src/Beutl.Framework/IAbstractProperty.cs index 67bb4faaa..e22c80940 100644 --- a/src/Beutl.Framework/IAbstractProperty.cs +++ b/src/Beutl.Framework/IAbstractProperty.cs @@ -9,7 +9,15 @@ public interface IAbstractProperty { Type ImplementedType { get; } - CoreProperty Property { get; } + Type PropertyType { get; } + + string DisplayName { get; } + + bool IsReadOnly { get; } + + object? GetDefaultValue(); + + CoreProperty? GetCoreProperty() => null; void SetValue(object? value); @@ -20,8 +28,6 @@ public interface IAbstractProperty public interface IAbstractProperty : IAbstractProperty { - new CoreProperty Property { get; } - void SetValue(T? value); new T? GetValue(); @@ -49,8 +55,6 @@ void IAbstractProperty.SetValue(object? value) { return GetObservable().Select(x => (object?)x); } - - CoreProperty IAbstractProperty.Property => Property; } public interface IAbstractAnimatableProperty : IAbstractProperty @@ -93,9 +97,13 @@ public KeyFramePropertyWrapper(KeyFrame keyFrame, KeyFrameAnimation animat _animation = animation; } - public CoreProperty Property => _animation.Property; + public Type ImplementedType => _animation.Property.OwnerType; - public Type ImplementedType => Property.OwnerType; + public Type PropertyType => _animation.Property.PropertyType; + + public string DisplayName => "KeyFrame Value"; + + public bool IsReadOnly => false; public IObservable GetObservable() { @@ -112,8 +120,15 @@ public void SetValue(T? value) _keyFrame.SetValue(GetProperty(), value); } - private CoreProperty GetProperty() + CoreProperty? IAbstractProperty.GetCoreProperty() => _animation.Property; + + private static CoreProperty GetProperty() { return KeyFrame.ValueProperty; } + + public object? GetDefaultValue() + { + return _animation.Property.GetMetadata(ImplementedType).GetDefaultValue(); + } } diff --git a/src/Beutl.Framework/PropertyEditorExtension.cs b/src/Beutl.Framework/PropertyEditorExtension.cs index f84559462..75e0a611d 100644 --- a/src/Beutl.Framework/PropertyEditorExtension.cs +++ b/src/Beutl.Framework/PropertyEditorExtension.cs @@ -1,5 +1,4 @@ using System.Diagnostics.CodeAnalysis; -using System.Text.Json.Nodes; using Avalonia.Controls; @@ -9,7 +8,7 @@ namespace Beutl.Framework; internal interface IPropertyEditorExtensionImpl { - IEnumerable MatchProperty(IReadOnlyList properties); + IEnumerable MatchProperty(IReadOnlyList properties); bool TryCreateContext(PropertyEditorExtension extension, IReadOnlyList properties, [NotNullWhen(true)] out IPropertyEditorContext? context); @@ -30,7 +29,7 @@ public class PropertyEditorExtension : Extension public override string DisplayName => ""; - public virtual IEnumerable MatchProperty(IReadOnlyList properties) + public virtual IEnumerable MatchProperty(IReadOnlyList properties) { _impl ??= ServiceLocator.Current.GetRequiredService(); return _impl.MatchProperty(properties); diff --git a/src/Beutl.Operators/Configure/CorePropertyImpl.cs b/src/Beutl.Operators/Configure/CorePropertyImpl.cs index 4edd3df87..cf0ddbbc3 100644 --- a/src/Beutl.Operators/Configure/CorePropertyImpl.cs +++ b/src/Beutl.Operators/Configure/CorePropertyImpl.cs @@ -19,6 +19,26 @@ public CorePropertyImpl(CoreProperty property, ICoreObject obj) public Type ImplementedType => _implementedType ??= Object.GetType(); + public Type PropertyType => Property.PropertyType; + + public string DisplayName + { + get + { + CorePropertyMetadata metadata = Property.GetMetadata(ImplementedType); + return metadata.DisplayAttribute?.GetName() ?? Property.Name; + } + } + + public bool IsReadOnly => Property is IStaticProperty { CanRead: false }; + + CoreProperty? IAbstractProperty.GetCoreProperty() => Property; + + public object? GetDefaultValue() + { + return Property.GetMetadata(ImplementedType).GetDefaultValue(); + } + public IObservable GetObservable() { return _observable ??= Object.GetObservable(Property); diff --git a/src/Beutl.ProjectSystem/NodeTree/InputSocket.cs b/src/Beutl.ProjectSystem/NodeTree/InputSocket.cs index 7255aa279..f3adccbb2 100644 --- a/src/Beutl.ProjectSystem/NodeTree/InputSocket.cs +++ b/src/Beutl.ProjectSystem/NodeTree/InputSocket.cs @@ -116,7 +116,7 @@ private void ReceiveInvalidValue() { value1 = Property.GetValue(); if (value1 == null - && Property?.Property.GetMetadata>(Property.ImplementedType) is { } metadata + && Property?.GetCoreProperty()?.GetMetadata>(Property.ImplementedType) is { } metadata && metadata.HasDefaultValue) { value1 = metadata.DefaultValue; diff --git a/src/Beutl.ProjectSystem/NodeTree/NodeDisplayNameHelper.cs b/src/Beutl.ProjectSystem/NodeTree/NodeDisplayNameHelper.cs index 5432af713..cbabeb54a 100644 --- a/src/Beutl.ProjectSystem/NodeTree/NodeDisplayNameHelper.cs +++ b/src/Beutl.ProjectSystem/NodeTree/NodeDisplayNameHelper.cs @@ -14,9 +14,7 @@ public static string GetDisplayName(INodeItem item) if (item.Property is { } property) { - CorePropertyMetadata metadata = property.Property.GetMetadata(property.ImplementedType); - - return name ?? metadata.DisplayAttribute?.GetName() ?? property.Property.Name; + return property.DisplayName; } else if (item is IGroupSocket { AssociatedProperty: { } asProperty }) { diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupInput.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupInput.cs index b876825df..559d75ffc 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupInput.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupInput.cs @@ -50,9 +50,10 @@ public bool AddSocket(ISocket socket, [NotNullWhen(true)] out Connection? connec if (Activator.CreateInstance(type) is IOutputSocket outputSocket) { + CoreProperty? coreProperty = inputSocket.Property?.GetCoreProperty(); ((NodeItem)outputSocket).LocalId = NextLocalId++; - ((IGroupSocket)outputSocket).AssociatedProperty = inputSocket.Property?.Property; - if (inputSocket.Property?.Property == null) + ((IGroupSocket)outputSocket).AssociatedProperty = coreProperty; + if (coreProperty == null) { ((CoreObject)outputSocket).Name = NodeDisplayNameHelper.GetDisplayName(inputSocket); } @@ -76,7 +77,7 @@ public bool AddSocket(ISocket socket, [NotNullWhen(true)] out Connection? connec public override void ReadFromJson(JsonObject json) { base.ReadFromJson(json); - if (json.TryGetPropertyValue("Items", out var itemsNode) + if (json.TryGetPropertyValue("Items", out JsonNode? itemsNode) && itemsNode is JsonArray itemsArray) { int index = 0; diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupOutput.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupOutput.cs index 18843ae54..5859fca48 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupOutput.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupOutput.cs @@ -50,9 +50,10 @@ public bool AddSocket(ISocket socket, [NotNullWhen(true)] out Connection? connec if (Activator.CreateInstance(type) is IInputSocket inputSocket) { + CoreProperty? coreProperty = outputSocket.Property?.GetCoreProperty(); ((NodeItem)inputSocket).LocalId = NextLocalId++; - ((IGroupSocket)inputSocket).AssociatedProperty = outputSocket.Property?.Property; - if (outputSocket.Property?.Property == null) + ((IGroupSocket)inputSocket).AssociatedProperty = coreProperty; + if (coreProperty == null) { ((CoreObject)inputSocket).Name = NodeDisplayNameHelper.GetDisplayName(outputSocket); } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/LayerInputNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/LayerInputNode.cs index 8ce386b06..a5b52134d 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/LayerInputNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/LayerInputNode.cs @@ -109,7 +109,8 @@ public override void WriteToJson(JsonObject json) public bool AddSocket(ISocket socket, [NotNullWhen(true)] out Connection? connection) { connection = null; - if (socket is IInputSocket { AssociatedType: { } valueType, Property.Property: { } property } inputSocket) + if (socket is IInputSocket { AssociatedType: { } valueType } inputSocket + && inputSocket.Property?.GetCoreProperty() is { } property) { Type type = typeof(LayerInputSocket<>).MakeGenericType(valueType); diff --git a/src/Beutl.ProjectSystem/NodeTree/SetterPropertyImpl.cs b/src/Beutl.ProjectSystem/NodeTree/SetterPropertyImpl.cs index dcf4be4e8..f7434a4b0 100644 --- a/src/Beutl.ProjectSystem/NodeTree/SetterPropertyImpl.cs +++ b/src/Beutl.ProjectSystem/NodeTree/SetterPropertyImpl.cs @@ -67,6 +67,21 @@ public IAnimation? Animation public Type ImplementedType { get; } + public Type PropertyType => Property.PropertyType; + + public string DisplayName + { + get + { + CorePropertyMetadata metadata = Property.GetMetadata(ImplementedType); + return metadata.DisplayAttribute?.GetName() ?? Property.Name; + } + } + + public bool IsReadOnly => false; + + CoreProperty? IAbstractProperty.GetCoreProperty() => Property; + public IObservable GetObservable() { return Setter; @@ -106,4 +121,9 @@ public void ReadFromJson(JsonObject json) } } } + + public object? GetDefaultValue() + { + return Property.GetMetadata(ImplementedType).GetDefaultValue(); + } } diff --git a/src/Beutl.ProjectSystem/Operation/StylingOperator.cs b/src/Beutl.ProjectSystem/Operation/StylingOperator.cs index 33923ead7..4fd9a4e95 100644 --- a/src/Beutl.ProjectSystem/Operation/StylingOperator.cs +++ b/src/Beutl.ProjectSystem/Operation/StylingOperator.cs @@ -84,8 +84,23 @@ public IAnimation? Animation public IObservable?> ObserveAnimation { get; } + public Type PropertyType => Property.PropertyType; + + public string DisplayName + { + get + { + CorePropertyMetadata metadata = Property.GetMetadata(ImplementedType); + return metadata.DisplayAttribute?.GetName() ?? Property.Name; + } + } + + public bool IsReadOnly => false; + public Type ImplementedType => Style.TargetType; + CoreProperty? IAbstractProperty.GetCoreProperty() => Property; + ISetter IStylingSetterPropertyImpl.Setter => Setter; IStyle IStylingSetterPropertyImpl.Style => Style; @@ -104,6 +119,11 @@ public void SetValue(T? value) { Setter.Value = value; } + + public object? GetDefaultValue() + { + return Property.GetMetadata(ImplementedType).GetDefaultValue(); + } } internal static class StylingOperatorPropertyDefinition diff --git a/src/Beutl/Services/PropertyEditorService.cs b/src/Beutl/Services/PropertyEditorService.cs index f4bae32a0..806d2faef 100644 --- a/src/Beutl/Services/PropertyEditorService.cs +++ b/src/Beutl/Services/PropertyEditorService.cs @@ -27,7 +27,7 @@ public static IAbstractProperty ToTyped(this IAbstractProperty pi) return (IAbstractProperty)pi; } - public static (CoreProperty[] Properties, PropertyEditorExtension Extension) MatchProperty(IReadOnlyList properties) + public static (IAbstractProperty[] Properties, PropertyEditorExtension Extension) MatchProperty(IReadOnlyList properties) { ExtensionProvider extp = ServiceLocator.Current.GetRequiredService(); @@ -35,7 +35,7 @@ public static (CoreProperty[] Properties, PropertyEditorExtension Extension) Mat for (int i = items.Length - 1; i >= 0; i--) { PropertyEditorExtension item = items[i]; - CoreProperty[] array = item.MatchProperty(properties).ToArray(); + IAbstractProperty[] array = item.MatchProperty(properties).ToArray(); if (array.Length > 0) { return (array, item); @@ -47,41 +47,41 @@ public static (CoreProperty[] Properties, PropertyEditorExtension Extension) Mat private static Control? CreateEnumEditor(IAbstractProperty s) { - Type type = typeof(EnumEditor<>).MakeGenericType(s.Property.PropertyType); + Type type = typeof(EnumEditor<>).MakeGenericType(s.PropertyType); return (Control?)Activator.CreateInstance(type); } private static BaseEditorViewModel? CreateEnumViewModel(IAbstractProperty s) { - Type type = typeof(EnumEditorViewModel<>).MakeGenericType(s.Property.PropertyType); + Type type = typeof(EnumEditorViewModel<>).MakeGenericType(s.PropertyType); return Activator.CreateInstance(type, s) as BaseEditorViewModel; } private static Control? CreateNavigationButton(IAbstractProperty s) { Type controlType = typeof(NavigateButton<>); - controlType = controlType.MakeGenericType(s.Property.PropertyType); + controlType = controlType.MakeGenericType(s.PropertyType); return Activator.CreateInstance(controlType) as Control; } private static BaseEditorViewModel? CreateNavigationButtonViewModel(IAbstractProperty s) { Type viewModelType = typeof(NavigationButtonViewModel<>); - viewModelType = viewModelType.MakeGenericType(s.Property.PropertyType); + viewModelType = viewModelType.MakeGenericType(s.PropertyType); return Activator.CreateInstance(viewModelType, s) as BaseEditorViewModel; } private static Control? CreateParsableEditor(IAbstractProperty s) { Type controlType = typeof(ParsableEditor<>); - controlType = controlType.MakeGenericType(s.Property.PropertyType); + controlType = controlType.MakeGenericType(s.PropertyType); return Activator.CreateInstance(controlType) as Control; } private static BaseEditorViewModel? CreateParsableEditorViewModel(IAbstractProperty s) { Type viewModelType = typeof(ParsableEditorViewModel<>); - viewModelType = viewModelType.MakeGenericType(s.Property.PropertyType); + viewModelType = viewModelType.MakeGenericType(s.PropertyType); return Activator.CreateInstance(viewModelType, s) as BaseEditorViewModel; } @@ -154,12 +154,13 @@ private record struct Editor(Func CreateEditor, Fun { typeof(IParsable<>), new(CreateParsableEditor, CreateParsableEditorViewModel) }, }; - public IEnumerable MatchProperty(IReadOnlyList properties) + public IEnumerable MatchProperty(IReadOnlyList properties) { for (int i = 0; i < properties.Count; i++) { - CoreProperty item = properties[i]; - if (s_editorsOverride.ContainsKey(item.Id) || s_editors.ContainsKey(item.PropertyType)) + IAbstractProperty item = properties[i]; + if ((item.GetCoreProperty() is { Id: int id } && s_editorsOverride.ContainsKey(id)) + || s_editors.ContainsKey(item.PropertyType)) { yield return item; yield break; @@ -185,7 +186,8 @@ public bool TryCreateContext(PropertyEditorExtension extension, IReadOnlyList 0 && properties[0] is { } property) { - if (s_editorsOverride.TryGetValue(property.Property.Id, out Editor editorOverrided)) + if (property.GetCoreProperty() is CoreProperty { Id: int propId } + && s_editorsOverride.TryGetValue(propId, out Editor editorOverrided)) { viewModel = editorOverrided.CreateViewModel(property); if (viewModel != null) @@ -196,7 +198,7 @@ public bool TryCreateContext(PropertyEditorExtension extension, IReadOnlyList item in s_editors) { - if (property.Property.PropertyType.IsAssignableTo(item.Key)) + if (property.PropertyType.IsAssignableTo(item.Key)) { viewModel = item.Value.CreateViewModel(property); if (viewModel != null) @@ -234,7 +236,8 @@ public bool TryCreateControl(IPropertyEditorContext context, [NotNullWhen(true)] { if (context is BaseEditorViewModel { WrappedProperty: { } property }) { - if (s_editorsOverride.TryGetValue(property.Property.Id, out Editor editorOverrided)) + if (property.GetCoreProperty() is CoreProperty { Id: int propId } + && s_editorsOverride.TryGetValue(propId, out Editor editorOverrided)) { control = editorOverrided.CreateEditor(property); if (control != null) @@ -243,7 +246,7 @@ public bool TryCreateControl(IPropertyEditorContext context, [NotNullWhen(true)] } } - if (s_editors.TryGetValue(property.Property.PropertyType, out Editor editor)) + if (s_editors.TryGetValue(property.PropertyType, out Editor editor)) { control = editor.CreateEditor(property); if (control != null) @@ -254,7 +257,7 @@ public bool TryCreateControl(IPropertyEditorContext context, [NotNullWhen(true)] foreach (KeyValuePair item in s_editors) { - if (property.Property.PropertyType.IsAssignableTo(item.Key)) + if (property.PropertyType.IsAssignableTo(item.Key)) { control = item.Value.CreateEditor(property); if (control != null) @@ -288,7 +291,8 @@ public bool TryCreateContextForNode(PropertyEditorExtension extension, IReadOnly if (properties.Count > 0 && properties[0] is { } property) { - if (s_editorsOverride.TryGetValue(property.Property.Id, out Editor editorOverrided)) + if (property.GetCoreProperty() is CoreProperty { Id: int propId } + && s_editorsOverride.TryGetValue(propId, out Editor editorOverrided)) { viewModel = editorOverrided.CreateViewModel(property); if (viewModel != null) @@ -299,7 +303,7 @@ public bool TryCreateContextForNode(PropertyEditorExtension extension, IReadOnly } } - if (s_editors.TryGetValue(property.Property.PropertyType, out Editor editor)) + if (s_editors.TryGetValue(property.PropertyType, out Editor editor)) { viewModel = editor.CreateViewModel(property); if (viewModel != null) @@ -312,7 +316,7 @@ public bool TryCreateContextForNode(PropertyEditorExtension extension, IReadOnly foreach (KeyValuePair item in s_editors) { - if (property.Property.PropertyType.IsAssignableTo(item.Key)) + if (property.PropertyType.IsAssignableTo(item.Key)) { viewModel = item.Value.CreateViewModel(property); if (viewModel != null) @@ -337,7 +341,8 @@ public bool TryCreateControlForNode(IPropertyEditorContext context, [NotNullWhen { if (context is BaseEditorViewModel { WrappedProperty: { } property }) { - if (s_editorsOverride.TryGetValue(property.Property.Id, out Editor editorOverrided)) + if (property.GetCoreProperty() is CoreProperty { Id: int propId } + && s_editorsOverride.TryGetValue(propId, out Editor editorOverrided)) { control = editorOverrided.CreateEditor(property); if (control != null) @@ -346,7 +351,7 @@ public bool TryCreateControlForNode(IPropertyEditorContext context, [NotNullWhen } } - if (s_editors.TryGetValue(property.Property.PropertyType, out Editor editor)) + if (s_editors.TryGetValue(property.PropertyType, out Editor editor)) { control = editor.CreateEditor(property); if (control != null) @@ -357,7 +362,7 @@ public bool TryCreateControlForNode(IPropertyEditorContext context, [NotNullWhen foreach (KeyValuePair item in s_editors) { - if (property.Property.PropertyType.IsAssignableTo(item.Key)) + if (property.PropertyType.IsAssignableTo(item.Key)) { control = item.Value.CreateEditor(property); if (control != null) diff --git a/src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs b/src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs index 2dcf8ba6d..5761f2371 100644 --- a/src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs +++ b/src/Beutl/ViewModels/Editors/BaseEditorViewModel.cs @@ -33,14 +33,13 @@ protected BaseEditorViewModel(IAbstractProperty property) { WrappedProperty = property; - CorePropertyMetadata metadata = property.Property.GetMetadata(property.ImplementedType); - Header = metadata.DisplayAttribute?.GetName() ?? property.Property.Name; + Header = property.DisplayName; IObservable hasAnimation = property is IAbstractAnimatableProperty anm ? anm.ObserveAnimation.Select(x => x != null) : Observable.Return(false); - IObservable? isReadOnly = Observable.Return(property.Property is IStaticProperty { CanWrite: false }); + IObservable? isReadOnly = Observable.Return(property.IsReadOnly); CanEdit = isReadOnly .Not() @@ -231,8 +230,7 @@ public virtual void Accept(IPropertyEditorContextVisitor visitor) protected object? GetDefaultValue() { - ICorePropertyMetadata metadata = WrappedProperty.Property.GetMetadata(WrappedProperty.ImplementedType); - return metadata.GetDefaultValue(); + return WrappedProperty.GetDefaultValue(); } protected virtual void Dispose(bool disposing) @@ -357,7 +355,7 @@ public override void RemoveKeyFrame(TimeSpan keyTime) if (WrappedProperty is IAbstractAnimatableProperty { Animation: IKeyFrameAnimation kfAnimation, - Property.PropertyType: { } ptype + PropertyType: { } ptype }) { keyTime = ConvertKeyTime(keyTime, kfAnimation); diff --git a/src/Beutl/ViewModels/Editors/NavigationButtonViewModel.cs b/src/Beutl/ViewModels/Editors/NavigationButtonViewModel.cs index 6d7ab15c6..d06a012eb 100644 --- a/src/Beutl/ViewModels/Editors/NavigationButtonViewModel.cs +++ b/src/Beutl/ViewModels/Editors/NavigationButtonViewModel.cs @@ -1,7 +1,4 @@ -using System.ComponentModel; -using System.Reflection; - -using Beutl.Framework; +using Beutl.Framework; using Reactive.Bindings; @@ -18,25 +15,15 @@ public interface INavigationButtonViewModel ReadOnlyReactivePropertySlim IsNotSetAndCanWrite { get; } bool CanWrite { get; } - - bool CanDelete { get; } } public sealed class NavigationButtonViewModel : BaseEditorViewModel, INavigationButtonViewModel where T : ICoreObject { - private static readonly NullabilityInfoContext s_context = new(); - public NavigationButtonViewModel(IAbstractProperty property) : base(property) { - CoreProperty coreProperty = property.Property; - PropertyInfo propertyInfo = coreProperty.OwnerType.GetProperty(coreProperty.Name)!; - NullabilityInfo? nullabilityInfo = s_context.Create(propertyInfo); - - CanWrite = propertyInfo.SetMethod?.IsPublic == true; - CanDelete = (CanWrite && nullabilityInfo.WriteState == NullabilityState.Nullable) - || IsStylingSetter; + CanWrite = !property.IsReadOnly; IsSet = property.GetObservable() .Select(x => x != null) @@ -59,6 +46,4 @@ public NavigationButtonViewModel(IAbstractProperty property) public ReadOnlyReactivePropertySlim IsNotSetAndCanWrite { get; } public bool CanWrite { get; } - - public bool CanDelete { get; } } diff --git a/src/Beutl/ViewModels/Editors/PenEditorViewModel.cs b/src/Beutl/ViewModels/Editors/PenEditorViewModel.cs index c4d55797d..110475d39 100644 --- a/src/Beutl/ViewModels/Editors/PenEditorViewModel.cs +++ b/src/Beutl/ViewModels/Editors/PenEditorViewModel.cs @@ -30,9 +30,9 @@ public PenEditorViewModel(IAbstractProperty property) private void Update(IPen? pen) { - void CreateContexts(PooledList props, CoreList dst) + static void CreateContexts(PooledList props, CoreList dst) { - CoreProperty[]? foundItems; + IAbstractProperty[]? foundItems; PropertyEditorExtension? extension; do @@ -40,15 +40,7 @@ void CreateContexts(PooledList props, CoreList).MakeGenericType(item.PropertyType); - tmp[i] = (IAbstractProperty)Activator.CreateInstance(wrapperGType, item, pen)!; - } - - if (extension.TryCreateContext(tmp, out IPropertyEditorContext? context)) + if (extension.TryCreateContext(foundItems, out IPropertyEditorContext? context)) { dst.Add(context); } @@ -60,23 +52,23 @@ void CreateContexts(PooledList props, CoreList(); - Span span = props.AddSpan(4); - span[0] = Pen.ThicknessProperty; - span[1] = Pen.StrokeCapProperty; - span[2] = Pen.StrokeAlignmentProperty; - span[3] = Pen.BrushProperty; + using var props = new PooledList(); + Span span = props.AddSpan(4); + span[0] = new AnimatableCorePropertyImpl(Pen.ThicknessProperty, mutablePen); + span[1] = new AnimatableCorePropertyImpl(Pen.StrokeCapProperty, mutablePen); + span[2] = new AnimatableCorePropertyImpl(Pen.StrokeAlignmentProperty, mutablePen); + span[3] = new AnimatableCorePropertyImpl(Pen.BrushProperty, mutablePen); CreateContexts(props, MajorProperties); props.Clear(); span = props.AddSpan(4); - span[0] = Pen.MiterLimitProperty; - span[1] = Pen.StrokeJoinProperty; - span[2] = Pen.DashArrayProperty; - span[3] = Pen.DashOffsetProperty; + span[0] = new AnimatableCorePropertyImpl(Pen.MiterLimitProperty, mutablePen); + span[1] = new AnimatableCorePropertyImpl(Pen.StrokeJoinProperty, mutablePen); + span[2] = new AnimatableCorePropertyImpl?>(Pen.DashArrayProperty, mutablePen); + span[3] = new AnimatableCorePropertyImpl(Pen.DashOffsetProperty, mutablePen); CreateContexts(props, MinorProperties); } diff --git a/src/Beutl/ViewModels/Editors/PropertiesEditorViewModel.cs b/src/Beutl/ViewModels/Editors/PropertiesEditorViewModel.cs index b29c26f6a..883fe2912 100644 --- a/src/Beutl/ViewModels/Editors/PropertiesEditorViewModel.cs +++ b/src/Beutl/ViewModels/Editors/PropertiesEditorViewModel.cs @@ -34,28 +34,25 @@ private void InitializeCoreObject(ICoreObject obj, Predicate); bool isAnimatable = obj is IAnimatable; - List props = PropertyRegistry.GetRegistered(objType).ToList(); + List cprops = PropertyRegistry.GetRegistered(objType).ToList(); + cprops.RemoveAll(x => !(predicate?.Invoke(x.GetMetadata(objType)) ?? true)); + List props = cprops.ConvertAll(x => + { + CorePropertyMetadata metadata = x.GetMetadata(objType); + Type wtype = isAnimatable ? animatableWrapperType : wrapperType; + Type wrapperGType = wtype.MakeGenericType(x.PropertyType); + return (IAbstractProperty)Activator.CreateInstance(wrapperGType, x, obj)!; + }); Properties.EnsureCapacity(props.Count); - CoreProperty[]? foundItems; + IAbstractProperty[]? foundItems; PropertyEditorExtension? extension; - props.RemoveAll(x => !(predicate?.Invoke(x.GetMetadata(objType)) ?? true)); do { (foundItems, extension) = PropertyEditorService.MatchProperty(props); if (foundItems != null && extension != null) { - var tmp = new IAbstractProperty[foundItems.Length]; - for (int i = 0; i < foundItems.Length; i++) - { - CoreProperty item = foundItems[i]; - CorePropertyMetadata metadata = item.GetMetadata(objType); - Type wtype = isAnimatable ? animatableWrapperType : wrapperType; - Type wrapperGType = wtype.MakeGenericType(item.PropertyType); - tmp[i] = (IAbstractProperty)Activator.CreateInstance(wrapperGType, item, obj)!; - } - - if (extension.TryCreateContext(tmp, out IPropertyEditorContext? context)) + if (extension.TryCreateContext(foundItems, out IPropertyEditorContext? context)) { Properties.Add(context); } diff --git a/src/Beutl/ViewModels/InlineAnimationLayerViewModel.cs b/src/Beutl/ViewModels/InlineAnimationLayerViewModel.cs index c2b55e39c..159654b0b 100644 --- a/src/Beutl/ViewModels/InlineAnimationLayerViewModel.cs +++ b/src/Beutl/ViewModels/InlineAnimationLayerViewModel.cs @@ -107,8 +107,7 @@ protected InlineAnimationLayerViewModel( // Widthプロパティを構成 Timeline.Options.Subscribe(_ => UpdateWidth()).DisposeWith(_disposables); - CorePropertyMetadata metadata = property.Property.GetMetadata(property.ImplementedType); - Header = metadata.DisplayAttribute?.GetName() ?? property.Property.Name; + Header = property.DisplayName; Close = new ReactiveCommand() .WithSubscribe(() => Timeline.DetachInline(this)) diff --git a/src/Beutl/ViewModels/NodeTree/NodeInputViewModel.cs b/src/Beutl/ViewModels/NodeTree/NodeInputViewModel.cs index 76447a6e6..a9994e5a6 100644 --- a/src/Beutl/ViewModels/NodeTree/NodeInputViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/NodeInputViewModel.cs @@ -91,11 +91,10 @@ public void Dispose() private void InitializeProperties() { - var ctmp = new CoreProperty[1]; var atmp = new IAbstractProperty[1]; foreach (INodeItem item in Node.Items) { - Properties.Add(CreatePropertyContext(ctmp, atmp, item)); + Properties.Add(CreatePropertyContext(atmp, item)); } Node.Items.CollectionChanged += OnItemsCollectionChanged; @@ -105,11 +104,10 @@ private void OnItemsCollectionChanged(object? sender, NotifyCollectionChangedEve { void Add(int index, IList items) { - var ctmp = new CoreProperty[1]; var atmp = new IAbstractProperty[1]; foreach (INodeItem item in items) { - Properties.Insert(index++, CreatePropertyContext(ctmp, atmp, item)); + Properties.Insert(index++, CreatePropertyContext(atmp, item)); } } @@ -149,7 +147,7 @@ void Remove(int index, IList items) } } - private IPropertyEditorContext? CreatePropertyContext(CoreProperty[] ctmp, IAbstractProperty[] atmp, INodeItem item) + private IPropertyEditorContext? CreatePropertyContext(IAbstractProperty[] atmp, INodeItem item) { IPropertyEditorContext? context = null; if (item is LayerInputNode.ILayerInputSocket socket) @@ -157,9 +155,8 @@ void Remove(int index, IList items) IAbstractProperty? aproperty = socket.GetProperty(); if (aproperty != null) { - ctmp[0] = aproperty.Property; atmp[0] = aproperty; - (_, PropertyEditorExtension ext) = PropertyEditorService.MatchProperty(ctmp); + (_, PropertyEditorExtension ext) = PropertyEditorService.MatchProperty(atmp); ext?.TryCreateContext(atmp, out context); context?.Accept(this); diff --git a/src/Beutl/ViewModels/NodeTree/NodeViewModel.cs b/src/Beutl/ViewModels/NodeTree/NodeViewModel.cs index 59e9bcbb0..f2faf38c3 100644 --- a/src/Beutl/ViewModels/NodeTree/NodeViewModel.cs +++ b/src/Beutl/ViewModels/NodeTree/NodeViewModel.cs @@ -109,11 +109,10 @@ public void Dispose() private void InitItems() { - var ctmp = new CoreProperty[1]; var atmp = new IAbstractProperty[1]; foreach (INodeItem item in Node.Items) { - Items.Add(CreateNodeItemViewModel(ctmp, atmp, item)); + Items.Add(CreateNodeItemViewModel(atmp, item)); } Node.Items.CollectionChanged += OnItemsCollectionChanged; @@ -123,11 +122,10 @@ private void OnItemsCollectionChanged(object? sender, NotifyCollectionChangedEve { void Add(int index, IList items) { - var ctmp = new CoreProperty[1]; var atmp = new IAbstractProperty[1]; foreach (INodeItem item in items) { - Items.Insert(index++, CreateNodeItemViewModel(ctmp, atmp, item)); + Items.Insert(index++, CreateNodeItemViewModel(atmp, item)); } } @@ -167,14 +165,13 @@ void Remove(int index, IList items) } } - private NodeItemViewModel CreateNodeItemViewModel(CoreProperty[] ctmp, IAbstractProperty[] atmp, INodeItem item) + private NodeItemViewModel CreateNodeItemViewModel(IAbstractProperty[] atmp, INodeItem item) { IPropertyEditorContext? context = null; if (item.Property is { } aproperty) { - ctmp[0] = aproperty.Property; atmp[0] = aproperty; - (_, PropertyEditorExtension ext) = PropertyEditorService.MatchProperty(ctmp); + (_, PropertyEditorExtension ext) = PropertyEditorService.MatchProperty(atmp); ext?.TryCreateContextForNode(atmp, out context); } diff --git a/src/Beutl/ViewModels/TimelineViewModel.cs b/src/Beutl/ViewModels/TimelineViewModel.cs index b2901ca07..96ca10e25 100644 --- a/src/Beutl/ViewModels/TimelineViewModel.cs +++ b/src/Beutl/ViewModels/TimelineViewModel.cs @@ -179,7 +179,7 @@ public void AttachInline(IAbstractAnimatableProperty property, Element layer) && Layers.FirstOrDefault(x => x.Model == layer) is { } viewModel) { // タイムラインのタブを開く - Type type = typeof(InlineAnimationLayerViewModel<>).MakeGenericType(property.Property.PropertyType); + Type type = typeof(InlineAnimationLayerViewModel<>).MakeGenericType(property.PropertyType); if (Activator.CreateInstance(type, property, this, viewModel) is InlineAnimationLayerViewModel anmTimelineViewModel) { Inlines.Add(anmTimelineViewModel); diff --git a/src/Beutl/ViewModels/Tools/SourceOperatorViewModel.cs b/src/Beutl/ViewModels/Tools/SourceOperatorViewModel.cs index fea6a8fad..d63ed4fbb 100644 --- a/src/Beutl/ViewModels/Tools/SourceOperatorViewModel.cs +++ b/src/Beutl/ViewModels/Tools/SourceOperatorViewModel.cs @@ -113,9 +113,9 @@ public void Dispose() private void Init() { - List props = Model.Properties.Select(x => x.Property).ToList(); + List props = Model.Properties.ToList(); Properties.EnsureCapacity(props.Count); - CoreProperty[]? foundItems; + IAbstractProperty[]? foundItems; PropertyEditorExtension? extension; do @@ -123,14 +123,7 @@ private void Init() (foundItems, extension) = PropertyEditorService.MatchProperty(props); if (foundItems != null && extension != null) { - var tmp = new IAbstractProperty[foundItems.Length]; - for (int i = 0; i < foundItems.Length; i++) - { - CoreProperty item = foundItems[i]; - tmp[i] = Model.Properties.First(x => x.Property.Id == item.Id); - } - - if (extension.TryCreateContext(tmp, out IPropertyEditorContext? context)) + if (extension.TryCreateContext(foundItems, out IPropertyEditorContext? context)) { Properties.Add(context); context.Accept(this); diff --git a/src/Beutl/ViewModels/Tools/StyleEditorViewModel.cs b/src/Beutl/ViewModels/Tools/StyleEditorViewModel.cs index 1e1c8faca..7b6794e04 100644 --- a/src/Beutl/ViewModels/Tools/StyleEditorViewModel.cs +++ b/src/Beutl/ViewModels/Tools/StyleEditorViewModel.cs @@ -60,13 +60,11 @@ public StyleEditorViewModel(EditViewModel editViewModel) Type wrapperType = typeof(StylingSetterPropertyImpl<>); wrapperType = wrapperType.MakeGenericType(item.Property.PropertyType); var wrapper = (IAbstractProperty)Activator.CreateInstance(wrapperType, item)!; - CoreProperty[] tmp1 = ArrayPool.Shared.Rent(1); - var tmp2 = new IAbstractProperty[1]; - tmp1[0] = item.Property; - tmp2[0] = wrapper; + var tmp = new IAbstractProperty[1]; + tmp[0] = wrapper; - if (PropertyEditorService.MatchProperty(tmp1) is { Extension: { } ext, Properties.Length: 1 } - && ext.TryCreateContext(tmp2, out IPropertyEditorContext? context)) + if (PropertyEditorService.MatchProperty(tmp) is { Extension: { } ext, Properties.Length: 1 } + && ext.TryCreateContext(tmp, out IPropertyEditorContext? context)) { Properties.Insert(idx, context); } @@ -75,8 +73,7 @@ public StyleEditorViewModel(EditViewModel editViewModel) Properties.Insert(idx, null); } - ArrayPool.Shared.Return(tmp1); - ArrayPool.Shared.Return(tmp2); + ArrayPool.Shared.Return(tmp); }, (idx, _) => { diff --git a/src/Beutl/Views/Editors/NavigateButton.axaml b/src/Beutl/Views/Editors/NavigateButton.axaml index 415060289..a592f7002 100644 --- a/src/Beutl/Views/Editors/NavigateButton.axaml +++ b/src/Beutl/Views/Editors/NavigateButton.axaml @@ -59,9 +59,6 @@ - diff --git a/src/Beutl/Views/Editors/NavigateButton.axaml.cs b/src/Beutl/Views/Editors/NavigateButton.axaml.cs index 5fd2440e5..c7f9316f9 100644 --- a/src/Beutl/Views/Editors/NavigateButton.axaml.cs +++ b/src/Beutl/Views/Editors/NavigateButton.axaml.cs @@ -52,11 +52,6 @@ private void New_Click(object? sender, RoutedEventArgs e) { OnNew(); } - - private void Delete_Click(object? sender, RoutedEventArgs e) - { - OnDelete(); - } } public sealed class NavigateButton : NavigateButton @@ -83,7 +78,7 @@ protected override async void OnNew() { await Task.Run(async () => { - Type type = viewModel.WrappedProperty.Property.PropertyType; + Type type = viewModel.WrappedProperty.PropertyType; Type[] types = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(x => x.GetTypes()) .Where(x => !x.IsAbstract @@ -142,24 +137,4 @@ await Task.Run(async () => //progress.IsVisible = false; } - - protected override void OnDelete() - { - if (DataContext is NavigationButtonViewModel viewModel) - { - if (this.FindLogicalAncestorOfType()?.DataContext is StyleEditorViewModel parentViewModel - && viewModel.WrappedProperty is IStylingSetterPropertyImpl wrapper - && parentViewModel.Style.Value is Style style) - { - style.Setters.BeginRecord() - .Remove(wrapper.Setter) - .ToCommand() - .DoAndRecord(CommandRecorder.Default); - } - else if (viewModel.Value.Value is T obj) - { - viewModel.SetValue(obj, default); - } - } - } } diff --git a/src/Beutl/Views/Editors/PropertyEditorMenu.axaml.cs b/src/Beutl/Views/Editors/PropertyEditorMenu.axaml.cs index 673b8a1c6..cbb80e23e 100644 --- a/src/Beutl/Views/Editors/PropertyEditorMenu.axaml.cs +++ b/src/Beutl/Views/Editors/PropertyEditorMenu.axaml.cs @@ -63,14 +63,18 @@ private void EditInlineAnimation_Click(object? sender, RoutedEventArgs e) && viewModel.GetService() is { } layer && editViewModel.FindToolTab() is { } timeline) { - if (animatableProperty.Animation is not IKeyFrameAnimation) + if (animatableProperty.Animation is not IKeyFrameAnimation + && animatableProperty.GetCoreProperty() is { } coreProp) { - Type type = typeof(KeyFrameAnimation<>).MakeGenericType(animatableProperty.Property.PropertyType); - animatableProperty.Animation = Activator.CreateInstance(type, animatableProperty.Property) as IAnimation; + Type type = typeof(KeyFrameAnimation<>).MakeGenericType(animatableProperty.PropertyType); + animatableProperty.Animation = Activator.CreateInstance(type, coreProp) as IAnimation; } - // タイムラインのタブを開く - timeline.AttachInline(animatableProperty, layer); + if (animatableProperty.Animation is IKeyFrameAnimation) + { + // タイムラインのタブを開く + timeline.AttachInline(animatableProperty, layer); + } } } } From 0b3ef1ddb809ae75d050bd1e6f098bfa6167d66c Mon Sep 17 00:00:00 2001 From: indigo-san Date: Sat, 10 Jun 2023 13:38:47 +0900 Subject: [PATCH 53/84] ListEditor --- .../PropertyEditors/BooleanEditor.cs | 10 +- .../PropertyEditors/PropertyEditor.cs | 71 ++++- src/Beutl.Controls/Styles.axaml | 5 + .../PropertyEditors/AlignmentXEditor.axaml | 88 +++++- .../PropertyEditors/BooleanEditor.axaml | 32 ++ .../Styling/PropertyEditors/ColorEditor.axaml | 40 ++- .../PropertyEditors/CornerRadiusEditor.axaml | 80 +++++ .../Styling/PropertyEditors/EnumEditor.axaml | 34 ++ .../PropertyEditors/FontFamilyEditor.axaml | 37 ++- .../MiniExpanderToggleButtonBehavior.cs | 46 +++ .../PropertyEditorResources.axaml | 128 +++++++- .../PropertyEditors/StorageFileEditor.axaml | 41 ++- .../PropertyEditors/StringEditor.axaml | 32 ++ .../PropertyEditors/ThicknessEditor.axaml | 80 +++++ .../PropertyEditors/Vector2Editor.axaml | 68 ++++ .../PropertyEditors/Vector3Editor.axaml | 85 +++++ .../PropertyEditors/Vector4Editor.axaml | 104 +++++++ src/Beutl.Framework/IListItemEditor.cs | 12 + .../PropertyEditorExtension.cs | 20 ++ src/Beutl/Services/PropertyEditorService.cs | 202 +++++++----- .../ViewModels/Editors/ListEditorViewModel.cs | 254 +++++++++++++-- src/Beutl/Views/Editors/BrushEditor.axaml | 95 +++--- src/Beutl/Views/Editors/ListEditor.axaml | 294 +++--------------- src/Beutl/Views/Editors/ListEditor.axaml.cs | 241 +++++++------- src/Beutl/Views/Editors/ListItemEditor.axaml | 54 ++++ .../Views/Editors/ListItemEditor.axaml.cs | 30 ++ src/Beutl/Views/Editors/PenEditor.axaml | 59 ++-- src/Beutl/Views/NodeTree/SocketView.axaml.cs | 2 +- 28 files changed, 1673 insertions(+), 571 deletions(-) create mode 100644 src/Beutl.Controls/Styling/PropertyEditors/MiniExpanderToggleButtonBehavior.cs create mode 100644 src/Beutl.Framework/IListItemEditor.cs create mode 100644 src/Beutl/Views/Editors/ListItemEditor.axaml create mode 100644 src/Beutl/Views/Editors/ListItemEditor.axaml.cs diff --git a/src/Beutl.Controls/PropertyEditors/BooleanEditor.cs b/src/Beutl.Controls/PropertyEditors/BooleanEditor.cs index 791d980ba..e90e30db9 100644 --- a/src/Beutl.Controls/PropertyEditors/BooleanEditor.cs +++ b/src/Beutl.Controls/PropertyEditors/BooleanEditor.cs @@ -32,16 +32,16 @@ public bool Value protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); - CheckBox checkBox = e.NameScope.Get("PART_CheckBox"); - checkBox.Click += OnCheckBoxClick; + ToggleButton toggleButton = e.NameScope.Get("PART_CheckBox"); + toggleButton.Click += OnCheckBoxClick; } private void OnCheckBoxClick(object sender, RoutedEventArgs e) { - if (sender is CheckBox checkBox - && checkBox.IsChecked.HasValue) + if (sender is ToggleButton toggleButton + && toggleButton.IsChecked.HasValue) { - bool value = checkBox.IsChecked.Value; + bool value = toggleButton.IsChecked.Value; RaiseEvent(new PropertyEditorValueChangedEventArgs(value, !value, ValueChangingEvent)); Value = value; RaiseEvent(new PropertyEditorValueChangedEventArgs(value, !value, ValueChangedEvent)); diff --git a/src/Beutl.Controls/PropertyEditors/PropertyEditor.cs b/src/Beutl.Controls/PropertyEditors/PropertyEditor.cs index 8ea52a535..597682388 100644 --- a/src/Beutl.Controls/PropertyEditors/PropertyEditor.cs +++ b/src/Beutl.Controls/PropertyEditors/PropertyEditor.cs @@ -12,10 +12,19 @@ namespace Beutl.Controls.PropertyEditors; -[PseudoClasses(":compact", ":visible-left-button", ":visible-right-button")] +public enum PropertyEditorStyle +{ + Normal, + Compact, + ListItem +} + +[PseudoClasses(":compact", ":list-item", ":visible-left-button", ":visible-right-button")] [TemplatePart("PART_LeftButton", typeof(Button))] [TemplatePart("PART_RightButton", typeof(Button))] -public class PropertyEditor : TemplatedControl, IPropertyEditorContextVisitor +[TemplatePart("PART_DeleteButton", typeof(Button))] +[TemplatePart("PART_ReorderHandle", typeof(Control))] +public class PropertyEditor : TemplatedControl, IPropertyEditorContextVisitor, IListItemEditor { public static readonly StyledProperty HeaderProperty = AvaloniaProperty.Register(nameof(Header)); @@ -26,6 +35,9 @@ public class PropertyEditor : TemplatedControl, IPropertyEditorContextVisitor public static readonly StyledProperty UseCompactProperty = AvaloniaProperty.Register(nameof(UseCompact), false); + public static readonly StyledProperty EditorStyleProperty = + AvaloniaProperty.Register(nameof(EditorStyle), PropertyEditorStyle.Normal); + public static readonly StyledProperty MenuContentProperty = AvaloniaProperty.Register(nameof(MenuContent)); @@ -38,13 +50,16 @@ public class PropertyEditor : TemplatedControl, IPropertyEditorContextVisitor public static readonly StyledProperty KeyFrameCountProperty = AvaloniaProperty.Register(nameof(KeyFrameCount), 0, coerce: (_, v) => Math.Max(v, 0)); + public static readonly StyledProperty ReorderHandleProperty = + AvaloniaProperty.Register(nameof(ReorderHandle), null); + public static readonly RoutedEvent ValueChangingEvent = RoutedEvent.Register(nameof(ValueChanging), RoutingStrategies.Bubble); public static readonly RoutedEvent ValueChangedEvent = RoutedEvent.Register(nameof(ValueChanged), RoutingStrategies.Bubble); - private readonly CompositeDisposable _eventRevokers = new(2); + private readonly CompositeDisposable _eventRevokers = new(3); public string Header { @@ -58,10 +73,16 @@ public bool IsReadOnly set => SetValue(IsReadOnlyProperty, value); } + public PropertyEditorStyle EditorStyle + { + get => GetValue(EditorStyleProperty); + set => SetValue(EditorStyleProperty, value); + } + public bool UseCompact { get => GetValue(UseCompactProperty); - set => SetValue(UseCompactProperty, value); + private set => SetValue(UseCompactProperty, value); } public object MenuContent @@ -88,6 +109,14 @@ public int KeyFrameCount set => SetValue(KeyFrameCountProperty, value); } + public Control ReorderHandle + { + get => GetValue(ReorderHandleProperty); + private set => SetValue(ReorderHandleProperty, value); + } + + public event EventHandler DeleteRequested; + public event EventHandler ValueChanging { add => AddHandler(ValueChangingEvent, value); @@ -104,16 +133,29 @@ public virtual void Visit(IPropertyEditorContext context) { } + private void UpdateStyle() + { + PseudoClasses.Remove(":compact"); + PseudoClasses.Remove(":list-item"); + switch (EditorStyle) + { + case PropertyEditorStyle.Compact: + PseudoClasses.Add(":compact"); + break; + + case PropertyEditorStyle.ListItem: + PseudoClasses.Add(":list-item"); + break; + } + } + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); - if (change.Property == UseCompactProperty) + if (change.Property == EditorStyleProperty) { - PseudoClasses.Remove(":compact"); - if (UseCompact) - { - PseudoClasses.Add(":compact"); - } + UseCompact = EditorStyle == PropertyEditorStyle.Compact; + UpdateStyle(); } else if (change.Property == MenuContentProperty) { @@ -150,6 +192,15 @@ protected override void OnApplyTemplate(TemplateAppliedEventArgs e) leftButton.AddDisposableHandler(Button.ClickEvent, OnLeftButtonClick).DisposeWith(_eventRevokers); rightButton.AddDisposableHandler(Button.ClickEvent, OnRightButtonClick).DisposeWith(_eventRevokers); } + + ReorderHandle = e.NameScope.Find("PART_ReorderHandle"); + Button deleteButton = e.NameScope.Find + + + + + 200 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Beutl.Controls/Styling/PropertyEditors/StorageFileEditor.axaml b/src/Beutl.Controls/Styling/PropertyEditors/StorageFileEditor.axaml index d58a54f1e..d2ff52216 100644 --- a/src/Beutl.Controls/Styling/PropertyEditors/StorageFileEditor.axaml +++ b/src/Beutl.Controls/Styling/PropertyEditors/StorageFileEditor.axaml @@ -1,10 +1,12 @@ + + Text="{TemplateBinding Header}" + TextTrimming="CharacterEllipsis" /> + + + diff --git a/src/Beutl.Controls/Styling/PropertyEditors/ThicknessEditor.axaml b/src/Beutl.Controls/Styling/PropertyEditors/ThicknessEditor.axaml index 02099b100..74c27d3e3 100644 --- a/src/Beutl.Controls/Styling/PropertyEditors/ThicknessEditor.axaml +++ b/src/Beutl.Controls/Styling/PropertyEditors/ThicknessEditor.axaml @@ -6,6 +6,9 @@ + @@ -99,6 +102,83 @@ + + diff --git a/src/Beutl.Controls/Styling/PropertyEditors/Vector2Editor.axaml b/src/Beutl.Controls/Styling/PropertyEditors/Vector2Editor.axaml index 34f46f9e1..aaa31e00c 100644 --- a/src/Beutl.Controls/Styling/PropertyEditors/Vector2Editor.axaml +++ b/src/Beutl.Controls/Styling/PropertyEditors/Vector2Editor.axaml @@ -1,10 +1,12 @@ + @@ -189,6 +191,72 @@ + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - - + + + + + + + - - - - - - - - - - - - - - - + diff --git a/src/Beutl/Views/Editors/ListEditor.axaml.cs b/src/Beutl/Views/Editors/ListEditor.axaml.cs index 5c6454922..0f47c53f1 100644 --- a/src/Beutl/Views/Editors/ListEditor.axaml.cs +++ b/src/Beutl/Views/Editors/ListEditor.axaml.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Reflection; +using System.Runtime.CompilerServices; using Avalonia; using Avalonia.Animation; @@ -11,27 +12,37 @@ using Beutl.Commands; using Beutl.Controls.Behaviors; +using Beutl.Framework.Service; using Beutl.ViewModels; using Beutl.ViewModels.Editors; using Beutl.ViewModels.Tools; using FluentAvalonia.UI.Controls; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Extensions.DependencyInjection; + namespace Beutl.Views.Editors; public sealed class ListEditorDragBehavior : GenericDragBehavior { protected override void OnMoveDraggedItem(ItemsControl? itemsControl, int oldIndex, int newIndex) { - if (itemsControl?.Items is not IList items) - { - return; - } - - items.BeginRecord() - .Move(oldIndex, newIndex) - .ToCommand() - .DoAndRecord(CommandRecorder.Default); + //if (itemsControl?.Items is not IList items) + //{ + // return; + //} + + //items.BeginRecord() + // .Move(oldIndex, newIndex) + // .ToCommand() + // .DoAndRecord(CommandRecorder.Default); + + // Todo: ListEditorItemの移動処理 + //if(itemsControl?.DataContext is ListEditorViewModel viewModel) + //{ + // viewModel.MoveItem(oldIndex, newIndex); + //} } } @@ -43,7 +54,7 @@ public partial class ListEditor : UserControl public ListEditor() { InitializeComponent(); - toggle.GetObservable(ToggleButton.IsCheckedProperty) + expandToggle.GetObservable(ToggleButton.IsCheckedProperty) .Subscribe(async value => { _lastTransitionCts?.Cancel(); @@ -62,39 +73,117 @@ public ListEditor() }); } + private void InitializeClick(object? sender, RoutedEventArgs e) + { + OnInitializeClick(); + } + + private void DeleteClick(object? sender, RoutedEventArgs e) + { + OnDeleteClick(); + } + private async void Add_Click(object? sender, RoutedEventArgs e) { - progress.IsVisible = progress.IsIndeterminate = true; - if (DataContext is ListEditorViewModel viewModel && viewModel.List.Value != null) + await OnAddClick(sender); + } + + protected virtual void OnInitializeClick() + { + } + + protected virtual void OnDeleteClick() + { + } + + protected virtual ValueTask OnAddClick(object? sender) + { + return ValueTask.CompletedTask; + } +} + +public sealed class ListEditor : ListEditor +{ + protected override void OnInitializeClick() + { + if (DataContext is ListEditorViewModel viewModel) + { + try + { + viewModel.Initialize(); + } + catch (InvalidOperationException ex) + { + ServiceLocator.Current.GetRequiredService() + .Show(new("Error", ex.Message, NotificationType.Error)); + } + } + } + + protected override void OnDeleteClick() + { + if (DataContext is ListEditorViewModel viewModel) + { + try + { + viewModel.Delete(); + } + catch (InvalidOperationException ex) + { + ServiceLocator.Current.GetRequiredService() + .Show(new("Error", ex.Message, NotificationType.Error)); + } + } + } + + protected override async ValueTask OnAddClick(object? sender) + { + if (DataContext is ListEditorViewModel viewModel) { - await Task.Run(async () => + if (viewModel.List.Value == null && sender is Button btn) { - Type type = viewModel.WrappedProperty.Property.PropertyType; - Type? interfaceType = Array.Find(type.GetInterfaces(), x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IList<>)); - Type? itemtype = interfaceType?.GenericTypeArguments?.FirstOrDefault(); - if (itemtype != null) + btn.ContextFlyout?.ShowAt(btn); + } + else if (viewModel.List.Value != null) + { + progress.IsVisible = progress.IsIndeterminate = true; + + await Task.Run(async () => { - Type[] types = AppDomain.CurrentDomain.GetAssemblies() - .SelectMany(x => x.GetTypes()) - .Where(x => !x.IsAbstract - && x.IsPublic - && x.IsAssignableTo(itemtype) - && x.GetConstructor(Array.Empty()) != null) - .ToArray(); - Type? type2 = null; - ConstructorInfo? constructorInfo = null; - - if (types.Length == 1) + Type itemType = typeof(TItem); + Type[]? availableTypes = null; + + if (itemType.IsSealed + && (itemType.GetConstructor(Array.Empty()) != null + || itemType.GetConstructors().Length == 0)) { - type2 = types[0]; + availableTypes = new[] { itemType }; } - else if (types.Length > 1) + else { - type2 = await Dispatcher.UIThread.InvokeAsync(async () => + availableTypes = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(x => x.GetTypes()) + .Where(x => !x.IsAbstract + && x.IsPublic + && x.IsAssignableTo(itemType) + && (itemType.GetConstructor(Array.Empty()) != null + || itemType.GetConstructors().Length == 0)) + .ToArray(); + } + + Type? selectedType = null; + + if (availableTypes.Length == 1) + { + selectedType = availableTypes[0]; + } + else if (availableTypes.Length > 1) + { + selectedType = await Dispatcher.UIThread.InvokeAsync(async () => { var combobox = new ComboBox { - Items = types, + Items = availableTypes, SelectedIndex = 0 }; @@ -116,93 +205,19 @@ await Task.Run(async () => } }); } - else if (itemtype.IsSealed) - { - type2 = itemtype; - } - - constructorInfo = type2?.GetConstructor(Array.Empty()); - if (constructorInfo != null) + if (selectedType != null && Activator.CreateInstance(selectedType) is TItem item) { - object? obj = constructorInfo.Invoke(null); - if (obj != null) - { - await Dispatcher.UIThread.InvokeAsync(() => viewModel.List.Value.Add(obj)); - } + viewModel.AddItem(item); } - } - }); - - // ListがINotifyProeprtyChangedを実装していない可能性があるので - if (viewModel.ObserveCount.Value != viewModel.List.Value.Count) - { - viewModel.ObserveCount.Value = viewModel.List.Value.Count; - } - } - - progress.IsVisible = progress.IsIndeterminate = false; - } - - private void Menu_Click(object? sender, RoutedEventArgs e) - { - if (sender is Button button) - { - button.ContextMenu?.Open(); - } - } - - private void Edit_Click(object? sender, RoutedEventArgs e) - { - if (this.FindLogicalAncestorOfType()?.DataContext is EditViewModel editViewModel - && sender is ILogical logical - && DataContext is ListEditorViewModel { List.Value: { } list } viewModel) - { - ObjectPropertyEditorViewModel objViewModel - = editViewModel.FindToolTab() - ?? new ObjectPropertyEditorViewModel(editViewModel); - - if (logical.FindLogicalAncestorOfType() is { } grid) - { - int index = items.ItemContainerGenerator.IndexFromContainer(grid.Parent); - - if (index >= 0) - { - switch (list[index]) + else { - case CoreObject coreObject: - objViewModel.NavigateCore(coreObject, false); - break; - case Styling.Style style: - StyleEditorViewModel styleEditor - = objViewModel.ParentContext.FindToolTab() - ?? new StyleEditorViewModel(editViewModel); - - styleEditor.Style.Value = style; - editViewModel.OpenToolTab(styleEditor); - break; + ServiceLocator.Current.GetRequiredService() + .Show(new("Error", "ListEditor.OnAddClick", NotificationType.Error)); } - } - } - - editViewModel.OpenToolTab(objViewModel); - } - } + }); - private void Delete_Click(object? sender, RoutedEventArgs e) - { - if (sender is MenuItem menuItem - && DataContext is ListEditorViewModel { List.Value: { } list } viewModel - && menuItem.FindLogicalAncestorOfType() is { } grid) - { - int index = items.ItemContainerGenerator.IndexFromContainer(grid.Parent); - - if (index >= 0) - { - list.BeginRecord() - .Remove(list[index]) - .ToCommand() - .DoAndRecord(CommandRecorder.Default); + progress.IsVisible = progress.IsIndeterminate = false; } } } diff --git a/src/Beutl/Views/Editors/ListItemEditor.axaml b/src/Beutl/Views/Editors/ListItemEditor.axaml new file mode 100644 index 000000000..d21cb5444 --- /dev/null +++ b/src/Beutl/Views/Editors/ListItemEditor.axaml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/Beutl/Views/Editors/ListItemEditor.axaml.cs b/src/Beutl/Views/Editors/ListItemEditor.axaml.cs new file mode 100644 index 000000000..b2480d92a --- /dev/null +++ b/src/Beutl/Views/Editors/ListItemEditor.axaml.cs @@ -0,0 +1,30 @@ +using Avalonia.Controls; +using Avalonia.Input; +using Avalonia.Interactivity; + +namespace Beutl.Views.Editors; + +public partial class ListItemEditor : UserControl +{ + public ListItemEditor() + { + InitializeComponent(); + } + + private void Menu_Click(object? sender, RoutedEventArgs e) + { + if(sender is Button btn) + { + btn.ContextMenu?.Open(); + } + } + + private void Edit_Click(object? sender, RoutedEventArgs e) + { + } + + private void Delete_Click(object? sender, RoutedEventArgs e) + { + + } +} diff --git a/src/Beutl/Views/Editors/PenEditor.axaml b/src/Beutl/Views/Editors/PenEditor.axaml index 1438f0c9f..9fac22e3c 100644 --- a/src/Beutl/Views/Editors/PenEditor.axaml +++ b/src/Beutl/Views/Editors/PenEditor.axaml @@ -19,42 +19,29 @@ - - - - - - - - + + + + + Date: Sun, 11 Jun 2023 01:36:56 +0900 Subject: [PATCH 54/84] ListEditor --- src/Beutl.Core/CollectionBatchExtension.cs | 16 ++++ src/Beutl.Core/Commands/RemoveCommand.cs | 8 +- src/Beutl.Core/Commands/RemoveCommand{T}.cs | 8 +- src/Beutl.Graphics/Graphics/Shapes/Shape.cs | 7 ++ src/Beutl/Services/PropertyEditorService.cs | 5 + .../Editors/BrushEditorViewModel.cs | 8 +- .../ViewModels/Editors/ListEditorViewModel.cs | 59 +++++++++--- .../ViewModels/Editors/PenEditorViewModel.cs | 9 +- src/Beutl/Views/Editors/ListEditor.axaml | 3 +- src/Beutl/Views/Editors/ListEditor.axaml.cs | 30 +----- src/Beutl/Views/Editors/ListItemEditor.axaml | 44 +-------- .../Views/Editors/ListItemEditor.axaml.cs | 91 +++++++++++++++++-- 12 files changed, 184 insertions(+), 104 deletions(-) diff --git a/src/Beutl.Core/CollectionBatchExtension.cs b/src/Beutl.Core/CollectionBatchExtension.cs index 9c2e41f0e..0f615613a 100644 --- a/src/Beutl.Core/CollectionBatchExtension.cs +++ b/src/Beutl.Core/CollectionBatchExtension.cs @@ -25,6 +25,8 @@ public interface ICollectionBatchChanges ICollectionBatchChanges Move(int oldIndex, int newIndex); ICollectionBatchChanges Remove(object? item); + + ICollectionBatchChanges RemoveAt(int index); ICollectionBatchChanges Clear(); @@ -41,6 +43,8 @@ public interface ICollectionBatchChanges ICollectionBatchChanges Remove(T item); + ICollectionBatchChanges RemoveAt(int index); + ICollectionBatchChanges Clear(); IRecordableCommand ToCommand(); @@ -86,6 +90,12 @@ public ICollectionBatchChanges Remove(object? item) return this; } + public ICollectionBatchChanges RemoveAt(int index) + { + _commands.Add(new RemoveCommand(_list, index)); + return this; + } + public IRecordableCommand ToCommand() { return new Command(_commands.ToArray()); @@ -132,6 +142,12 @@ public ICollectionBatchChanges Remove(T item) return this; } + public ICollectionBatchChanges RemoveAt(int index) + { + _commands.Add(new RemoveCommand(_list, index)); + return this; + } + public IRecordableCommand ToCommand() { return new Command(_commands.ToArray()); diff --git a/src/Beutl.Core/Commands/RemoveCommand.cs b/src/Beutl.Core/Commands/RemoveCommand.cs index 91b153b00..ae7edda0e 100644 --- a/src/Beutl.Core/Commands/RemoveCommand.cs +++ b/src/Beutl.Core/Commands/RemoveCommand.cs @@ -10,6 +10,13 @@ public RemoveCommand(IList list, object? item) Item = item; Index = list.IndexOf(Item); } + + public RemoveCommand(IList list, int index) + { + List = list; + Index = index; + Item = list[index]; + } public IList List { get; } @@ -19,7 +26,6 @@ public RemoveCommand(IList list, object? item) public void Do() { - Index = List.IndexOf(Item); List.RemoveAt(Index); } diff --git a/src/Beutl.Core/Commands/RemoveCommand{T}.cs b/src/Beutl.Core/Commands/RemoveCommand{T}.cs index 534b014e9..0bb7b7b3f 100644 --- a/src/Beutl.Core/Commands/RemoveCommand{T}.cs +++ b/src/Beutl.Core/Commands/RemoveCommand{T}.cs @@ -9,6 +9,13 @@ public RemoveCommand(IList list, T item) Index = list.IndexOf(Item); } + public RemoveCommand(IList list, int index) + { + List = list; + Index = index; + Item = list[index]; + } + public IList List { get; } public T Item { get; } @@ -17,7 +24,6 @@ public RemoveCommand(IList list, T item) public void Do() { - Index = List.IndexOf(Item); List.RemoveAt(Index); } diff --git a/src/Beutl.Graphics/Graphics/Shapes/Shape.cs b/src/Beutl.Graphics/Graphics/Shapes/Shape.cs index cc2f5cd78..e8af352e9 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/Shape.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/Shape.cs @@ -1,5 +1,6 @@ using System.ComponentModel.DataAnnotations; +using Beutl.Animation; using Beutl.Language; using Beutl.Media; @@ -249,4 +250,10 @@ protected override void OnDraw(ICanvas canvas) canvas.DrawGeometry(geometry); } } + + public override void ApplyAnimations(IClock clock) + { + base.ApplyAnimations(clock); + (Pen as IAnimatable)?.ApplyAnimations(clock); + } } diff --git a/src/Beutl/Services/PropertyEditorService.cs b/src/Beutl/Services/PropertyEditorService.cs index 8d3cdac17..c47938b38 100644 --- a/src/Beutl/Services/PropertyEditorService.cs +++ b/src/Beutl/Services/PropertyEditorService.cs @@ -391,6 +391,11 @@ public bool TryCreateControlForListItem(IPropertyEditorContext context, [NotNull { context.Accept(visitor); } + + if (control is PropertyEditor pe) + { + pe.EditorStyle = PropertyEditorStyle.ListItem; + } } } diff --git a/src/Beutl/ViewModels/Editors/BrushEditorViewModel.cs b/src/Beutl/ViewModels/Editors/BrushEditorViewModel.cs index 1bcb67226..75d1bf779 100644 --- a/src/Beutl/ViewModels/Editors/BrushEditorViewModel.cs +++ b/src/Beutl/ViewModels/Editors/BrushEditorViewModel.cs @@ -37,8 +37,6 @@ public void Undo() public sealed class BrushEditorViewModel : BaseEditorViewModel { - private bool _accepted; - public BrushEditorViewModel(IAbstractProperty property) : base(property) { @@ -72,8 +70,6 @@ public BrushEditorViewModel(IAbstractProperty property) private void AcceptChildren(PropertiesEditorViewModel? obj) { - _accepted = false; - if (obj != null) { var visitor = new Visitor(this); @@ -81,8 +77,6 @@ private void AcceptChildren(PropertiesEditorViewModel? obj) { item.Accept(visitor); } - - _accepted = true; } } @@ -119,7 +113,7 @@ public void SetValue(IBrush? oldValue, IBrush? newValue) public override void Accept(IPropertyEditorContextVisitor visitor) { base.Accept(visitor); - if (visitor is IServiceProvider && !_accepted) + if (visitor is IServiceProvider) { AcceptChildren(ChildContext.Value); } diff --git a/src/Beutl/ViewModels/Editors/ListEditorViewModel.cs b/src/Beutl/ViewModels/Editors/ListEditorViewModel.cs index c1719b07f..89be9e359 100644 --- a/src/Beutl/ViewModels/Editors/ListEditorViewModel.cs +++ b/src/Beutl/ViewModels/Editors/ListEditorViewModel.cs @@ -56,7 +56,17 @@ public void OnItemChanged(T? value) } } -public sealed class ListItemEditorViewModel : IDisposable +public interface IListItemEditorViewModel +{ + void OnDeleteRequested(); +} + +public interface IListEditorViewModel +{ + void MoveItem(int oldIndex, int newIndex); +} + +public sealed class ListItemEditorViewModel : IDisposable, IListItemEditorViewModel { public ListItemEditorViewModel(ListEditorViewModel parent, ListItemAccessorImpl itemAccessor) { @@ -81,9 +91,14 @@ public void Dispose() { Context?.Dispose(); } + + public void OnDeleteRequested() + { + Parent.RemoveItem(ItemAccessor.Index); + } } -public sealed class ListEditorViewModel : BaseEditorViewModel +public sealed class ListEditorViewModel : BaseEditorViewModel, IListEditorViewModel { private static readonly NotifyCollectionChangedEventArgs s_resetCollectionChanged = new(NotifyCollectionChangedAction.Reset); private INotifyCollectionChanged? _incc; @@ -120,7 +135,7 @@ public ListEditorViewModel(IAbstractProperty property) _incc = incc; _incc.CollectionChanged += OnCollectionChanged; - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, changedItems: list.ToArray())); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, changedItems: list.ToArray(), startingIndex: 0)); } }).DisposeWith(Disposables); } @@ -178,9 +193,11 @@ void UpdateIndex(int start) break; case NotifyCollectionChangedAction.Replace: - for (int i = e.NewStartingIndex; i < e.NewItems!.Count; i++) + index = e.NewStartingIndex; + for (int i = 0; i < e.NewItems!.Count; i++) { - Items[i].ItemAccessor.OnItemChanged(List.Value![i]); + Items[index].ItemAccessor.OnItemChanged(List.Value![index]); + index++; } break; @@ -242,14 +259,6 @@ public void Delete() } } - public void AddItem(TItem? item) - { - List.Value!.BeginRecord() - .Add(item) - .ToCommand() - .DoAndRecord(CommandRecorder.Default); - } - public override void Reset() { try @@ -260,4 +269,28 @@ public override void Reset() { } } + + public void AddItem(TItem? item) + { + List.Value!.BeginRecord() + .Add(item) + .ToCommand() + .DoAndRecord(CommandRecorder.Default); + } + + public void RemoveItem(int index) + { + List.Value!.BeginRecord() + .RemoveAt(index) + .ToCommand() + .DoAndRecord(CommandRecorder.Default); + } + + public void MoveItem(int oldIndex, int newIndex) + { + List.Value!.BeginRecord() + .Move(oldIndex, newIndex) + .ToCommand() + .DoAndRecord(CommandRecorder.Default); + } } diff --git a/src/Beutl/ViewModels/Editors/PenEditorViewModel.cs b/src/Beutl/ViewModels/Editors/PenEditorViewModel.cs index 110475d39..5544729d5 100644 --- a/src/Beutl/ViewModels/Editors/PenEditorViewModel.cs +++ b/src/Beutl/ViewModels/Editors/PenEditorViewModel.cs @@ -14,8 +14,6 @@ namespace Beutl.ViewModels.Editors; public sealed class PenEditorViewModel : BaseEditorViewModel { - private bool _accepted; - public PenEditorViewModel(IAbstractProperty property) : base(property) { @@ -78,8 +76,6 @@ static void CreateContexts(PooledList props, CoreList + Items="{Binding Items}"> diff --git a/src/Beutl/Views/Editors/ListEditor.axaml.cs b/src/Beutl/Views/Editors/ListEditor.axaml.cs index 0f47c53f1..f2a93a6b6 100644 --- a/src/Beutl/Views/Editors/ListEditor.axaml.cs +++ b/src/Beutl/Views/Editors/ListEditor.axaml.cs @@ -1,25 +1,16 @@ -using System.Collections; -using System.Reflection; -using System.Runtime.CompilerServices; - -using Avalonia; +using Avalonia; using Avalonia.Animation; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Interactivity; -using Avalonia.LogicalTree; using Avalonia.Threading; -using Beutl.Commands; using Beutl.Controls.Behaviors; using Beutl.Framework.Service; -using Beutl.ViewModels; using Beutl.ViewModels.Editors; -using Beutl.ViewModels.Tools; using FluentAvalonia.UI.Controls; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.Extensions.DependencyInjection; namespace Beutl.Views.Editors; @@ -28,21 +19,10 @@ public sealed class ListEditorDragBehavior : GenericDragBehavior { protected override void OnMoveDraggedItem(ItemsControl? itemsControl, int oldIndex, int newIndex) { - //if (itemsControl?.Items is not IList items) - //{ - // return; - //} - - //items.BeginRecord() - // .Move(oldIndex, newIndex) - // .ToCommand() - // .DoAndRecord(CommandRecorder.Default); - - // Todo: ListEditorItemの移動処理 - //if(itemsControl?.DataContext is ListEditorViewModel viewModel) - //{ - // viewModel.MoveItem(oldIndex, newIndex); - //} + if (itemsControl?.DataContext is IListEditorViewModel viewModel) + { + viewModel.MoveItem(oldIndex, newIndex); + } } } diff --git a/src/Beutl/Views/Editors/ListItemEditor.axaml b/src/Beutl/Views/Editors/ListItemEditor.axaml index d21cb5444..d3f318c9a 100644 --- a/src/Beutl/Views/Editors/ListItemEditor.axaml +++ b/src/Beutl/Views/Editors/ListItemEditor.axaml @@ -10,45 +10,5 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignHeight="450" d:DesignWidth="800" - mc:Ignorable="d"> - - - - - - - - - - - - - - - - - - + Content="{Binding Context, Converter={StaticResource ViewModelToViewConverter}}" + mc:Ignorable="d" /> diff --git a/src/Beutl/Views/Editors/ListItemEditor.axaml.cs b/src/Beutl/Views/Editors/ListItemEditor.axaml.cs index b2480d92a..d5d034e75 100644 --- a/src/Beutl/Views/Editors/ListItemEditor.axaml.cs +++ b/src/Beutl/Views/Editors/ListItemEditor.axaml.cs @@ -1,30 +1,109 @@ -using Avalonia.Controls; +using System.ComponentModel; + +using Avalonia; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Data.Converters; using Avalonia.Input; using Avalonia.Interactivity; +using Avalonia.Layout; +using Avalonia.Xaml.Interactivity; + +using Beutl.Framework; +using Beutl.ViewModels.Editors; namespace Beutl.Views.Editors; public partial class ListItemEditor : UserControl { + private readonly ListEditorDragBehavior _behavior = new() + { + Orientation = Orientation.Vertical + }; + public ListItemEditor() { + Resources["ViewModelToViewConverter"] = ViewModelToViewConverter.Instance; InitializeComponent(); } - private void Menu_Click(object? sender, RoutedEventArgs e) + public sealed class ViewModelToViewConverter : IValueConverter { - if(sender is Button btn) + public static readonly ViewModelToViewConverter Instance = new(); + + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) { - btn.ContextMenu?.Open(); + if (value is IPropertyEditorContext viewModel) + { + if (viewModel.Extension.TryCreateControlForListItem(viewModel, out IListItemEditor? control)) + { + return control; + } + else + { + return new Label + { + Height = 24, + Margin = new Thickness(0, 4), + Content = viewModel.Extension.DisplayName + }; + } + } + else + { + return BindingNotification.Null; + } + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return BindingNotification.Null; } } - private void Edit_Click(object? sender, RoutedEventArgs e) + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { + base.OnPropertyChanged(change); + if (change.Property == ContentProperty) + { + BehaviorCollection behaviors = Interaction.GetBehaviors(this); + behaviors.Clear(); + if (change.NewValue is IListItemEditor newControl) + { + newControl.DeleteRequested += OnDeleteRequested; + newControl.PropertyChanged += OnInnerPropertyChanged; + if (newControl.ReorderHandle != null) + { + _behavior.DragControl = newControl.ReorderHandle; + behaviors.Add(_behavior); + } + } + + if (change.OldValue is IListItemEditor oldControl) + { + oldControl.DeleteRequested -= OnDeleteRequested; + oldControl.PropertyChanged -= OnInnerPropertyChanged; + } + } } - private void Delete_Click(object? sender, RoutedEventArgs e) + private void OnInnerPropertyChanged(object? sender, PropertyChangedEventArgs e) { + if (e.PropertyName is nameof(IListItemEditor.ReorderHandle) + && sender is IListItemEditor typedSender) + { + BehaviorCollection behaviors = Interaction.GetBehaviors(this); + behaviors.Clear(); + _behavior.DragControl = typedSender.ReorderHandle!; + behaviors.Add(_behavior); + } + } + private void OnDeleteRequested(object? sender, EventArgs e) + { + if (DataContext is IListItemEditorViewModel viewModel) + { + viewModel.OnDeleteRequested(); + } } } From 0649da9d5e07f84480aa8fd916e5ea9fbf4dfbd8 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Sat, 17 Jun 2023 13:08:56 +0900 Subject: [PATCH 55/84] ImageFilterEditor --- src/Beutl.Graphics/Graphics/Drawable.cs | 4 +- .../Source/DrawablePublishOperator.cs | 2 +- src/Beutl.Operators/Source/RectOperator.cs | 3 + src/Beutl/Services/PropertyEditorService.cs | 3 + .../Editors/ImageFilterEditorViewModel.cs | 147 +++++++++ .../ViewModels/Editors/ListEditorViewModel.cs | 64 ++-- .../Editors/PropertiesEditorViewModel.cs | 18 +- src/Beutl/Views/Editors/BrushEditor.axaml | 6 +- .../Views/Editors/ImageFilterEditor.axaml | 88 ++++++ .../Views/Editors/ImageFilterEditor.axaml.cs | 138 +++++++++ .../Editors/ImageFilterListItemEditor.axaml | 61 ++++ .../ImageFilterListItemEditor.axaml.cs | 45 +++ src/Beutl/Views/Editors/ListEditor.axaml | 6 +- src/Beutl/Views/Editors/ListEditor.axaml.cs | 290 +++++++++++++++++- .../Views/Editors/ListItemEditor.axaml.cs | 11 +- 15 files changed, 831 insertions(+), 55 deletions(-) create mode 100644 src/Beutl/ViewModels/Editors/ImageFilterEditorViewModel.cs create mode 100644 src/Beutl/Views/Editors/ImageFilterEditor.axaml create mode 100644 src/Beutl/Views/Editors/ImageFilterEditor.axaml.cs create mode 100644 src/Beutl/Views/Editors/ImageFilterListItemEditor.axaml create mode 100644 src/Beutl/Views/Editors/ImageFilterListItemEditor.axaml.cs diff --git a/src/Beutl.Graphics/Graphics/Drawable.cs b/src/Beutl.Graphics/Graphics/Drawable.cs index 38fdec7b0..08a66cb23 100644 --- a/src/Beutl.Graphics/Graphics/Drawable.cs +++ b/src/Beutl.Graphics/Graphics/Drawable.cs @@ -227,8 +227,8 @@ private void HasBitmapEffect(ICanvas canvas) rect = rect.TransformToAABB(transform); using (Foreground == null ? new() : canvas.PushFillBrush(Foreground)) using (canvas.PushBlendMode(BlendMode)) - using (canvas.PushTransform(transformFact)) using (_filter == null ? new() : canvas.PushImageFilter(_filter)) + using (canvas.PushTransform(transformFact)) using (OpacityMask == null ? new() : canvas.PushOpacityMask(OpacityMask, rect)) { IBitmap bitmap = ToBitmap(); @@ -286,8 +286,8 @@ public void Draw(ICanvas canvas) using (Foreground == null ? new() : canvas.PushFillBrush(Foreground)) using (canvas.PushBlendMode(BlendMode)) - using (canvas.PushTransform(transform)) using (_filter == null ? new() : canvas.PushImageFilter(_filter)) + using (canvas.PushTransform(transform)) using (OpacityMask == null ? new() : canvas.PushOpacityMask(OpacityMask, new Rect(size))) { OnDraw(canvas); diff --git a/src/Beutl.Operators/Source/DrawablePublishOperator.cs b/src/Beutl.Operators/Source/DrawablePublishOperator.cs index 1bddc5218..719694d68 100644 --- a/src/Beutl.Operators/Source/DrawablePublishOperator.cs +++ b/src/Beutl.Operators/Source/DrawablePublishOperator.cs @@ -36,7 +36,7 @@ protected override void OnAfterApplying() drawable.AlignmentY = AlignmentY.Top; drawable.TransformOrigin = RelativePoint.TopLeft; drawable.Transform = null; - drawable.Filter = null; + //drawable.Filter = null; drawable.Effect = null; } } diff --git a/src/Beutl.Operators/Source/RectOperator.cs b/src/Beutl.Operators/Source/RectOperator.cs index 188e21bee..703cf94cc 100644 --- a/src/Beutl.Operators/Source/RectOperator.cs +++ b/src/Beutl.Operators/Source/RectOperator.cs @@ -1,4 +1,5 @@ using Beutl.Graphics; +using Beutl.Graphics.Filters; using Beutl.Graphics.Shapes; using Beutl.Media; using Beutl.Operation; @@ -15,4 +16,6 @@ public sealed class RectOperator : DrawablePublishOperator public Setter Pen { get; set; } = new(Shape.PenProperty); public Setter Fill { get; set; } = new(Drawable.ForegroundProperty, new SolidColorBrush(Colors.White)); + + public Setter Filter { get; set; } = new(Drawable.FilterProperty, null); } diff --git a/src/Beutl/Services/PropertyEditorService.cs b/src/Beutl/Services/PropertyEditorService.cs index c47938b38..55bd0b7cd 100644 --- a/src/Beutl/Services/PropertyEditorService.cs +++ b/src/Beutl/Services/PropertyEditorService.cs @@ -10,6 +10,7 @@ using Beutl.Controls.PropertyEditors; using Beutl.Framework; using Beutl.Graphics; +using Beutl.Graphics.Filters; using Beutl.Graphics.Transformation; using Beutl.Media; using Beutl.Media.Source; @@ -129,6 +130,7 @@ private record struct ListItemEditor(Func C private static readonly Dictionary s_listItemEditorsOverride = new() { + { typeof(IImageFilter), new(_ => new ImageFilterListItemEditor(), s => new ImageFilterEditorViewModel(s.ToTyped())) } }; private static readonly Dictionary s_editorsOverride = new() @@ -190,6 +192,7 @@ private record struct ListItemEditor(Func C { typeof(IBrush), new(_ => new BrushEditor(), s => new BrushEditorViewModel(s)) }, { typeof(IPen), new(_ => new PenEditor(), s => new PenEditorViewModel(s)) }, + { typeof(IImageFilter), new(_ => new ImageFilterEditor(), s => new ImageFilterEditorViewModel(s.ToTyped())) }, { typeof(GradientStops), new(_ => new GradientStopsEditor(), s => new GradientStopsEditorViewModel(s.ToTyped())) }, { typeof(IList), new(CreateListEditor, CreateListEditorViewModel) }, { typeof(ICoreObject), new(CreateNavigationButton, CreateNavigationButtonViewModel) }, diff --git a/src/Beutl/ViewModels/Editors/ImageFilterEditorViewModel.cs b/src/Beutl/ViewModels/Editors/ImageFilterEditorViewModel.cs new file mode 100644 index 000000000..717b9744d --- /dev/null +++ b/src/Beutl/ViewModels/Editors/ImageFilterEditorViewModel.cs @@ -0,0 +1,147 @@ +using Beutl.Commands; +using Beutl.Framework; +using Beutl.Graphics.Filters; +using Beutl.Operators.Configure; +using Beutl.ViewModels.Tools; + +using Reactive.Bindings; + +namespace Beutl.ViewModels.Editors; + +public sealed class ImageFilterEditorViewModel : ValueEditorViewModel +{ + public ImageFilterEditorViewModel(IAbstractProperty property) + : base(property) + { + FilterName = Value.Select(v => v?.GetType().Name ?? "Null") + .ToReadOnlyReactivePropertySlim() + .DisposeWith(Disposables); + + IsGroup = Value.Select(v => v is ImageFilterGroup) + .ToReadOnlyReactivePropertySlim() + .DisposeWith(Disposables); + + IsGroupOrNull = Value.Select(v => v is ImageFilterGroup || v == null) + .ToReadOnlyReactivePropertySlim() + .DisposeWith(Disposables); + + IsExpanded.Skip(1) + .Take(1) + .Subscribe(_ => + Value.Subscribe(v => + { + Properties.Value?.Dispose(); + Properties.Value = null; + Group.Value?.Dispose(); + Group.Value = null; + + if (v is ImageFilterGroup group) + { + var prop = new CorePropertyImpl(ImageFilterGroup.ChildrenProperty, group); + Group.Value = new ListEditorViewModel(prop) + { + IsExpanded = { Value = true } + }; + } + else if (v is ImageFilter filter) + { + Properties.Value = new PropertiesEditorViewModel(filter, (p, m) => m.Browsable && p != ImageFilter.IsEnabledProperty); + } + + AcceptChild(); + }) + .DisposeWith(Disposables)) + .DisposeWith(Disposables); + + IsEnabled = Value.Select(x => (x as ImageFilter)?.GetObservable(ImageFilter.IsEnabledProperty) ?? Observable.Return(x?.IsEnabled ?? false)) + .Switch() + .ToReactiveProperty() + .DisposeWith(Disposables); + + IsEnabled.Skip(1) + .Subscribe(v => + { + if (Value.Value is ImageFilter filter) + { + var command = new ChangePropertyCommand(filter, ImageFilter.IsEnabledProperty, v, !v); + command.DoAndRecord(CommandRecorder.Default); + } + }) + .DisposeWith(Disposables); + } + + public ReadOnlyReactivePropertySlim FilterName { get; } + + public ReadOnlyReactivePropertySlim IsGroup { get; } + + public ReadOnlyReactivePropertySlim IsGroupOrNull { get; } + + public ReactivePropertySlim IsExpanded { get; } = new(); + + public ReactiveProperty IsEnabled { get; } + + public ReactivePropertySlim Properties { get; } = new(); + + public ReactivePropertySlim?> Group { get; } = new(); + + public ReactivePropertySlim IsSeparatorVisible { get; } = new(); + + public override void Accept(IPropertyEditorContextVisitor visitor) + { + base.Accept(visitor); + AcceptChild(); + + IsSeparatorVisible.Value = visitor is SourceOperatorViewModel; + } + + private void AcceptChild() + { + var visitor = new Visitor(this); + Group.Value?.Accept(visitor); + + if (Properties.Value != null) + { + foreach (IPropertyEditorContext item in Properties.Value.Properties) + { + item.Accept(visitor); + } + } + } + + public void ChangeFilterType(Type type) + { + if (Activator.CreateInstance(type) is IImageFilter instance) + { + SetValue(Value.Value, instance); + } + } + + public void AddItem(Type type) + { + if (Value.Value is ImageFilterGroup group + && Activator.CreateInstance(type) is IImageFilter instance) + { + group.Children.BeginRecord() + .Add(instance) + .ToCommand() + .DoAndRecord(CommandRecorder.Default); + } + } + + public void SetNull() + { + SetValue(Value.Value, null); + } + + private sealed record Visitor(ImageFilterEditorViewModel Obj) : IServiceProvider, IPropertyEditorContextVisitor + { + public object? GetService(Type serviceType) + { + return Obj.GetService(serviceType); + } + + public void Visit(IPropertyEditorContext context) + { + } + } +} diff --git a/src/Beutl/ViewModels/Editors/ListEditorViewModel.cs b/src/Beutl/ViewModels/Editors/ListEditorViewModel.cs index 89be9e359..86c534c37 100644 --- a/src/Beutl/ViewModels/Editors/ListEditorViewModel.cs +++ b/src/Beutl/ViewModels/Editors/ListEditorViewModel.cs @@ -1,4 +1,5 @@ using System.Collections.Specialized; +using System.Reactive.Linq; using Beutl.Framework; using Beutl.Services; @@ -111,34 +112,39 @@ public ListEditorViewModel(IAbstractProperty property) .ToReadOnlyReactivePropertySlim() .DisposeWith(Disposables); - ObserveCount = Items.ObserveProperty(o => o.Count) - .ToReadOnlyReactivePropertySlim() - .DisposeWith(Disposables); - - CountString = ObserveCount - .Select(x => string.Format(Message.CountItems, x)) - .ToReadOnlyReactivePropertySlim(string.Empty) + IsExpanded.Skip(1) + .Take(1) + .Subscribe(_ => + List.Subscribe(list => + { + if (_incc != null) + { + _incc.CollectionChanged -= OnCollectionChanged; + _incc = null; + + OnCollectionChanged(s_resetCollectionChanged); + } + + if (list is INotifyCollectionChanged incc) + { + _incc = incc; + _incc.CollectionChanged += OnCollectionChanged; + var args = new NotifyCollectionChangedEventArgs( + action: NotifyCollectionChangedAction.Add, + changedItems: list.ToArray(), + startingIndex: 0); + OnCollectionChanged(args); + } + }) + .DisposeWith(Disposables)) .DisposeWith(Disposables); + } - List.Subscribe(list => - { - if (_incc != null) - { - _incc.CollectionChanged -= OnCollectionChanged; - _incc = null; - - OnCollectionChanged(s_resetCollectionChanged); - } + public ReadOnlyReactivePropertySlim?> List { get; } - if (list is INotifyCollectionChanged incc) - { - _incc = incc; - _incc.CollectionChanged += OnCollectionChanged; + public CoreList> Items { get; } = new(); - OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, changedItems: list.ToArray(), startingIndex: 0)); - } - }).DisposeWith(Disposables); - } + public ReactivePropertySlim IsExpanded { get; } = new(false); private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { @@ -184,7 +190,7 @@ void UpdateIndex(int start) case NotifyCollectionChangedAction.Remove: index = e.OldStartingIndex; - for (int i = List.Value!.Count - 1; i >= 0; --i) + for (int i = e.OldItems!.Count - 1; i >= 0; --i) { Removed(index + i); } @@ -218,14 +224,6 @@ void UpdateIndex(int start) } } - public ReadOnlyReactivePropertySlim?> List { get; } - - public CoreList> Items { get; } = new(); - - public ReadOnlyReactivePropertySlim ObserveCount { get; } - - public ReadOnlyReactivePropertySlim CountString { get; } - public void Initialize() { if (List.Value == null) diff --git a/src/Beutl/ViewModels/Editors/PropertiesEditorViewModel.cs b/src/Beutl/ViewModels/Editors/PropertiesEditorViewModel.cs index 883fe2912..faa88fb88 100644 --- a/src/Beutl/ViewModels/Editors/PropertiesEditorViewModel.cs +++ b/src/Beutl/ViewModels/Editors/PropertiesEditorViewModel.cs @@ -9,7 +9,19 @@ namespace Beutl.ViewModels.Editors; public sealed class PropertiesEditorViewModel : IDisposable { - public PropertiesEditorViewModel(ICoreObject obj, Predicate? predicate = null) + public PropertiesEditorViewModel(ICoreObject obj) + { + Target = obj; + InitializeCoreObject(obj); + } + + public PropertiesEditorViewModel(ICoreObject obj, Predicate predicate) + { + Target = obj; + InitializeCoreObject(obj, (_, v) => predicate(v)); + } + + public PropertiesEditorViewModel(ICoreObject obj, Func predicate) { Target = obj; InitializeCoreObject(obj, predicate); @@ -27,7 +39,7 @@ public void Dispose() } } - private void InitializeCoreObject(ICoreObject obj, Predicate? predicate = null) + private void InitializeCoreObject(ICoreObject obj, Func? predicate = null) { Type objType = obj.GetType(); Type wrapperType = typeof(CorePropertyImpl<>); @@ -35,7 +47,7 @@ private void InitializeCoreObject(ICoreObject obj, Predicate cprops = PropertyRegistry.GetRegistered(objType).ToList(); - cprops.RemoveAll(x => !(predicate?.Invoke(x.GetMetadata(objType)) ?? true)); + cprops.RemoveAll(x => !(predicate?.Invoke(x, x.GetMetadata(objType)) ?? true)); List props = cprops.ConvertAll(x => { CorePropertyMetadata metadata = x.GetMetadata(objType); diff --git a/src/Beutl/Views/Editors/BrushEditor.axaml b/src/Beutl/Views/Editors/BrushEditor.axaml index 6fcb351d1..48fca0bbd 100644 --- a/src/Beutl/Views/Editors/BrushEditor.axaml +++ b/src/Beutl/Views/Editors/BrushEditor.axaml @@ -6,7 +6,6 @@ xmlns:lang="using:Beutl.Language" xmlns:local="using:Beutl.Views.Editors" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:media="using:Beutl.Media" xmlns:ui="using:FluentAvalonia.UI.Controls" xmlns:vm="using:Beutl.ViewModels.Editors" x:Name="root" @@ -27,11 +26,10 @@ FontWeight="DemiBold" Theme="{DynamicResource PropertyEditorMiniExpanderToggleButton}"> - + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Beutl/Views/Editors/ImageFilterEditor.axaml.cs b/src/Beutl/Views/Editors/ImageFilterEditor.axaml.cs new file mode 100644 index 000000000..6300e7559 --- /dev/null +++ b/src/Beutl/Views/Editors/ImageFilterEditor.axaml.cs @@ -0,0 +1,138 @@ +using Avalonia; +using Avalonia.Animation; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Interactivity; +using Avalonia.Threading; + +using Beutl.Framework.Service; +using Beutl.Graphics.Filters; +using Beutl.ViewModels.Editors; + +using FluentAvalonia.UI.Controls; + +using Microsoft.Extensions.DependencyInjection; + +namespace Beutl.Views.Editors; + +public partial class ImageFilterEditor : UserControl +{ + private static readonly CrossFade s_transition = new(TimeSpan.FromMilliseconds(250)); + + private CancellationTokenSource? _lastTransitionCts; + + public ImageFilterEditor() + { + InitializeComponent(); + expandToggle.GetObservable(ToggleButton.IsCheckedProperty) + .Subscribe(async v => + { + _lastTransitionCts?.Cancel(); + _lastTransitionCts = new CancellationTokenSource(); + CancellationToken localToken = _lastTransitionCts.Token; + + if (v == true) + { + await s_transition.Start(null, content, localToken); + } + else + { + await s_transition.Start(content, null, localToken); + } + }); + } + + private async void Tag_Click(object? sender, RoutedEventArgs e) + { + if (DataContext is ImageFilterEditorViewModel viewModel) + { + if (viewModel.IsGroup.Value) + { + Type? type = await SelectType(); + if (type != null) + { + try + { + viewModel.AddItem(type); + } + catch (Exception ex) + { + ServiceLocator.Current.GetRequiredService() + .Show(new("Error", ex.Message, NotificationType.Error)); + } + } + } + else + { + expandToggle.ContextFlyout?.ShowAt(expandToggle); + } + } + } + + private static async Task SelectType() + { + Type[] availableTypes = await Task.Run(() => + { + Type itemType = typeof(IImageFilter); + return AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(x => x.GetTypes()) + .Where(x => !x.IsAbstract + && x.IsPublic + && x.IsAssignableTo(itemType) + && (itemType.GetConstructor(Array.Empty()) != null + || itemType.GetConstructors().Length == 0)) + .ToArray(); + }); + + var combobox = new ComboBox + { + Items = availableTypes, + SelectedIndex = 0 + }; + + var dialog = new ContentDialog + { + Content = combobox, + Title = Message.MultipleTypesAreAvailable, + PrimaryButtonText = Strings.OK, + CloseButtonText = Strings.Cancel + }; + + if (await dialog.ShowAsync() == ContentDialogResult.Primary) + { + return combobox.SelectedItem as Type; + } + else + { + return null; + } + } + + private async void ChangeFilterTypeClick(object? sender, RoutedEventArgs e) + { + if (DataContext is ImageFilterEditorViewModel viewModel) + { + Type? type = await SelectType(); + if (type != null) + { + try + { + viewModel.ChangeFilterType(type); + } + catch (Exception ex) + { + ServiceLocator.Current.GetRequiredService() + .Show(new("Error", ex.Message, NotificationType.Error)); + } + } + } + } + + private void SetNullClick(object? sender, RoutedEventArgs e) + { + if (DataContext is ImageFilterEditorViewModel viewModel) + { + viewModel.SetNull(); + } + } +} diff --git a/src/Beutl/Views/Editors/ImageFilterListItemEditor.axaml b/src/Beutl/Views/Editors/ImageFilterListItemEditor.axaml new file mode 100644 index 000000000..cac232f79 --- /dev/null +++ b/src/Beutl/Views/Editors/ImageFilterListItemEditor.axaml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Beutl/Views/Editors/ImageFilterListItemEditor.axaml.cs b/src/Beutl/Views/Editors/ImageFilterListItemEditor.axaml.cs new file mode 100644 index 000000000..42b338db4 --- /dev/null +++ b/src/Beutl/Views/Editors/ImageFilterListItemEditor.axaml.cs @@ -0,0 +1,45 @@ +using Avalonia; +using Avalonia.Animation; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Interactivity; + +using Beutl.Framework; + +namespace Beutl.Views.Editors; + +public partial class ImageFilterListItemEditor : UserControl, IListItemEditor +{ + private static readonly CrossFade s_transition = new(TimeSpan.FromMilliseconds(250)); + private CancellationTokenSource? _lastTransitionCts; + + public ImageFilterListItemEditor() + { + InitializeComponent(); + reorderHandle.GetObservable(ToggleButton.IsCheckedProperty) + .Subscribe(async v => + { + _lastTransitionCts?.Cancel(); + _lastTransitionCts = new CancellationTokenSource(); + CancellationToken localToken = _lastTransitionCts.Token; + + if (v == true) + { + await s_transition.Start(null, content, localToken); + } + else + { + await s_transition.Start(content, null, localToken); + } + }); + } + + public Control? ReorderHandle => reorderHandle; + + public event EventHandler? DeleteRequested; + + private void DeleteClick(object? sender, RoutedEventArgs e) + { + DeleteRequested?.Invoke(this, EventArgs.Empty); + } +} diff --git a/src/Beutl/Views/Editors/ListEditor.axaml b/src/Beutl/Views/Editors/ListEditor.axaml index 0e43dcb13..98e8cd751 100644 --- a/src/Beutl/Views/Editors/ListEditor.axaml +++ b/src/Beutl/Views/Editors/ListEditor.axaml @@ -16,13 +16,13 @@ - + + + + + + + + + + + + + + + + + + + + diff --git a/src/Beutl/Views/Editors/TransformEditor.axaml.cs b/src/Beutl/Views/Editors/TransformEditor.axaml.cs new file mode 100644 index 000000000..9b0113f73 --- /dev/null +++ b/src/Beutl/Views/Editors/TransformEditor.axaml.cs @@ -0,0 +1,142 @@ +using Avalonia; +using Avalonia.Animation; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Data.Converters; +using Avalonia.Interactivity; + +using Beutl.ViewModels.Editors; + +using FluentAvalonia.UI.Controls; + +namespace Beutl.Views.Editors; + +public partial class TransformEditor : UserControl +{ + private static readonly CrossFade s_transition = new(TimeSpan.FromMilliseconds(250)); + private CancellationTokenSource? _lastTransitionCts; + + private static FAMenuFlyout? s_flyout; + private static EventHandler? s_handler; + + public TransformEditor() + { + InitializeComponent(); + expandToggle.GetObservable(ToggleButton.IsCheckedProperty) + .Subscribe(async v => + { + _lastTransitionCts?.Cancel(); + _lastTransitionCts = new CancellationTokenSource(); + CancellationToken localToken = _lastTransitionCts.Token; + + if (v == true) + { + await s_transition.Start(null, content, localToken); + } + else + { + await s_transition.Start(content, null, localToken); + } + }); + + ChangeTypeMenu.Items = CreateMenuItems(TransformTypeClicked); + } + + private static MenuFlyoutItem[] CreateMenuItems(EventHandler? handler) + { + var items = new (KnownTransformType Tag, string Name, string? Icon)[] + { + (KnownTransformType.Group, "Group", null), + (KnownTransformType.Translate, "Translate", "TranslateTransformIconData"), + (KnownTransformType.Rotation, "Rotation", "RotationTransformIconData"), + (KnownTransformType.Scale, "Scale", "ScaleTransformIconData"), + (KnownTransformType.Skew, "Skew", "SkewTransformIconData"), + (KnownTransformType.Rotation3D, "Rotation 3D", "Rotation3DTransformIconData"), + }; + return items.Select(x => + { + var obj = new MenuFlyoutItem() + { + Tag = x.Tag, + Text = x.Name + }; + if (handler != null) + { + obj.Click += handler; + } + + if (x.Icon != null) + { + obj.Icon = new FAPathIcon + { + Data = Application.Current?.FindResource(x.Icon) as Avalonia.Media.Geometry + }; + } + + return obj; + }) + .ToArray(); + } + + private static FAMenuFlyout GetOrCreateFlyout() + { + return s_flyout ??= new FAMenuFlyout() + { + Items = CreateMenuItems((s, e) => s_handler?.Invoke(s, e)) + }; + } + + private void Tag_Click(object? sender, RoutedEventArgs e) + { + void Flyout_Closed(object? sender, EventArgs e) + { + if (sender is FAMenuFlyout flyout) + { + flyout.Closed -= Flyout_Closed; + s_handler -= AddTransformClick; + } + } + + if (DataContext is TransformEditorViewModel viewModel) + { + if (viewModel.IsGroup.Value) + { + FAMenuFlyout flyout = GetOrCreateFlyout(); + flyout.ShowAt(this); + + s_handler += AddTransformClick; + flyout.Closed += Flyout_Closed; + } + else + { + expandToggle.ContextFlyout?.ShowAt(expandToggle); + } + } + } + + private void AddTransformClick(object? sender, RoutedEventArgs e) + { + if (sender is MenuFlyoutItem { Tag: KnownTransformType type } + && DataContext is TransformEditorViewModel viewModel) + { + viewModel.AddItem(type); + } + } + + private void TransformTypeClicked(object? sender, RoutedEventArgs e) + { + if (sender is MenuFlyoutItem { Tag: KnownTransformType type } + && DataContext is TransformEditorViewModel viewModel) + { + viewModel.ChangeType(type); + } + } + + private void SetNullClick(object? sender, RoutedEventArgs e) + { + if (DataContext is TransformEditorViewModel viewModel) + { + viewModel.SetNull(); + } + } +} diff --git a/src/Beutl/Views/Editors/TransformListItemEditor.axaml b/src/Beutl/Views/Editors/TransformListItemEditor.axaml new file mode 100644 index 000000000..660fc4fca --- /dev/null +++ b/src/Beutl/Views/Editors/TransformListItemEditor.axaml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Beutl/Views/Editors/TransformListItemEditor.axaml.cs b/src/Beutl/Views/Editors/TransformListItemEditor.axaml.cs new file mode 100644 index 000000000..1cdb4c1d2 --- /dev/null +++ b/src/Beutl/Views/Editors/TransformListItemEditor.axaml.cs @@ -0,0 +1,71 @@ +using Avalonia; +using Avalonia.Animation; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Data.Converters; +using Avalonia.Interactivity; + +using Beutl.Framework; +using Beutl.ViewModels.Editors; + +namespace Beutl.Views.Editors; + +public partial class TransformListItemEditor : UserControl, IListItemEditor +{ + private static readonly CrossFade s_transition = new(TimeSpan.FromMilliseconds(250)); + private CancellationTokenSource? _lastTransitionCts; + + public TransformListItemEditor() + { + Resources["TransformTypeToIconConverter"] = TransformTypeToIconConverter.Instance; + InitializeComponent(); + reorderHandle.GetObservable(ToggleButton.IsCheckedProperty) + .Subscribe(async v => + { + _lastTransitionCts?.Cancel(); + _lastTransitionCts = new CancellationTokenSource(); + CancellationToken localToken = _lastTransitionCts.Token; + + if (v == true) + { + await s_transition.Start(null, content, localToken); + } + else + { + await s_transition.Start(content, null, localToken); + } + }); + } + + public Control? ReorderHandle => reorderHandle; + + public event EventHandler? DeleteRequested; + + private void DeleteClick(object? sender, RoutedEventArgs e) + { + DeleteRequested?.Invoke(this, EventArgs.Empty); + } + + private sealed class TransformTypeToIconConverter : IValueConverter + { + public static readonly TransformTypeToIconConverter Instance = new(); + + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return value switch + { + KnownTransformType.Translate => Application.Current!.FindResource("TranslateTransformIconData"), + KnownTransformType.Rotation => Application.Current!.FindResource("RotationTransformIconData"), + KnownTransformType.Scale => Application.Current!.FindResource("ScaleTransformIconData"), + KnownTransformType.Skew => Application.Current!.FindResource("SkewTransformIconData"), + KnownTransformType.Rotation3D => Application.Current!.FindResource("Rotation3DTransformIconData"), + _ => Application.Current!.FindResource("TransformListItemEditor_ReOrderIcon") + }; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + return null; + } + } +} From d1e8a0012aa3f3c916cc407a1a7a9c09c9ad0966 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Tue, 20 Jun 2023 00:11:39 +0900 Subject: [PATCH 57/84] =?UTF-8?q?TransformOrigin=E3=81=AE=E4=BB=95?= =?UTF-8?q?=E6=A7=98=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Graphics/Graphics/Drawable.cs | 67 ++++++++----------- .../Configure/AlignmentOperator.cs | 6 +- .../NodeTree/Nodes/LayerOutputNode.cs | 6 +- 3 files changed, 33 insertions(+), 46 deletions(-) diff --git a/src/Beutl.Graphics/Graphics/Drawable.cs b/src/Beutl.Graphics/Graphics/Drawable.cs index 08a66cb23..ced6588c7 100644 --- a/src/Beutl.Graphics/Graphics/Drawable.cs +++ b/src/Beutl.Graphics/Graphics/Drawable.cs @@ -25,9 +25,9 @@ public abstract class Drawable : Renderable, IDrawable, IHierarchical private ITransform? _transform; private IImageFilter? _filter; private IBitmapEffect? _effect; - private AlignmentX _alignX; - private AlignmentY _alignY; - private RelativePoint _transformOrigin; + private AlignmentX _alignX = AlignmentX.Center; + private AlignmentY _alignY = AlignmentY.Center; + private RelativePoint _transformOrigin = RelativePoint.Center; private IBrush? _foreground; private IBrush? _opacityMask; private BlendMode _blendMode = BlendMode.SrcOver; @@ -51,16 +51,17 @@ static Drawable() AlignmentXProperty = ConfigureProperty(nameof(AlignmentX)) .Accessor(o => o.AlignmentX, (o, v) => o.AlignmentX = v) - .DefaultValue(AlignmentX.Left) + .DefaultValue(AlignmentX.Center) .Register(); AlignmentYProperty = ConfigureProperty(nameof(AlignmentY)) .Accessor(o => o.AlignmentY, (o, v) => o.AlignmentY = v) - .DefaultValue(AlignmentY.Top) + .DefaultValue(AlignmentY.Center) .Register(); TransformOriginProperty = ConfigureProperty(nameof(TransformOrigin)) .Accessor(o => o.TransformOrigin, (o, v) => o.TransformOrigin = v) + .DefaultValue(RelativePoint.Center) .Register(); ForegroundProperty = ConfigureProperty(nameof(Foreground)) @@ -193,17 +194,17 @@ public void Measure(Size availableSize) private Matrix GetTransformMatrix(Size availableSize, Size coreBounds) { - Vector pt = CalculateTranslate(coreBounds); - Vector origin = CalculateOriginPoint(availableSize); + Vector pt = CalculateTranslate(coreBounds, availableSize); + Vector origin = TransformOrigin.ToPixels(coreBounds); Matrix offset = Matrix.CreateTranslation(origin); if (Transform is { }) { - return Matrix.CreateTranslation(pt) * Transform.Value * offset; + return (-offset) * Transform.Value * offset * Matrix.CreateTranslation(pt); } else { - return offset * Matrix.CreateTranslation(pt); + return Matrix.CreateTranslation(pt); } } @@ -324,43 +325,29 @@ public override void ApplyAnimations(IClock clock) protected abstract void OnDraw(ICanvas canvas); - private Point CalculateOriginPoint(Size size) + private Point CalculateTranslate(Size bounds, Size canvasSize) { - if (float.IsNormal(size.Width) && float.IsNormal(size.Height)) - { - return TransformOrigin.ToPixels(size); - } - else if (TransformOrigin.Unit == RelativeUnit.Absolute) - { - return TransformOrigin.Point; - } - else - { - return default; - } - } - - private Point CalculateTranslate(Size bounds) - { - float x = 0; - float y = 0; + float x = -bounds.Width / 2; + float y = -bounds.Height / 2; - if (AlignmentX == AlignmentX.Center) + switch (AlignmentX) { - x -= bounds.Width / 2; - } - else if (AlignmentX == AlignmentX.Right) - { - x -= bounds.Width; + case AlignmentX.Center: + x += canvasSize.Width / 2; + break; + case AlignmentX.Right: + x += canvasSize.Width; + break; } - if (AlignmentY == AlignmentY.Center) - { - y -= bounds.Height / 2; - } - else if (AlignmentY == AlignmentY.Bottom) + switch (AlignmentY) { - y -= bounds.Height; + case AlignmentY.Center: + y += canvasSize.Height / 2; + break; + case AlignmentY.Bottom: + y += canvasSize.Height; + break; } return new Point(x, y); diff --git a/src/Beutl.Operators/Configure/AlignmentOperator.cs b/src/Beutl.Operators/Configure/AlignmentOperator.cs index 6fae82a6e..3751c4b03 100644 --- a/src/Beutl.Operators/Configure/AlignmentOperator.cs +++ b/src/Beutl.Operators/Configure/AlignmentOperator.cs @@ -16,8 +16,8 @@ protected override Style OnInitializeStyle(Func> setters) protected override void OnInitializeSetters(IList initializing) { - initializing.Add(new Setter(Drawable.AlignmentXProperty, AlignmentX.Left)); - initializing.Add(new Setter(Drawable.AlignmentYProperty, AlignmentY.Top)); - initializing.Add(new Setter(Drawable.TransformOriginProperty, new RelativePoint(default, RelativeUnit.Relative))); + initializing.Add(new Setter(Drawable.AlignmentXProperty, AlignmentX.Center)); + initializing.Add(new Setter(Drawable.AlignmentYProperty, AlignmentY.Center)); + initializing.Add(new Setter(Drawable.TransformOriginProperty, RelativePoint.Center)); } } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/LayerOutputNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/LayerOutputNode.cs index d1469ab23..177f9c449 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/LayerOutputNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/LayerOutputNode.cs @@ -25,9 +25,9 @@ protected override void Attach(Drawable drawable) protected override void Detach(Drawable drawable) { drawable.BlendMode = BlendMode.SrcOver; - drawable.AlignmentX = AlignmentX.Left; - drawable.AlignmentY = AlignmentY.Top; - drawable.TransformOrigin = RelativePoint.TopLeft; + drawable.AlignmentX = AlignmentX.Center; + drawable.AlignmentY = AlignmentY.Center; + drawable.TransformOrigin = RelativePoint.Center; } protected override void EvaluateCore(NodeEvaluationContext context) From 8b39b709540917e0873efa6056e63180bbd45c24 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Tue, 20 Jun 2023 01:27:02 +0900 Subject: [PATCH 58/84] =?UTF-8?q?PropertyEditor=E5=BE=AE=E8=AA=BF=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Controls/PropertyEditors/PropertyEditor.cs | 5 +++++ .../PropertyEditors/PropertyEditorResources.axaml | 4 ++-- src/Beutl.Graphics/Animation/AnimatorRegistry.cs | 10 +++++++++- .../NodeTree/Nodes/NodesRegistrar.cs | 11 +++++++++-- .../Editors/GradientStopsEditorViewModel.cs | 7 ------- src/Beutl/Views/Editors/BrushEditor.axaml | 2 +- src/Beutl/Views/Editors/GradientStopsEditor.axaml | 4 ++-- src/Beutl/Views/Editors/ImageFilterEditor.axaml | 2 +- .../Views/Editors/ImageFilterListItemEditor.axaml | 4 ++-- src/Beutl/Views/Editors/ImageSourceEditor.axaml | 2 +- src/Beutl/Views/Editors/ListEditor.axaml | 2 +- src/Beutl/Views/Editors/NavigateButton.axaml | 3 ++- src/Beutl/Views/Editors/OpacityEditor.axaml | 4 ++-- src/Beutl/Views/Editors/ParsableEditor.axaml | 2 +- src/Beutl/Views/Editors/PenEditor.axaml | 2 +- src/Beutl/Views/Editors/SoundSourceEditor.axaml | 2 +- src/Beutl/Views/Editors/TimeSpanEditor.axaml | 2 +- src/Beutl/Views/Editors/TransformEditor.axaml | 2 +- src/Beutl/Views/Editors/TransformListItemEditor.axaml | 4 ++-- src/Beutl/Views/NodeTree/SocketView.axaml.cs | 2 ++ 20 files changed, 46 insertions(+), 30 deletions(-) diff --git a/src/Beutl.Controls/PropertyEditors/PropertyEditor.cs b/src/Beutl.Controls/PropertyEditors/PropertyEditor.cs index 597682388..32c23c817 100644 --- a/src/Beutl.Controls/PropertyEditors/PropertyEditor.cs +++ b/src/Beutl.Controls/PropertyEditors/PropertyEditor.cs @@ -61,6 +61,11 @@ public class PropertyEditor : TemplatedControl, IPropertyEditorContextVisitor, I private readonly CompositeDisposable _eventRevokers = new(3); + static PropertyEditor() + { + MarginProperty.OverrideDefaultValue(new(4, 0)); + } + public string Header { get => GetValue(HeaderProperty); diff --git a/src/Beutl.Controls/Styling/PropertyEditors/PropertyEditorResources.axaml b/src/Beutl.Controls/Styling/PropertyEditors/PropertyEditorResources.axaml index 8b89af133..e64c7bf97 100644 --- a/src/Beutl.Controls/Styling/PropertyEditors/PropertyEditorResources.axaml +++ b/src/Beutl.Controls/Styling/PropertyEditors/PropertyEditorResources.axaml @@ -6,7 +6,7 @@ xmlns:local="using:Beutl.Controls.Styling.PropertyEditors"> - - + diff --git a/src/Beutl.Graphics/Animation/AnimatorRegistry.cs b/src/Beutl.Graphics/Animation/AnimatorRegistry.cs index ad33c5829..a310c3f6c 100644 --- a/src/Beutl.Graphics/Animation/AnimatorRegistry.cs +++ b/src/Beutl.Graphics/Animation/AnimatorRegistry.cs @@ -50,7 +50,7 @@ public static Type GetAnimatorType(Type type) } } - throw new Exception($"Could not find an Animator that supports type {type.Name}."); + return typeof(_Animator<>).MakeGenericType(type); } public static void RegisterAnimator(Type animatorType, Func condition) @@ -71,4 +71,12 @@ public static void RegisterAnimator(Func condition) { s_animators.Insert(0, (condition, typeof(TAnimator))); } + + private sealed class _Animator : Animator + { + public override T Interpolate(float progress, T oldValue, T newValue) + { + return newValue; + } + } } diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/NodesRegistrar.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/NodesRegistrar.cs index 8231b0e53..7a195da9f 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/NodesRegistrar.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/NodesRegistrar.cs @@ -1,6 +1,7 @@ using Beutl.Language; using Beutl.NodeTree.Nodes.Brushes; using Beutl.NodeTree.Nodes.Filters; +using Beutl.NodeTree.Nodes.Geometry; using Beutl.NodeTree.Nodes.Group; using Beutl.NodeTree.Nodes.Transform; @@ -12,7 +13,13 @@ public static void RegisterAll() { NodeRegistry.RegisterNode("Layer input"); NodeRegistry.RegisterNode("Layer output"); - NodeRegistry.RegisterNode(Strings.Rectangle); + NodeRegistry.RegisterNode("GeometryShape"); + + NodeRegistry.RegisterNodes("Geometry") + .Add(Strings.Rectangle) + .Add(Strings.Ellipse) + .Add(Strings.RoundedRect) + .Register(); NodeRegistry.RegisterNodes("Group") .Add("Group Input") @@ -23,7 +30,7 @@ public static void RegisterAll() NodeRegistry.RegisterNodes(Strings.ImageFilter) .Add(Strings.DropShadow) .Register(); - + NodeRegistry.RegisterNodes("Brush") .Add("Set Foreground") .Add("Solid Color Brush") diff --git a/src/Beutl/ViewModels/Editors/GradientStopsEditorViewModel.cs b/src/Beutl/ViewModels/Editors/GradientStopsEditorViewModel.cs index a27d647ab..61fd97701 100644 --- a/src/Beutl/ViewModels/Editors/GradientStopsEditorViewModel.cs +++ b/src/Beutl/ViewModels/Editors/GradientStopsEditorViewModel.cs @@ -35,11 +35,6 @@ public GradientStopsEditorViewModel(IAbstractProperty property) (idx, _) => Stops.RemoveAt(idx), () => Stops.Clear()); }).DisposeWith(Disposables); - - CanEditStop = CanEdit.CombineLatest(SelectedItem.Select(x => x != null)) - .Select(x => x.First && x.Second) - .ToReadOnlyReactivePropertySlim() - .DisposeWith(Disposables); } public ReadOnlyReactivePropertySlim Value { get; } @@ -48,8 +43,6 @@ public GradientStopsEditorViewModel(IAbstractProperty property) public ReactivePropertySlim SelectedItem { get; } = new(); - public ReadOnlyReactivePropertySlim CanEditStop { get; } - public void PushChange(AM.GradientStop stop) { int index = Stops.IndexOf(stop); diff --git a/src/Beutl/Views/Editors/BrushEditor.axaml b/src/Beutl/Views/Editors/BrushEditor.axaml index 48fca0bbd..69487609f 100644 --- a/src/Beutl/Views/Editors/BrushEditor.axaml +++ b/src/Beutl/Views/Editors/BrushEditor.axaml @@ -21,7 +21,7 @@ diff --git a/src/Beutl/Views/Editors/GradientStopsEditor.axaml b/src/Beutl/Views/Editors/GradientStopsEditor.axaml index 76f1c0323..16294c516 100644 --- a/src/Beutl/Views/Editors/GradientStopsEditor.axaml +++ b/src/Beutl/Views/Editors/GradientStopsEditor.axaml @@ -11,7 +11,7 @@ x:DataType="vm:GradientStopsEditorViewModel" mc:Ignorable="d"> - @@ -72,7 +72,7 @@ - - - diff --git a/src/Beutl/Views/Tools/StyleEditor.axaml.cs b/src/Beutl/Views/Tools/StyleEditor.axaml.cs deleted file mode 100644 index c7bb3cc19..000000000 --- a/src/Beutl/Views/Tools/StyleEditor.axaml.cs +++ /dev/null @@ -1,183 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Controls.Primitives; -using Avalonia.Controls.Templates; -using Avalonia.Data; -using Avalonia.Interactivity; -using Avalonia.Threading; - -using Beutl.Commands; -using Beutl.ViewModels.Tools; - -using FluentAvalonia.UI.Controls; - -using Reactive.Bindings; - -namespace Beutl.Views.Tools; - -public sealed partial class StyleEditor : UserControl -{ - private IDisposable? _disposable; - - public StyleEditor() - { - InitializeComponent(); - targetTypeBox.ItemSelector = (_, obj) => obj.ToString() ?? ""; - targetTypeBox.FilterMode = AutoCompleteFilterMode.Contains; - } - - protected override void OnDataContextChanged(EventArgs e) - { - base.OnDataContextChanged(e); - _disposable?.Dispose(); - _disposable = null; - if (DataContext is StyleEditorViewModel viewModel) - { - _disposable = viewModel.TargetType.Skip(1).Subscribe(async type => - { - if (DataContext is StyleEditorViewModel viewModel - && type != null - && viewModel.Style.Value is Styling.Style style) - { - var mismatches = new List(style.Setters.Count); - foreach (Styling.ISetter item in style.Setters) - { - if (!item.Property.OwnerType.IsAssignableTo(type)) - { - mismatches.Add(item); - } - } - - if (mismatches.Count > 0) - { - await Dispatcher.UIThread.InvokeAsync(async () => - { - var dialog = new ContentDialog - { - Title = Strings.RemoveUnavailableSetters, - Content = new ListBox - { - Items = mismatches, - ItemTemplate = new FuncDataTemplate((setter, _) => - { - return new TextBlock() - { - Text = setter.Property.Name - }; - }) - }, - PrimaryButtonText = Strings.Yes, - CloseButtonText = Strings.No - }; - - if (await dialog.ShowAsync() == ContentDialogResult.Primary) - { - var command = new RemoveAllCommand(style.Setters, mismatches); - command.DoAndRecord(CommandRecorder.Default); - } - }); - } - } - }); - } - } - - private async void Add_Click(object? sender, RoutedEventArgs e) - { - if (DataContext is StyleEditorViewModel viewModel && viewModel.Style.Value is Styling.Style style) - { - CoreProperty[] props = PropertyRegistry.GetRegistered(style.TargetType).ToArray(); - - var selectedItem = new ReactivePropertySlim(); - var listBox = new ListBox - { - [!SelectingItemsControl.SelectedItemProperty] = new Binding("Value", BindingMode.TwoWay) - { - Source = selectedItem - }, - SelectionMode = SelectionMode.Single, - Items = props, - ItemTemplate = new FuncDataTemplate((prop, _) => - { - return new TextBlock() - { - Text = prop.Name - }; - }) - }; - - var dialog = new ContentDialog - { - [!ContentDialog.IsPrimaryButtonEnabledProperty] = new Binding("Value") - { - Source = selectedItem.Select(x => x != null).ToReadOnlyReactivePropertySlim() - }, - Title = Strings.AddSetter, - Content = listBox, - PrimaryButtonText = Strings.Add, - CloseButtonText = Strings.Cancel - }; - - if (await dialog.ShowAsync() == ContentDialogResult.Primary - && selectedItem.Value is CoreProperty property) - { - Type setterType = typeof(Styling.Setter<>); - setterType = setterType.MakeGenericType(property.PropertyType); - ICorePropertyMetadata metadata = property.GetMetadata(style.TargetType); - object? defaultValue = metadata.GetDefaultValue(); - - if (Activator.CreateInstance(setterType, property, defaultValue) is Styling.ISetter setter) - { - style.Setters.BeginRecord() - .Add(setter) - .ToCommand() - .DoAndRecord(CommandRecorder.Default); - } - } - } - } - - private sealed class RemoveAllCommand : IRecordableCommand - { - public RemoveAllCommand(IList list, IReadOnlyList items) - { - List = list; - Items = items; - Indices = new int[items.Count]; - - for (int i = 0; i < items.Count; i++) - { - Indices[i] = list.IndexOf(items[i]); - } - - Array.Sort(Indices); - } - - public IList List { get; } - - public IReadOnlyList Items { get; } - - public int[] Indices { get; } - - public void Do() - { - for (int i = Indices.Length - 1; i >= 0; i--) - { - List.RemoveAt(Indices[i]); - } - } - - public void Redo() - { - Do(); - } - - public void Undo() - { - for (int i = 0; i < Indices.Length; i++) - { - List.Insert(Indices[i], Items[i]); - } - } - } - -} From ce03c8db01543d0cc9dabd3f6d30c2fb5b470ebb Mon Sep 17 00:00:00 2001 From: indigo-san Date: Sat, 1 Jul 2023 00:42:34 +0900 Subject: [PATCH 64/84] =?UTF-8?q?ComposedImageFilter=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Graphics/Filters/ComposedImageFilter.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Beutl.Graphics/Graphics/Filters/ComposedImageFilter.cs b/src/Beutl.Graphics/Graphics/Filters/ComposedImageFilter.cs index 6a2f10d78..f53c199f9 100644 --- a/src/Beutl.Graphics/Graphics/Filters/ComposedImageFilter.cs +++ b/src/Beutl.Graphics/Graphics/Filters/ComposedImageFilter.cs @@ -57,8 +57,17 @@ public IImageFilter? Inner return Outer.ToSKImageFilter(bounds); case ({ IsEnabled: true }, { IsEnabled: true }): - return SKImageFilter.CreateCompose(Outer.ToSKImageFilter(bounds), Inner.ToSKImageFilter(bounds)); + var inner = Inner.ToSKImageFilter(bounds); + var outer = Outer.ToSKImageFilter(bounds); + if ((inner, outer) is ({ }, { })) + { + return SKImageFilter.CreateCompose(outer, inner); + } + else + { + return inner ?? outer; + } default: return null; } From 82ffa0d054f8d6318a23dc4e68e90d91ca43c8ea Mon Sep 17 00:00:00 2001 From: indigo-san Date: Sat, 1 Jul 2023 00:42:52 +0900 Subject: [PATCH 65/84] =?UTF-8?q?AlignmentEditor=E3=81=AE=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=81=8C=E3=81=9A=E3=82=8C=E3=82=8B=E3=81=AE=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Styling/PropertyEditors/AlignmentXEditor.axaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Beutl.Controls/Styling/PropertyEditors/AlignmentXEditor.axaml b/src/Beutl.Controls/Styling/PropertyEditors/AlignmentXEditor.axaml index b08d058ae..ee0bc3574 100644 --- a/src/Beutl.Controls/Styling/PropertyEditors/AlignmentXEditor.axaml +++ b/src/Beutl.Controls/Styling/PropertyEditors/AlignmentXEditor.axaml @@ -189,7 +189,7 @@ @@ -368,7 +368,7 @@ From b7693a0c0cb35a854a2044b502890d0c6152c0c1 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Sat, 1 Jul 2023 01:55:46 +0900 Subject: [PATCH 66/84] =?UTF-8?q?=E3=83=97=E3=83=AD=E3=83=91=E3=83=86?= =?UTF-8?q?=E3=82=A3=E3=81=AE=E3=82=B0=E3=83=AB=E3=83=BC=E3=83=97=E5=8C=96?= =?UTF-8?q?=E3=82=92=E6=94=B9=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Graphics/Graphics/Drawable.cs | 24 +++--- src/Beutl.Graphics/Graphics/Shapes/Shape.cs | 1 + .../Source/DrawablePublishOperator.cs | 6 +- .../Source/EllipseOperation.cs | 6 ++ src/Beutl.Operators/Source/RectOperator.cs | 6 ++ .../Source/RoundedRectOperator.cs | 6 ++ .../Source/SourceImageOperator.cs | 6 ++ src/Beutl/Services/PropertyEditorService.cs | 5 ++ .../Editors/BrushEditorViewModel.cs | 4 - .../Editors/ImageFilterEditorViewModel.cs | 4 - .../ViewModels/Editors/PenEditorViewModel.cs | 4 - .../Editors/PropertyEditorGroupContext.cs | 73 +++++++++++++++++++ .../Editors/TransformEditorViewModel.cs | 4 - .../Tools/SourceOperatorViewModel.cs | 35 ++++++++- src/Beutl/Views/Editors/BrushEditor.axaml | 9 +-- .../Views/Editors/ImageFilterEditor.axaml | 9 +-- src/Beutl/Views/Editors/PenEditor.axaml | 8 +- .../Views/Editors/PropertiesEditor.axaml | 8 +- .../Views/Editors/PropertiesEditor.axaml.cs | 1 + .../Views/Editors/PropertyEditorGroup.axaml | 47 ++++++++++++ .../Editors/PropertyEditorGroup.axaml.cs | 59 +++++++++++++++ src/Beutl/Views/Editors/TransformEditor.axaml | 9 +-- 22 files changed, 271 insertions(+), 63 deletions(-) create mode 100644 src/Beutl/ViewModels/Editors/PropertyEditorGroupContext.cs create mode 100644 src/Beutl/Views/Editors/PropertyEditorGroup.axaml create mode 100644 src/Beutl/Views/Editors/PropertyEditorGroup.axaml.cs diff --git a/src/Beutl.Graphics/Graphics/Drawable.cs b/src/Beutl.Graphics/Graphics/Drawable.cs index f14fdfce8..1dd3e259a 100644 --- a/src/Beutl.Graphics/Graphics/Drawable.cs +++ b/src/Beutl.Graphics/Graphics/Drawable.cs @@ -88,14 +88,7 @@ static Drawable() public Rect Bounds { get; private set; } - [Display(Name = nameof(Strings.Transform), ResourceType = typeof(Strings))] - public ITransform? Transform - { - get => _transform; - set => SetAndRaise(TransformProperty, ref _transform, value); - } - - [Display(Name = nameof(Strings.ImageFilter), ResourceType = typeof(Strings))] + [Display(Name = nameof(Strings.ImageFilter), ResourceType = typeof(Strings), GroupName = nameof(Strings.ImageFilter))] public IImageFilter? Filter { get => _filter; @@ -109,28 +102,35 @@ public IBitmapEffect? Effect set => SetAndRaise(EffectProperty, ref _effect, value); } - [Display(Name = nameof(Strings.AlignmentX), ResourceType = typeof(Strings))] + [Display(Name = nameof(Strings.Transform), ResourceType = typeof(Strings), GroupName = nameof(Strings.Transform))] + public ITransform? Transform + { + get => _transform; + set => SetAndRaise(TransformProperty, ref _transform, value); + } + + [Display(Name = nameof(Strings.AlignmentX), ResourceType = typeof(Strings), GroupName = nameof(Strings.Transform))] public AlignmentX AlignmentX { get => _alignX; set => SetAndRaise(AlignmentXProperty, ref _alignX, value); } - [Display(Name = nameof(Strings.AlignmentY), ResourceType = typeof(Strings))] + [Display(Name = nameof(Strings.AlignmentY), ResourceType = typeof(Strings), GroupName = nameof(Strings.Transform))] public AlignmentY AlignmentY { get => _alignY; set => SetAndRaise(AlignmentYProperty, ref _alignY, value); } - [Display(Name = nameof(Strings.TransformOrigin), ResourceType = typeof(Strings))] + [Display(Name = nameof(Strings.TransformOrigin), ResourceType = typeof(Strings), GroupName = nameof(Strings.Transform))] public RelativePoint TransformOrigin { get => _transformOrigin; set => SetAndRaise(TransformOriginProperty, ref _transformOrigin, value); } - [Display(Name = nameof(Strings.Foreground), ResourceType = typeof(Strings))] + [Display(Name = nameof(Strings.Foreground), ResourceType = typeof(Strings), GroupName = nameof(Strings.Foreground))] public IBrush? Foreground { get => _foreground; diff --git a/src/Beutl.Graphics/Graphics/Shapes/Shape.cs b/src/Beutl.Graphics/Graphics/Shapes/Shape.cs index e8af352e9..d4384e1d5 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/Shape.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/Shape.cs @@ -70,6 +70,7 @@ public Stretch Stretch set => SetAndRaise(StretchProperty, ref _stretch, value); } + [Display(GroupName = "Pen")] public IPen? Pen { get => _pen; diff --git a/src/Beutl.Operators/Source/DrawablePublishOperator.cs b/src/Beutl.Operators/Source/DrawablePublishOperator.cs index e36e7a01a..8ac47903c 100644 --- a/src/Beutl.Operators/Source/DrawablePublishOperator.cs +++ b/src/Beutl.Operators/Source/DrawablePublishOperator.cs @@ -32,9 +32,9 @@ protected override void OnAfterApplying() if (Instance?.Target is T drawable) { drawable.BlendMode = BlendMode.SrcOver; - drawable.AlignmentX = AlignmentX.Left; - drawable.AlignmentY = AlignmentY.Top; - drawable.TransformOrigin = RelativePoint.TopLeft; + //drawable.AlignmentX = AlignmentX.Left; + //drawable.AlignmentY = AlignmentY.Top; + //drawable.TransformOrigin = RelativePoint.TopLeft; //drawable.Transform = null; //drawable.Filter = null; drawable.Effect = null; diff --git a/src/Beutl.Operators/Source/EllipseOperation.cs b/src/Beutl.Operators/Source/EllipseOperation.cs index 27d223559..dca66cafc 100644 --- a/src/Beutl.Operators/Source/EllipseOperation.cs +++ b/src/Beutl.Operators/Source/EllipseOperation.cs @@ -17,6 +17,12 @@ public sealed class EllipseOperator : DrawablePublishOperator public Setter Transform { get; set; } = new(Drawable.TransformProperty, null); + public Setter AlignmentX { get; set; } = new(Drawable.AlignmentXProperty, Media.AlignmentX.Center); + + public Setter AlignmentY { get; set; } = new(Drawable.AlignmentYProperty, Media.AlignmentY.Center); + + public Setter TransformOrigin { get; set; } = new(Drawable.TransformOriginProperty, RelativePoint.Center); + public Setter Pen { get; set; } = new(Shape.PenProperty); public Setter Fill { get; set; } = new(Drawable.ForegroundProperty, new SolidColorBrush(Colors.White)); diff --git a/src/Beutl.Operators/Source/RectOperator.cs b/src/Beutl.Operators/Source/RectOperator.cs index b87703379..c114988bc 100644 --- a/src/Beutl.Operators/Source/RectOperator.cs +++ b/src/Beutl.Operators/Source/RectOperator.cs @@ -16,6 +16,12 @@ public sealed class RectOperator : DrawablePublishOperator public Setter Transform { get; set; } = new(Drawable.TransformProperty, null); + public Setter AlignmentX { get; set; } = new(Drawable.AlignmentXProperty, Media.AlignmentX.Center); + + public Setter AlignmentY { get; set; } = new(Drawable.AlignmentYProperty, Media.AlignmentY.Center); + + public Setter TransformOrigin { get; set; } = new(Drawable.TransformOriginProperty, RelativePoint.Center); + public Setter Pen { get; set; } = new(Shape.PenProperty); public Setter Fill { get; set; } = new(Drawable.ForegroundProperty, new SolidColorBrush(Colors.White)); diff --git a/src/Beutl.Operators/Source/RoundedRectOperator.cs b/src/Beutl.Operators/Source/RoundedRectOperator.cs index 6eb91a844..594e8c435 100644 --- a/src/Beutl.Operators/Source/RoundedRectOperator.cs +++ b/src/Beutl.Operators/Source/RoundedRectOperator.cs @@ -18,6 +18,12 @@ public sealed class RoundedRectOperator : DrawablePublishOperator Transform { get; set; } = new(Drawable.TransformProperty, null); + public Setter AlignmentX { get; set; } = new(Drawable.AlignmentXProperty, Media.AlignmentX.Center); + + public Setter AlignmentY { get; set; } = new(Drawable.AlignmentYProperty, Media.AlignmentY.Center); + + public Setter TransformOrigin { get; set; } = new(Drawable.TransformOriginProperty, RelativePoint.Center); + public Setter Pen { get; set; } = new(Shape.PenProperty); public Setter Fill { get; set; } = new(Drawable.ForegroundProperty, new SolidColorBrush(Colors.White)); diff --git a/src/Beutl.Operators/Source/SourceImageOperator.cs b/src/Beutl.Operators/Source/SourceImageOperator.cs index 979628b62..8c4ba200c 100644 --- a/src/Beutl.Operators/Source/SourceImageOperator.cs +++ b/src/Beutl.Operators/Source/SourceImageOperator.cs @@ -14,6 +14,12 @@ public sealed class SourceImageOperator : DrawablePublishOperator public Setter Source { get; set; } = new(SourceImage.SourceProperty, null); public Setter Transform { get; set; } = new(Drawable.TransformProperty, null); + + public Setter AlignmentX { get; set; } = new(Drawable.AlignmentXProperty, Media.AlignmentX.Center); + + public Setter AlignmentY { get; set; } = new(Drawable.AlignmentYProperty, Media.AlignmentY.Center); + + public Setter TransformOrigin { get; set; } = new(Drawable.TransformOriginProperty, RelativePoint.Center); public Setter Fill { get; set; } = new(Drawable.ForegroundProperty, new SolidColorBrush(Colors.White)); diff --git a/src/Beutl/Services/PropertyEditorService.cs b/src/Beutl/Services/PropertyEditorService.cs index 515050ff0..be474b6dc 100644 --- a/src/Beutl/Services/PropertyEditorService.cs +++ b/src/Beutl/Services/PropertyEditorService.cs @@ -339,6 +339,11 @@ public bool TryCreateControl(IPropertyEditorContext context, [NotNullWhen(true)] control = control1; return true; } + else if (context is PropertyEditorGroupContext) + { + control = new PropertyEditorGroup(); + return true; + } else { control = null; diff --git a/src/Beutl/ViewModels/Editors/BrushEditorViewModel.cs b/src/Beutl/ViewModels/Editors/BrushEditorViewModel.cs index ea3e39145..7ccc1520f 100644 --- a/src/Beutl/ViewModels/Editors/BrushEditorViewModel.cs +++ b/src/Beutl/ViewModels/Editors/BrushEditorViewModel.cs @@ -94,8 +94,6 @@ private void AcceptChildren(PropertiesEditorViewModel? obj) public ReadOnlyReactivePropertySlim IsRadialGradient { get; } - public ReactivePropertySlim IsSeparatorVisible { get; } = new(); - public ReactivePropertySlim IsExpanded { get; } = new(); public override void Reset() @@ -121,8 +119,6 @@ public override void Accept(IPropertyEditorContextVisitor visitor) { AcceptChildren(ChildContext.Value); } - - IsSeparatorVisible.Value = visitor is SourceOperatorViewModel; } public override void ReadFromJson(JsonObject json) diff --git a/src/Beutl/ViewModels/Editors/ImageFilterEditorViewModel.cs b/src/Beutl/ViewModels/Editors/ImageFilterEditorViewModel.cs index de9a68e18..04cf11b5e 100644 --- a/src/Beutl/ViewModels/Editors/ImageFilterEditorViewModel.cs +++ b/src/Beutl/ViewModels/Editors/ImageFilterEditorViewModel.cs @@ -86,14 +86,10 @@ public ImageFilterEditorViewModel(IAbstractProperty property) public ReactivePropertySlim?> Group { get; } = new(); - public ReactivePropertySlim IsSeparatorVisible { get; } = new(); - public override void Accept(IPropertyEditorContextVisitor visitor) { base.Accept(visitor); AcceptChild(); - - IsSeparatorVisible.Value = visitor is SourceOperatorViewModel; } private void AcceptChild() diff --git a/src/Beutl/ViewModels/Editors/PenEditorViewModel.cs b/src/Beutl/ViewModels/Editors/PenEditorViewModel.cs index 5d651a568..13e9de580 100644 --- a/src/Beutl/ViewModels/Editors/PenEditorViewModel.cs +++ b/src/Beutl/ViewModels/Editors/PenEditorViewModel.cs @@ -100,8 +100,6 @@ private void AcceptChildren() public CoreList MinorProperties { get; } = new(); - public ReactivePropertySlim IsSeparatorVisible { get; } = new(); - public override void Reset() { if (GetDefaultValue() is { } defaultValue) @@ -126,8 +124,6 @@ public override void Accept(IPropertyEditorContextVisitor visitor) { AcceptChildren(); } - - IsSeparatorVisible.Value = visitor is SourceOperatorViewModel; } public override void ReadFromJson(JsonObject json) diff --git a/src/Beutl/ViewModels/Editors/PropertyEditorGroupContext.cs b/src/Beutl/ViewModels/Editors/PropertyEditorGroupContext.cs new file mode 100644 index 000000000..4949dae73 --- /dev/null +++ b/src/Beutl/ViewModels/Editors/PropertyEditorGroupContext.cs @@ -0,0 +1,73 @@ +using System.Text.Json.Nodes; + +using Beutl.Framework; + +namespace Beutl.ViewModels.Editors; + +public sealed class PropertyEditorGroupContext : IPropertyEditorContext +{ + private IPropertyEditorContext?[] _properties; + + public PropertyEditorGroupContext(IPropertyEditorContext?[] children, string groupName) + { + _properties = children; + GroupName = groupName; + } + + public IReadOnlyList Properties => _properties; + + public PropertyEditorExtension Extension => PropertyEditorExtension.Instance; + + public string GroupName { get; } + + public void Accept(IPropertyEditorContextVisitor visitor) + { + throw new InvalidOperationException("PropertiesはSourceOperatotViewModelがAcceptしています。"); + } + + public void Dispose() + { + foreach (IPropertyEditorContext? item in _properties.AsSpan()) + { + item?.Dispose(); + } + + _properties = Array.Empty(); + } + + public void ReadFromJson(JsonObject json) + { + if (json.TryGetPropertyValue(nameof(Properties), out JsonNode? childrenNode) + && childrenNode is JsonArray childrenArray) + { + foreach ((JsonNode? node, IPropertyEditorContext? context) in childrenArray.Zip(_properties)) + { + if (context != null && node != null) + { + context.ReadFromJson(node.AsObject()); + } + } + } + } + + public void WriteToJson(JsonObject json) + { + var array = new JsonArray(); + + foreach (IPropertyEditorContext? item in _properties.AsSpan()) + { + if (item == null) + { + array.Add(null); + } + else + { + var node = new JsonObject(); + item.WriteToJson(node); + array.Add(node); + } + } + + json[nameof(Properties)] = array; + } +} diff --git a/src/Beutl/ViewModels/Editors/TransformEditorViewModel.cs b/src/Beutl/ViewModels/Editors/TransformEditorViewModel.cs index 3cda2cb2e..f8b1ad038 100644 --- a/src/Beutl/ViewModels/Editors/TransformEditorViewModel.cs +++ b/src/Beutl/ViewModels/Editors/TransformEditorViewModel.cs @@ -131,14 +131,10 @@ public TransformEditorViewModel(IAbstractProperty property) public ReactivePropertySlim?> Group { get; } = new(); - public ReactivePropertySlim IsSeparatorVisible { get; } = new(); - public override void Accept(IPropertyEditorContextVisitor visitor) { base.Accept(visitor); AcceptChild(); - - IsSeparatorVisible.Value = visitor is SourceOperatorViewModel; } private void AcceptChild() diff --git a/src/Beutl/ViewModels/Tools/SourceOperatorViewModel.cs b/src/Beutl/ViewModels/Tools/SourceOperatorViewModel.cs index d63ed4fbb..3cc7cfaf4 100644 --- a/src/Beutl/ViewModels/Tools/SourceOperatorViewModel.cs +++ b/src/Beutl/ViewModels/Tools/SourceOperatorViewModel.cs @@ -8,6 +8,8 @@ using DynamicData; using Reactive.Bindings; +using Beutl.ViewModels.Editors; +using Microsoft.CodeAnalysis; namespace Beutl.ViewModels.Tools; @@ -114,7 +116,7 @@ public void Dispose() private void Init() { List props = Model.Properties.ToList(); - Properties.EnsureCapacity(props.Count); + var tempItems = new List(props.Count); IAbstractProperty[]? foundItems; PropertyEditorExtension? extension; @@ -125,13 +127,42 @@ private void Init() { if (extension.TryCreateContext(foundItems, out IPropertyEditorContext? context)) { - Properties.Add(context); + tempItems.Add(context); context.Accept(this); } props.RemoveMany(foundItems); } } while (foundItems != null && extension != null); + + foreach ((string? Key, IPropertyEditorContext?[] Value) group in tempItems.GroupBy(x => + { + if (x is BaseEditorViewModel { WrappedProperty: { } abProperty } + && abProperty.GetCoreProperty() is { } coreProperty + && coreProperty.TryGetMetadata(abProperty.ImplementedType, out var metadata)) + { + return metadata.DisplayAttribute?.GetGroupName(); + } + else + { + return null; + } + }) + .Select(x => (x.Key, x.ToArray()))) + { + if (group.Key != null) + { + IPropertyEditorContext?[] array = group.Value; + if (array.Length >= 1) + { + int index = tempItems.IndexOf(array[0]); + tempItems.RemoveMany(array); + tempItems.Insert(index, new PropertyEditorGroupContext(array, group.Key)); + } + } + } + + Properties.AddRange(tempItems); } public void Visit(IPropertyEditorContext context) diff --git a/src/Beutl/Views/Editors/BrushEditor.axaml b/src/Beutl/Views/Editors/BrushEditor.axaml index 0944d92f9..fc62b462b 100644 --- a/src/Beutl/Views/Editors/BrushEditor.axaml +++ b/src/Beutl/Views/Editors/BrushEditor.axaml @@ -14,13 +14,8 @@ x:CompileBindings="True" x:DataType="vm:BrushEditorViewModel" mc:Ignorable="d"> - - - + diff --git a/src/Beutl/Views/Editors/ImageFilterEditor.axaml b/src/Beutl/Views/Editors/ImageFilterEditor.axaml index 21d4ea300..3510d691d 100644 --- a/src/Beutl/Views/Editors/ImageFilterEditor.axaml +++ b/src/Beutl/Views/Editors/ImageFilterEditor.axaml @@ -11,13 +11,8 @@ d:DesignWidth="800" x:DataType="viewModel:ImageFilterEditorViewModel" mc:Ignorable="d"> - - - + @@ -67,7 +62,7 @@ - + - - + diff --git a/src/Beutl/Views/Editors/PropertiesEditor.axaml b/src/Beutl/Views/Editors/PropertiesEditor.axaml index 553fc3c1e..e3d90a77b 100644 --- a/src/Beutl/Views/Editors/PropertiesEditor.axaml +++ b/src/Beutl/Views/Editors/PropertiesEditor.axaml @@ -1,9 +1,11 @@ - + d:DesignHeight="450" + d:DesignWidth="800" + mc:Ignorable="d"> diff --git a/src/Beutl/Views/Editors/PropertiesEditor.axaml.cs b/src/Beutl/Views/Editors/PropertiesEditor.axaml.cs index 9d4e64a5f..5f334036a 100644 --- a/src/Beutl/Views/Editors/PropertiesEditor.axaml.cs +++ b/src/Beutl/Views/Editors/PropertiesEditor.axaml.cs @@ -4,6 +4,7 @@ using Avalonia.Data.Converters; using Beutl.Framework; +using Beutl.ViewModels.Editors; namespace Beutl.Views.Editors; diff --git a/src/Beutl/Views/Editors/PropertyEditorGroup.axaml b/src/Beutl/Views/Editors/PropertyEditorGroup.axaml new file mode 100644 index 000000000..d81029c7b --- /dev/null +++ b/src/Beutl/Views/Editors/PropertyEditorGroup.axaml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Beutl/Views/Editors/PropertyEditorGroup.axaml.cs b/src/Beutl/Views/Editors/PropertyEditorGroup.axaml.cs new file mode 100644 index 000000000..db15e1a51 --- /dev/null +++ b/src/Beutl/Views/Editors/PropertyEditorGroup.axaml.cs @@ -0,0 +1,59 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; + +using static Beutl.Views.Editors.PropertiesEditor; + +namespace Beutl.Views.Editors; + +public partial class PropertyEditorGroup : UserControl +{ + private bool _pressed; + + public PropertyEditorGroup() + { + Resources["ViewModelToViewConverter"] = ViewModelToViewConverter.Instance; + InitializeComponent(); + + separator.PointerPressed += OnSeparatorPointerPressed; + separator.PointerReleased += OnSeparatorPointerReleased; + } + + private void OnSeparatorPointerReleased(object? sender, Avalonia.Input.PointerReleasedEventArgs e) + { + if (_pressed) + { + if (properties.IsVisible) + Hide(); + else + Show(); + + _pressed = false; + } + } + + private void OnSeparatorPointerPressed(object? sender, Avalonia.Input.PointerPressedEventArgs e) + { + if (e.GetCurrentPoint(separator).Properties.IsLeftButtonPressed + && e.ClickCount == 2) + { + _pressed = true; + } + } + + private void ShowClick(object? sender, RoutedEventArgs e) + { + Show(); + } + + private void Hide() + { + properties.IsVisible = false; + summary.IsVisible = true; + } + + private void Show() + { + properties.IsVisible = true; + summary.IsVisible = false; + } +} diff --git a/src/Beutl/Views/Editors/TransformEditor.axaml b/src/Beutl/Views/Editors/TransformEditor.axaml index c9b810d60..0dd19ee1b 100644 --- a/src/Beutl/Views/Editors/TransformEditor.axaml +++ b/src/Beutl/Views/Editors/TransformEditor.axaml @@ -12,13 +12,8 @@ d:DesignWidth="300" x:DataType="viewModel:TransformEditorViewModel" mc:Ignorable="d"> - - - + @@ -63,7 +58,7 @@ - + Date: Sat, 1 Jul 2023 01:56:35 +0900 Subject: [PATCH 67/84] Update DisplacementMap.cs --- src/Beutl.Graphics/Graphics/Filters/DisplacementMap.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Beutl.Graphics/Graphics/Filters/DisplacementMap.cs b/src/Beutl.Graphics/Graphics/Filters/DisplacementMap.cs index a72242ec3..e4e59ee15 100644 --- a/src/Beutl.Graphics/Graphics/Filters/DisplacementMap.cs +++ b/src/Beutl.Graphics/Graphics/Filters/DisplacementMap.cs @@ -68,6 +68,11 @@ public override void ApplyAnimations(IClock clock) (Displacement as Animatable)?.ApplyAnimations(clock); } + public override Rect TransformBounds(Rect rect) + { + return rect.Inflate(_scale / 2); + } + protected internal override SKImageFilter? ToSKImageFilter(Rect bounds) { if (Displacement?.IsEnabled == true) From bed89754ac1cb72f0ca237827abfa57e544e98e5 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Sat, 1 Jul 2023 22:46:54 +0900 Subject: [PATCH 68/84] =?UTF-8?q?Canvas=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Beutl.Graphics/Graphics/Canvas.cs | 232 +++++------------- .../Graphics/CanvasPushedState.cs | 58 +++++ src/Beutl.Graphics/Graphics/Drawable.cs | 48 ++-- .../Graphics/Drawables/VideoFrame.cs | 4 +- src/Beutl.Graphics/Graphics/Effects/Border.cs | 6 +- .../Graphics/Effects/InnerShadow.cs | 4 +- src/Beutl.Graphics/Graphics/ICanvas.cs | 48 +--- src/Beutl.Graphics/Graphics/NullCanvas.cs | 117 --------- src/Beutl.Graphics/Graphics/PushedState.cs | 52 +--- .../Graphics/PushedStateType.cs | 14 -- src/Beutl.Graphics/Graphics/Shapes/Shape.cs | 3 +- .../Graphics/Shapes/TextBlock.cs | 4 +- src/Beutl.Graphics/Graphics/SourceImage.cs | 2 +- src/Beutl.Graphics/Media/Geometry/Geometry.cs | 146 +++++++++-- .../Media/Geometry/GeometryContext.cs | 2 +- .../Rendering/ImmediateRenderer.cs | 2 +- .../TextBlockTests.cs | 5 +- tests/PackageSample/SSETExtenison.cs | 9 +- tests/PackageSample/SampleEditorExtension.cs | 5 + 19 files changed, 322 insertions(+), 439 deletions(-) create mode 100644 src/Beutl.Graphics/Graphics/CanvasPushedState.cs delete mode 100644 src/Beutl.Graphics/Graphics/NullCanvas.cs delete mode 100644 src/Beutl.Graphics/Graphics/PushedStateType.cs diff --git a/src/Beutl.Graphics/Graphics/Canvas.cs b/src/Beutl.Graphics/Graphics/Canvas.cs index 96aa4a0d3..850c8407f 100644 --- a/src/Beutl.Graphics/Graphics/Canvas.cs +++ b/src/Beutl.Graphics/Graphics/Canvas.cs @@ -14,19 +14,14 @@ namespace Beutl.Graphics; // https://github.com/AvaloniaUI/Avalonia/blob/master/src/Skia/Avalonia.Skia/DrawingContextImpl.cs -public class Canvas : ICanvas +public partial class Canvas : ICanvas { private readonly SKSurface _surface; internal readonly SKCanvas _canvas; private readonly Dispatcher? _dispatcher; - private readonly Stack _brushesStack = new(); - private readonly Stack _maskStack = new(); - private readonly Stack _filterStack = new(); - private readonly Stack _penStack = new(); - private readonly Stack _strokeWidthStack = new(); - private readonly Stack _blendModeStack = new(); private readonly SKPaint _sharedFillPaint = new(); private readonly SKPaint _sharedStrokePaint = new(); + private readonly Stack _states = new(); private Matrix _currentTransform; public Canvas(int width, int height) @@ -48,21 +43,6 @@ public Canvas(int width, int height) public bool IsDisposed { get; private set; } - public IBrush FillBrush { get; set; } = Brushes.White; - - public IPen? Pen { get; set; } - - public IImageFilter? Filter - { - get - { - if (_filterStack.Count == 0) - return null; - - return _filterStack.PeekOrDefault(default).ImageFilter; - } - } - public BlendMode BlendMode { get; set; } = BlendMode.SrcOver; public PixelSize Size { get; } @@ -128,15 +108,15 @@ void DisposeCore() } } - public void DrawBitmap(IBitmap bmp) + public void DrawBitmap(IBitmap bmp, IBrush? fill, IPen? pen) { if (bmp.ByteCount <= 0) return; VerifyAccess(); var size = new Size(bmp.Width, bmp.Height); - ConfigureFillPaint(size); - ConfigureStrokePaint(new Rect(size)); + ConfigureFillPaint(size, fill); + ConfigureStrokePaint(new Rect(size), pen); if (bmp is Bitmap) { @@ -151,18 +131,17 @@ public void DrawBitmap(IBitmap bmp) } } - public void DrawCircle(Rect rect) + public void DrawEllipse(Rect rect, IBrush? fill, IPen? pen) { VerifyAccess(); - ConfigureFillPaint(rect.Size); + ConfigureFillPaint(rect.Size, fill); _canvas.DrawOval(rect.ToSKRect(), _sharedFillPaint); - IPen? pen = Pen; if (pen != null && pen.Thickness != 0) { if (pen.StrokeAlignment == StrokeAlignment.Center) { - ConfigureStrokePaint(rect); + ConfigureStrokePaint(rect, pen); _canvas.DrawOval(rect.ToSKRect(), _sharedStrokePaint); } else @@ -170,24 +149,23 @@ public void DrawCircle(Rect rect) using (var path = new SKPath()) { path.AddOval(rect.ToSKRect()); - DrawSKPath(path, true); + DrawSKPath(path, true, fill, pen); } } } } - public void DrawRect(Rect rect) + public void DrawRectangle(Rect rect, IBrush? fill, IPen? pen) { VerifyAccess(); - ConfigureFillPaint(rect.Size); + ConfigureFillPaint(rect.Size, fill); _canvas.DrawRect(rect.ToSKRect(), _sharedFillPaint); - IPen? pen = Pen; if (pen != null && pen.Thickness != 0) { if (pen.StrokeAlignment == StrokeAlignment.Center) { - ConfigureStrokePaint(rect); + ConfigureStrokePaint(rect, pen); _canvas.DrawRect(rect.ToSKRect(), _sharedStrokePaint); } else @@ -195,28 +173,27 @@ public void DrawRect(Rect rect) using (var path = new SKPath()) { path.AddRect(rect.ToSKRect()); - DrawSKPath(path, true); + DrawSKPath(path, true, fill, pen); } } } } - public void DrawText(FormattedText text) + public void DrawText(FormattedText text, IBrush? fill, IPen? pen) { VerifyAccess(); var typeface = new Typeface(text.Font, text.Style, text.Weight); Size size = text.Bounds; SKTypeface sktypeface = typeface.ToSkia(); - ConfigureFillPaint(size); + ConfigureFillPaint(size, fill); _sharedFillPaint.TextSize = text.Size; _sharedFillPaint.Typeface = sktypeface; - IPen? pen = Pen; bool enableStroke = pen != null && pen.Thickness != 0; if (enableStroke) { - ConfigureStrokePaint(new Rect(size)); + ConfigureStrokePaint(new Rect(size), pen); _sharedStrokePaint.TextSize = text.Size; _sharedStrokePaint.Typeface = sktypeface; } @@ -272,20 +249,19 @@ public void DrawText(FormattedText text) } } - internal void DrawSKPath(SKPath skPath, bool strokeOnly) + internal void DrawSKPath(SKPath skPath, bool strokeOnly, IBrush? fill, IPen? pen) { Rect rect = skPath.Bounds.ToGraphicsRect(); if (!strokeOnly) { - ConfigureFillPaint(rect.Size); + ConfigureFillPaint(rect.Size, fill); _canvas.DrawPath(skPath, _sharedFillPaint); } - IPen? pen = Pen; if (pen != null && pen.Thickness != 0) { - ConfigureStrokePaint(rect); + ConfigureStrokePaint(rect, pen); switch (pen.StrokeAlignment) { case StrokeAlignment.Center: @@ -309,11 +285,11 @@ internal void DrawSKPath(SKPath skPath, bool strokeOnly) } } - public void DrawGeometry(Geometry geometry) + public void DrawGeometry(Geometry geometry, IBrush? fill, IPen? pen) { VerifyAccess(); SKPath skPath = geometry.GetNativeObject(); - DrawSKPath(skPath, false); + DrawSKPath(skPath, false, fill, pen); } public unsafe Bitmap GetBitmap() @@ -326,54 +302,54 @@ public unsafe Bitmap GetBitmap() return result; } - public PushedState PushPen(IPen? pen) + public void Pop(int count = -1) { VerifyAccess(); - int level = _brushesStack.Count; - _penStack.Push(Pen); - Pen = pen; - return new PushedState(this, level, PushedStateType.Pen); + + if (count < 0) + { + if (_states.TryPop(out CanvasPushedState? state)) + { + state.Pop(this); + } + } + else + { + while (_states.Count >= count + && _states.TryPop(out CanvasPushedState? state)) + { + state.Pop(this); + } + } } - public void PopPen(int level = -1) + public PushedState Push() { VerifyAccess(); - level = level < 0 ? _penStack.Count - 1 : level; + int count = _canvas.Save(); - while (_penStack.Count > level && - _penStack.TryPop(out IPen? state)) - { - Pen = state; - } + _states.Push(new CanvasPushedState.SKCanvasPushedState(count)); + return new PushedState(this, _states.Count); } public PushedState PushClip(Rect clip, ClipOperation operation = ClipOperation.Intersect) { VerifyAccess(); - int level = _canvas.Save(); + int count = _canvas.Save(); ClipRect(clip, operation); - return new PushedState(this, level, PushedStateType.Clip); - } - public void PopClip(int level = -1) - { - VerifyAccess(); - _canvas.RestoreToCount(level); - _currentTransform = _canvas.TotalMatrix.ToMatrix(); + _states.Push(new CanvasPushedState.SKCanvasPushedState(count)); + return new PushedState(this, _states.Count); } - public PushedState PushCanvas() + public PushedState PushClip(Geometry geometry, ClipOperation operation = ClipOperation.Intersect) { VerifyAccess(); - int level = _canvas.Save(); - return new PushedState(this, level, PushedStateType.Canvas); - } + int count = _canvas.Save(); + ClipPath(geometry, operation); - public void PopCanvas(int level = -1) - { - VerifyAccess(); - _canvas.RestoreToCount(level); - _currentTransform = _canvas.TotalMatrix.ToMatrix(); + _states.Push(new CanvasPushedState.SKCanvasPushedState(count)); + return new PushedState(this, _states.Count); } public PushedState PushOpacityMask(IBrush mask, Rect bounds, bool invert = false) @@ -381,49 +357,10 @@ public PushedState PushOpacityMask(IBrush mask, Rect bounds, bool invert = false VerifyAccess(); var paint = new SKPaint(); - int level = _canvas.SaveLayer(paint); + int count = _canvas.SaveLayer(paint); ConfigurePaint(paint, bounds.Size, mask, (BlendMode)paint.BlendMode); - _maskStack.Push(new MaskInfo(invert, paint)); - return new PushedState(this, level, PushedStateType.OpacityMask); - } - - public void PopOpacityMask(int level = -1) - { - VerifyAccess(); - MaskInfo maskInfo = _maskStack.Pop(); - _sharedFillPaint.Reset(); - _sharedFillPaint.BlendMode = maskInfo.Invert ? SKBlendMode.DstOut : SKBlendMode.DstIn; - - _canvas.SaveLayer(_sharedFillPaint); - using (SKPaint maskPaint = maskInfo.Paint) - { - _canvas.DrawPaint(maskPaint); - } - - _canvas.Restore(); - - _canvas.RestoreToCount(level); - } - - public PushedState PushFillBrush(IBrush brush) - { - VerifyAccess(); - int level = _brushesStack.Count; - _brushesStack.Push(FillBrush); - FillBrush = brush; - return new PushedState(this, level, PushedStateType.FillBrush); - } - - public void PopFillBrush(int level = -1) - { - VerifyAccess(); - level = level < 0 ? _brushesStack.Count - 1 : level; - - while (_brushesStack.Count > level && - _brushesStack.TryPop(out IBrush? state)) - { - FillBrush = state; - } + _states.Push(new CanvasPushedState.MaskPushedState(count, invert, paint)); + return new PushedState(this, _states.Count); } public PushedState PushImageFilter(IImageFilter filter, Rect bounds) @@ -438,49 +375,16 @@ public PushedState PushImageFilter(IImageFilter filter, Rect bounds) }; } - int level = _canvas.SaveLayer(paint); - - _filterStack.Push(new ImageFilterInfo(filter, paint, level)); - return new PushedState(this, level, PushedStateType.Filter); - } - - public void PopImageFilter(int level = -1) - { - VerifyAccess(); - while (_filterStack.TryPop(out ImageFilterInfo state) - && state.SaveCount >= level) - { - state.Paint.Dispose(); - } - - _canvas.RestoreToCount(level); - } - - public PushedState PushBlendMode(BlendMode blendMode) - { - VerifyAccess(); - int level = _blendModeStack.Count; - _blendModeStack.Push(BlendMode); - BlendMode = blendMode; - return new PushedState(this, level, PushedStateType.BlendMode); - } - - public void PopBlendMode(int level = -1) - { - VerifyAccess(); - level = level < 0 ? _blendModeStack.Count - 1 : level; + int count = _canvas.SaveLayer(paint); - while (_blendModeStack.Count > level && - _blendModeStack.TryPop(out BlendMode state)) - { - BlendMode = state; - } + _states.Push(new CanvasPushedState.ImageFilterPushedState(count, filter, paint)); + return new PushedState(this, _states.Count); } public PushedState PushTransform(Matrix matrix, TransformOperator transformOperator = TransformOperator.Prepend) { VerifyAccess(); - int level = _canvas.Save(); + int count = _canvas.Save(); if (transformOperator == TransformOperator.Prepend) { @@ -495,14 +399,15 @@ public PushedState PushTransform(Matrix matrix, TransformOperator transformOpera Transform = matrix; } - return new PushedState(this, level, PushedStateType.Transform); + _states.Push(new CanvasPushedState.SKCanvasPushedState(count)); + return new PushedState(this, _states.Count); } - public void PopTransform(int level = -1) + public PushedState PushBlendMode(BlendMode blendMode) { VerifyAccess(); - _canvas.RestoreToCount(level); - _currentTransform = _canvas.TotalMatrix.ToMatrix(); + _states.Push(new CanvasPushedState.BlendModePushedState(blendMode)); + return new PushedState(this, _states.Count); } private void VerifyAccess() @@ -721,10 +626,9 @@ private static void ConfigureTileBrush(SKPaint paint, Size targetSize, ITileBrus } } - private void ConfigureStrokePaint(Rect rect) + private void ConfigureStrokePaint(Rect rect, IPen? pen) { _sharedStrokePaint.Reset(); - IPen? pen = Pen; if (pen != null && pen.Thickness != 0) { @@ -776,10 +680,10 @@ private void ConfigureStrokePaint(Rect rect) } } - private void ConfigureFillPaint(Size targetSize) + private void ConfigureFillPaint(Size targetSize, IBrush? brush) { _sharedFillPaint.Reset(); - ConfigurePaint(_sharedFillPaint, targetSize, FillBrush, BlendMode); + ConfigurePaint(_sharedFillPaint, targetSize, brush, BlendMode); } internal static void ConfigurePaint(SKPaint paint, Size targetSize, IBrush? brush, BlendMode blendMode) @@ -808,12 +712,6 @@ internal static void ConfigurePaint(SKPaint paint, Size targetSize, IBrush? brus } } - private readonly record struct MaskInfo(bool Invert, SKPaint Paint); - - private readonly record struct ImageFilterInfo(IImageFilter ImageFilter, SKPaint Paint, int SaveCount); - - private readonly record struct PenInfo(IPen? Pen, SKPaint? Paint, int SaveCount); - private readonly struct TileBrushCalculator { private readonly Size _imageSize; diff --git a/src/Beutl.Graphics/Graphics/CanvasPushedState.cs b/src/Beutl.Graphics/Graphics/CanvasPushedState.cs new file mode 100644 index 000000000..fbd1cac55 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/CanvasPushedState.cs @@ -0,0 +1,58 @@ +using Beutl.Graphics.Filters; + +using SkiaSharp; + +namespace Beutl.Graphics; + +public partial class Canvas +{ + private abstract record CanvasPushedState + { + internal record SKCanvasPushedState(int Count) : CanvasPushedState + { + public override void Pop(Canvas canvas) + { + canvas._canvas.RestoreToCount(Count); + canvas._currentTransform = canvas._canvas.TotalMatrix.ToMatrix(); + } + } + + internal record MaskPushedState(int Count, bool Invert, SKPaint Paint) : CanvasPushedState + { + public override void Pop(Canvas canvas) + { + canvas._sharedFillPaint.Reset(); + canvas._sharedFillPaint.BlendMode = Invert ? SKBlendMode.DstOut : SKBlendMode.DstIn; + + canvas._canvas.SaveLayer(canvas._sharedFillPaint); + using (SKPaint maskPaint = Paint) + { + canvas._canvas.DrawPaint(maskPaint); + } + + canvas._canvas.Restore(); + + canvas._canvas.RestoreToCount(Count); + } + } + + internal record ImageFilterPushedState(int Count, IImageFilter ImageFilter, SKPaint Paint) : CanvasPushedState + { + public override void Pop(Canvas canvas) + { + canvas._canvas.RestoreToCount(Count); + Paint.Dispose(); + } + } + + internal record BlendModePushedState(BlendMode BlendMode) : CanvasPushedState + { + public override void Pop(Canvas canvas) + { + canvas.BlendMode = BlendMode; + } + } + + public abstract void Pop(Canvas canvas); + } +} diff --git a/src/Beutl.Graphics/Graphics/Drawable.cs b/src/Beutl.Graphics/Graphics/Drawable.cs index 1dd3e259a..9cd97d090 100644 --- a/src/Beutl.Graphics/Graphics/Drawable.cs +++ b/src/Beutl.Graphics/Graphics/Drawable.cs @@ -28,7 +28,7 @@ public abstract class Drawable : Renderable, IDrawable, IHierarchical private AlignmentX _alignX = AlignmentX.Center; private AlignmentY _alignY = AlignmentY.Center; private RelativePoint _transformOrigin = RelativePoint.Center; - private IBrush? _foreground; + private IBrush? _foreground = new SolidColorBrush(Colors.White); private IBrush? _opacityMask; private BlendMode _blendMode = BlendMode.SrcOver; @@ -159,7 +159,6 @@ public IBitmap ToBitmap() if (width > 0 && height > 0) { using (var canvas = new Canvas(width, height)) - using (Foreground == null ? new() : canvas.PushFillBrush(Foreground)) { OnDraw(canvas); return canvas.GetBitmap(); @@ -226,7 +225,6 @@ private void HasBitmapEffect(ICanvas canvas) } rect = rect.TransformToAABB(transform); - using (Foreground == null ? new() : canvas.PushFillBrush(Foreground)) using (canvas.PushBlendMode(BlendMode)) using (_filter == null ? new() : canvas.PushImageFilter(_filter, rect)) using (canvas.PushTransform(transformFact)) @@ -250,7 +248,7 @@ private void HasBitmapEffect(ICanvas canvas) //} //else { - canvas.DrawBitmap(outBmp); + canvas.DrawBitmap(outBmp, Brushes.White, null); } outBmp.Dispose(); @@ -285,8 +283,6 @@ public void Draw(ICanvas canvas) Matrix transform = GetTransformMatrix(availableSize, size); Rect transformedBounds = rect.TransformToAABB(transform); - - using (Foreground == null ? new() : canvas.PushFillBrush(Foreground)) using (canvas.PushBlendMode(BlendMode)) using (_filter == null ? new() : canvas.PushImageFilter(_filter, /*new Rect(size)*/transformedBounds)) using (canvas.PushTransform(transform)) @@ -328,27 +324,35 @@ public override void ApplyAnimations(IClock clock) private Point CalculateTranslate(Size bounds, Size canvasSize) { - float x = -bounds.Width / 2; - float y = -bounds.Height / 2; + float x = 0; + float y = 0; - switch (AlignmentX) + if (float.IsFinite(canvasSize.Width)) { - case AlignmentX.Center: - x += canvasSize.Width / 2; - break; - case AlignmentX.Right: - x += canvasSize.Width; - break; + x = -bounds.Width / 2; + switch (AlignmentX) + { + case AlignmentX.Center: + x += canvasSize.Width / 2; + break; + case AlignmentX.Right: + x += canvasSize.Width; + break; + } } - switch (AlignmentY) + if (float.IsFinite(canvasSize.Height)) { - case AlignmentY.Center: - y += canvasSize.Height / 2; - break; - case AlignmentY.Bottom: - y += canvasSize.Height; - break; + y = -bounds.Height / 2; + switch (AlignmentY) + { + case AlignmentY.Center: + y += canvasSize.Height / 2; + break; + case AlignmentY.Bottom: + y += canvasSize.Height; + break; + } } return new Point(x, y); diff --git a/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs b/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs index 4ebcf6da9..5ef2f2c8d 100644 --- a/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs +++ b/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs @@ -169,12 +169,12 @@ protected override void OnDraw(ICanvas canvas) if (_previousBitmap?.IsDisposed == false && MathUtilities.AreClose(frameNum, _previousFrame)) { - canvas.DrawBitmap(_previousBitmap); + canvas.DrawBitmap(_previousBitmap, Foreground, null); } else if (_mediaReader.ReadVideo((int)frameNum, out IBitmap? bmp) && bmp?.IsDisposed == false) { - canvas.DrawBitmap(bmp); + canvas.DrawBitmap(bmp, Foreground, null); _previousBitmap?.Dispose(); _previousBitmap = bmp; diff --git a/src/Beutl.Graphics/Graphics/Effects/Border.cs b/src/Beutl.Graphics/Graphics/Effects/Border.cs index a6a014e33..80824cfba 100644 --- a/src/Beutl.Graphics/Graphics/Effects/Border.cs +++ b/src/Beutl.Graphics/Graphics/Effects/Border.cs @@ -174,7 +174,7 @@ public void Process(in Bitmap src, out Bitmap dst) if (style == BorderStyles.Foreground) { using (canvas.PushTransform(srcTranslate)) - canvas.DrawBitmap(src); + canvas.DrawBitmap(src, Brushes.White, null); } float xx = -(offset.X - Math.Max(offset.X, thickness)) - thickness; @@ -183,13 +183,13 @@ public void Process(in Bitmap src, out Bitmap dst) using (canvas.PushTransform(Matrix.CreateTranslation(offset.X + xx, offset.Y + yy))) using (maskBrush != null ? canvas.PushOpacityMask(maskBrush, borderRect, maskType == MaskTypes.Invert) : new()) { - canvas.DrawBitmap(border); + canvas.DrawBitmap(border, Brushes.White, null); } if (style == BorderStyles.Background) { using (canvas.PushTransform(srcTranslate)) - canvas.DrawBitmap(src); + canvas.DrawBitmap(src, Brushes.White, null); } dst = canvas.GetBitmap(); diff --git a/src/Beutl.Graphics/Graphics/Effects/InnerShadow.cs b/src/Beutl.Graphics/Graphics/Effects/InnerShadow.cs index 475723a7e..e152c9ab3 100644 --- a/src/Beutl.Graphics/Graphics/Effects/InnerShadow.cs +++ b/src/Beutl.Graphics/Graphics/Effects/InnerShadow.cs @@ -105,10 +105,10 @@ public void Process(in Bitmap src, out Bitmap dst) maskBrush.Transform = null; canvas.Clear(); - canvas.DrawBitmap(src); + canvas.DrawBitmap(src, Brushes.White, null); using (canvas.PushOpacityMask(maskBrush, rect, false)) { - canvas.DrawBitmap(blurred); + canvas.DrawBitmap(blurred, Brushes.White, null); } dst = canvas.GetBitmap(); diff --git a/src/Beutl.Graphics/Graphics/ICanvas.cs b/src/Beutl.Graphics/Graphics/ICanvas.cs index ccbdab1e9..6e1041e3f 100644 --- a/src/Beutl.Graphics/Graphics/ICanvas.cs +++ b/src/Beutl.Graphics/Graphics/ICanvas.cs @@ -13,12 +13,6 @@ public interface ICanvas : IDisposable bool IsDisposed { get; } - IBrush FillBrush { get; set; } - - IPen? Pen { get; set; } - - IImageFilter? Filter { get; } - BlendMode BlendMode { get; set; } Matrix Transform { get; set; } @@ -27,53 +21,33 @@ public interface ICanvas : IDisposable void Clear(Color color); - void ClipRect(Rect clip, ClipOperation operation = ClipOperation.Intersect); - - void ClipPath(Geometry geometry, ClipOperation operation = ClipOperation.Intersect); - - void DrawBitmap(IBitmap bmp); + void DrawBitmap(IBitmap bmp, IBrush? fill, IPen? pen); - void DrawCircle(Rect rect); + void DrawEllipse(Rect rect, IBrush? fill, IPen? pen); - void DrawRect(Rect rect); + void DrawRectangle(Rect rect, IBrush? fill, IPen? pen); - void DrawGeometry(Geometry geometry); + void DrawGeometry(Geometry geometry, IBrush? fill, IPen? pen); - void DrawText(FormattedText text); + void DrawText(FormattedText text, IBrush? fill, IPen? pen); Bitmap GetBitmap(); - PushedState PushClip(Rect clip, ClipOperation operation = ClipOperation.Intersect); - - void PopClip(int level = -1); + void Pop(int count = -1); - PushedState PushCanvas(); + PushedState Push(); - void PopCanvas(int level = -1); - - PushedState PushOpacityMask(IBrush mask, Rect bounds, bool invert = false); - - void PopOpacityMask(int level = -1); - - PushedState PushFillBrush(IBrush brush); - - void PopFillBrush(int level = -1); + PushedState PushClip(Rect clip, ClipOperation operation = ClipOperation.Intersect); - PushedState PushPen(IPen? pen); + PushedState PushClip(Geometry geometry, ClipOperation operation = ClipOperation.Intersect); - void PopPen(int level = -1); + PushedState PushOpacityMask(IBrush mask, Rect bounds, bool invert = false); PushedState PushImageFilter(IImageFilter filter, Rect bounds); - void PopImageFilter(int level = -1); - PushedState PushBlendMode(BlendMode blendMode); - void PopBlendMode(int level = -1); - PushedState PushTransform(Matrix matrix, TransformOperator transformOperator = TransformOperator.Prepend); - - void PopTransform(int level = -1); } public enum TransformOperator @@ -82,5 +56,5 @@ public enum TransformOperator Append, - Assign + Set } diff --git a/src/Beutl.Graphics/Graphics/NullCanvas.cs b/src/Beutl.Graphics/Graphics/NullCanvas.cs deleted file mode 100644 index a3bf5ac35..000000000 --- a/src/Beutl.Graphics/Graphics/NullCanvas.cs +++ /dev/null @@ -1,117 +0,0 @@ -using Beutl.Graphics.Filters; -using Beutl.Media; -using Beutl.Media.Pixel; -using Beutl.Media.TextFormatting; - -using SkiaSharp; - -namespace Beutl.Graphics; - -internal sealed class NullCanvas : ICanvas -{ - public PixelSize Size => throw new NotImplementedException(); - - public bool IsDisposed => throw new NotImplementedException(); - - public IBrush FillBrush - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - public IImageFilter? Filter - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - public float StrokeWidth - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - - public BlendMode BlendMode - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - public Matrix Transform - { - get => throw new NotImplementedException(); - set => throw new NotImplementedException(); - } - public IPen? Pen { get; set; } - - public void Clear() => throw new NotImplementedException(); - - public void Clear(Color color) => throw new NotImplementedException(); - - public void ClipRect(Rect clip, ClipOperation operation = ClipOperation.Intersect) => throw new NotImplementedException(); - - public void ClipPath(SKPath path, ClipOperation operation = ClipOperation.Intersect) => throw new NotImplementedException(); - - public void Dispose() => throw new NotImplementedException(); - - public void DrawBitmap(IBitmap bmp) => throw new NotImplementedException(); - - public void DrawCircle(Size size) => throw new NotImplementedException(); - - public void DrawRect(Size size) => throw new NotImplementedException(); - - public void DrawText(FormattedText text) => throw new NotImplementedException(); - - public void FillCircle(Size size) => throw new NotImplementedException(); - - public void FillRect(Size size) => throw new NotImplementedException(); - - public Bitmap GetBitmap() => throw new NotImplementedException(); - - public void PopBlendMode(int level = -1) => throw new NotImplementedException(); - - public void PopCanvas(int level = -1) => throw new NotImplementedException(); - - public void PopClip(int level = -1) => throw new NotImplementedException(); - - public void PopImageFilter(int level = -1) => throw new NotImplementedException(); - - public void PopFillBrush(int level = -1) => throw new NotImplementedException(); - - public void PopOpacityMask(int level = -1) => throw new NotImplementedException(); - - public void PopStrokeWidth(int level = -1) => throw new NotImplementedException(); - - public void PopTransform(int level = -1) => throw new NotImplementedException(); - - public PushedState PushBlendMode(BlendMode blendMode) => throw new NotImplementedException(); - - public PushedState PushCanvas() => throw new NotImplementedException(); - - public PushedState PushClip(Rect clip, ClipOperation operation = ClipOperation.Intersect) => throw new NotImplementedException(); - - public PushedState PushImageFilter(IImageFilter? filter, Rect bounds) => throw new NotImplementedException(); - - public PushedState PushFillBrush(IBrush brush) => throw new NotImplementedException(); - - public PushedState PushOpacityMask(IBrush mask, Rect bounds, bool invert = false) => throw new NotImplementedException(); - - public PushedState PushStrokeWidth(float strokeWidth) => throw new NotImplementedException(); - - public PushedState PushTransform(Matrix matrix, TransformOperator transformOperator = TransformOperator.Prepend) => throw new NotImplementedException(); - - public void RotateDegrees(float degrees) => throw new NotImplementedException(); - - public void RotateRadians(float radians) => throw new NotImplementedException(); - - public void Scale(Vector vector) => throw new NotImplementedException(); - - public void Skew(Vector vector) => throw new NotImplementedException(); - - public void Translate(Vector vector) => throw new NotImplementedException(); - public PushedState PushPen(IPen? pen) => throw new NotImplementedException(); - public void PopPen(int level = -1) => throw new NotImplementedException(); - public void ClipPath(Geometry geometry, ClipOperation operation = ClipOperation.Intersect) => throw new NotImplementedException(); - public void DrawCircle(Rect rect) => throw new NotImplementedException(); - public void DrawRect(Rect rect) => throw new NotImplementedException(); - public void DrawGeometry(Geometry geometry) => throw new NotImplementedException(); -} diff --git a/src/Beutl.Graphics/Graphics/PushedState.cs b/src/Beutl.Graphics/Graphics/PushedState.cs index 2b3f788cc..62487a029 100644 --- a/src/Beutl.Graphics/Graphics/PushedState.cs +++ b/src/Beutl.Graphics/Graphics/PushedState.cs @@ -2,63 +2,23 @@ public readonly record struct PushedState : IDisposable { - private static readonly NullCanvas s_nullCanvas = new(); - - public PushedState( - ICanvas canvas, - int level, - PushedStateType type) + public PushedState(ICanvas canvas, int level) { Canvas = canvas; - Level = level; - Type = type; + Count = level; } public PushedState() { - Canvas = s_nullCanvas; - Level = -1; - Type = PushedStateType.None; + Count = -1; } - public ICanvas Canvas { get; init; } - - public int Level { get; init; } + public ICanvas? Canvas { get; init; } - public PushedStateType Type { get; init; } + public int Count { get; init; } public void Dispose() { - switch (Type) - { - case PushedStateType.None: - break; - case PushedStateType.FillBrush: - Canvas.PopFillBrush(Level); - break; - case PushedStateType.Filter: - Canvas.PopImageFilter(Level); - break; - case PushedStateType.BlendMode: - Canvas.PopBlendMode(Level); - break; - case PushedStateType.Transform: - Canvas.PopTransform(Level); - break; - case PushedStateType.Clip: - Canvas.PopClip(Level); - break; - case PushedStateType.OpacityMask: - Canvas.PopOpacityMask(Level); - break; - case PushedStateType.Canvas: - Canvas.PopCanvas(Level); - break; - case PushedStateType.Pen: - Canvas.PopPen(Level); - break; - default: - break; - } + Canvas?.Pop(Count); } } diff --git a/src/Beutl.Graphics/Graphics/PushedStateType.cs b/src/Beutl.Graphics/Graphics/PushedStateType.cs deleted file mode 100644 index ef8e09265..000000000 --- a/src/Beutl.Graphics/Graphics/PushedStateType.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Beutl.Graphics; - -public enum PushedStateType -{ - None, - FillBrush, - Filter, - BlendMode, - Transform, - Clip, - OpacityMask, - Canvas, - Pen -} diff --git a/src/Beutl.Graphics/Graphics/Shapes/Shape.cs b/src/Beutl.Graphics/Graphics/Shapes/Shape.cs index d4384e1d5..bd6e31b26 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/Shape.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/Shape.cs @@ -246,9 +246,8 @@ protected override void OnDraw(ICanvas canvas) matrix *= Matrix.CreateScale(scale); using (canvas.PushTransform(matrix)) - using (canvas.PushPen(Pen)) { - canvas.DrawGeometry(geometry); + canvas.DrawGeometry(geometry, Foreground, Pen); } } diff --git a/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs b/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs index 3465bf773..bc10b5732 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs @@ -254,9 +254,7 @@ protected override void OnDraw(ICanvas canvas) canvas.Transform = Matrix.CreateTranslation(prevRight, 0) * canvas.Transform; Size elementBounds = item.Bounds; - using (item.Brush != null ? canvas.PushFillBrush(item.Brush) : default) - using (canvas.PushPen(item.Pen)) - canvas.DrawText(item); + canvas.DrawText(item, item.Brush ?? Foreground, item.Pen ?? Pen); prevRight = elementBounds.Width + item.Margin.Right; } diff --git a/src/Beutl.Graphics/Graphics/SourceImage.cs b/src/Beutl.Graphics/Graphics/SourceImage.cs index 3d722e100..06e722414 100644 --- a/src/Beutl.Graphics/Graphics/SourceImage.cs +++ b/src/Beutl.Graphics/Graphics/SourceImage.cs @@ -42,7 +42,7 @@ protected override void OnDraw(ICanvas canvas) { using (bitmap) { - canvas.DrawBitmap(bitmap); + canvas.DrawBitmap(bitmap, Brushes.White, null); } } } diff --git a/src/Beutl.Graphics/Media/Geometry/Geometry.cs b/src/Beutl.Graphics/Media/Geometry/Geometry.cs index e04ea0e0d..b356fb98b 100644 --- a/src/Beutl.Graphics/Media/Geometry/Geometry.cs +++ b/src/Beutl.Graphics/Media/Geometry/Geometry.cs @@ -11,6 +11,7 @@ public abstract class Geometry : Animatable, IAffectsRender public static readonly CoreProperty FillTypeProperty; public static readonly CoreProperty TransformProperty; private readonly GeometryContext _context = new(); + private PathCache _pathCache; private PathFillType _fillType; private ITransform? _transform; private bool _isDirty = true; @@ -36,6 +37,7 @@ public Geometry() ~Geometry() { _context.Dispose(); + _pathCache.Invalidate(); } public event EventHandler? Invalidated; @@ -52,20 +54,7 @@ public ITransform? Transform set => SetAndRaise(TransformProperty, ref _transform, value); } - public Rect Bounds - { - get - { - if (_isDirty) - { - _context.Clear(); - ApplyTo(_context); - _isDirty = false; - } - - return _context.Bounds; - } - } + public Rect Bounds => GetNativeObject().TightBounds.ToGraphicsRect(); protected static void AffectsRender(params CoreProperty[] properties) where T : Geometry @@ -115,6 +104,7 @@ public virtual void ApplyTo(IGeometryContext context) private void OnInvalidated(object? sender, RenderInvalidatedEventArgs e) { _isDirty = true; + _pathCache.Invalidate(); } internal SKPath GetNativeObject() @@ -128,4 +118,132 @@ internal SKPath GetNativeObject() return _context.NativeObject; } + + public bool FillContains(Point point) + { + return PathContainsCore(GetNativeObject(), point); + } + + public bool StrokeContains(IPen? pen, Point point) + { + if (pen == null) return false; + + float strokeWidth = pen.Thickness; + StrokeAlignment alignment = pen.StrokeAlignment; + + if (!_pathCache.HasCacheFor(strokeWidth, alignment)) + { + UpdatePathCache(strokeWidth, alignment); + } + + return PathContainsCore(_pathCache.CachedStrokePath, point); + } + + private void UpdatePathCache(float strokeWidth, StrokeAlignment alignment) + { + var strokePath = new SKPath(); + + if (Math.Abs(strokeWidth) < float.Epsilon) + { + _pathCache.Cache(strokePath, strokeWidth, alignment, Bounds); + } + else + { + using (var paint = new SKPaint()) + { + paint.IsStroke = true; + SKPath fillPath = GetNativeObject(); + + switch (alignment) + { + case StrokeAlignment.Center: + paint.StrokeWidth = strokeWidth; + paint.GetFillPath(fillPath, strokePath); + break; + case StrokeAlignment.Inside or StrokeAlignment.Outside: + paint.StrokeWidth = strokeWidth * 2; + paint.GetFillPath(fillPath, strokePath); + + using (var strokePathCopy = new SKPath(strokePath)) + { + strokePathCopy.Op( + fillPath, + alignment == StrokeAlignment.Inside ? SKPathOp.Intersect : SKPathOp.Difference, + strokePath); + } + break; + default: + break; + } + } + + _pathCache.Cache(strokePath, strokeWidth, alignment, strokePath.TightBounds.ToGraphicsRect()); + } + } + + private static bool PathContainsCore(SKPath? path, Point point) + { + return path is not null && path.Contains(point.X, point.Y); + } + + public Rect GetRenderBounds(IPen? pen) + { + if (pen == null) + { + return Bounds; + } + else + { + float strokeWidth = pen.Thickness; + StrokeAlignment alignment = pen.StrokeAlignment; + + if (!_pathCache.HasCacheFor(strokeWidth, alignment)) + { + UpdatePathCache(strokeWidth, alignment); + } + + return _pathCache.CachedGeometryRenderBounds; + } + } + + private struct PathCache + { + private float _cachedStrokeWidth; + private StrokeAlignment _cachedStrokeAlignment; + + public const float Tolerance = float.Epsilon; + + public SKPath? CachedStrokePath { get; private set; } + + public Rect CachedGeometryRenderBounds { get; private set; } + + public readonly bool HasCacheFor(float strokeWidth, StrokeAlignment strokeAlignment) + { + return CachedStrokePath != null + && Math.Abs(_cachedStrokeWidth - strokeWidth) < Tolerance + && _cachedStrokeAlignment == strokeAlignment; + } + + public void Cache(SKPath path, float strokeWidth, StrokeAlignment strokeAlignment, Rect geometryRenderBounds) + { + if (CachedStrokePath != path) + { + CachedStrokePath?.Dispose(); + } + + CachedStrokePath = path; + CachedGeometryRenderBounds = geometryRenderBounds; + _cachedStrokeWidth = strokeWidth; + _cachedStrokeAlignment = strokeAlignment; + } + + public void Invalidate() + { + CachedStrokePath?.Dispose(); + CachedStrokePath = null; + CachedGeometryRenderBounds = default; + _cachedStrokeWidth = default; + _cachedStrokeAlignment = default; + } + } } diff --git a/src/Beutl.Graphics/Media/Geometry/GeometryContext.cs b/src/Beutl.Graphics/Media/Geometry/GeometryContext.cs index 957b47d8b..1662b699c 100644 --- a/src/Beutl.Graphics/Media/Geometry/GeometryContext.cs +++ b/src/Beutl.Graphics/Media/Geometry/GeometryContext.cs @@ -24,7 +24,7 @@ public PathFillType FillType public Point LastPoint => NativeObject.LastPoint.ToGraphicsPoint(); - public Rect Bounds => NativeObject.Bounds.ToGraphicsRect(); + public Rect Bounds => NativeObject.TightBounds.ToGraphicsRect(); public void ArcTo(Size radius, float angle, bool isLargeArc, bool sweepClockwise, Point point) { diff --git a/src/Beutl.Graphics/Rendering/ImmediateRenderer.cs b/src/Beutl.Graphics/Rendering/ImmediateRenderer.cs index 92efc7f44..f82d8954b 100644 --- a/src/Beutl.Graphics/Rendering/ImmediateRenderer.cs +++ b/src/Beutl.Graphics/Rendering/ImmediateRenderer.cs @@ -102,7 +102,7 @@ public IRenderer.RenderResult RenderGraphics(TimeSpan timeSpan) protected virtual void RenderGraphicsCore() { - using (Graphics.PushCanvas()) + using (Graphics.Push()) { Graphics.Clear(); diff --git a/tests/Beutl.Graphics.UnitTests/TextBlockTests.cs b/tests/Beutl.Graphics.UnitTests/TextBlockTests.cs index c44fcca91..c3a5862b4 100644 --- a/tests/Beutl.Graphics.UnitTests/TextBlockTests.cs +++ b/tests/Beutl.Graphics.UnitTests/TextBlockTests.cs @@ -88,10 +88,7 @@ public void ToSKPath() Thickness = 5, StrokeAlignment = StrokeAlignment.Outside }; - using (graphics.PushPen(pen)) - { - graphics.DrawSKPath(skpath, false); - } + graphics.DrawSKPath(skpath, false, null, pen); using Bitmap bmp = graphics.GetBitmap(); diff --git a/tests/PackageSample/SSETExtenison.cs b/tests/PackageSample/SSETExtenison.cs index 91f60c732..c63ff30af 100644 --- a/tests/PackageSample/SSETExtenison.cs +++ b/tests/PackageSample/SSETExtenison.cs @@ -60,14 +60,17 @@ public void Dispose() { } - public void ReadFromJson(JsonNode json) + public object? GetService(Type serviceType) { - + return null; } - public void WriteToJson(ref JsonNode json) + public void ReadFromJson(JsonObject json) { + } + public void WriteToJson(JsonObject json) + { } } } diff --git a/tests/PackageSample/SampleEditorExtension.cs b/tests/PackageSample/SampleEditorExtension.cs index af6e1544e..71f97a4c9 100644 --- a/tests/PackageSample/SampleEditorExtension.cs +++ b/tests/PackageSample/SampleEditorExtension.cs @@ -51,6 +51,11 @@ public void Dispose() return default; } + public object? GetService(Type serviceType) + { + return null; + } + public bool OpenToolTab(IToolContext item) { return false; From 97d4868bd684f210a4a9b7b7d43ee9ce3b786e92 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Wed, 12 Jul 2023 21:32:49 +0900 Subject: [PATCH 69/84] nuget cpm --- Beutl.sln | 22 +----- Directory.Packages.props | 73 +++++++++++++++++++ packages/Beutl.Sdk/Beutl.Sdk.csproj | 28 ++++--- src/Beutl.Api/Beutl.Api.csproj | 20 +++-- src/Beutl.Controls/Beutl.Controls.csproj | 17 +++-- src/Beutl.Core/Beutl.Core.csproj | 8 +- .../Beutl.Extensions.FFmpeg.csproj | 2 +- src/Beutl.Framework/Beutl.Framework.csproj | 12 +-- src/Beutl.Graphics/Beutl.Graphics.csproj | 13 +++- src/Beutl.Language/Beutl.Language.csproj | 11 ++- .../Beutl.PackageTools.csproj | 14 ++-- .../Beutl.ProjectSystem.csproj | 6 +- .../Beutl.WaitingDialog.csproj | 21 ++++-- src/Beutl/Beutl.csproj | 38 ++++++---- .../Beutl.Benchmarks/Beutl.Benchmarks.csproj | 2 +- .../Beutl.Configiration.UnitTests.csproj | 8 +- .../Beutl.Core.UnitTests.csproj | 10 ++- .../Beutl.Graphics.UnitTests.csproj | 8 +- .../Beutl.Threading.UnitTests.csproj | 8 +- .../DirectoryViewTest.csproj | 7 +- tests/KeySplineEditor/KeySplineEditor.csproj | 14 ++-- .../PropertyEditorViewTests.csproj | 6 +- .../TextFormattingPlayground.csproj | 14 ++-- 23 files changed, 230 insertions(+), 132 deletions(-) create mode 100644 Directory.Packages.props diff --git a/Beutl.sln b/Beutl.sln index eaa234ad7..18b16ed76 100644 --- a/Beutl.sln +++ b/Beutl.sln @@ -27,30 +27,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{BBF37951 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{74C34E00-E924-4463-A38B-F691AFBAE362}" ProjectSection(SolutionItems) = preProject - build\props\AsyncImageLoader.Avalonia.props = build\props\AsyncImageLoader.Avalonia.props - build\props\Avalonia.props = build\props\Avalonia.props - build\props\Avalonia.Svg.Skia.props = build\props\Avalonia.Svg.Skia.props - build\props\Avalonia.Xaml.Interactivity.props = build\props\Avalonia.Xaml.Interactivity.props build\props\CopyProjectReferencesToPackage.props = build\props\CopyProjectReferencesToPackage.props build\props\CopyProjectReferencesToPackage.targets = build\props\CopyProjectReferencesToPackage.targets build\props\CoreLibraries.props = build\props\CoreLibraries.props - build\props\FluentAvalonia.props = build\props\FluentAvalonia.props - build\props\FluentIcons.FluentAvalonia.props = build\props\FluentIcons.FluentAvalonia.props - build\props\Microsoft.CodeAnalysis.Analyzers.props = build\props\Microsoft.CodeAnalysis.Analyzers.props - build\props\Microsoft.CodeAnalysis.CSharp.Scripting.props = build\props\Microsoft.CodeAnalysis.CSharp.Scripting.props - build\props\Microsoft.Extensions.DependencyInjection.props = build\props\Microsoft.Extensions.DependencyInjection.props - build\props\Microsoft.Extensions.FileSystemGlobbing.props = build\props\Microsoft.Extensions.FileSystemGlobbing.props - build\props\Microsoft.Extensions.Http.props = build\props\Microsoft.Extensions.Http.props - build\props\Microsoft.Extensions.Logging.props = build\props\Microsoft.Extensions.Logging.props - build\props\Nito.AsyncEx.props = build\props\Nito.AsyncEx.props - build\props\OpenCvSharp4.props = build\props\OpenCvSharp4.props - build\props\ReactiveProperty.props = build\props\ReactiveProperty.props - build\props\Serilog.props = build\props\Serilog.props - build\props\SkiaSharp.props = build\props\SkiaSharp.props - build\props\System.Interactive.props = build\props\System.Interactive.props - build\props\System.Reactive.props = build\props\System.Reactive.props - build\props\UnitTest.props = build\props\UnitTest.props - build\props\XamlNameReferenceGenerator.props = build\props\XamlNameReferenceGenerator.props EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Beutl.Configuration", "src\Beutl.Configuration\Beutl.Configuration.csproj", "{985AFD44-4A83-43DE-A700-269F5175E8C5}" @@ -67,6 +46,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Root", "Root", "{80264A90-C .gitattributes = .gitattributes .gitignore = .gitignore Directory.Build.props = Directory.Build.props + Directory.Packages.props = Directory.Packages.props LICENSE = LICENSE README.md = README.md EndProjectSection diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 000000000..1d9ec3610 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,73 @@ + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/Beutl.Sdk/Beutl.Sdk.csproj b/packages/Beutl.Sdk/Beutl.Sdk.csproj index 78e007a8c..6a389c904 100644 --- a/packages/Beutl.Sdk/Beutl.Sdk.csproj +++ b/packages/Beutl.Sdk/Beutl.Sdk.csproj @@ -18,17 +18,25 @@ - - - - - - - - - - + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Beutl.Api/Beutl.Api.csproj b/src/Beutl.Api/Beutl.Api.csproj index 94f52c57e..b6ca3672d 100644 --- a/src/Beutl.Api/Beutl.Api.csproj +++ b/src/Beutl.Api/Beutl.Api.csproj @@ -6,22 +6,20 @@ enable - - - - - - - - - - - + + + + + + + + + diff --git a/src/Beutl.Controls/Beutl.Controls.csproj b/src/Beutl.Controls/Beutl.Controls.csproj index 83984e4b7..9d8fe0230 100644 --- a/src/Beutl.Controls/Beutl.Controls.csproj +++ b/src/Beutl.Controls/Beutl.Controls.csproj @@ -4,14 +4,17 @@ disable - - - - - + + + + + + + - - + + + diff --git a/src/Beutl.Core/Beutl.Core.csproj b/src/Beutl.Core/Beutl.Core.csproj index d8af828f7..ccf04e86b 100644 --- a/src/Beutl.Core/Beutl.Core.csproj +++ b/src/Beutl.Core/Beutl.Core.csproj @@ -12,6 +12,10 @@ - - + + + + + + diff --git a/src/Beutl.Extensions.FFmpeg/Beutl.Extensions.FFmpeg.csproj b/src/Beutl.Extensions.FFmpeg/Beutl.Extensions.FFmpeg.csproj index 479262e74..634c129b6 100644 --- a/src/Beutl.Extensions.FFmpeg/Beutl.Extensions.FFmpeg.csproj +++ b/src/Beutl.Extensions.FFmpeg/Beutl.Extensions.FFmpeg.csproj @@ -24,7 +24,7 @@ - + diff --git a/src/Beutl.Framework/Beutl.Framework.csproj b/src/Beutl.Framework/Beutl.Framework.csproj index 20a12a287..6feef8a1b 100644 --- a/src/Beutl.Framework/Beutl.Framework.csproj +++ b/src/Beutl.Framework/Beutl.Framework.csproj @@ -5,11 +5,13 @@ - - - + + + + - - + + + diff --git a/src/Beutl.Graphics/Beutl.Graphics.csproj b/src/Beutl.Graphics/Beutl.Graphics.csproj index 4ea9303bb..3294c6a66 100644 --- a/src/Beutl.Graphics/Beutl.Graphics.csproj +++ b/src/Beutl.Graphics/Beutl.Graphics.csproj @@ -21,9 +21,14 @@ - - + + + + + + + + + - - diff --git a/src/Beutl.Language/Beutl.Language.csproj b/src/Beutl.Language/Beutl.Language.csproj index 40605a208..d76eea31d 100644 --- a/src/Beutl.Language/Beutl.Language.csproj +++ b/src/Beutl.Language/Beutl.Language.csproj @@ -4,16 +4,15 @@ en - - - - - + - + + + + diff --git a/src/Beutl.PackageTools/Beutl.PackageTools.csproj b/src/Beutl.PackageTools/Beutl.PackageTools.csproj index 6e76f242e..7c60f5b27 100644 --- a/src/Beutl.PackageTools/Beutl.PackageTools.csproj +++ b/src/Beutl.PackageTools/Beutl.PackageTools.csproj @@ -14,16 +14,14 @@ - - - - - + + + + + + - - - diff --git a/src/Beutl.ProjectSystem/Beutl.ProjectSystem.csproj b/src/Beutl.ProjectSystem/Beutl.ProjectSystem.csproj index afe2edd0c..fba82129f 100644 --- a/src/Beutl.ProjectSystem/Beutl.ProjectSystem.csproj +++ b/src/Beutl.ProjectSystem/Beutl.ProjectSystem.csproj @@ -7,6 +7,8 @@ - - + + + + diff --git a/src/Beutl.WaitingDialog/Beutl.WaitingDialog.csproj b/src/Beutl.WaitingDialog/Beutl.WaitingDialog.csproj index 3b2e435d2..d662dae94 100644 --- a/src/Beutl.WaitingDialog/Beutl.WaitingDialog.csproj +++ b/src/Beutl.WaitingDialog/Beutl.WaitingDialog.csproj @@ -12,15 +12,20 @@ - - + - - - - + + + + + + + + + - - + + + diff --git a/src/Beutl/Beutl.csproj b/src/Beutl/Beutl.csproj index 745717c32..564df5211 100644 --- a/src/Beutl/Beutl.csproj +++ b/src/Beutl/Beutl.csproj @@ -13,10 +13,29 @@ - - - - + + + + + + + + + + + + + + + + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -44,16 +63,5 @@ - - - - - - - - - - - diff --git a/tests/Beutl.Benchmarks/Beutl.Benchmarks.csproj b/tests/Beutl.Benchmarks/Beutl.Benchmarks.csproj index 4886e5e58..f4719ec86 100644 --- a/tests/Beutl.Benchmarks/Beutl.Benchmarks.csproj +++ b/tests/Beutl.Benchmarks/Beutl.Benchmarks.csproj @@ -12,7 +12,7 @@ - + diff --git a/tests/Beutl.Configiration.UnitTests/Beutl.Configiration.UnitTests.csproj b/tests/Beutl.Configiration.UnitTests/Beutl.Configiration.UnitTests.csproj index 6e09eae42..474133c79 100644 --- a/tests/Beutl.Configiration.UnitTests/Beutl.Configiration.UnitTests.csproj +++ b/tests/Beutl.Configiration.UnitTests/Beutl.Configiration.UnitTests.csproj @@ -6,10 +6,10 @@ false - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Beutl.Core.UnitTests/Beutl.Core.UnitTests.csproj b/tests/Beutl.Core.UnitTests/Beutl.Core.UnitTests.csproj index e1a497799..acf7d45ca 100644 --- a/tests/Beutl.Core.UnitTests/Beutl.Core.UnitTests.csproj +++ b/tests/Beutl.Core.UnitTests/Beutl.Core.UnitTests.csproj @@ -7,10 +7,10 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -18,6 +18,8 @@ + + diff --git a/tests/Beutl.Graphics.UnitTests/Beutl.Graphics.UnitTests.csproj b/tests/Beutl.Graphics.UnitTests/Beutl.Graphics.UnitTests.csproj index 028bdb73e..4591b24da 100644 --- a/tests/Beutl.Graphics.UnitTests/Beutl.Graphics.UnitTests.csproj +++ b/tests/Beutl.Graphics.UnitTests/Beutl.Graphics.UnitTests.csproj @@ -8,10 +8,10 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/Beutl.Threading.UnitTests/Beutl.Threading.UnitTests.csproj b/tests/Beutl.Threading.UnitTests/Beutl.Threading.UnitTests.csproj index c679b864d..3cc18e6b3 100644 --- a/tests/Beutl.Threading.UnitTests/Beutl.Threading.UnitTests.csproj +++ b/tests/Beutl.Threading.UnitTests/Beutl.Threading.UnitTests.csproj @@ -8,10 +8,10 @@ - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/tests/DirectoryViewTest/DirectoryViewTest.csproj b/tests/DirectoryViewTest/DirectoryViewTest.csproj index fd814e1d1..2d8b78d81 100644 --- a/tests/DirectoryViewTest/DirectoryViewTest.csproj +++ b/tests/DirectoryViewTest/DirectoryViewTest.csproj @@ -14,7 +14,8 @@ - - - + + + + diff --git a/tests/KeySplineEditor/KeySplineEditor.csproj b/tests/KeySplineEditor/KeySplineEditor.csproj index 70f954075..d2be3596b 100644 --- a/tests/KeySplineEditor/KeySplineEditor.csproj +++ b/tests/KeySplineEditor/KeySplineEditor.csproj @@ -10,10 +10,14 @@ + + + + + + - - - - - + + + diff --git a/tests/PropertyEditorViewTests/PropertyEditorViewTests.csproj b/tests/PropertyEditorViewTests/PropertyEditorViewTests.csproj index 4f9ba1ec0..a4ea1ac72 100644 --- a/tests/PropertyEditorViewTests/PropertyEditorViewTests.csproj +++ b/tests/PropertyEditorViewTests/PropertyEditorViewTests.csproj @@ -12,7 +12,9 @@ - - + + + + diff --git a/tests/TextFormattingPlayground/TextFormattingPlayground.csproj b/tests/TextFormattingPlayground/TextFormattingPlayground.csproj index 1e255ca22..445e3a588 100644 --- a/tests/TextFormattingPlayground/TextFormattingPlayground.csproj +++ b/tests/TextFormattingPlayground/TextFormattingPlayground.csproj @@ -14,10 +14,14 @@ + + + + + + - - - - - + + + From af2cff1c8ea4364d02f03975efc9ce10b80d667f Mon Sep 17 00:00:00 2001 From: indigo-san Date: Wed, 12 Jul 2023 21:46:15 +0900 Subject: [PATCH 70/84] Fix DisposeWith imports. --- src/Beutl.Controls/PropertyEditors/AlignmentXEditor.cs | 1 + src/Beutl.Controls/PropertyEditors/AlignmentYEditor.cs | 1 + src/Beutl.Controls/PropertyEditors/PropertyEditor.cs | 1 + src/Beutl.Controls/PropertyEditors/StringEditor.cs | 1 + src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupNode.cs | 2 ++ 5 files changed, 6 insertions(+) diff --git a/src/Beutl.Controls/PropertyEditors/AlignmentXEditor.cs b/src/Beutl.Controls/PropertyEditors/AlignmentXEditor.cs index 41cf16502..e9bb24dc8 100644 --- a/src/Beutl.Controls/PropertyEditors/AlignmentXEditor.cs +++ b/src/Beutl.Controls/PropertyEditors/AlignmentXEditor.cs @@ -3,6 +3,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Metadata; +using Avalonia.Controls.Mixins; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Data; diff --git a/src/Beutl.Controls/PropertyEditors/AlignmentYEditor.cs b/src/Beutl.Controls/PropertyEditors/AlignmentYEditor.cs index 7752a7b30..c7a9a3fe3 100644 --- a/src/Beutl.Controls/PropertyEditors/AlignmentYEditor.cs +++ b/src/Beutl.Controls/PropertyEditors/AlignmentYEditor.cs @@ -3,6 +3,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Metadata; +using Avalonia.Controls.Mixins; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Data; diff --git a/src/Beutl.Controls/PropertyEditors/PropertyEditor.cs b/src/Beutl.Controls/PropertyEditors/PropertyEditor.cs index 32c23c817..539bfb093 100644 --- a/src/Beutl.Controls/PropertyEditors/PropertyEditor.cs +++ b/src/Beutl.Controls/PropertyEditors/PropertyEditor.cs @@ -3,6 +3,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Metadata; +using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Interactivity; diff --git a/src/Beutl.Controls/PropertyEditors/StringEditor.cs b/src/Beutl.Controls/PropertyEditors/StringEditor.cs index f1b551b9d..e23d9e15b 100644 --- a/src/Beutl.Controls/PropertyEditors/StringEditor.cs +++ b/src/Beutl.Controls/PropertyEditors/StringEditor.cs @@ -3,6 +3,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Metadata; +using Avalonia.Controls.Mixins; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Data; diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupNode.cs index fd343b756..e2bccca0a 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Group/GroupNode.cs @@ -4,6 +4,8 @@ using System.Reactive.Linq; using System.Text.Json.Nodes; +using Avalonia.Controls.Mixins; + namespace Beutl.NodeTree.Nodes.Group; // Todo: ファイルからノードグループを読み込めるようにする。 From e0b51dc56f2e0ea8201e951bf8623054953d3bb5 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Fri, 14 Jul 2023 12:07:44 +0900 Subject: [PATCH 71/84] SceneGraph --- src/Beutl.Controls/Beutl.Controls.csproj | 1 + src/Beutl.Graphics/Audio/Effects/Delay.cs | 20 +- .../Audio/Effects/SoundEffect.cs | 6 - src/Beutl.Graphics/Audio/Sound.cs | 9 +- src/Beutl.Graphics/Audio/SoundNode.cs | 14 + .../Graphics/CanvasPushedState.cs | 12 +- src/Beutl.Graphics/Graphics/Drawable.cs | 32 +- .../Graphics/Drawables/VideoFrame.cs | 19 +- src/Beutl.Graphics/Graphics/Effects/Border.cs | 4 +- .../Graphics/Effects/InnerShadow.cs | 4 +- .../Graphics/FilterEffects/Blur.cs | 41 +++ .../Graphics/FilterEffects/DisplacementMap.cs | 86 +++++ .../Graphics/FilterEffects/DropShadow.cs | 89 +++++ .../Graphics/FilterEffects/FilterEffect.cs | 71 ++++ .../FilterEffects/FilterEffectContext.cs | 245 ++++++++++++++ .../FilterEffects/FilterEffectGroup.cs | 91 +++++ .../FilterEffects/FilterEffects.cs} | 15 +- .../Graphics/Filters/BrushImageFilter.cs | 2 +- src/Beutl.Graphics/Graphics/ICanvas.cs | 10 +- src/Beutl.Graphics/Graphics/IDrawable.cs | 7 - src/Beutl.Graphics/Graphics/Image.cs | 7 + .../{Canvas.cs => ImmediateCanvas.cs} | 51 ++- src/Beutl.Graphics/Graphics/Rect.cs | 12 + .../Graphics/Rendering/BitmapNode.cs | 50 +++ .../Graphics/Rendering/BlendModeNode.cs | 34 ++ .../Graphics/Rendering/BrushDrawNode.cs | 17 + .../Graphics/Rendering/ClearNode.cs | 33 ++ .../Graphics/Rendering/ContainerNode.cs | 87 +++++ .../Graphics/Rendering/DeferradCanvas.cs | 316 ++++++++++++++++++ .../Graphics/Rendering/DrawNode.cs | 19 ++ .../Graphics/Rendering/DrawableNode.cs | 17 + .../Graphics/Rendering/EllipseNode.cs | 79 +++++ .../Graphics/Rendering/FilterEffectNode.cs | 52 +++ .../Graphics/Rendering/GeometryClipNode.cs | 35 ++ .../Graphics/Rendering/GeometryNode.cs | 37 ++ .../Graphics/Rendering/IGraphicNode.cs | 10 + .../Graphics/Rendering/INode.cs | 5 + .../Graphics/Rendering/ImageFilterNode.cs | 36 ++ .../Graphics/Rendering/OpacityMaskNode.cs | 39 +++ .../Graphics/Rendering/PenHelper.cs | 47 +++ .../Graphics/Rendering/PushNode.cs | 16 + .../Graphics/Rendering/RectClipNode.cs | 28 ++ .../Graphics/Rendering/RectangleNode.cs | 49 +++ .../Graphics/Rendering/TextNode.cs | 37 ++ .../Graphics/Rendering/TransformNode.cs | 40 +++ .../Graphics/Shapes/TextBlock.cs | 10 +- .../Graphics/SkiaSharpExtensions.cs | 5 + .../Media/Music/Samples/Stereo32BitFloat.cs | 9 +- .../Media/Source/BitmapSource.cs | 58 ++++ .../Media/Source/IImageSource.cs | 2 + .../Media/Source/ImageSource.cs | 55 ++- .../Media/Source/MediaSourceManager.cs | 2 +- .../Media/Source/VideoSource.cs | 9 +- src/Beutl.Graphics/Rendering/IRenderLayer.cs | 23 -- src/Beutl.Graphics/Rendering/IRenderable.cs | 9 +- src/Beutl.Graphics/Rendering/IRenderer.cs | 2 +- .../Rendering/ImmediateRenderer.cs | 60 +--- src/Beutl.Graphics/Rendering/RenderLayer.cs | 174 +++++----- .../Rendering/RenderLayerSpan.cs | 100 ------ src/Beutl.Graphics/Rendering/RenderScene.cs | 54 +++ src/Beutl.Graphics/Rendering/Renderable.cs | 46 +-- .../NodeTree/ElementNodeTreeModel.cs | 9 +- .../Operation/SourceOperation.cs | 52 +-- .../ProjectSystem/Element.cs | 105 +----- .../ProjectSystem/Scene.cs | 11 - src/Beutl.ProjectSystem/SceneRenderer.cs | 5 +- .../JsonSerializationTest.cs | 57 ++++ .../DeferradCanvasTests.cs | 76 +++++ tests/Beutl.Graphics.UnitTests/ShapeTests.cs | 14 +- .../TextBlockTests.cs | 4 +- .../MainWindow.axaml.cs | 2 +- 71 files changed, 2299 insertions(+), 585 deletions(-) create mode 100644 src/Beutl.Graphics/Audio/SoundNode.cs create mode 100644 src/Beutl.Graphics/Graphics/FilterEffects/Blur.cs create mode 100644 src/Beutl.Graphics/Graphics/FilterEffects/DisplacementMap.cs create mode 100644 src/Beutl.Graphics/Graphics/FilterEffects/DropShadow.cs create mode 100644 src/Beutl.Graphics/Graphics/FilterEffects/FilterEffect.cs create mode 100644 src/Beutl.Graphics/Graphics/FilterEffects/FilterEffectContext.cs create mode 100644 src/Beutl.Graphics/Graphics/FilterEffects/FilterEffectGroup.cs rename src/Beutl.Graphics/{Rendering/Renderables.cs => Graphics/FilterEffects/FilterEffects.cs} (81%) rename src/Beutl.Graphics/Graphics/{Canvas.cs => ImmediateCanvas.cs} (95%) create mode 100644 src/Beutl.Graphics/Graphics/Rendering/BitmapNode.cs create mode 100644 src/Beutl.Graphics/Graphics/Rendering/BlendModeNode.cs create mode 100644 src/Beutl.Graphics/Graphics/Rendering/BrushDrawNode.cs create mode 100644 src/Beutl.Graphics/Graphics/Rendering/ClearNode.cs create mode 100644 src/Beutl.Graphics/Graphics/Rendering/ContainerNode.cs create mode 100644 src/Beutl.Graphics/Graphics/Rendering/DeferradCanvas.cs create mode 100644 src/Beutl.Graphics/Graphics/Rendering/DrawNode.cs create mode 100644 src/Beutl.Graphics/Graphics/Rendering/DrawableNode.cs create mode 100644 src/Beutl.Graphics/Graphics/Rendering/EllipseNode.cs create mode 100644 src/Beutl.Graphics/Graphics/Rendering/FilterEffectNode.cs create mode 100644 src/Beutl.Graphics/Graphics/Rendering/GeometryClipNode.cs create mode 100644 src/Beutl.Graphics/Graphics/Rendering/GeometryNode.cs create mode 100644 src/Beutl.Graphics/Graphics/Rendering/IGraphicNode.cs create mode 100644 src/Beutl.Graphics/Graphics/Rendering/INode.cs create mode 100644 src/Beutl.Graphics/Graphics/Rendering/ImageFilterNode.cs create mode 100644 src/Beutl.Graphics/Graphics/Rendering/OpacityMaskNode.cs create mode 100644 src/Beutl.Graphics/Graphics/Rendering/PenHelper.cs create mode 100644 src/Beutl.Graphics/Graphics/Rendering/PushNode.cs create mode 100644 src/Beutl.Graphics/Graphics/Rendering/RectClipNode.cs create mode 100644 src/Beutl.Graphics/Graphics/Rendering/RectangleNode.cs create mode 100644 src/Beutl.Graphics/Graphics/Rendering/TextNode.cs create mode 100644 src/Beutl.Graphics/Graphics/Rendering/TransformNode.cs create mode 100644 src/Beutl.Graphics/Media/Source/BitmapSource.cs delete mode 100644 src/Beutl.Graphics/Rendering/IRenderLayer.cs delete mode 100644 src/Beutl.Graphics/Rendering/RenderLayerSpan.cs create mode 100644 src/Beutl.Graphics/Rendering/RenderScene.cs create mode 100644 tests/Beutl.Core.UnitTests/JsonSerializationTest.cs create mode 100644 tests/Beutl.Graphics.UnitTests/DeferradCanvasTests.cs diff --git a/src/Beutl.Controls/Beutl.Controls.csproj b/src/Beutl.Controls/Beutl.Controls.csproj index 9d8fe0230..c2a2a5677 100644 --- a/src/Beutl.Controls/Beutl.Controls.csproj +++ b/src/Beutl.Controls/Beutl.Controls.csproj @@ -9,6 +9,7 @@ + diff --git a/src/Beutl.Graphics/Audio/Effects/Delay.cs b/src/Beutl.Graphics/Audio/Effects/Delay.cs index 2dc935b6a..c91688e0b 100644 --- a/src/Beutl.Graphics/Audio/Effects/Delay.cs +++ b/src/Beutl.Graphics/Audio/Effects/Delay.cs @@ -142,23 +142,17 @@ public float WetMix public override ISoundProcessor CreateProcessor() { - return new DelayProcessor(_delayTime, _feedback, _dryMix, _wetMix); + return new DelayProcessor(this); } private sealed class DelayProcessor : ISoundProcessor { - private readonly float _delayTime; - private readonly float _feedback; - private readonly float _dryMix; - private readonly float _wetMix; + private readonly Delay _delay; private SimpleCircularBuffer? _delayBuffer; - public DelayProcessor(float delayTime, float feedback, float dryMix, float wetMix) + public DelayProcessor(Delay delay) { - _delayTime = delayTime; - _feedback = feedback; - _dryMix = dryMix; - _wetMix = wetMix; + _delay = delay; } ~DelayProcessor() @@ -191,11 +185,11 @@ public void Process(in Pcm src, out Pcm dst) { ref Vector2 input = ref Unsafe.As(ref channel_data[sample]); - Vector2 delay = _delayBuffer.Read((int)(_delayTime * sampleRate)); + Vector2 delay = _delayBuffer.Read((int)(_delay._delayTime * sampleRate)); - _delayBuffer.Write(input + (_feedback * delay)); + _delayBuffer.Write(input + (_delay._feedback * delay)); - input = ((_dryMix) * input) + (_wetMix * delay); + input = ((_delay._dryMix) * input) + (_delay._wetMix * delay); } dst = src; diff --git a/src/Beutl.Graphics/Audio/Effects/SoundEffect.cs b/src/Beutl.Graphics/Audio/Effects/SoundEffect.cs index 643fdc8d5..b1dc8aa5b 100644 --- a/src/Beutl.Graphics/Audio/Effects/SoundEffect.cs +++ b/src/Beutl.Graphics/Audio/Effects/SoundEffect.cs @@ -33,12 +33,6 @@ public bool IsEnabled public abstract ISoundProcessor CreateProcessor(); - public override void ApplyAnimations(IClock clock) - { - // SoundEffectはアニメーションに対応しない。 - //base.ApplyAnimations(clock); - } - protected static void AffectsRender(params CoreProperty[] properties) where T : SoundEffect { diff --git a/src/Beutl.Graphics/Audio/Sound.cs b/src/Beutl.Graphics/Audio/Sound.cs index 209771171..f0f47b62e 100644 --- a/src/Beutl.Graphics/Audio/Sound.cs +++ b/src/Beutl.Graphics/Audio/Sound.cs @@ -7,6 +7,8 @@ namespace Beutl.Audio; +// Animationに対応させる (手動) + public abstract class Sound : Renderable { public static readonly CoreProperty GainProperty; @@ -78,12 +80,7 @@ public Pcm ToPcm(int sampleRate) } } - public override void Render(IRenderer renderer) - { - Record(renderer.Audio); - } - - public void Record(IAudio audio) + public void Render(IAudio audio) { if (_effect is { IsEnabled: true } effect) { diff --git a/src/Beutl.Graphics/Audio/SoundNode.cs b/src/Beutl.Graphics/Audio/SoundNode.cs new file mode 100644 index 000000000..f4ebd0745 --- /dev/null +++ b/src/Beutl.Graphics/Audio/SoundNode.cs @@ -0,0 +1,14 @@ +using Beutl.Graphics.Rendering; + +namespace Beutl.Audio; + +public sealed class SoundNode : INode +{ + public SoundNode(Sound sound) => Sound = sound; + + public Sound Sound { get; } + + public void Dispose() + { + } +} diff --git a/src/Beutl.Graphics/Graphics/CanvasPushedState.cs b/src/Beutl.Graphics/Graphics/CanvasPushedState.cs index fbd1cac55..e9801749a 100644 --- a/src/Beutl.Graphics/Graphics/CanvasPushedState.cs +++ b/src/Beutl.Graphics/Graphics/CanvasPushedState.cs @@ -4,13 +4,13 @@ namespace Beutl.Graphics; -public partial class Canvas +public partial class ImmediateCanvas { private abstract record CanvasPushedState { internal record SKCanvasPushedState(int Count) : CanvasPushedState { - public override void Pop(Canvas canvas) + public override void Pop(ImmediateCanvas canvas) { canvas._canvas.RestoreToCount(Count); canvas._currentTransform = canvas._canvas.TotalMatrix.ToMatrix(); @@ -19,7 +19,7 @@ public override void Pop(Canvas canvas) internal record MaskPushedState(int Count, bool Invert, SKPaint Paint) : CanvasPushedState { - public override void Pop(Canvas canvas) + public override void Pop(ImmediateCanvas canvas) { canvas._sharedFillPaint.Reset(); canvas._sharedFillPaint.BlendMode = Invert ? SKBlendMode.DstOut : SKBlendMode.DstIn; @@ -38,7 +38,7 @@ public override void Pop(Canvas canvas) internal record ImageFilterPushedState(int Count, IImageFilter ImageFilter, SKPaint Paint) : CanvasPushedState { - public override void Pop(Canvas canvas) + public override void Pop(ImmediateCanvas canvas) { canvas._canvas.RestoreToCount(Count); Paint.Dispose(); @@ -47,12 +47,12 @@ public override void Pop(Canvas canvas) internal record BlendModePushedState(BlendMode BlendMode) : CanvasPushedState { - public override void Pop(Canvas canvas) + public override void Pop(ImmediateCanvas canvas) { canvas.BlendMode = BlendMode; } } - public abstract void Pop(Canvas canvas); + public abstract void Pop(ImmediateCanvas canvas); } } diff --git a/src/Beutl.Graphics/Graphics/Drawable.cs b/src/Beutl.Graphics/Graphics/Drawable.cs index 9cd97d090..2221081eb 100644 --- a/src/Beutl.Graphics/Graphics/Drawable.cs +++ b/src/Beutl.Graphics/Graphics/Drawable.cs @@ -16,6 +16,7 @@ public abstract class Drawable : Renderable, IDrawable, IHierarchical public static readonly CoreProperty TransformProperty; public static readonly CoreProperty FilterProperty; public static readonly CoreProperty EffectProperty; + public static readonly CoreProperty FilterEffectProperty; public static readonly CoreProperty AlignmentXProperty; public static readonly CoreProperty AlignmentYProperty; public static readonly CoreProperty TransformOriginProperty; @@ -25,6 +26,7 @@ public abstract class Drawable : Renderable, IDrawable, IHierarchical private ITransform? _transform; private IImageFilter? _filter; private IBitmapEffect? _effect; + private FilterEffect? _filterEffect; private AlignmentX _alignX = AlignmentX.Center; private AlignmentY _alignY = AlignmentY.Center; private RelativePoint _transformOrigin = RelativePoint.Center; @@ -48,6 +50,11 @@ static Drawable() .Accessor(o => o.Effect, (o, v) => o.Effect = v) .DefaultValue(null) .Register(); + + FilterEffectProperty = ConfigureProperty(nameof(FilterEffect)) + .Accessor(o => o.FilterEffect, (o, v) => o.FilterEffect = v) + .DefaultValue(null) + .Register(); AlignmentXProperty = ConfigureProperty(nameof(AlignmentX)) .Accessor(o => o.AlignmentX, (o, v) => o.AlignmentX = v) @@ -101,6 +108,13 @@ public IBitmapEffect? Effect get => _effect; set => SetAndRaise(EffectProperty, ref _effect, value); } + + [Display(Name = "FilterEffect")] + public FilterEffect? FilterEffect + { + get => _filterEffect; + set => SetAndRaise(FilterEffectProperty, ref _filterEffect, value); + } [Display(Name = nameof(Strings.Transform), ResourceType = typeof(Strings), GroupName = nameof(Strings.Transform))] public ITransform? Transform @@ -158,7 +172,7 @@ public IBitmap ToBitmap() int height = (int)size.Height; if (width > 0 && height > 0) { - using (var canvas = new Canvas(width, height)) + using (var canvas = new ImmediateCanvas(width, height)) { OnDraw(canvas); return canvas.GetBitmap(); @@ -226,7 +240,11 @@ private void HasBitmapEffect(ICanvas canvas) rect = rect.TransformToAABB(transform); using (canvas.PushBlendMode(BlendMode)) + +#pragma warning disable CS0618 using (_filter == null ? new() : canvas.PushImageFilter(_filter, rect)) +#pragma warning restore CS0618 + using (canvas.PushTransform(transformFact)) using (OpacityMask == null ? new() : canvas.PushOpacityMask(OpacityMask, rect)) { @@ -263,7 +281,7 @@ private void HasBitmapEffect(ICanvas canvas) Bounds = rect; } - public void Draw(ICanvas canvas) + public void Render(ICanvas canvas) { if (IsVisible) { @@ -284,7 +302,12 @@ public void Draw(ICanvas canvas) Matrix transform = GetTransformMatrix(availableSize, size); Rect transformedBounds = rect.TransformToAABB(transform); using (canvas.PushBlendMode(BlendMode)) + +#pragma warning disable CS0618 using (_filter == null ? new() : canvas.PushImageFilter(_filter, /*new Rect(size)*/transformedBounds)) +#pragma warning restore CS0618 + + using (_filterEffect == null ? new() : canvas.PushFilterEffect(_filterEffect)) using (canvas.PushTransform(transform)) using (OpacityMask == null ? new() : canvas.PushOpacityMask(OpacityMask, new Rect(size))) { @@ -305,11 +328,6 @@ public void Draw(ICanvas canvas) #endif } - public override void Render(IRenderer renderer) - { - Draw(renderer.Graphics); - } - public override void ApplyAnimations(IClock clock) { base.ApplyAnimations(clock); diff --git a/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs b/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs index 5ef2f2c8d..03979ccec 100644 --- a/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs +++ b/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs @@ -31,7 +31,6 @@ public class VideoFrame : Drawable private TimeSpan _requestedPosition; private IBitmap? _previousBitmap; private double _previousFrame; - private RenderLayerSpan? _layerNode; static VideoFrame() { @@ -121,26 +120,10 @@ public override void ApplyAnimations(IClock clock) if (PositionMode == VideoPositionMode.Automatic) { _requestedPosition = clock.CurrentTime; - - if (_layerNode != null) - { - _requestedPosition -= _layerNode.Start; - } + _requestedPosition -= clock.BeginTime; } } - protected override void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs args) - { - base.OnAttachedToHierarchy(args); - _layerNode = this.FindHierarchicalParent(); - } - - protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) - { - base.OnDetachedFromHierarchy(args); - _layerNode = null; - } - protected override Size MeasureCore(Size availableSize) { if (_mediaReader?.IsDisposed == false) diff --git a/src/Beutl.Graphics/Graphics/Effects/Border.cs b/src/Beutl.Graphics/Graphics/Effects/Border.cs index 80824cfba..00636cd57 100644 --- a/src/Beutl.Graphics/Graphics/Effects/Border.cs +++ b/src/Beutl.Graphics/Graphics/Effects/Border.cs @@ -162,10 +162,10 @@ public void Process(in Bitmap src, out Bitmap dst) var borderRect = new Rect(0, 0, border.Width, border.Height); ImmutableImageBrush? maskBrush = maskType != MaskTypes.None - ? new ImmutableImageBrush(new ImageSource(Ref.Create(src), "Temp"), stretch: Stretch.None) + ? new ImmutableImageBrush(new BitmapSource(Ref.Create(src), "Temp"), stretch: Stretch.None) : null; - using (var canvas = new Canvas((int)canvasRect.Width, (int)canvasRect.Height)) + using (var canvas = new ImmediateCanvas((int)canvasRect.Width, (int)canvasRect.Height)) using (canvas.PushTransform(Matrix.CreateTranslation(8, 8))) { var srcTranslate = Matrix.CreateTranslation( diff --git a/src/Beutl.Graphics/Graphics/Effects/InnerShadow.cs b/src/Beutl.Graphics/Graphics/Effects/InnerShadow.cs index e152c9ab3..7fe17b87e 100644 --- a/src/Beutl.Graphics/Graphics/Effects/InnerShadow.cs +++ b/src/Beutl.Graphics/Graphics/Effects/InnerShadow.cs @@ -82,8 +82,8 @@ public void Process(in Bitmap src, out Bitmap dst) var rect = new Rect(0, 0, src.Width, src.Height); PixelSize ksize = _shadow.KernelSize; - using var canvas = new Canvas(src.Width, src.Height); - var maskSource = new ImageSource(Ref.Create(src), "Temp"); + using var canvas = new ImmediateCanvas(src.Width, src.Height); + var maskSource = new BitmapSource(Ref.Create(src), "Temp"); var maskBrush = new ImageBrush(maskSource) { Transform = new TranslateTransform(_shadow._position), diff --git a/src/Beutl.Graphics/Graphics/FilterEffects/Blur.cs b/src/Beutl.Graphics/Graphics/FilterEffects/Blur.cs new file mode 100644 index 000000000..c14aa79c6 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/FilterEffects/Blur.cs @@ -0,0 +1,41 @@ +using System.ComponentModel.DataAnnotations; + +using Beutl.Language; + +using SkiaSharp; + +namespace Beutl.Graphics.Effects; + +public sealed class Blur : FilterEffect +{ + public static readonly CoreProperty SigmaProperty; + private Vector _sigma; + + static Blur() + { + SigmaProperty = ConfigureProperty(nameof(Sigma)) + .Accessor(o => o.Sigma, (o, v) => o.Sigma = v) + .DefaultValue(Vector.Zero) + .Register(); + + AffectsRender(SigmaProperty); + } + + [Display(Name = nameof(Strings.Sigma), ResourceType = typeof(Strings))] + [Range(typeof(Vector), "0,0", "max,max")] + public Vector Sigma + { + get => _sigma; + set => SetAndRaise(SigmaProperty, ref _sigma, value); + } + + public override void ApplyTo(FilterEffectContext context) + { + context.Blur(_sigma); + } + + public override Rect TransformBounds(Rect bounds) + { + return bounds.Inflate(new Thickness(_sigma.X * 3, _sigma.Y * 3)); + } +} diff --git a/src/Beutl.Graphics/Graphics/FilterEffects/DisplacementMap.cs b/src/Beutl.Graphics/Graphics/FilterEffects/DisplacementMap.cs new file mode 100644 index 000000000..fc842887f --- /dev/null +++ b/src/Beutl.Graphics/Graphics/FilterEffects/DisplacementMap.cs @@ -0,0 +1,86 @@ +using Beutl.Animation; + +using SkiaSharp; + +namespace Beutl.Graphics.Effects; + +public sealed class DisplacementMap : FilterEffect +{ + public static readonly CoreProperty XChannelSelectorProperty; + public static readonly CoreProperty YChannelSelectorProperty; + public static readonly CoreProperty ScaleProperty; + public static readonly CoreProperty DisplacementProperty; + private SKColorChannel _xChannelSelector = SKColorChannel.A; + private SKColorChannel _yChannelSelector = SKColorChannel.A; + private float _scale; + private FilterEffect? _displacement; + + static DisplacementMap() + { + XChannelSelectorProperty = ConfigureProperty(o => o.XChannelSelector) + .DefaultValue(SKColorChannel.A) + .Register(); + + YChannelSelectorProperty = ConfigureProperty(o => o.YChannelSelector) + .DefaultValue(SKColorChannel.A) + .Register(); + + ScaleProperty = ConfigureProperty(o => o.Scale) + .DefaultValue(1f) + .Register(); + + DisplacementProperty = ConfigureProperty(o => o.Displacement) + .DefaultValue(null) + .Register(); + + AffectsRender(XChannelSelectorProperty, YChannelSelectorProperty, ScaleProperty, DisplacementProperty); + } + + public SKColorChannel XChannelSelector + { + get => _xChannelSelector; + set => SetAndRaise(XChannelSelectorProperty, ref _xChannelSelector, value); + } + + public SKColorChannel YChannelSelector + { + get => _yChannelSelector; + set => SetAndRaise(YChannelSelectorProperty, ref _yChannelSelector, value); + } + + public float Scale + { + get => _scale; + set => SetAndRaise(ScaleProperty, ref _scale, value); + } + + public FilterEffect? Displacement + { + get => _displacement; + set => SetAndRaise(DisplacementProperty, ref _displacement, value); + } + + public override void ApplyAnimations(IClock clock) + { + base.ApplyAnimations(clock); + (Displacement as Animatable)?.ApplyAnimations(clock); + } + + public override void ApplyTo(FilterEffectContext context) + { + if (Displacement?.IsEnabled == true) + { + context.DisplacementMap(XChannelSelector, YChannelSelector, Scale, Displacement); + } + } + + public override Rect TransformBounds(Rect bounds) + { + if (Displacement?.IsEnabled == true) + { + bounds = bounds.Inflate(_scale / 2); + } + + return bounds; + } +} diff --git a/src/Beutl.Graphics/Graphics/FilterEffects/DropShadow.cs b/src/Beutl.Graphics/Graphics/FilterEffects/DropShadow.cs new file mode 100644 index 000000000..a8432bb9a --- /dev/null +++ b/src/Beutl.Graphics/Graphics/FilterEffects/DropShadow.cs @@ -0,0 +1,89 @@ +using System.ComponentModel.DataAnnotations; + +using Beutl.Language; +using Beutl.Media; + +using SkiaSharp; + +namespace Beutl.Graphics.Effects; + +public sealed class DropShadow : FilterEffect +{ + public static readonly CoreProperty PositionProperty; + public static readonly CoreProperty SigmaProperty; + public static readonly CoreProperty ColorProperty; + public static readonly CoreProperty ShadowOnlyProperty; + private Point _position; + private Vector _sigma; + private Color _color; + private bool _shadowOnly; + + static DropShadow() + { + PositionProperty = ConfigureProperty(nameof(Position)) + .Accessor(o => o.Position, (o, v) => o.Position = v) + .DefaultValue(new Point()) + .Register(); + + SigmaProperty = ConfigureProperty(nameof(Sigma)) + .Accessor(o => o.Sigma, (o, v) => o.Sigma = v) + .DefaultValue(Vector.Zero) + .Register(); + + ColorProperty = ConfigureProperty(nameof(Color)) + .Accessor(o => o.Color, (o, v) => o.Color = v) + .DefaultValue(Colors.Transparent) + .Register(); + + ShadowOnlyProperty = ConfigureProperty(nameof(ShadowOnly)) + .Accessor(o => o.ShadowOnly, (o, v) => o.ShadowOnly = v) + .DefaultValue(false) + .Register(); + + AffectsRender(PositionProperty, SigmaProperty, ColorProperty, ShadowOnlyProperty); + } + + + [Display(Name = nameof(Strings.Position), ResourceType = typeof(Strings))] + public Point Position + { + get => _position; + set => SetAndRaise(PositionProperty, ref _position, value); + } + + [Display(Name = nameof(Strings.Sigma), ResourceType = typeof(Strings))] + [Range(typeof(Vector), "0,0", "max,max")] + public Vector Sigma + { + get => _sigma; + set => SetAndRaise(SigmaProperty, ref _sigma, value); + } + + [Display(Name = nameof(Strings.Color), ResourceType = typeof(Strings))] + public Color Color + { + get => _color; + set => SetAndRaise(ColorProperty, ref _color, value); + } + + [Display(Name = nameof(Strings.ShadowOnly), ResourceType = typeof(Strings))] + public bool ShadowOnly + { + get => _shadowOnly; + set => SetAndRaise(ShadowOnlyProperty, ref _shadowOnly, value); + } + + public override void ApplyTo(FilterEffectContext context) + { + context.DropShadow(Position, Sigma, Color, ShadowOnly); + } + + public override Rect TransformBounds(Rect bounds) + { + Rect shadowBounds = bounds + .Translate(_position) + .Inflate(new Thickness(_sigma.X * 3, _sigma.Y * 3)); + + return _shadowOnly ? shadowBounds : bounds.Union(shadowBounds); + } +} diff --git a/src/Beutl.Graphics/Graphics/FilterEffects/FilterEffect.cs b/src/Beutl.Graphics/Graphics/FilterEffects/FilterEffect.cs new file mode 100644 index 000000000..a76c7e68f --- /dev/null +++ b/src/Beutl.Graphics/Graphics/FilterEffects/FilterEffect.cs @@ -0,0 +1,71 @@ +using Beutl.Animation; +using Beutl.Media; + +namespace Beutl.Graphics.Effects; + +public abstract class FilterEffect : Animatable, IAffectsRender +{ + public static readonly CoreProperty IsEnabledProperty; + private bool _isEnabled = true; + + static FilterEffect() + { + IsEnabledProperty = ConfigureProperty(nameof(IsEnabled)) + .Accessor(o => o.IsEnabled, (o, v) => o.IsEnabled = v) + .DefaultValue(true) + .Register(); + + AffectsRender(IsEnabledProperty); + } + + protected FilterEffect() + { + AnimationInvalidated += (_, e) => RaiseInvalidated(e); + } + + public event EventHandler? Invalidated; + + public bool IsEnabled + { + get => _isEnabled; + set => SetAndRaise(IsEnabledProperty, ref _isEnabled, value); + } + + protected static void AffectsRender(params CoreProperty[] properties) + where T : FilterEffect + { + foreach (CoreProperty? item in properties) + { + item.Changed.Subscribe(e => + { + if (e.Sender is T s) + { + s.RaiseInvalidated(new RenderInvalidatedEventArgs(s, e.Property.Name)); + + if (e.OldValue is IAffectsRender oldAffectsRender) + oldAffectsRender.Invalidated -= s.OnAffectsRenderInvalidated; + + if (e.NewValue is IAffectsRender newAffectsRender) + newAffectsRender.Invalidated += s.OnAffectsRenderInvalidated; + } + }); + } + } + + private void OnAffectsRenderInvalidated(object? sender, RenderInvalidatedEventArgs e) + { + Invalidated?.Invoke(this, e); + } + + protected void RaiseInvalidated(RenderInvalidatedEventArgs args) + { + Invalidated?.Invoke(this, args); + } + + public abstract void ApplyTo(FilterEffectContext context); + + public virtual Rect TransformBounds(Rect bounds) + { + return bounds; + } +} diff --git a/src/Beutl.Graphics/Graphics/FilterEffects/FilterEffectContext.cs b/src/Beutl.Graphics/Graphics/FilterEffects/FilterEffectContext.cs new file mode 100644 index 000000000..db054775b --- /dev/null +++ b/src/Beutl.Graphics/Graphics/FilterEffects/FilterEffectContext.cs @@ -0,0 +1,245 @@ +using System.ComponentModel; + +using Beutl.Graphics.Rendering; +using Beutl.Media; +using Beutl.Media.Pixel; +using Beutl.Media.Source; + +using SkiaSharp; + +namespace Beutl.Graphics.Effects; + +public class FilterEffectBuilder : IDisposable +{ + private SKImageFilter? _filter; + + public void AppendSkiaFilter(T data, Func factory) + { + SKImageFilter? input = _filter; + _filter = factory(data, input); + input?.Dispose(); + } + + public bool HasFilter() => _filter != null; + + public SKImageFilter? GetFilter() + { + return _filter; + } + + public void Clear() + { + _filter?.Dispose(); + _filter = null; + } + + public void Dispose() + { + Clear(); + } +} + +public sealed class EffectTarget : IDisposable +{ + public EffectTarget(FilterEffectNode node) + { + Node = node; + } + + public EffectTarget(Ref surface) + { + Surface = surface; + } + + public FilterEffectNode? Node { get; } + + public Ref? Surface { get; } + + public EffectTarget Clone() + { + if (Node != null) + { + return this; + } + else + { + return new EffectTarget(Surface!.Clone()); + } + } + + public void Dispose() + { + Surface?.Dispose(); + } + + public void Draw(ImmediateCanvas canvas) + { + if (Node != null) + { + // ImageEffectContextの処理はImageEffectNode.Renderで + // 行うので以下は循環する + // _node.Render(canvas); + foreach (IGraphicNode item in Node.Children) + { + item.Render(canvas); + } + } + else if (Surface != null) + { + canvas._canvas.DrawSurface(Surface.Value, default); + } + } +} + +public sealed class FilterEffectContext : IDisposable +{ + private readonly FilterEffectBuilder _builder; + private readonly EffectTarget _initialTarget; + + private EffectTarget _target; + private Rect _originalBounds; + private Rect _bounds; + + public FilterEffectContext(Rect bounds, EffectTarget target, FilterEffectBuilder builder) + { + _bounds = _originalBounds = bounds; + _initialTarget = target; + _target = target; + _builder = builder; + } + + public Rect OriginalBounds => _originalBounds; + + public Rect Bounds => _bounds; + + [EditorBrowsable(EditorBrowsableState.Advanced)] + public FilterEffectBuilder Builder => _builder; + + [EditorBrowsable(EditorBrowsableState.Advanced)] + public EffectTarget CurrentTarget => _target; + + public void Dispose() + { + if (_initialTarget != _target) + { + _target.Dispose(); + } + } + + //public ICanvas Open() + //{ + // throw new NotImplementedException(); + //} + + public Bitmap Snapshot() + { + Flush(true); + if (_target.Surface != null) + { + return _target.Surface.Value.Snapshot().ToBitmap(); + } + else + { + throw new InvalidOperationException(); + } + } + + public void Flush(bool force = true) + { + if (force || _builder.HasFilter()) + { + var surface = SKSurface.Create( + new SKImageInfo((int)_bounds.Width, (int)_bounds.Height, SKColorType.Bgra8888, SKAlphaType.Unpremul)); + + using var canvas = new ImmediateCanvas(surface, true); + using var paint = new SKPaint + { + ImageFilter = _builder.GetFilter(), + }; + + int restoreCount = surface.Canvas.SaveLayer(paint); + using (canvas.PushTransform(Matrix.CreateTranslation(_originalBounds.X - _bounds.X, _originalBounds.Y - _bounds.Y))) + { + _target.Draw(canvas); + } + + surface.Canvas.RestoreToCount(restoreCount); + + _target?.Dispose(); + _target = new EffectTarget(Ref.Create(surface)); + _builder.Clear(); + + _originalBounds = _bounds; + } + } + + public void DropShadow(Point position, Vector sigma, Color color, bool shadowOnly) + { + Rect shadowBounds = Bounds + .Translate(position) + .Inflate(new Thickness(sigma.X * 3, sigma.Y * 3)); + + _bounds = shadowOnly ? shadowBounds : _bounds.Union(shadowBounds); + + if (shadowOnly) + { + _builder.AppendSkiaFilter((position, sigma, color), (t, input) => + { + return SKImageFilter.CreateDropShadowOnly(t.position.X, t.position.Y, t.sigma.X, t.sigma.Y, t.color.ToSKColor(), input); + }); + } + else + { + _builder.AppendSkiaFilter((position, sigma, color), (t, input) => + { + return SKImageFilter.CreateDropShadow(t.position.X, t.position.Y, t.sigma.X, t.sigma.Y, t.color.ToSKColor(), input); + }); + } + } + + public void Blur(Vector sigma) + { + _bounds = _bounds.Inflate(new Thickness(sigma.X * 3, sigma.Y * 3)); + + _builder.AppendSkiaFilter(sigma, (t, input) => + { + return SKImageFilter.CreateBlur(t.X, t.Y, input); + }); + } + + public void DisplacementMap(SKColorChannel xChannelSelector, SKColorChannel yChannelSelector, float scale, FilterEffect displacement) + { + SKImageFilter? skDisplacement; + Flush(false); + using (EffectTarget cloned = _target.Clone()) + using (var builder = new FilterEffectBuilder()) + using (var context = new FilterEffectContext(_bounds, cloned, builder)) + { + displacement.ApplyTo(context); + + context.Flush(false); + + skDisplacement = builder.GetFilter(); + if (skDisplacement == null && context._target.Surface != null) + { + SKSurface innerSurface = context._target.Surface.Value; + using (SKImage skImage = innerSurface.Snapshot()) + { + skDisplacement = SKImageFilter.CreateImage(skImage); + } + } + } + + _builder.AppendSkiaFilter((xChannelSelector, yChannelSelector, scale, skDisplacement), (t, input) => + { + return SKImageFilter.CreateDisplacementMapEffect(t.xChannelSelector, t.yChannelSelector, t.scale, t.skDisplacement, input); + }); + + _bounds = _bounds.Inflate(scale / 2); + } + + public void Compose(FilterEffect outer, FilterEffect inner) + { + } +} + diff --git a/src/Beutl.Graphics/Graphics/FilterEffects/FilterEffectGroup.cs b/src/Beutl.Graphics/Graphics/FilterEffects/FilterEffectGroup.cs new file mode 100644 index 000000000..2444294af --- /dev/null +++ b/src/Beutl.Graphics/Graphics/FilterEffects/FilterEffectGroup.cs @@ -0,0 +1,91 @@ +using System.Text.Json.Nodes; + +using Beutl.Animation; + +namespace Beutl.Graphics.Effects; + +public sealed class FilterEffectGroup : FilterEffect +{ + public static readonly CoreProperty ChildrenProperty; + private readonly FilterEffects _children; + + static FilterEffectGroup() + { + ChildrenProperty = ConfigureProperty(nameof(Children)) + .Accessor(o => o.Children, (o, v) => o.Children = v) + .Register(); + } + + public FilterEffectGroup() + { + _children = new FilterEffects(); + _children.Invalidated += (_, e) => RaiseInvalidated(e); + } + + [NotAutoSerialized] + public FilterEffects Children + { + get => _children; + set => _children.Replace(value); + } + + public override void ReadFromJson(JsonObject json) + { + base.ReadFromJson(json); + if (json.TryGetPropertyValue(nameof(Children), out JsonNode? childrenNode) + && childrenNode is JsonArray childrenArray) + { + _children.Clear(); + _children.EnsureCapacity(childrenArray.Count); + + foreach (JsonObject childJson in childrenArray.OfType()) + { + if (childJson.TryGetDiscriminator(out Type? type) + && type.IsAssignableTo(typeof(FilterEffect)) + && Activator.CreateInstance(type) is FilterEffect imageFilter) + { + imageFilter.ReadFromJson(childJson); + _children.Add(imageFilter); + } + } + } + } + + public override void WriteToJson(JsonObject json) + { + base.WriteToJson(json); + + var array = new JsonArray(); + + foreach (FilterEffect item in _children.GetMarshal().Value) + { + var itemJson = new JsonObject(); + item.WriteToJson(itemJson); + itemJson.WriteDiscriminator(item.GetType()); + + array.Add(itemJson); + } + + json[nameof(Children)] = array; + } + + public override void ApplyAnimations(IClock clock) + { + base.ApplyAnimations(clock); + foreach (FilterEffect item in Children.GetMarshal().Value) + { + (item as IAnimatable)?.ApplyAnimations(clock); + } + } + + public override void ApplyTo(FilterEffectContext context) + { + foreach (FilterEffect item in _children.GetMarshal().Value) + { + if (item.IsEnabled) + { + item.ApplyTo(context); + } + } + } +} diff --git a/src/Beutl.Graphics/Rendering/Renderables.cs b/src/Beutl.Graphics/Graphics/FilterEffects/FilterEffects.cs similarity index 81% rename from src/Beutl.Graphics/Rendering/Renderables.cs rename to src/Beutl.Graphics/Graphics/FilterEffects/FilterEffects.cs index 5305162ac..26743fb50 100644 --- a/src/Beutl.Graphics/Rendering/Renderables.cs +++ b/src/Beutl.Graphics/Graphics/FilterEffects/FilterEffects.cs @@ -2,14 +2,14 @@ using System.Collections.Specialized; using Beutl.Collections; +using Beutl.Graphics.Effects; using Beutl.Media; -namespace Beutl.Rendering; +namespace Beutl.Graphics.Effects; -public sealed class Renderables : HierarchicalList, IAffectsRender +public sealed class FilterEffects : CoreList, IAffectsRender { - public Renderables(IModifiableHierarchical parent) - : base(parent) + public FilterEffects() { ResetBehavior = ResetBehavior.Remove; CollectionChanged += OnCollectionChanged; @@ -21,7 +21,7 @@ void AddHandlers(IList list) { foreach (IAffectsRender? item in list.OfType()) { - item.Invalidated += OnItemInvalidated; + item.Invalidated += Item_Invalidated; } } @@ -29,7 +29,7 @@ void RemoveHandlers(IList list) { foreach (IAffectsRender? item in list.OfType()) { - item.Invalidated -= OnItemInvalidated; + item.Invalidated -= Item_Invalidated; } } @@ -56,7 +56,7 @@ void RemoveHandlers(IList list) public event EventHandler? Invalidated; - private void OnItemInvalidated(object? sender, RenderInvalidatedEventArgs e) + private void Item_Invalidated(object? sender, RenderInvalidatedEventArgs e) { RaiseInvalidated(e); } @@ -65,5 +65,4 @@ private void RaiseInvalidated(RenderInvalidatedEventArgs args) { Invalidated?.Invoke(this, args); } - } diff --git a/src/Beutl.Graphics/Graphics/Filters/BrushImageFilter.cs b/src/Beutl.Graphics/Graphics/Filters/BrushImageFilter.cs index ced7d9460..ffdb4a524 100644 --- a/src/Beutl.Graphics/Graphics/Filters/BrushImageFilter.cs +++ b/src/Beutl.Graphics/Graphics/Filters/BrushImageFilter.cs @@ -48,7 +48,7 @@ public override void ApplyAnimations(IClock clock) if (Brush != null) { var paint = new SKPaint(); - Canvas.ConfigurePaint(paint, bounds.Size, Brush, BlendMode); + ImmediateCanvas.ConfigurePaint(paint, bounds.Size, Brush, BlendMode); return SKImageFilter.CreatePaint(paint); } else diff --git a/src/Beutl.Graphics/Graphics/ICanvas.cs b/src/Beutl.Graphics/Graphics/ICanvas.cs index 6e1041e3f..6afa61bf9 100644 --- a/src/Beutl.Graphics/Graphics/ICanvas.cs +++ b/src/Beutl.Graphics/Graphics/ICanvas.cs @@ -1,4 +1,5 @@ -using Beutl.Graphics.Filters; +using Beutl.Graphics.Effects; +using Beutl.Graphics.Filters; using Beutl.Media; using Beutl.Media.Pixel; using Beutl.Media.TextFormatting; @@ -13,9 +14,9 @@ public interface ICanvas : IDisposable bool IsDisposed { get; } - BlendMode BlendMode { get; set; } + BlendMode BlendMode { get; } - Matrix Transform { get; set; } + Matrix Transform { get; } void Clear(); @@ -43,8 +44,11 @@ public interface ICanvas : IDisposable PushedState PushOpacityMask(IBrush mask, Rect bounds, bool invert = false); + [Obsolete("Use PushFilterEffect")] PushedState PushImageFilter(IImageFilter filter, Rect bounds); + PushedState PushFilterEffect(FilterEffect effect); + PushedState PushBlendMode(BlendMode blendMode); PushedState PushTransform(Matrix matrix, TransformOperator transformOperator = TransformOperator.Prepend); diff --git a/src/Beutl.Graphics/Graphics/IDrawable.cs b/src/Beutl.Graphics/Graphics/IDrawable.cs index 8cd2af100..5fe258afa 100644 --- a/src/Beutl.Graphics/Graphics/IDrawable.cs +++ b/src/Beutl.Graphics/Graphics/IDrawable.cs @@ -23,12 +23,5 @@ public interface IDrawable : IRenderable RelativePoint TransformOrigin { get; set; } - void Draw(ICanvas canvas); - IBitmap ToBitmap(); - - void IRenderable.Render(IRenderer renderer) - { - Draw(renderer.Graphics); - } } diff --git a/src/Beutl.Graphics/Graphics/Image.cs b/src/Beutl.Graphics/Graphics/Image.cs index 8d706e429..14ac57469 100644 --- a/src/Beutl.Graphics/Graphics/Image.cs +++ b/src/Beutl.Graphics/Graphics/Image.cs @@ -83,6 +83,13 @@ public static Bitmap ToBitmap(this SKBitmap self) } } + public static Bitmap ToBitmap(this SKImage self) + { + var result = new Bitmap(self.Width, self.Height); + self.ReadPixels(new SKImageInfo(self.Width, self.Height, SKColorType.Bgra8888), result.Data); + return result; + } + public static SKBitmap ToSKBitmap(this Bitmap self) { var result = new SKBitmap(new(self.Width, self.Height, SKColorType.Bgra8888)); diff --git a/src/Beutl.Graphics/Graphics/Canvas.cs b/src/Beutl.Graphics/Graphics/ImmediateCanvas.cs similarity index 95% rename from src/Beutl.Graphics/Graphics/Canvas.cs rename to src/Beutl.Graphics/Graphics/ImmediateCanvas.cs index 850c8407f..36aced109 100644 --- a/src/Beutl.Graphics/Graphics/Canvas.cs +++ b/src/Beutl.Graphics/Graphics/ImmediateCanvas.cs @@ -1,8 +1,10 @@ using System.Runtime.CompilerServices; +using Beutl.Graphics.Effects; using Beutl.Graphics.Filters; using Beutl.Media; using Beutl.Media.Pixel; +using Beutl.Media.Source; using Beutl.Media.TextFormatting; using Beutl.Threading; @@ -13,8 +15,7 @@ namespace Beutl.Graphics; -// https://github.com/AvaloniaUI/Avalonia/blob/master/src/Skia/Avalonia.Skia/DrawingContextImpl.cs -public partial class Canvas : ICanvas +public partial class ImmediateCanvas : ICanvas { private readonly SKSurface _surface; internal readonly SKCanvas _canvas; @@ -22,21 +23,25 @@ public partial class Canvas : ICanvas private readonly SKPaint _sharedFillPaint = new(); private readonly SKPaint _sharedStrokePaint = new(); private readonly Stack _states = new(); + private readonly bool _leaveOpen; private Matrix _currentTransform; - public Canvas(int width, int height) + public ImmediateCanvas(SKSurface surface, bool leaveOpen) { _dispatcher = Dispatcher.Current; - Size = new PixelSize(width, height); - var info = new SKImageInfo(width, height, SKColorType.Bgra8888, SKAlphaType.Unpremul); - - _surface = SKSurface.Create(info); - + Size = surface.Canvas.DeviceClipBounds.Size.ToGraphicsSize(); + _surface = surface; _canvas = _surface.Canvas; _currentTransform = _canvas.TotalMatrix.ToMatrix(); + _leaveOpen = leaveOpen; + } + + public ImmediateCanvas(int width, int height) + : this(SKSurface.Create(new SKImageInfo(width, height, SKColorType.Bgra8888, SKAlphaType.Unpremul)), false) + { } - ~Canvas() + ~ImmediateCanvas() { Dispose(); } @@ -88,7 +93,11 @@ public void Dispose() { void DisposeCore() { - _surface.Dispose(); + if (!_leaveOpen) + { + _surface.Dispose(); + } + _sharedFillPaint.Dispose(); _sharedStrokePaint.Dispose(); GC.SuppressFinalize(this); @@ -308,9 +317,11 @@ public void Pop(int count = -1) if (count < 0) { - if (_states.TryPop(out CanvasPushedState? state)) + while (count < 0 + && _states.TryPop(out CanvasPushedState? state)) { state.Pop(this); + count++; } } else @@ -410,10 +421,15 @@ public PushedState PushBlendMode(BlendMode blendMode) return new PushedState(this, _states.Count); } + public PushedState PushFilterEffect(FilterEffect effect) + { + throw new NotSupportedException("ImmediateCanvasはFilterEffectに対応しません"); + } + private void VerifyAccess() { if (IsDisposed) - throw new ObjectDisposedException(nameof(Canvas)); + throw new ObjectDisposedException(nameof(ImmediateCanvas)); _dispatcher?.VerifyAccess(); } @@ -560,12 +576,13 @@ private static void ConfigureGradientBrush(SKPaint paint, Size targetSize, IGrad private static void ConfigureTileBrush(SKPaint paint, Size targetSize, ITileBrush tileBrush) { - IBitmap? bitmap; + // Todo: DrawableBrush + Ref? bitmap; if (tileBrush is IDrawableBrush { Drawable: { } } drawableBrush) { - bitmap = drawableBrush.Drawable.ToBitmap(); + bitmap = Ref.Create(drawableBrush.Drawable.ToBitmap()); } - else if ((tileBrush as IImageBrush)?.Source?.Read(out bitmap) == true) + else if ((tileBrush as IImageBrush)?.Source?.TryGetRef(out bitmap) == true) { } else @@ -573,13 +590,13 @@ private static void ConfigureTileBrush(SKPaint paint, Size targetSize, ITileBrus throw new InvalidOperationException(); } - var calc = new TileBrushCalculator(tileBrush, new Size(bitmap.Width, bitmap.Height), targetSize); + var calc = new TileBrushCalculator(tileBrush, new Size(bitmap.Value.Width, bitmap.Value.Height), targetSize); SKSizeI intermediateSize = calc.IntermediateSize.ToSKSize().ToSizeI(); var intermediate = new SKBitmap(new SKImageInfo(intermediateSize.Width, intermediateSize.Height, SKColorType.Bgra8888)); using (var canvas = new SKCanvas(intermediate)) { - using var target = bitmap.ToSKBitmap(); + using var target = bitmap.Value.ToSKBitmap(); using var ipaint = new SKPaint(); ipaint.FilterQuality = tileBrush.BitmapInterpolationMode.ToSKFilterQuality(); diff --git a/src/Beutl.Graphics/Graphics/Rect.cs b/src/Beutl.Graphics/Graphics/Rect.cs index 6294e1123..7c05d3578 100644 --- a/src/Beutl.Graphics/Graphics/Rect.cs +++ b/src/Beutl.Graphics/Graphics/Rect.cs @@ -243,6 +243,18 @@ public bool Contains(Point p) p.Y >= Y && p.Y <= Y + Height; } + /// + /// Determines whether a point is in the bounds of the rectangle, exclusive of the + /// rectangle's bottom/right edge. + /// + /// The point. + /// true if the point is in the bounds of the rectangle; otherwise false. + public bool ContainsExclusive(Point p) + { + return p.X >= X && p.X < X + Width && + p.Y >= Y && p.Y < Y + Height; + } + /// /// Determines whether the rectangle fully contains another rectangle. /// diff --git a/src/Beutl.Graphics/Graphics/Rendering/BitmapNode.cs b/src/Beutl.Graphics/Graphics/Rendering/BitmapNode.cs new file mode 100644 index 000000000..4a1e2d10a --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/BitmapNode.cs @@ -0,0 +1,50 @@ +using Beutl.Media; + +namespace Beutl.Graphics.Rendering; + +public sealed class BitmapNode : BrushDrawNode +{ + public BitmapNode(IBitmap bitmap, IBrush? fill, IPen? pen) + : base(fill, pen, PenHelper.GetBounds(new Rect(0, 0, bitmap.Width, bitmap.Height), pen)) + { + Bitmap = bitmap; + } + + public IBitmap Bitmap { get; } + + public bool Equals(IBitmap bitmap, IBrush? fill, IPen? pen) + { + return Bitmap == bitmap + && Fill == fill + && Pen == pen; + } + + public override void Render(ImmediateCanvas canvas) + { + canvas.DrawBitmap(Bitmap, Fill, Pen); + } + + public override void Dispose() + { + Bitmap.Dispose(); + } + + public override bool HitTest(Point point) + { + StrokeAlignment alignment = Pen?.StrokeAlignment ?? StrokeAlignment.Inside; + float thickness = Pen?.Thickness ?? 0; + thickness = PenHelper.GetRealThickness(alignment, thickness); + + if (Fill != null) + { + Rect rect = Bounds.Inflate(thickness); + return rect.ContainsExclusive(point); + } + else + { + Rect borderRect = Bounds.Inflate(thickness); + Rect emptyRect = Bounds.Deflate(thickness); + return borderRect.ContainsExclusive(point) && !emptyRect.ContainsExclusive(point); + } + } +} diff --git a/src/Beutl.Graphics/Graphics/Rendering/BlendModeNode.cs b/src/Beutl.Graphics/Graphics/Rendering/BlendModeNode.cs new file mode 100644 index 000000000..d70fad5b9 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/BlendModeNode.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Beutl.Graphics.Rendering; + +public sealed class BlendModeNode : ContainerNode +{ + public BlendModeNode(BlendMode blendMode) + { + BlendMode = blendMode; + } + + public BlendMode BlendMode { get; } + + public bool Equals(BlendMode blendMode) + { + return BlendMode == blendMode; + } + + public override void Dispose() + { + } + + public override void Render(ImmediateCanvas canvas) + { + using (canvas.PushBlendMode(BlendMode)) + { + base.Render(canvas); + } + } +} diff --git a/src/Beutl.Graphics/Graphics/Rendering/BrushDrawNode.cs b/src/Beutl.Graphics/Graphics/Rendering/BrushDrawNode.cs new file mode 100644 index 000000000..0a30f0f61 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/BrushDrawNode.cs @@ -0,0 +1,17 @@ +using Beutl.Media; + +namespace Beutl.Graphics.Rendering; + +public abstract class BrushDrawNode : DrawNode +{ + protected BrushDrawNode(IBrush? fill, IPen? pen, Rect bounds) + : base(bounds) + { + Fill = fill; + Pen = pen; + } + + public IBrush? Fill { get; } + + public IPen? Pen { get; } +} diff --git a/src/Beutl.Graphics/Graphics/Rendering/ClearNode.cs b/src/Beutl.Graphics/Graphics/Rendering/ClearNode.cs new file mode 100644 index 000000000..6a447ed22 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/ClearNode.cs @@ -0,0 +1,33 @@ +using Beutl.Media; + +namespace Beutl.Graphics.Rendering; + +public sealed class ClearNode : DrawNode +{ + public ClearNode(Color color) + : base(Rect.Empty) + { + Color = color; + } + + public Color Color { get; } + + public bool Equals(Color color) + { + return Color == color; + } + + public override void Render(ImmediateCanvas canvas) + { + canvas.Clear(Color); + } + + public override void Dispose() + { + } + + public override bool HitTest(Point point) + { + return false; + } +} diff --git a/src/Beutl.Graphics/Graphics/Rendering/ContainerNode.cs b/src/Beutl.Graphics/Graphics/Rendering/ContainerNode.cs new file mode 100644 index 000000000..ca44b7e96 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/ContainerNode.cs @@ -0,0 +1,87 @@ +namespace Beutl.Graphics.Rendering; + +public class ContainerNode : IGraphicNode +{ + private readonly List _children = new List(); + private bool _isBoundsDirty = true; + private Rect _originalBounds; + + public Rect OriginalBounds + { + get + { + //if (_isBoundsDirty) + { + _originalBounds = default; + foreach (IGraphicNode child in _children) + { + _originalBounds = _originalBounds.Union(child.Bounds); + } + + _originalBounds = _originalBounds.Normalize(); + _isBoundsDirty = false; + } + + return _originalBounds; + } + } + + public Rect Bounds => TransformBounds(OriginalBounds); + + public IReadOnlyList Children => _children; + + public virtual void Dispose() + { + GC.SuppressFinalize(this); + } + + public virtual bool HitTest(Point point) + { + foreach (IGraphicNode child in Children) + { + if (child.HitTest(point)) + return true; + } + + return false; + } + + public virtual void Render(ImmediateCanvas canvas) + { + foreach (IGraphicNode item in _children) + { + item.Render(canvas); + } + } + + protected virtual Rect TransformBounds(Rect bounds) + { + return bounds; + } + + public void AddChild(IGraphicNode item) + { + ArgumentNullException.ThrowIfNull(item); + _children.Add(item); + _isBoundsDirty = true; + } + + public void RemoveChild(IGraphicNode item) + { + ArgumentNullException.ThrowIfNull(item); + _children.Remove(item); + _isBoundsDirty = true; + } + + public void RemoveRange(int index, int count) + { + _children.RemoveRange(index, count); + _isBoundsDirty = _isBoundsDirty || count > 0; + } + + public void SetChild(int index, IGraphicNode item) + { + _children[index] = item; + _isBoundsDirty = true; + } +} diff --git a/src/Beutl.Graphics/Graphics/Rendering/DeferradCanvas.cs b/src/Beutl.Graphics/Graphics/Rendering/DeferradCanvas.cs new file mode 100644 index 000000000..ad15d595a --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/DeferradCanvas.cs @@ -0,0 +1,316 @@ +using Beutl.Graphics.Effects; +using Beutl.Graphics.Filters; +using Beutl.Media; +using Beutl.Media.Pixel; +using Beutl.Media.TextFormatting; + +namespace Beutl.Graphics.Rendering; + +public sealed class DeferradCanvas : ICanvas +{ + private readonly Stack<(ContainerNode, int)> _nodes; + private ContainerNode _container; + private int _drawOperationindex; + + public DeferradCanvas(ContainerNode container, PixelSize canvasSize = default) + { + _container = container; + _nodes = new Stack<(ContainerNode, int)>(); + Size = canvasSize; + } + + public PixelSize Size { get; } + + public bool IsDisposed { get; } + + public BlendMode BlendMode { get; } = BlendMode.SrcOver; + + public Matrix Transform { get; } = Matrix.Identity; + + private void Add(IGraphicNode node) + { + if (_drawOperationindex < _container.Children.Count) + { + _container.SetChild(_drawOperationindex, node); + } + else + { + _container.AddChild(node); + } + } + + private void AddAndPush(ContainerNode node) + { + Add(node); + Push(node); + } + + private void Push(ContainerNode node) + { + _nodes.Push((_container, _drawOperationindex + 1)); + + _drawOperationindex = 0; + _container = node; + } + + private T? Next() where T : class, IGraphicNode + { + return _drawOperationindex < _container.Children.Count ? _container.Children[_drawOperationindex] as T : null; + } + + public void Dispose() + { + } + + public void Reset() + { + _drawOperationindex = 0; + _nodes.Clear(); + } + + public void Clear() + { + ClearNode? next = Next(); + + if (next == null || !next.Equals(default)) + { + Add(new ClearNode(default)); + } + + ++_drawOperationindex; + } + + public void Clear(Color color) + { + ClearNode? next = Next(); + + if (next == null || !next.Equals(color)) + { + Add(new ClearNode(color)); + } + + ++_drawOperationindex; + } + + public void DrawBitmap(IBitmap bitmap, IBrush? fill, IPen? pen) + { + BitmapNode? next = Next(); + + if (next == null || !next.Equals(bitmap, fill, pen)) + { + Add(new BitmapNode(bitmap, fill, pen)); + } + + ++_drawOperationindex; + } + + public void DrawEllipse(Rect rect, IBrush? fill, IPen? pen) + { + EllipseNode? next = Next(); + + if (next == null || !next.Equals(rect, fill, pen)) + { + Add(new EllipseNode(rect, fill, pen)); + } + + ++_drawOperationindex; + } + + public void DrawGeometry(Geometry geometry, IBrush? fill, IPen? pen) + { + GeometryNode? next = Next(); + + if (next == null || !next.Equals(geometry, fill, pen)) + { + Add(new GeometryNode(geometry, fill, pen)); + } + + ++_drawOperationindex; + } + + public void DrawRectangle(Rect rect, IBrush? fill, IPen? pen) + { + RectangleNode? next = Next(); + + if (next == null || !next.Equals(rect, fill, pen)) + { + Add(new RectangleNode(rect, fill, pen)); + } + + ++_drawOperationindex; + } + + public void DrawText(FormattedText text, IBrush? fill, IPen? pen) + { + TextNode? next = Next(); + + if (next == null || !next.Equals(text, fill, pen)) + { + Add(new TextNode(text, fill, pen)); + } + + ++_drawOperationindex; + } + + public Bitmap GetBitmap() + { + throw new NotImplementedException(); + } + + public void Pop(int count = -1) + { + if (count < 0) + { + while (count < 0 + && _nodes.TryPop(out (ContainerNode, int) state)) + { + _container.RemoveRange(_drawOperationindex, _container.Children.Count - _drawOperationindex); + + _container = state.Item1; + _drawOperationindex = state.Item2; + + count++; + } + } + else + { + while (_nodes.Count >= count + && _nodes.TryPop(out (ContainerNode, int) state)) + { + _container.RemoveRange(_drawOperationindex, _container.Children.Count - _drawOperationindex); + + _container = state.Item1; + _drawOperationindex = state.Item2; + } + } + } + + public PushedState Push() + { + PushNode? next = Next(); + + if (next == null) + { + AddAndPush(new PushNode()); + } + else + { + Push(next); + } + + return new(this, _nodes.Count); + } + + public PushedState PushBlendMode(BlendMode blendMode) + { + BlendModeNode? next = Next(); + + if (next == null || !next.Equals(blendMode)) + { + AddAndPush(new BlendModeNode(blendMode)); + } + else + { + Push(next); + } + + return new(this, _nodes.Count); + } + + public PushedState PushClip(Rect clip, ClipOperation operation = ClipOperation.Intersect) + { + RectClipNode? next = Next(); + + if (next == null || !next.Equals(clip, operation)) + { + AddAndPush(new RectClipNode(clip, operation)); + } + else + { + Push(next); + } + + return new(this, _nodes.Count); + } + + public PushedState PushClip(Geometry geometry, ClipOperation operation = ClipOperation.Intersect) + { + GeometryClipNode? next = Next(); + + if (next == null || !next.Equals(geometry, operation)) + { + AddAndPush(new GeometryClipNode(geometry, operation)); + } + else + { + Push(next); + } + + return new(this, _nodes.Count); + } + + [Obsolete("Use PushFilterEffect")] + public PushedState PushImageFilter(IImageFilter filter, Rect bounds) + { + ImageFilterNode? next = Next(); + + if (next == null || !next.Equals(filter, bounds)) + { + AddAndPush(new ImageFilterNode(filter, bounds)); + } + else + { + Push(next); + } + + return new(this, _nodes.Count); + } + + public PushedState PushFilterEffect(FilterEffect effect) + { + FilterEffectNode? next = Next(); + + if (next == null || !next.Equals(effect)) + { + AddAndPush(new FilterEffectNode(effect)); + } + else + { + Push(next); + } + + return new(this, _nodes.Count); + } + + public PushedState PushOpacityMask(IBrush mask, Rect bounds, bool invert = false) + { + OpacityMaskNode? next = Next(); + + if (next == null || !next.Equals(mask, bounds, invert)) + { + AddAndPush(new OpacityMaskNode(mask, bounds, invert)); + } + else + { + Push(next); + } + + return new(this, _nodes.Count); + } + + public PushedState PushTransform(Matrix matrix, TransformOperator transformOperator = TransformOperator.Prepend) + { + TransformNode? next = Next(); + + if (next == null || !next.Equals(matrix, transformOperator)) + { + AddAndPush(new TransformNode(matrix, transformOperator)); + } + else + { + Push(next); + } + + return new(this, _nodes.Count); + } +} diff --git a/src/Beutl.Graphics/Graphics/Rendering/DrawNode.cs b/src/Beutl.Graphics/Graphics/Rendering/DrawNode.cs new file mode 100644 index 000000000..e0bd50da4 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/DrawNode.cs @@ -0,0 +1,19 @@ +namespace Beutl.Graphics.Rendering; + +public abstract class DrawNode : IGraphicNode +{ + public DrawNode(Rect bounds) + { + bounds = bounds.Normalize(); + + Bounds = bounds; + } + + public Rect Bounds { get; } + + public abstract void Render(ImmediateCanvas canvas); + + public abstract void Dispose(); + + public abstract bool HitTest(Point point); +} diff --git a/src/Beutl.Graphics/Graphics/Rendering/DrawableNode.cs b/src/Beutl.Graphics/Graphics/Rendering/DrawableNode.cs new file mode 100644 index 000000000..dcc215da0 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/DrawableNode.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Beutl.Graphics.Rendering; + +public class DrawableNode : ContainerNode +{ + public DrawableNode(Drawable drawable) + { + Drawable = drawable; + } + + public Drawable Drawable { get; } +} diff --git a/src/Beutl.Graphics/Graphics/Rendering/EllipseNode.cs b/src/Beutl.Graphics/Graphics/Rendering/EllipseNode.cs new file mode 100644 index 000000000..0a2733505 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/EllipseNode.cs @@ -0,0 +1,79 @@ +using Beutl.Media; + +namespace Beutl.Graphics.Rendering; + +public sealed class EllipseNode : BrushDrawNode +{ + public EllipseNode(Rect rect, IBrush? fill, IPen? pen) + : base(fill, pen, PenHelper.GetBounds(rect, pen)) + { + Rect = rect; + } + + public Rect Rect { get; } + + public bool Equals(Rect rect, IBrush? fill, IPen? pen) + { + return Rect == rect + && Fill == fill + && Pen == pen; + } + + public override void Render(ImmediateCanvas canvas) + { + canvas.DrawEllipse(Rect, Fill, Pen); + } + + public override void Dispose() + { + } + + //https://github.com/AvaloniaUI/Avalonia/blob/release/0.10.21/src/Avalonia.Visuals/Rendering/SceneGraph/EllipseNode.cs + public override bool HitTest(Point point) + { + Point center = Rect.Center; + + float thickness = Pen?.Thickness ?? 0; + StrokeAlignment alignment = Pen?.StrokeAlignment ?? StrokeAlignment.Center; + float realThickness = PenHelper.GetRealThickness(alignment, thickness); + + float rx = Rect.Width / 2 + realThickness; + float ry = Rect.Height / 2 + realThickness; + + float dx = point.X - center.X; + float dy = point.Y - center.Y; + + if (Math.Abs(dx) > rx || Math.Abs(dy) > ry) + { + return false; + } + + if (Fill != null) + { + return Contains(rx, ry); + } + else if (thickness > 0) + { + bool inStroke = Contains(rx, ry); + + rx = Rect.Width / 2 - realThickness; + ry = Rect.Height / 2 - realThickness; + + bool inInner = Contains(rx, ry); + + return inStroke && !inInner; + } + + bool Contains(double radiusX, double radiusY) + { + double rx2 = radiusX * radiusX; + double ry2 = radiusY * radiusY; + + double distance = ry2 * dx * dx + rx2 * dy * dy; + + return distance <= rx2 * ry2; + } + + return false; + } +} diff --git a/src/Beutl.Graphics/Graphics/Rendering/FilterEffectNode.cs b/src/Beutl.Graphics/Graphics/Rendering/FilterEffectNode.cs new file mode 100644 index 000000000..4c47b5fb3 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/FilterEffectNode.cs @@ -0,0 +1,52 @@ +using Beutl.Graphics.Effects; + +using SkiaSharp; + +namespace Beutl.Graphics.Rendering; + +public class FilterEffectNode : ContainerNode +{ + public FilterEffectNode(FilterEffect filterEffect) + { + ImageEffect = filterEffect; + } + + public FilterEffect ImageEffect { get; } + + public bool Equals(FilterEffect imageEffect) + { + return ImageEffect == imageEffect; + } + + public override void Render(ImmediateCanvas canvas) + { + using (var builder = new FilterEffectBuilder()) + using (var target = new EffectTarget(this)) + using (var context = new FilterEffectContext(OriginalBounds, target, builder)) + { + ImageEffect.ApplyTo(context); + + if (context.Builder.HasFilter()) + { + using (var paint = new SKPaint()) + { + paint.ImageFilter = context.Builder.GetFilter(); + int count = canvas._canvas.SaveLayer(paint); + canvas._canvas.Translate(context.OriginalBounds.X, context.OriginalBounds.Y); + + context.CurrentTarget.Draw(canvas); + + canvas._canvas.RestoreToCount(count); + } + } + else if (context.CurrentTarget.Surface != null) + { + canvas._canvas.DrawSurface(context.CurrentTarget.Surface.Value, context.Bounds.X, context.Bounds.Y); + } + else + { + base.Render(canvas); + } + } + } +} diff --git a/src/Beutl.Graphics/Graphics/Rendering/GeometryClipNode.cs b/src/Beutl.Graphics/Graphics/Rendering/GeometryClipNode.cs new file mode 100644 index 000000000..3015e02b5 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/GeometryClipNode.cs @@ -0,0 +1,35 @@ +using Beutl.Media; + +namespace Beutl.Graphics.Rendering; + +public sealed class GeometryClipNode : ContainerNode +{ + public GeometryClipNode(Geometry clip, ClipOperation operation) + { + Clip = clip; + Operation = operation; + } + + public Geometry Clip { get; private set; } + + public ClipOperation Operation { get; } + + public bool Equals(Geometry clip, ClipOperation operation) + { + return Clip == clip + && Operation == operation; + } + + public override void Render(ImmediateCanvas canvas) + { + using (canvas.PushClip(Clip, Operation)) + { + base.Render(canvas); + } + } + + public override void Dispose() + { + Clip = null!; + } +} diff --git a/src/Beutl.Graphics/Graphics/Rendering/GeometryNode.cs b/src/Beutl.Graphics/Graphics/Rendering/GeometryNode.cs new file mode 100644 index 000000000..ba3b8c7d4 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/GeometryNode.cs @@ -0,0 +1,37 @@ +using Beutl.Media; + +namespace Beutl.Graphics.Rendering; + +public sealed class GeometryNode : BrushDrawNode +{ + public GeometryNode(Geometry geometry, IBrush? fill, IPen? pen) + : base(fill, pen, PenHelper.CalculateBoundsWithStrokeCap(geometry.GetRenderBounds(pen), pen)) + { + Geometry = geometry; + } + + public Geometry Geometry { get; private set; } + + public bool Equals(Geometry geometry, IBrush? fill, IPen? pen) + { + return Geometry == geometry + && Fill == fill + && Pen == pen; + } + + public override void Render(ImmediateCanvas canvas) + { + canvas.DrawGeometry(Geometry, Fill, Pen); + } + + public override void Dispose() + { + Geometry = null!; + } + + public override bool HitTest(Point point) + { + return (Fill != null && Geometry.FillContains(point)) + || (Pen != null && Geometry.StrokeContains(Pen, point)); + } +} diff --git a/src/Beutl.Graphics/Graphics/Rendering/IGraphicNode.cs b/src/Beutl.Graphics/Graphics/Rendering/IGraphicNode.cs new file mode 100644 index 000000000..073ab69b8 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/IGraphicNode.cs @@ -0,0 +1,10 @@ +namespace Beutl.Graphics.Rendering; + +public interface IGraphicNode : INode +{ + Rect Bounds { get; } + + bool HitTest(Point point); + + void Render(ImmediateCanvas canvas); +} diff --git a/src/Beutl.Graphics/Graphics/Rendering/INode.cs b/src/Beutl.Graphics/Graphics/Rendering/INode.cs new file mode 100644 index 000000000..b3646e1d9 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/INode.cs @@ -0,0 +1,5 @@ +namespace Beutl.Graphics.Rendering; + +public interface INode : IDisposable +{ +} diff --git a/src/Beutl.Graphics/Graphics/Rendering/ImageFilterNode.cs b/src/Beutl.Graphics/Graphics/Rendering/ImageFilterNode.cs new file mode 100644 index 000000000..ee3386ce2 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/ImageFilterNode.cs @@ -0,0 +1,36 @@ +using Beutl.Graphics.Filters; + +namespace Beutl.Graphics.Rendering; + +[Obsolete("Use FilterEffectNode")] +public sealed class ImageFilterNode : ContainerNode +{ + public ImageFilterNode(IImageFilter imageFilter, Rect filterBounds) + { + ImageFilter = imageFilter; + FilterBounds = filterBounds; + } + + public IImageFilter ImageFilter { get; private set; } + + public Rect FilterBounds { get; } + + public bool Equals(IImageFilter imageFilter, Rect filterBounds) + { + return ImageFilter == imageFilter + && FilterBounds == filterBounds; + } + + public override void Render(ImmediateCanvas canvas) + { + using (canvas.PushImageFilter(ImageFilter, FilterBounds)) + { + base.Render(canvas); + } + } + + public override void Dispose() + { + ImageFilter = null!; + } +} diff --git a/src/Beutl.Graphics/Graphics/Rendering/OpacityMaskNode.cs b/src/Beutl.Graphics/Graphics/Rendering/OpacityMaskNode.cs new file mode 100644 index 000000000..733ddd9b0 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/OpacityMaskNode.cs @@ -0,0 +1,39 @@ +using Beutl.Media; + +namespace Beutl.Graphics.Rendering; + +public sealed class OpacityMaskNode : ContainerNode +{ + public OpacityMaskNode(IBrush mask, Rect maskBounds, bool invert) + { + Mask = mask; + MaskBounds = maskBounds; + Invert = invert; + } + + public IBrush Mask { get; private set; } + + public Rect MaskBounds { get; } + + public bool Invert { get; } + + public bool Equals(IBrush? mask, Rect maskBounds, bool invert) + { + return Mask == mask + && MaskBounds == maskBounds + && Invert == invert; + } + + public override void Render(ImmediateCanvas canvas) + { + using (canvas.PushOpacityMask(Mask, MaskBounds, Invert)) + { + base.Render(canvas); + } + } + + public override void Dispose() + { + Mask = null!; + } +} diff --git a/src/Beutl.Graphics/Graphics/Rendering/PenHelper.cs b/src/Beutl.Graphics/Graphics/Rendering/PenHelper.cs new file mode 100644 index 000000000..252351656 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/PenHelper.cs @@ -0,0 +1,47 @@ +using Beutl.Media; +using Beutl.Utilities; + +namespace Beutl.Graphics.Rendering; + +internal static class PenHelper +{ + public static Rect GetBounds(Rect rect, IPen? pen) + { + if (pen != null) + { + float thickness = pen.Thickness; + rect = pen.StrokeAlignment switch + { + StrokeAlignment.Center => rect.Inflate(thickness / 2), + StrokeAlignment.Outside => rect.Inflate(thickness), + _ => rect, + }; + } + + return rect; + } + + public static float GetRealThickness(StrokeAlignment align, float thickness) + { + return align switch + { + StrokeAlignment.Inside => 0, + StrokeAlignment.Center => thickness / 2, + StrokeAlignment.Outside => thickness, + _ => 0, + }; + } + + public static Rect CalculateBoundsWithStrokeCap(Rect rect, IPen? pen) + { + if (pen == null || MathUtilities.IsZero(pen.Thickness)) return rect; + + return pen.StrokeCap switch + { + StrokeCap.Flat => rect, + StrokeCap.Round => rect.Inflate(pen.Thickness / 2), + StrokeCap.Square => rect.Inflate(pen.Thickness), + _ => rect, + }; + } +} diff --git a/src/Beutl.Graphics/Graphics/Rendering/PushNode.cs b/src/Beutl.Graphics/Graphics/Rendering/PushNode.cs new file mode 100644 index 000000000..1ec94e71f --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/PushNode.cs @@ -0,0 +1,16 @@ +namespace Beutl.Graphics.Rendering; + +public sealed class PushNode : ContainerNode +{ + public PushNode() + { + } + + public override void Render(ImmediateCanvas canvas) + { + using (canvas.Push()) + { + base.Render(canvas); + } + } +} diff --git a/src/Beutl.Graphics/Graphics/Rendering/RectClipNode.cs b/src/Beutl.Graphics/Graphics/Rendering/RectClipNode.cs new file mode 100644 index 000000000..ac9bca058 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/RectClipNode.cs @@ -0,0 +1,28 @@ +namespace Beutl.Graphics.Rendering; + +public sealed class RectClipNode : ContainerNode +{ + public RectClipNode(Rect clip, ClipOperation operation) + { + Clip = clip; + Operation = operation; + } + + public Rect Clip { get; } + + public ClipOperation Operation { get; } + + public bool Equals(Rect clip, ClipOperation operation) + { + return Clip == clip + && Operation == operation; + } + + public override void Render(ImmediateCanvas canvas) + { + using (canvas.PushClip(Clip, Operation)) + { + base.Render(canvas); + } + } +} diff --git a/src/Beutl.Graphics/Graphics/Rendering/RectangleNode.cs b/src/Beutl.Graphics/Graphics/Rendering/RectangleNode.cs new file mode 100644 index 000000000..1c2024e52 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/RectangleNode.cs @@ -0,0 +1,49 @@ +using Beutl.Media; + +namespace Beutl.Graphics.Rendering; + +public sealed class RectangleNode : BrushDrawNode +{ + public RectangleNode(Rect rect, IBrush? fill, IPen? pen) + : base(fill, pen, PenHelper.GetBounds(rect, pen)) + { + Rect = rect; + } + + public Rect Rect { get; } + + public bool Equals(Rect rect, IBrush? fill, IPen? pen) + { + return Rect == rect + && Fill == fill + && Pen == pen; + } + + public override void Render(ImmediateCanvas canvas) + { + canvas.DrawRectangle(Rect, Fill, Pen); + } + + public override void Dispose() + { + } + + public override bool HitTest(Point point) + { + StrokeAlignment alignment = Pen?.StrokeAlignment ?? StrokeAlignment.Inside; + float thickness = Pen?.Thickness ?? 0; + thickness = PenHelper.GetRealThickness(alignment, thickness); + + if (Fill != null) + { + Rect rect = Rect.Inflate(thickness); + return rect.ContainsExclusive(point); + } + else + { + Rect borderRect = Rect.Inflate(thickness); + Rect emptyRect = Rect.Deflate(thickness); + return borderRect.ContainsExclusive(point) && !emptyRect.ContainsExclusive(point); + } + } +} diff --git a/src/Beutl.Graphics/Graphics/Rendering/TextNode.cs b/src/Beutl.Graphics/Graphics/Rendering/TextNode.cs new file mode 100644 index 000000000..bfbb48ae6 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/TextNode.cs @@ -0,0 +1,37 @@ +using Beutl.Media; +using Beutl.Media.TextFormatting; + +namespace Beutl.Graphics.Rendering; + +// Todo: bounds,HitTest +public sealed class TextNode : BrushDrawNode +{ + public TextNode(FormattedText text, IBrush? fill, IPen? pen) + : base(fill, pen, new(text.Bounds)) + { + Text = text; + } + + public FormattedText Text { get; private set; } + + public bool Equals(FormattedText text, IBrush? fill, IPen? pen) + { + return Text == text + && Fill == fill + && Pen == pen; + } + + public override void Render(ImmediateCanvas canvas) + { + canvas.DrawText(Text, Fill, Pen); + } + + public override void Dispose() + { + } + + public override bool HitTest(Point point) + { + return Bounds.ContainsExclusive(point); + } +} diff --git a/src/Beutl.Graphics/Graphics/Rendering/TransformNode.cs b/src/Beutl.Graphics/Graphics/Rendering/TransformNode.cs new file mode 100644 index 000000000..94a5dc518 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/TransformNode.cs @@ -0,0 +1,40 @@ +namespace Beutl.Graphics.Rendering; + +public sealed class TransformNode : ContainerNode +{ + public TransformNode(Matrix transform, TransformOperator transformOperator) + { + Transform = transform; + TransformOperator = transformOperator; + } + + public Matrix Transform { get; } + + public TransformOperator TransformOperator { get; } + + public bool Equals(Matrix transform, TransformOperator transformOperator) + { + return Transform == transform + && TransformOperator == transformOperator; + } + + public override void Dispose() + { + } + + public override void Render(ImmediateCanvas canvas) + { + using (canvas.PushTransform(Transform, TransformOperator)) + { + base.Render(canvas); + } + } + + // Todo: Append, Setの時の動作 + public override bool HitTest(Point point) + { + if (Transform.HasInverse) + point *= Transform.Invert(); + return base.HitTest(point); + } +} diff --git a/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs b/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs index bc10b5732..c795707fb 100644 --- a/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs +++ b/src/Beutl.Graphics/Graphics/Shapes/TextBlock.cs @@ -251,12 +251,14 @@ protected override void OnDraw(ICanvas canvas) { if (item.Text.Length > 0) { - canvas.Transform = Matrix.CreateTranslation(prevRight, 0) * canvas.Transform; - Size elementBounds = item.Bounds; + using (canvas.PushTransform(Matrix.CreateTranslation(prevRight, 0))) + { + Size elementBounds = item.Bounds; - canvas.DrawText(item, item.Brush ?? Foreground, item.Pen ?? Pen); + canvas.DrawText(item, item.Brush ?? Foreground, item.Pen ?? Pen); - prevRight = elementBounds.Width + item.Margin.Right; + prevRight += elementBounds.Width + item.Margin.Right; + } } } } diff --git a/src/Beutl.Graphics/Graphics/SkiaSharpExtensions.cs b/src/Beutl.Graphics/Graphics/SkiaSharpExtensions.cs index 766532312..920ee8906 100644 --- a/src/Beutl.Graphics/Graphics/SkiaSharpExtensions.cs +++ b/src/Beutl.Graphics/Graphics/SkiaSharpExtensions.cs @@ -53,6 +53,11 @@ public static Size ToGraphicsSize(this in SKSize s) { return new Size(s.Width, s.Height); } + + public static PixelSize ToGraphicsSize(this in SKSizeI s) + { + return new PixelSize(s.Width, s.Height); + } public static SKMatrix ToSKMatrix(this in Matrix m) { diff --git a/src/Beutl.Graphics/Media/Music/Samples/Stereo32BitFloat.cs b/src/Beutl.Graphics/Media/Music/Samples/Stereo32BitFloat.cs index 611c97c95..a198fb16d 100644 --- a/src/Beutl.Graphics/Media/Music/Samples/Stereo32BitFloat.cs +++ b/src/Beutl.Graphics/Media/Music/Samples/Stereo32BitFloat.cs @@ -1,4 +1,5 @@ -using System.Runtime.InteropServices; +using System.Numerics; +using System.Runtime.InteropServices; namespace Beutl.Media.Music.Samples; @@ -29,7 +30,7 @@ public static Stereo32BitFloat Amplifier(Stereo32BitFloat s, Sample level) return new Stereo32BitFloat(s.Left * level.Left, s.Right * level.Right); } - public static Stereo32BitFloat Compound(Stereo32BitFloat s1,Stereo32BitFloat s2) + public static Stereo32BitFloat Compound(Stereo32BitFloat s1, Stereo32BitFloat s2) { return new Stereo32BitFloat(s1.Left + s2.Left, s1.Right + s2.Right); } @@ -49,4 +50,8 @@ public static int GetNumChannels() { return 2; } + + public static implicit operator Vector2(Stereo32BitFloat s) => new(s.Left, s.Right); + + public static implicit operator Stereo32BitFloat(Vector2 v) => new(v.X, v.Y); } diff --git a/src/Beutl.Graphics/Media/Source/BitmapSource.cs b/src/Beutl.Graphics/Media/Source/BitmapSource.cs new file mode 100644 index 000000000..1a38d8f84 --- /dev/null +++ b/src/Beutl.Graphics/Media/Source/BitmapSource.cs @@ -0,0 +1,58 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Beutl.Media.Source; + +public sealed class BitmapSource : ImageSource +{ + private readonly Ref _bitmap; + + public BitmapSource(Ref bitmap, string name) + { + _bitmap = bitmap.Clone(); + Name = name; + FrameSize = new PixelSize(_bitmap.Value.Width, _bitmap.Value.Height); + } + + public override PixelSize FrameSize { get; } + + public override string Name { get; } + + public override bool IsGenerated => true; + + public override IImageSource Clone() + { + if (IsDisposed) + throw new ObjectDisposedException(nameof(VideoSource)); + + return new BitmapSource(_bitmap, Name); + } + + public override bool Read([NotNullWhen(true)] out IBitmap? bitmap) + { + if (IsDisposed) + { + bitmap = null; + return false; + } + + bitmap = _bitmap.Value.Clone(); + return true; + } + + public override bool TryGetRef([NotNullWhen(true)] out Ref? bitmap) + { + if (IsDisposed) + { + bitmap = null; + return false; + } + + bitmap = _bitmap; + return true; + } + + protected override void OnDispose(bool disposing) + { + _bitmap.Dispose(); + } +} diff --git a/src/Beutl.Graphics/Media/Source/IImageSource.cs b/src/Beutl.Graphics/Media/Source/IImageSource.cs index bfa313e1d..6fedabd41 100644 --- a/src/Beutl.Graphics/Media/Source/IImageSource.cs +++ b/src/Beutl.Graphics/Media/Source/IImageSource.cs @@ -10,6 +10,8 @@ public interface IImageSource : IMediaSource bool Read([NotNullWhen(true)] out IBitmap? bitmap); + bool TryGetRef([NotNullWhen(true)] out Ref? bitmap); + new IImageSource Clone(); IMediaSource IMediaSource.Clone() => Clone(); diff --git a/src/Beutl.Graphics/Media/Source/ImageSource.cs b/src/Beutl.Graphics/Media/Source/ImageSource.cs index b57161b29..d9399171e 100644 --- a/src/Beutl.Graphics/Media/Source/ImageSource.cs +++ b/src/Beutl.Graphics/Media/Source/ImageSource.cs @@ -4,57 +4,40 @@ namespace Beutl.Media.Source; -public sealed class ImageSource : IImageSource +public abstract class ImageSource : IImageSource { - private readonly Ref _bitmap; - - public ImageSource(Ref bitmap, string name) - { - _bitmap = bitmap; - Name = name; - FrameSize = new PixelSize(_bitmap.Value.Width, _bitmap.Value.Height); - } - ~ImageSource() { - Dispose(); + if (!IsDisposed) + { + OnDispose(false); + IsDisposed = true; + } } - public PixelSize FrameSize { get; } + public abstract PixelSize FrameSize { get; } + + public abstract bool IsGenerated { get; } + + public abstract string Name { get; } public bool IsDisposed { get; private set; } - public string Name { get; } + public abstract IImageSource Clone(); + + public abstract bool Read([NotNullWhen(true)] out IBitmap? bitmap); + + public abstract bool TryGetRef([NotNullWhen(true)] out Ref? bitmap); + + protected abstract void OnDispose(bool disposing); public void Dispose() { if (!IsDisposed) { - _bitmap.Dispose(); + OnDispose(true); GC.SuppressFinalize(this); IsDisposed = true; } } - - public ImageSource Clone() - { - if (IsDisposed) - throw new ObjectDisposedException(nameof(VideoSource)); - - return new ImageSource(_bitmap.Clone(), Name); - } - - public bool Read([NotNullWhen(true)] out IBitmap? bitmap) - { - if (IsDisposed) - { - bitmap = null; - return false; - } - - bitmap = _bitmap.Value.Clone(); - return true; - } - - IImageSource IImageSource.Clone() => Clone(); } diff --git a/src/Beutl.Graphics/Media/Source/MediaSourceManager.cs b/src/Beutl.Graphics/Media/Source/MediaSourceManager.cs index 7c5d267ed..6ce243760 100644 --- a/src/Beutl.Graphics/Media/Source/MediaSourceManager.cs +++ b/src/Beutl.Graphics/Media/Source/MediaSourceManager.cs @@ -40,7 +40,7 @@ public bool OpenImageSource(string name, [NotNullWhen(true)] out IImageSource? v value = null; if (TryGetBitmapOrOpen(name, out Ref? bitmap)) { - value = new ImageSource(bitmap, name); + value = new BitmapSource(bitmap, name); } return value != null; diff --git a/src/Beutl.Graphics/Media/Source/VideoSource.cs b/src/Beutl.Graphics/Media/Source/VideoSource.cs index e3c585989..3790ac9cc 100644 --- a/src/Beutl.Graphics/Media/Source/VideoSource.cs +++ b/src/Beutl.Graphics/Media/Source/VideoSource.cs @@ -4,7 +4,7 @@ namespace Beutl.Media.Source; -public sealed class VideoSource : IVideoSource, IImageSource +public sealed class VideoSource : IVideoSource { private readonly Ref _mediaReader; private readonly double _frameRate; @@ -75,13 +75,6 @@ public bool Read(int frame, [NotNullWhen(true)] out IBitmap? bitmap) return _mediaReader.Value.ReadVideo(frame, out bitmap); } - public bool Read([NotNullWhen(true)] out IBitmap? bitmap) - { - return Read(0, out bitmap); - } - - IImageSource IImageSource.Clone() => Clone(); - IVideoSource IVideoSource.Clone() => Clone(); IMediaSource IMediaSource.Clone() => Clone(); diff --git a/src/Beutl.Graphics/Rendering/IRenderLayer.cs b/src/Beutl.Graphics/Rendering/IRenderLayer.cs deleted file mode 100644 index a03fd22be..000000000 --- a/src/Beutl.Graphics/Rendering/IRenderLayer.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Beutl.Rendering; - -// ProjectSystem側のLayerをRendering専用に抽象化 -public interface IRenderLayer -{ - RenderLayerSpan? this[TimeSpan timeSpan] { get; } - - IRenderer? Renderer { get; } - - void AddSpan(RenderLayerSpan node); - - void RemoveSpan(RenderLayerSpan node); - - bool ContainsSpan(RenderLayerSpan node); - - void RenderGraphics(); - - void RenderAudio(); - - void AttachToRenderer(IRenderer renderer); - - void DetachFromRenderer(); -} diff --git a/src/Beutl.Graphics/Rendering/IRenderable.cs b/src/Beutl.Graphics/Rendering/IRenderable.cs index d398d635c..e5aa3256c 100644 --- a/src/Beutl.Graphics/Rendering/IRenderable.cs +++ b/src/Beutl.Graphics/Rendering/IRenderable.cs @@ -1,4 +1,5 @@ -using Beutl.Media; +using Beutl.Graphics; +using Beutl.Media; namespace Beutl.Rendering; @@ -6,11 +7,9 @@ public interface IRenderable { bool IsVisible { get; set; } - //int ZIndex { get; set; } + int ZIndex { get; set; } - //TimeRange TimeRange { get; set; } + TimeRange TimeRange { get; set; } void Invalidate(); - - void Render(IRenderer renderer); } diff --git a/src/Beutl.Graphics/Rendering/IRenderer.cs b/src/Beutl.Graphics/Rendering/IRenderer.cs index 582e4a8ed..153b51815 100644 --- a/src/Beutl.Graphics/Rendering/IRenderer.cs +++ b/src/Beutl.Graphics/Rendering/IRenderer.cs @@ -11,7 +11,7 @@ namespace Beutl.Rendering; public interface IRenderer : IDisposable { - IRenderLayer? this[int index] { get; set; } + RenderScene RenderScene { get; } ICanvas Graphics { get; } diff --git a/src/Beutl.Graphics/Rendering/ImmediateRenderer.cs b/src/Beutl.Graphics/Rendering/ImmediateRenderer.cs index f82d8954b..bd6939d4a 100644 --- a/src/Beutl.Graphics/Rendering/ImmediateRenderer.cs +++ b/src/Beutl.Graphics/Rendering/ImmediateRenderer.cs @@ -6,22 +6,23 @@ namespace Beutl.Rendering; +// 後で名前を変更 public class ImmediateRenderer : IRenderer { internal static readonly Dispatcher s_dispatcher = Dispatcher.Spawn(); - private readonly SortedDictionary _objects = new(); - private readonly Canvas _graphics; + private readonly ImmediateCanvas _immediateCanvas; private readonly Audio.Audio _audio; private readonly FpsText _fpsText = new(); private readonly InstanceClock _instanceClock = new(); public ImmediateRenderer(int width, int height) { - _graphics = Dispatcher.Invoke(() => new Canvas(width, height)); + _immediateCanvas = Dispatcher.Invoke(() => new ImmediateCanvas(width, height)); _audio = new Audio.Audio(44100); + RenderScene = new RenderScene(new PixelSize(width, height)); } - public ICanvas Graphics => _graphics; + public ICanvas Graphics => _immediateCanvas; public Dispatcher Dispatcher => s_dispatcher; @@ -41,23 +42,7 @@ public bool DrawFps public IAudio Audio => _audio; - public IRenderLayer? this[int index] - { - get => _objects.TryGetValue(index, out IRenderLayer? value) ? value : null; - set - { - if (value != null) - { - value.AttachToRenderer(this); - _objects[index] = value; - } - else if (_objects.TryGetValue(index, out IRenderLayer? oldLayer)) - { - oldLayer.DetachFromRenderer(); - _objects.Remove(index); - } - } - } + public RenderScene RenderScene { get; } public event EventHandler? RenderInvalidated; @@ -102,36 +87,19 @@ public IRenderer.RenderResult RenderGraphics(TimeSpan timeSpan) protected virtual void RenderGraphicsCore() { - using (Graphics.Push()) - { - Graphics.Clear(); - - foreach (KeyValuePair item in _objects) - { - item.Value.RenderGraphics(); - } - } + RenderScene.Render(_immediateCanvas); } protected virtual void RenderAudioCore() { - _audio.Clear(); + //_audio.Clear(); - foreach (KeyValuePair item in _objects) - { - item.Value.RenderAudio(); - } + //foreach (KeyValuePair item in _objects) + //{ + // item.Value.RenderAudio(); + //} } - //void IRenderer.AddDirtyRect(Rect rect) - //{ - //} - - //void IRenderer.AddDirtyRange(TimeRange timeRange) - //{ - - //} - public IRenderer.RenderResult RenderAudio(TimeSpan timeSpan) { if (!IsAudioRendering) @@ -173,8 +141,4 @@ public IRenderer.RenderResult Render(TimeSpan timeSpan) return default; } } - - //public void AddDirty(IRenderable renderable) - //{ - //} } diff --git a/src/Beutl.Graphics/Rendering/RenderLayer.cs b/src/Beutl.Graphics/Rendering/RenderLayer.cs index 1df6c2480..33255f04a 100644 --- a/src/Beutl.Graphics/Rendering/RenderLayer.cs +++ b/src/Beutl.Graphics/Rendering/RenderLayer.cs @@ -1,130 +1,140 @@ -using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Beutl.Audio; using Beutl.Graphics; +using Beutl.Graphics.Rendering; using Beutl.Media; namespace Beutl.Rendering; -public class RenderLayer : IRenderLayer +public class RenderLayer { - private TimeSpan? _lastTimeSpan; - private RenderLayerSpan? _lastTimeResult; - private readonly List _spans = new(); - - public RenderLayerSpan? this[TimeSpan timeSpan] => Get(timeSpan); - - public IRenderer? Renderer { get; private set; } - - public void AddSpan(RenderLayerSpan span) - { - _spans.Add(span); - _lastTimeSpan = null; - _lastTimeResult = null; - - span.AttachToRenderLayer(this); - } - - public void RemoveSpan(RenderLayerSpan span) - { - _spans.Remove(span); - _lastTimeSpan = null; - _lastTimeResult = null; - - span.DetachFromRenderLayer(); - } - - public bool ContainsSpan(RenderLayerSpan span) + private class Entry : IDisposable { - return _spans.Contains(span); - } - - private RenderLayerSpan? Get(TimeSpan timeSpan) - { - if (_lastTimeSpan.HasValue && _lastTimeSpan == timeSpan) + public Entry(INode node) { - return _lastTimeResult; + Node = node; + IsDirty = true; } - _lastTimeSpan = timeSpan; - - foreach (RenderLayerSpan span in CollectionsMarshal.AsSpan(_spans)) + ~Entry() { - if (span.Range.Contains(timeSpan)) - { - _lastTimeResult = span; - return span; - } + Dispose(); } - _lastTimeResult = null; - return null; - } + public INode Node { get; } - public Span GetRange(TimeSpan start, TimeSpan duration) - { - var list = new List(); - var range = new TimeRange(start, duration); + public bool IsDirty { get; set; } + + public bool IsDisposed { get; private set; } - foreach (RenderLayerSpan node in CollectionsMarshal.AsSpan(_spans)) + public void Dispose() { - if (node.Range.Intersects(range)) + if (!IsDisposed) { - list.Add(node); + Node.Dispose(); + IsDisposed = true; + GC.SuppressFinalize(this); } } + } + + // このテーブルは本描画するときに、自分のレイヤー以外のものを削除する。 + private readonly ConditionalWeakTable _cache = new(); - return CollectionsMarshal.AsSpan(list); + private readonly List _currentFrame = new(1); + private readonly RenderScene _renderScene; + + public RenderLayer(RenderScene renderScene) + { + _renderScene = renderScene; + } + + public void Clear() + { + _currentFrame.Clear(); } - public void RenderGraphics() + public void UpdateAll(IReadOnlyList elements) { - if (Renderer is { Clock.CurrentTime: { } timeSpan } renderer) + _currentFrame.Clear(); + _currentFrame.EnsureCapacity(elements.Count); + // Todo: Drawable, Renderableを統合する予定 + foreach (Renderable element in elements) { - RenderLayerSpan? layer = Get(timeSpan); - if (layer != null) + if (!_cache.TryGetValue(element, out Entry? entry)) { - foreach (Renderable r in layer.Value.GetMarshal().Value) + if (element is Drawable drawable) { - if (r is Drawable drawable) + entry = new Entry(new DrawableNode(drawable)); + _cache.Add(element, entry); + + var weakRef = new WeakReference(entry); + EventHandler? handler = null; + handler = (_, _) => { - drawable.Render(renderer); - } + if (weakRef.TryGetTarget(out Entry? obj)) + { + obj.IsDirty = true; + } + else + { + element.Invalidated -= handler; + } + }; + element.Invalidated += handler; + } + else if (element is Sound sound) + { + entry = new Entry(new SoundNode(sound)); + _cache.Add(element, entry); + } + } + + if (entry != null) + { + if (entry.IsDirty + && entry.Node is DrawableNode { Drawable: var drawable } drawableNode) + { + // DeferredCanvasを作成し、記録 + var canvas = new DeferradCanvas(drawableNode, _renderScene.Size); + drawable.Render(canvas); + entry.IsDirty = false; } + + _currentFrame.Add(entry); } } } - public void RenderAudio() + public void Render(ImmediateCanvas canvas) { - if (Renderer is { Clock.AudioStartTime: { } timeSpan } renderer) + foreach (Entry? entry in CollectionsMarshal.AsSpan(_currentFrame)) { - Span span = GetRange(timeSpan, TimeSpan.FromSeconds(1)); - foreach (RenderLayerSpan item in span) + if (entry.Node is DrawableNode dnode) { - foreach (Renderable r in item.Value.GetMarshal().Value) + Drawable element = dnode.Drawable; + if (entry.IsDirty) { - if (r is Sound sound) - { - sound.Render(renderer); - } + var dcanvas = new DeferradCanvas(dnode, _renderScene.Size); + element.Render(dcanvas); + entry.IsDirty = false; } + + dnode.Render(canvas); } } } - public void AttachToRenderer(IRenderer renderer) + public void Render(Audio.Audio audio) { - if (Renderer != null && Renderer != renderer) + foreach (Entry? entry in CollectionsMarshal.AsSpan(_currentFrame)) { - throw new InvalidOperationException(); + if (entry.Node is SoundNode snode) + { + snode.Sound.Render(audio); + } } - - Renderer = renderer; - } - - public void DetachFromRenderer() - { - Renderer = null; } } diff --git a/src/Beutl.Graphics/Rendering/RenderLayerSpan.cs b/src/Beutl.Graphics/Rendering/RenderLayerSpan.cs deleted file mode 100644 index 119cd9a0d..000000000 --- a/src/Beutl.Graphics/Rendering/RenderLayerSpan.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -using Beutl.Language; -using Beutl.Media; - -namespace Beutl.Rendering; - -public sealed class RenderLayerSpan : Hierarchical, IAffectsRender -{ - public static readonly CoreProperty StartProperty; - public static readonly CoreProperty DurationProperty; - public static readonly CoreProperty ValueProperty; - public static readonly CoreProperty RenderLayerProperty; - private readonly Renderables _value; - private TimeSpan _start; - private TimeSpan _duration; - private RenderLayer? _renderLayer; - - static RenderLayerSpan() - { - StartProperty = ConfigureProperty(nameof(Start)) - .Accessor(o => o.Start, (o, v) => o.Start = v) - .Register(); - - DurationProperty = ConfigureProperty(nameof(Duration)) - .Accessor(o => o.Duration, (o, v) => o.Duration = v) - .Register(); - - ValueProperty = ConfigureProperty(nameof(Value)) - .Accessor(o => o.Value) - .Register(); - - RenderLayerProperty = ConfigureProperty(nameof(RenderLayer)) - .Accessor(o => o.RenderLayer) - .Register(); - } - - public RenderLayerSpan() - { - _value = new(this); - _value.Invalidated += OnValueInvalidated; - } - - [Display(Name = nameof(Strings.StartTime), ResourceType = typeof(Strings))] - public TimeSpan Start - { - get => _start; - set => SetAndRaise(StartProperty, ref _start, value); - } - - [Display(Name = nameof(Strings.DurationTime), ResourceType = typeof(Strings))] - public TimeSpan Duration - { - get => _duration; - set => SetAndRaise(DurationProperty, ref _duration, value); - } - - public TimeRange Range => new(Start, Duration); - - public Renderables Value => _value; - - [NotAutoSerialized] - public RenderLayer? RenderLayer - { - get => _renderLayer; - private set => SetAndRaise(RenderLayerProperty, ref _renderLayer, value); - } - - public event EventHandler? Invalidated; - - public event EventHandler? AttachedToRenderLayer; - - public event EventHandler? DetachedFromRenderLayer; - - private void OnValueInvalidated(object? sender, RenderInvalidatedEventArgs e) - { - Invalidated?.Invoke(this, e); - } - - public void AttachToRenderLayer(RenderLayer renderLayer) - { - if (RenderLayer != null && RenderLayer != renderLayer) - { - throw new InvalidOperationException(); - } - - RenderLayer = renderLayer; - AttachedToRenderLayer?.Invoke(this, renderLayer); - } - - public void DetachFromRenderLayer() - { - RenderLayer? tmp = RenderLayer; - RenderLayer = null; - if (tmp != null) - { - DetachedFromRenderLayer?.Invoke(this, tmp); - } - } -} diff --git a/src/Beutl.Graphics/Rendering/RenderScene.cs b/src/Beutl.Graphics/Rendering/RenderScene.cs new file mode 100644 index 000000000..61d454679 --- /dev/null +++ b/src/Beutl.Graphics/Rendering/RenderScene.cs @@ -0,0 +1,54 @@ +using Beutl.Audio; +using Beutl.Graphics; +using Beutl.Media; + +namespace Beutl.Rendering; + +public class RenderScene +{ + private readonly SortedDictionary _layer = new(); + + public RenderScene(PixelSize size) + { + Size = size; + } + + public RenderLayer this[int index] + { + get + { + if (!_layer.TryGetValue(index, out RenderLayer? value)) + { + value = new RenderLayer(this); + _layer.Add(index, value); + } + + return value; + } + } + + public PixelSize Size { get; } + + public void Render(ImmediateCanvas canvas) + { + using (canvas.Push()) + { + canvas.Clear(); + + foreach (RenderLayer item in _layer.Values) + { + item.Render(canvas); + } + } + } + + public void Render(Audio.Audio audio) + { + audio.Clear(); + + foreach (RenderLayer item in _layer.Values) + { + item.Render(audio); + } + } +} diff --git a/src/Beutl.Graphics/Rendering/Renderable.cs b/src/Beutl.Graphics/Rendering/Renderable.cs index 44939ae20..63b201964 100644 --- a/src/Beutl.Graphics/Rendering/Renderable.cs +++ b/src/Beutl.Graphics/Rendering/Renderable.cs @@ -1,4 +1,6 @@ -using Beutl.Media; +using Beutl.Audio; +using Beutl.Graphics; +using Beutl.Media; using Beutl.Styling; namespace Beutl.Rendering; @@ -6,11 +8,11 @@ namespace Beutl.Rendering; public abstract class Renderable : Styleable, IRenderable, IAffectsRender { public static readonly CoreProperty IsVisibleProperty; - //public static readonly CoreProperty ZIndexProperty; - //public static readonly CoreProperty TimeRangeProperty; + public static readonly CoreProperty ZIndexProperty; + public static readonly CoreProperty TimeRangeProperty; private bool _isVisible = true; - //private int _zIndex; - //private TimeRange _timeRange; + private int _zIndex; + private TimeRange _timeRange; public event EventHandler? Invalidated; @@ -21,13 +23,13 @@ static Renderable() .DefaultValue(true) .Register(); - //ZIndexProperty = ConfigureProperty(nameof(ZIndex)) - // .Accessor(o => o.ZIndex, (o, v) => o.ZIndex = v) - // .Register(); + ZIndexProperty = ConfigureProperty(nameof(ZIndex)) + .Accessor(o => o.ZIndex, (o, v) => o.ZIndex = v) + .Register(); - //TimeRangeProperty = ConfigureProperty(nameof(TimeRange)) - // .Accessor(o => o.TimeRange, (o, v) => o.TimeRange = v) - // .Register(); + TimeRangeProperty = ConfigureProperty(nameof(TimeRange)) + .Accessor(o => o.TimeRange, (o, v) => o.TimeRange = v) + .Register(); AffectsRender(IsVisibleProperty); } @@ -38,17 +40,17 @@ public bool IsVisible set => SetAndRaise(IsVisibleProperty, ref _isVisible, value); } - //public int ZIndex - //{ - // get => _zIndex; - // set => SetAndRaise(ZIndexProperty, ref _zIndex, value); - //} + public int ZIndex + { + get => _zIndex; + set => SetAndRaise(ZIndexProperty, ref _zIndex, value); + } - //public TimeRange TimeRange - //{ - // get => _timeRange; - // set => SetAndRaise(TimeRangeProperty, ref _timeRange, value); - //} + public TimeRange TimeRange + { + get => _timeRange; + set => SetAndRaise(TimeRangeProperty, ref _timeRange, value); + } private void AffectsRender_Invalidated(object? sender, RenderInvalidatedEventArgs e) { @@ -84,6 +86,4 @@ public void Invalidate() { Invalidated?.Invoke(this, new RenderInvalidatedEventArgs(this)); } - - public abstract void Render(IRenderer renderer); } diff --git a/src/Beutl.ProjectSystem/NodeTree/ElementNodeTreeModel.cs b/src/Beutl.ProjectSystem/NodeTree/ElementNodeTreeModel.cs index b001faf40..e03011383 100644 --- a/src/Beutl.ProjectSystem/NodeTree/ElementNodeTreeModel.cs +++ b/src/Beutl.ProjectSystem/NodeTree/ElementNodeTreeModel.cs @@ -63,7 +63,14 @@ public void Evaluate(IRenderer renderer, Element layer) } } - layer.Span.Value.Replace(list); + // Todo: LayerOutputNodeに移動 + foreach (Renderable item in list.Span) + { + item.ZIndex = layer.ZIndex; + item.TimeRange = new TimeRange(layer.Start, layer.Length); + } + + renderer.RenderScene[layer.ZIndex].UpdateAll(list); } private void Uninitialize() diff --git a/src/Beutl.ProjectSystem/Operation/SourceOperation.cs b/src/Beutl.ProjectSystem/Operation/SourceOperation.cs index 96457bb4e..ff1e96ca5 100644 --- a/src/Beutl.ProjectSystem/Operation/SourceOperation.cs +++ b/src/Beutl.ProjectSystem/Operation/SourceOperation.cs @@ -7,6 +7,7 @@ using Beutl.Animation; using Beutl.Collections; +using Beutl.Graphics; using Beutl.Media; using Beutl.ProjectSystem; using Beutl.Rendering; @@ -77,63 +78,34 @@ public override void WriteToJson(JsonObject json) } } - public void Evaluate(IRenderer renderer, Element layer, IList unhandled) + public void Evaluate(IRenderer renderer, Element layer) { - void Detach(IList renderables) - { - foreach (Renderable item in renderables) - { - if (item.HierarchicalParent is RenderLayerSpan span - && layer.Span != span) - { - span.Value.Remove(item); - } - } - } - Initialize(renderer, layer.Clock); if (_contexts != null) { - if (!layer.AllowOutflow) + using var flow = new PooledList(); + foreach (OperatorEvaluationContext? item in _contexts.AsSpan().Slice(0, _contextsLength)) { - using var flow = new PooledList(); - foreach (OperatorEvaluationContext? item in _contexts.AsSpan().Slice(0, _contextsLength)) - { - item.FlowRenderables = flow; - item.Operator.Evaluate(item); - } - - Detach(flow); - layer.Span.Value.Replace(flow); + item.FlowRenderables = flow; + item.Operator.Evaluate(item); } - else - { - using var pooled = new PooledList(); - foreach (OperatorEvaluationContext? item in _contexts.AsSpan().Slice(0, _contextsLength)) - { - item._renderables = pooled; - item.FlowRenderables = unhandled; - item.Operator.Evaluate(item); - } - Detach(pooled); - layer.Span.Value.Replace(pooled); - } - foreach (Renderable item in layer.Span.Value.GetMarshal().Value) + foreach (Renderable item in flow.Span) { + item.ZIndex = layer.ZIndex; + item.TimeRange = new TimeRange(layer.Start, layer.Length); item.ApplyStyling(layer.Clock); item.ApplyAnimations(layer.Clock); item.IsVisible = layer.IsEnabled; + while (item.BatchUpdate) { item.EndBatchUpdate(); } } - } - else - { - layer.Span.Value.Clear(); + + renderer.RenderScene[layer.ZIndex].UpdateAll(flow); } } diff --git a/src/Beutl.ProjectSystem/ProjectSystem/Element.cs b/src/Beutl.ProjectSystem/ProjectSystem/Element.cs index 80010a6f9..6fd65842b 100644 --- a/src/Beutl.ProjectSystem/ProjectSystem/Element.cs +++ b/src/Beutl.ProjectSystem/ProjectSystem/Element.cs @@ -22,8 +22,9 @@ public class Element : ProjectItem public static readonly CoreProperty ZIndexProperty; public static readonly CoreProperty AccentColorProperty; public static readonly CoreProperty IsEnabledProperty; + + [Obsolete] public static readonly CoreProperty AllowOutflowProperty; - public static readonly CoreProperty SpanProperty; public static readonly CoreProperty OperationProperty; public static readonly CoreProperty NodeTreeProperty; public static readonly CoreProperty UseNodeProperty; @@ -59,15 +60,6 @@ static Element() .DefaultValue(true) .Register(); - AllowOutflowProperty = ConfigureProperty(nameof(AllowOutflow)) - .Accessor(o => o.AllowOutflow, (o, v) => o.AllowOutflow = v) - .DefaultValue(false) - .Register(); - - SpanProperty = ConfigureProperty(nameof(Span)) - .Accessor(o => o.Span, null) - .Register(); - OperationProperty = ConfigureProperty(nameof(Operation)) .Accessor(o => o.Operation, null) .Register(); @@ -81,24 +73,6 @@ static Element() .DefaultValue(false) .Register(); - ZIndexProperty.Changed.Subscribe(args => - { - if (args.Sender is Element layer && layer.HierarchicalParent is Scene { Renderer: { IsDisposed: false } renderer }) - { - renderer[args.OldValue]?.RemoveSpan(layer.Span); - if (args.NewValue >= 0) - { - IRenderLayer? context = renderer[args.NewValue]; - if (context == null) - { - context = new RenderLayer(); - renderer[args.NewValue] = context; - } - context.AddSpan(layer.Span); - } - } - }); - IsEnabledProperty.Changed.Subscribe(args => { if (args.Sender is Element layer) @@ -106,13 +80,6 @@ static Element() layer.ForceRender(); } }); - AllowOutflowProperty.Changed.Subscribe(args => - { - if (args.Sender is Element layer) - { - layer.ForceRender(); - } - }); UseNodeProperty.Changed.Subscribe(args => { if (args.Sender is Element layer) @@ -120,22 +87,20 @@ static Element() layer.ForceRender(); } }); +#pragma warning disable CS0612 + AllowOutflowProperty = ConfigureProperty(nameof(AllowOutflow)) + .Accessor(o => o.AllowOutflow, (o, v) => o.AllowOutflow = v) + .DefaultValue(false) + .Register(); - StartProperty.Changed.Subscribe(e => - { - if (e.Sender is Element layer) - { - layer.Span.Start = e.NewValue; - } - }); - - LengthProperty.Changed.Subscribe(e => + AllowOutflowProperty.Changed.Subscribe(args => { - if (e.Sender is Element layer) + if (args.Sender is Element layer) { - layer.Span.Duration = e.NewValue; + layer.ForceRender(); } }); +#pragma warning restore CS0612 } public Element() @@ -149,7 +114,6 @@ public Element() NodeTree.Invalidated += (_, _) => ForceRender(); HierarchicalChildren.Add(Operation); - HierarchicalChildren.Add(Span); HierarchicalChildren.Add(NodeTree); } @@ -212,14 +176,13 @@ public bool IsEnabled set => SetAndRaise(IsEnabledProperty, ref _isEnabled, value); } + [Obsolete] public bool AllowOutflow { get => _allowOutflow; set => SetAndRaise(AllowOutflowProperty, ref _allowOutflow, value); } - public RenderLayerSpan Span { get; } = new(); - public SourceOperation Operation { get; } public ElementNodeTreeModel NodeTree { get; } @@ -272,7 +235,7 @@ public override void WriteToJson(JsonObject json) json[nameof(NodeTree)] = nodeTreeJson; } - public void Evaluate(IRenderer renderer, List unhandleds) + public void Evaluate(IRenderer renderer) { _instanceClock.GlobalClock = renderer.Clock; _instanceClock.BeginTime = Start; @@ -285,35 +248,7 @@ public void Evaluate(IRenderer renderer, List unhandleds) } else { - Operation.Evaluate(renderer, this, unhandleds); - } - } - - protected override void OnAttachedToHierarchy(in HierarchyAttachmentEventArgs args) - { - base.OnAttachedToHierarchy(args); - if (args.Parent is Scene { Renderer: { IsDisposed: false } renderer } && ZIndex >= 0) - { - IRenderLayer? context = renderer[ZIndex]; - if (context == null) - { - context = new RenderLayer(); - renderer[ZIndex] = context; - } - context.AddSpan(Span); - - _disposable = SubscribeToLayerNode(); - } - } - - protected override void OnDetachedFromHierarchy(in HierarchyAttachmentEventArgs args) - { - base.OnDetachedFromHierarchy(args); - if (args.Parent is Scene { Renderer: { IsDisposed: false } renderer } && ZIndex >= 0) - { - renderer[ZIndex]?.RemoveSpan(Span); - _disposable?.Dispose(); - _disposable = null; + Operation.Evaluate(renderer, this); } } @@ -330,18 +265,6 @@ private void ForceRender() } } - private IDisposable SubscribeToLayerNode() - { - return Span.GetObservable(RenderLayerSpan.ValueProperty) - .SelectMany(value => value != null - ? Observable.FromEventPattern(h => value.Invalidated += h, h => value.Invalidated -= h) - .Select(_ => Unit.Default) - .Publish(Unit.Default) - .RefCount() - : Observable.Return(Unit.Default)) - .Subscribe(_ => ForceRender()); - } - internal Element? GetBefore(int zindex, TimeSpan start) { if (HierarchicalParent is Scene scene) diff --git a/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs b/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs index 9acb38915..059022202 100644 --- a/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs +++ b/src/Beutl.ProjectSystem/ProjectSystem/Scene.cs @@ -132,17 +132,6 @@ public void Initialize(int width, int height) Renderer = new SceneRenderer(this, width, height); _renderer = Renderer; - foreach (Element item in _children.GetMarshal().Value) - { - IRenderLayer? context = _renderer[item.ZIndex]; - if (context == null) - { - context = new RenderLayer(); - _renderer[item.ZIndex] = context; - } - context.AddSpan(item.Span); - } - OnPropertyChanged(new CorePropertyChangedEventArgs( sender: this, property: WidthProperty, diff --git a/src/Beutl.ProjectSystem/SceneRenderer.cs b/src/Beutl.ProjectSystem/SceneRenderer.cs index 6ae3d9469..fa7b7f57f 100644 --- a/src/Beutl.ProjectSystem/SceneRenderer.cs +++ b/src/Beutl.ProjectSystem/SceneRenderer.cs @@ -15,7 +15,6 @@ internal sealed class SceneRenderer : private readonly List _entered = new(); private readonly List _exited = new(); private readonly List _layers = new(); - private readonly List _unhandleds = new(); private TimeSpan _recentTime = TimeSpan.MinValue; public SceneRenderer(Scene scene, int width, int height) @@ -36,10 +35,10 @@ protected override void RenderGraphicsCore() Span layers = CollectionsMarshal.AsSpan(_layers); Span entered = CollectionsMarshal.AsSpan(_entered); Span exited = CollectionsMarshal.AsSpan(_exited); - _unhandleds.Clear(); foreach (Element item in exited) { + RenderScene[item.ZIndex].Clear(); ExitSourceOperators(item); } @@ -50,7 +49,7 @@ protected override void RenderGraphicsCore() foreach (Element layer in layers) { - layer.Evaluate(this, _unhandleds); + layer.Evaluate(this); } base.RenderGraphicsCore(); diff --git a/tests/Beutl.Core.UnitTests/JsonSerializationTest.cs b/tests/Beutl.Core.UnitTests/JsonSerializationTest.cs new file mode 100644 index 000000000..61aa0d6fc --- /dev/null +++ b/tests/Beutl.Core.UnitTests/JsonSerializationTest.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Beutl.Graphics; +using Beutl.Graphics.Shapes; +using Beutl.Media; +using Beutl.NodeTree; +using Beutl.NodeTree.Nodes; +using Beutl.NodeTree.Nodes.Geometry; +using Beutl.Operators.Configure.Transform; +using Beutl.Operators.Source; +using Beutl.ProjectSystem; + +using NUnit.Framework; + +namespace Beutl.Core.UnitTests; + +public class JsonSerializationTest +{ + [Test] + public void Serialize() + { + var app = new BeutlApplication(); + var proj = new Project(); + + proj.Save(Path.Combine(ArtifactProvider.GetArtifactDirectory(), $"0.bproj")); + app.Project = proj; + + var scene = new Scene(); + scene.Save(Path.Combine(ArtifactProvider.GetArtifactDirectory(), $"0.scene")); + proj.Items.Add(scene); + var elm1 = new Element(); + elm1.Save(Path.Combine(ArtifactProvider.GetArtifactDirectory(), $"0.layer")); + scene.AddChild(elm1).Do(); + elm1.Operation.Children.Add(new EllipseOperator()); + elm1.Operation.Children.Add(new TranslateOperator()); + + var elm2 = new Element(); + elm2.ZIndex = 2; + elm2.Save(Path.Combine(ArtifactProvider.GetArtifactDirectory(), $"1.layer")); + scene.AddChild(elm2).Do(); + var rectNode = new RectGeometryNode(); + var shapeNode = new GeometryShapeNode(); + var outNode = new LayerOutputNode(); + elm2.NodeTree.Nodes.Add(rectNode); + elm2.NodeTree.Nodes.Add(shapeNode); + elm2.NodeTree.Nodes.Add(outNode); + + Assert.IsTrue(((OutputSocket)rectNode.Items[0]).TryConnect((InputSocket)shapeNode.Items[1])); + Assert.IsTrue(((OutputSocket)shapeNode.Items[0]).TryConnect((InputSocket)outNode.Items[0])); + + + } +} diff --git a/tests/Beutl.Graphics.UnitTests/DeferradCanvasTests.cs b/tests/Beutl.Graphics.UnitTests/DeferradCanvasTests.cs new file mode 100644 index 000000000..421747a3e --- /dev/null +++ b/tests/Beutl.Graphics.UnitTests/DeferradCanvasTests.cs @@ -0,0 +1,76 @@ +using Beutl.Graphics.Effects; +using Beutl.Graphics.Rendering; +using Beutl.Media; +using Beutl.Media.Pixel; + +using NUnit.Framework; + +namespace Beutl.Graphics.UnitTests; + +public class DeferradCanvasTests +{ + private static void Draw(ICanvas canvas) + { + using (canvas.PushTransform(Matrix.CreateTranslation(100, 100))) + { + canvas.DrawRectangle(new Rect(0, 0, 500, 500), Brushes.White, null); + + using (canvas.PushTransform(Matrix.CreateTranslation(500, 500))) + { + canvas.DrawRectangle(new Rect(0, 0, 50, 50), Brushes.Blue, null); + } + + canvas.DrawRectangle(new Rect(700, 0, 10, 10), Brushes.Red, null); + } + } + + [Test] + public void Draw() + { + var container = new ContainerNode(); + var dcanvas = new DeferradCanvas(container); + Draw(dcanvas); + + using var canvas = new ImmediateCanvas(1000, 700); + using (canvas.Push()) + { + container.Render(canvas); + using Bitmap bmp = canvas.GetBitmap(); + Assert.IsTrue(bmp.Save(Path.Combine(ArtifactProvider.GetArtifactDirectory(), "canvas1.png"), EncodedImageFormat.Png)); + } + + using (canvas.Push()) + { + canvas.Clear(); + Draw(canvas); + using Bitmap bmp1 = canvas.GetBitmap(); + Assert.IsTrue(bmp1.Save(Path.Combine(ArtifactProvider.GetArtifactDirectory(), "canvas2.png"), EncodedImageFormat.Png)); + } + } + + [Test] + public void DrawEffect() + { + var container = new ContainerNode(); + var dcanvas = new DeferradCanvas(container); + using (dcanvas.PushTransform(Matrix.CreateTranslation(75, 0))) + using (dcanvas.PushTransform(Matrix.CreateRotation(MathF.PI / 4))) + { + using (dcanvas.PushFilterEffect(new Blur() { Sigma = new Vector(10, 10) })) + { + dcanvas.DrawRectangle(new Rect(0, 0, 100, 100), Brushes.White, null); + } + + dcanvas.DrawRectangle(new Rect(100, 100, 100, 100), Brushes.White, null); + } + + using var canvas = new ImmediateCanvas(150, 150); + using (canvas.Push()) + { + container.Render(canvas); + using Bitmap bmp = canvas.GetBitmap(); + Assert.IsTrue(bmp.Save(Path.Combine(ArtifactProvider.GetArtifactDirectory(), "direct.png"), EncodedImageFormat.Png)); + } + + } +} diff --git a/tests/Beutl.Graphics.UnitTests/ShapeTests.cs b/tests/Beutl.Graphics.UnitTests/ShapeTests.cs index 1a0c54b0f..25a7acb67 100644 --- a/tests/Beutl.Graphics.UnitTests/ShapeTests.cs +++ b/tests/Beutl.Graphics.UnitTests/ShapeTests.cs @@ -23,7 +23,7 @@ public void DrawRectangle() Foreground = Brushes.White }; - using var canvas = new Canvas(250, 250); + using var canvas = new ImmediateCanvas(250, 250); canvas.Clear(Colors.Black); shape.Draw(canvas); @@ -58,7 +58,7 @@ public void DrawRectangleWithPen() } }; - using var canvas = new Canvas(250, 250); + using var canvas = new ImmediateCanvas(250, 250); canvas.Clear(Colors.Black); @@ -83,7 +83,7 @@ public void DrawEllipse() Foreground = Brushes.White }; - using var canvas = new Canvas(250, 250); + using var canvas = new ImmediateCanvas(250, 250); canvas.Clear(Colors.Black); shape.Draw(canvas); @@ -109,7 +109,7 @@ public void DrawRoundedRect() Foreground = Brushes.White }; - using var canvas = new Canvas(250, 250); + using var canvas = new ImmediateCanvas(250, 250); canvas.Clear(Colors.Black); shape.Draw(canvas); @@ -145,7 +145,7 @@ public void DrawRoundedRectWithStroke(StrokeAlignment alignment) } }; - using var canvas = new Canvas(250, 250); + using var canvas = new ImmediateCanvas(250, 250); canvas.Clear(Colors.Black); shape.Draw(canvas); @@ -181,7 +181,7 @@ public void DrawGeometry() Foreground = Brushes.White }; - using var canvas = new Canvas(250, 250); + using var canvas = new ImmediateCanvas(250, 250); canvas.Clear(Colors.Black); shape.Draw(canvas); @@ -234,7 +234,7 @@ public void DrawGeometryWithPen(StrokeAlignment alignment, PathFillType fillType } }; - using var canvas = new Canvas(250, 250); + using var canvas = new ImmediateCanvas(250, 250); canvas.Clear(Colors.Black); shape.Draw(canvas); diff --git a/tests/Beutl.Graphics.UnitTests/TextBlockTests.cs b/tests/Beutl.Graphics.UnitTests/TextBlockTests.cs index c3a5862b4..c9b9467d3 100644 --- a/tests/Beutl.Graphics.UnitTests/TextBlockTests.cs +++ b/tests/Beutl.Graphics.UnitTests/TextBlockTests.cs @@ -48,7 +48,7 @@ public void ParseAndDraw(string str, int id) tb.Measure(Size.Infinity); Rect bounds = tb.Bounds; - using var graphics = new Canvas((int)bounds.Width, (int)bounds.Height); + using var graphics = new ImmediateCanvas((int)bounds.Width, (int)bounds.Height); graphics.Clear(Colors.White); @@ -78,7 +78,7 @@ public void ToSKPath() Rect bounds = tb.Bounds; using var skpath = TextBlock.ToSKPath(tb.Elements!); - using var graphics = new Canvas((int)bounds.Width, (int)bounds.Height); + using var graphics = new ImmediateCanvas((int)bounds.Width, (int)bounds.Height); graphics.Clear(Colors.White); diff --git a/tests/TextFormattingPlayground/MainWindow.axaml.cs b/tests/TextFormattingPlayground/MainWindow.axaml.cs index 685e5a714..58ee9cb83 100644 --- a/tests/TextFormattingPlayground/MainWindow.axaml.cs +++ b/tests/TextFormattingPlayground/MainWindow.axaml.cs @@ -2,7 +2,7 @@ using Avalonia.Controls; using Avalonia.Media.Imaging; -using Canvas = Beutl.Graphics.Canvas; +using Canvas = Beutl.Graphics.ImmediateCanvas; namespace TextFormattingPlayground; From 4183278eca31427fb632a57b66244700728951ad Mon Sep 17 00:00:00 2001 From: indigo-san Date: Fri, 14 Jul 2023 22:27:24 +0900 Subject: [PATCH 72/84] Update Avalonia 11.0.0 --- .editorconfig | 2 +- Directory.Packages.props | 90 +- packages/Beutl.Sdk/Beutl.Sdk.csproj | 1 - .../BcTabItem/BcTabItem.properties.cs | 6 +- src/Beutl.Controls/BcTabView/BcTabView.cs | 16 +- .../Behaviors/GenericDragBehavior.cs | 26 +- .../Behaviors/ItemDragBehavior.cs | 16 +- .../Behaviors/SharedContentTemplate.cs | 6 +- src/Beutl.Controls/Beutl.Controls.csproj | 1 - src/Beutl.Controls/DirectoryTreeView.cs | 65 +- src/Beutl.Controls/FileInputArea.cs | 42 +- .../Generators/BcTabItemContainerGenerator.cs | 4 +- src/Beutl.Controls/NavItemHelper.cs | 17 +- src/Beutl.Controls/OptionsDisplayItem.cs | 18 +- .../PropertyEditors/AlignmentXEditor.cs | 3 +- .../PropertyEditors/AlignmentYEditor.cs | 3 +- .../PropertyEditors/EnumEditor.cs | 6 +- .../PropertyEditors/FontFamilyEditor.cs | 6 +- .../PropertyEditors/PropertyEditor.cs | 2 +- .../PropertyEditorValueChangedEventArgs.cs | 4 +- .../PropertyEditors/StorageFileEditor.cs | 45 +- .../PropertyEditors/StringEditor.cs | 10 +- .../PropertyEditors/Vector2Editor.cs | 4 +- .../PropertyEditors/Vector3Editor.cs | 4 +- .../PropertyEditors/Vector4Editor.cs | 6 +- .../Styling/AppWindowStyles.axaml | 2 +- src/Beutl.Controls/Styling/BcTabView.axaml | 8 - ...mandBarStyles_WithoutCompiledBinding.axaml | 2 +- src/Beutl.Controls/Styling/ContextMenu.axaml | 2 - src/Beutl.Controls/Styling/LiteNav.axaml | 5 +- .../Styling/PropertyEditors/EnumEditor.axaml | 6 +- .../Styling/SegmentedControl.axaml | 5 +- .../Collections/Pooled/ClearMode.cs | 31 + .../Collections/Pooled/PooledList.cs | 1512 +++++++++++++++++ .../Collections/Pooled/ThrowHelper.cs | 698 ++++++++ src/Beutl.Core/EnumExtensions.cs | 73 + .../Reactive/DisposableExtension.cs | 11 + src/Beutl.Framework/EditorExtension.cs | 3 +- src/Beutl.Framework/IEditor.cs | 20 - src/Beutl.Framework/IStorableControl.cs | 7 - src/Beutl.Framework/OutputExtension.cs | 2 +- src/Beutl.Framework/PageExtension.cs | 2 +- .../PropertyEditorExtension.cs | 8 +- .../SceneEditorTabExtension.cs | 2 +- src/Beutl.Graphics/Rendering/FpsText.cs | 2 +- .../NodeTree/ElementNodeTreeModel.cs | 3 +- .../NodeTree/Nodes/Group/GroupNode.cs | 2 +- .../Operation/SourceOperation.cs | 4 +- src/Beutl.WaitingDialog/App.axaml | 1 - .../Beutl.WaitingDialog.csproj | 2 - src/Beutl.WaitingDialog/MainWindow.axaml.cs | 3 +- src/Beutl.WaitingDialog/Program.cs | 3 +- src/Beutl/App.axaml.cs | 17 +- src/Beutl/Beutl.csproj | 20 +- src/Beutl/BindingHelper.cs | 4 +- src/Beutl/CustomFontManagerImpl.cs | 4 +- src/Beutl/Pages/AddOutputQueueDialog.axaml | 4 +- src/Beutl/Pages/AddOutputQueueDialog.axaml.cs | 9 +- src/Beutl/Pages/EditPage.axaml.cs | 6 +- src/Beutl/Pages/EditPageFallback.axaml.cs | 50 +- src/Beutl/Pages/ExtensionsPage.axaml.cs | 8 +- .../Pages/ExtensionsPages/DevelopPage.axaml | 4 +- .../ExtensionsPages/DevelopPage.axaml.cs | 9 +- .../Dialogs/AddReleaseDialog.axaml.cs | 4 +- .../Dialogs/CreatePackageDialog.axaml.cs | 4 +- .../Dialogs/UpdatePackageDialog.axaml.cs | 4 +- .../DevelopPages/PackageReleasesPage.axaml | 8 +- .../DevelopPages/PackageSettingsPage.axaml | 2 +- .../Pages/ExtensionsPages/DiscoverPage.axaml | 6 +- .../PublicPackageDetailsPage.axaml | 4 +- .../DiscoverPages/RankingPage.axaml | 4 +- .../DiscoverPages/SearchPage.axaml | 4 +- .../DiscoverPages/UserProfilePage.axaml | 2 +- .../DiscoverPages/UserProfilePage.axaml.cs | 46 +- .../FadeNavigationTransitionInfo.cs | 6 +- .../Pages/ExtensionsPages/LibraryPage.axaml | 4 +- src/Beutl/Pages/OutputPage.axaml | 6 +- src/Beutl/Pages/OutputPage.axaml.cs | 6 +- src/Beutl/Pages/SettingsPage.axaml.cs | 14 +- .../SettingsPages/AccountSettingsPage.axaml | 6 +- .../AccountSettingsPage.axaml.cs | 44 +- .../ExtensionsSettingsPage.axaml | 18 +- .../SettingsPages/FontSettingsPage.axaml | 6 +- .../SettingsPages/FontSettingsPage.axaml.cs | 4 +- .../SettingsPages/StorageDetailPage.axaml | 24 +- .../SettingsPages/StorageDetailPage.axaml.cs | 5 +- .../SettingsPages/StorageSettingsPage.axaml | 2 +- .../SettingsPages/ViewSettingsPage.axaml | 2 +- src/Beutl/Program.cs | 5 +- .../PrimitiveImpls/EditPageExtension.cs | 2 +- .../PrimitiveImpls/ExtensionPageExtension.cs | 2 +- .../PrimitiveImpls/GraphEditorTabExtension.cs | 2 +- .../NodeTreeInputTabExtension.cs | 2 +- .../PrimitiveImpls/NodeTreeTabExtension.cs | 2 +- .../ObjectPropertyTabExtension.cs | 2 +- .../PrimitiveImpls/OutputPageExtension.cs | 2 +- .../PrimitiveImpls/SceneEditorExtension.cs | 3 +- .../PrimitiveImpls/SceneOutputExtension.cs | 2 +- .../PrimitiveImpls/SettingsPageExtension.cs | 2 +- .../SourceOperatorsTabExtension.cs | 2 +- .../PrimitiveImpls/TimelineTabExtension.cs | 2 +- src/Beutl/Services/PropertyEditorService.cs | 8 +- .../Editors/FileInfoEditorViewModel.cs | 15 +- .../ViewModels/Editors/PenEditorViewModel.cs | 4 +- src/Beutl/ViewModels/ElementViewModel.cs | 8 +- .../Dialogs/CreatePackageDialogViewModel.cs | 4 +- .../Dialogs/UpdatePackageDialogViewModel.cs | 4 +- .../NodeTree/NodeTreeTabViewModel.cs | 2 +- .../Views/Dialogs/AddElementDialog.axaml.cs | 4 +- src/Beutl/Views/Dialogs/CreateAsset.axaml.cs | 9 +- .../Views/Dialogs/CreateNewProject.axaml.cs | 9 +- .../Views/Dialogs/CreateNewScene.axaml.cs | 10 +- .../Views/Dialogs/SceneSettings.axaml.cs | 4 +- src/Beutl/Views/Dialogs/SelectAsset.axaml | 4 +- src/Beutl/Views/Dialogs/SelectAsset.axaml.cs | 4 +- .../Views/Dialogs/SelectImageAsset.axaml | 4 +- .../Views/Dialogs/SelectImageAsset.axaml.cs | 4 +- src/Beutl/Views/EditView.axaml.cs | 12 +- .../Views/Editors/GradientStopsEditor.axaml | 2 +- .../Views/Editors/ImageFilterEditor.axaml | 4 +- .../Views/Editors/ImageFilterEditor.axaml.cs | 2 +- .../Editors/ImageFilterListItemEditor.axaml | 2 +- .../Views/Editors/ImageSourceEditor.axaml.cs | 5 +- src/Beutl/Views/Editors/ListEditor.axaml | 2 +- src/Beutl/Views/Editors/ListEditor.axaml.cs | 28 +- .../Views/Editors/NavigateButton.axaml.cs | 2 +- src/Beutl/Views/Editors/OpacityEditor.axaml | 4 +- src/Beutl/Views/Editors/PenEditor.axaml | 4 +- .../Views/Editors/PropertiesEditor.axaml | 2 +- .../Views/Editors/PropertyEditorGroup.axaml | 2 +- .../Views/Editors/SoundSourceEditor.axaml.cs | 5 +- src/Beutl/Views/Editors/TransformEditor.axaml | 4 +- .../Views/Editors/TransformEditor.axaml.cs | 6 +- .../Editors/TransformListItemEditor.axaml | 2 +- src/Beutl/Views/ElementView.axaml | 36 +- src/Beutl/Views/ElementView.axaml.cs | 12 +- src/Beutl/Views/GraphEditorBackground.cs | 9 +- src/Beutl/Views/GraphEditorScale.cs | 11 +- src/Beutl/Views/GraphEditorView.axaml | 8 +- src/Beutl/Views/GraphEditorView.axaml.cs | 23 +- src/Beutl/Views/InlineAnimationLayer.axaml | 2 +- src/Beutl/Views/InlineAnimationLayer.axaml.cs | 24 +- src/Beutl/Views/LayerHeader.axaml | 2 +- src/Beutl/Views/Library.axaml | 8 +- src/Beutl/Views/Library.axaml.cs | 57 +- src/Beutl/Views/MainView.axaml.cs | 86 +- src/Beutl/Views/MainWindow.axaml.cs | 42 +- .../Views/NodeTree/NodeTreeInputTab.axaml | 2 +- src/Beutl/Views/NodeTree/NodeTreeTab.axaml | 2 +- .../Views/NodeTree/NodeTreeView.axaml.cs | 12 +- src/Beutl/Views/NodeTree/NodeView.axaml | 14 +- src/Beutl/Views/NodeTree/NodeView.axaml.cs | 21 +- src/Beutl/Views/NodeTree/SocketPoint.cs | 4 +- src/Beutl/Views/NodeTree/SocketView.axaml.cs | 12 +- src/Beutl/Views/OutputView.axaml | 2 +- src/Beutl/Views/OutputView.axaml.cs | 6 +- src/Beutl/Views/Timeline.axaml | 2 +- src/Beutl/Views/Timeline.axaml.cs | 35 +- src/Beutl/Views/TimelineScale.cs | 15 +- .../Views/Tools/SourceOperatorsTab.axaml | 2 +- .../Views/Tools/SourceOperatorsTab.axaml.cs | 2 +- tests/Beutl.Graphics.UnitTests/ShapeTests.cs | 14 +- .../TextBlockTests.cs | 2 +- tests/DirectoryViewTest/App.axaml | 8 +- .../DirectoryViewTest.csproj | 1 + tests/DirectoryViewTest/MainWindow.axaml.cs | 3 - tests/DirectoryViewTest/Program.cs | 5 - tests/KeySplineEditor/App.axaml | 2 +- tests/KeySplineEditor/KeySplineEditor.csproj | 1 - tests/PackageSample/SSETExtenison.cs | 2 +- tests/PackageSample/SampleEditorExtension.cs | 6 +- tests/PackageSample/SamplePageExtension.cs | 2 +- tests/PropertyEditorViewTests/App.axaml | 3 - .../MainWindow.axaml.cs | 3 - tests/PropertyEditorViewTests/Program.cs | 6 - .../PropertyEditorViewTests.csproj | 2 +- tests/TextFormattingPlayground/App.axaml | 8 +- .../MainWindow.axaml.cs | 3 +- .../TextFormattingPlayground.csproj | 1 - 179 files changed, 3031 insertions(+), 899 deletions(-) create mode 100644 src/Beutl.Core/Collections/Pooled/ClearMode.cs create mode 100644 src/Beutl.Core/Collections/Pooled/PooledList.cs create mode 100644 src/Beutl.Core/Collections/Pooled/ThrowHelper.cs create mode 100644 src/Beutl.Core/EnumExtensions.cs create mode 100644 src/Beutl.Core/Reactive/DisposableExtension.cs delete mode 100644 src/Beutl.Framework/IEditor.cs delete mode 100644 src/Beutl.Framework/IStorableControl.cs diff --git a/.editorconfig b/.editorconfig index 6e1a6eba1..8d493fcb4 100644 --- a/.editorconfig +++ b/.editorconfig @@ -155,7 +155,7 @@ csharp_space_between_parentheses = false csharp_space_between_square_brackets = false # License header -# file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license. +# file_header_template = {fileName}\r\n\r\nCopyright (C) b-editor\r\n\r\nThis software may be modified and distributed under the terms\r\nof the MIT license. See the LICENSE file for details. # C++ Files [*.{cpp,h,in}] diff --git a/Directory.Packages.props b/Directory.Packages.props index 1d9ec3610..f71118e71 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,70 +4,56 @@ - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - + + + - - + - + - + + + + + + + + + + + + + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/packages/Beutl.Sdk/Beutl.Sdk.csproj b/packages/Beutl.Sdk/Beutl.Sdk.csproj index 6a389c904..6af976553 100644 --- a/packages/Beutl.Sdk/Beutl.Sdk.csproj +++ b/packages/Beutl.Sdk/Beutl.Sdk.csproj @@ -27,7 +27,6 @@ - diff --git a/src/Beutl.Controls/BcTabItem/BcTabItem.properties.cs b/src/Beutl.Controls/BcTabItem/BcTabItem.properties.cs index 2c6629b18..9943eb5ad 100644 --- a/src/Beutl.Controls/BcTabItem/BcTabItem.properties.cs +++ b/src/Beutl.Controls/BcTabItem/BcTabItem.properties.cs @@ -10,8 +10,8 @@ namespace Beutl.Controls; public partial class BcTabItem { - public static readonly StyledProperty IconProperty = - AvaloniaProperty.Register(nameof(Icon)); + public static readonly StyledProperty IconProperty = + AvaloniaProperty.Register(nameof(Icon)); public static readonly StyledProperty IsClosableProperty = AvaloniaProperty.Register(nameof(IsClosable), true); @@ -24,7 +24,7 @@ public partial class BcTabItem private bool _isclosing = false; - public IControl Icon + public Control Icon { get => GetValue(IconProperty); set => SetValue(IconProperty, value); diff --git a/src/Beutl.Controls/BcTabView/BcTabView.cs b/src/Beutl.Controls/BcTabView/BcTabView.cs index 3ca34a931..cd7d89617 100644 --- a/src/Beutl.Controls/BcTabView/BcTabView.cs +++ b/src/Beutl.Controls/BcTabView/BcTabView.cs @@ -4,7 +4,6 @@ using Avalonia.Animation; using Avalonia.Animation.Easings; using Avalonia.Controls; -using Avalonia.Controls.Generators; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Input; @@ -14,7 +13,6 @@ using Avalonia.VisualTree; using Beutl.Controls.Extensions; -using Beutl.Controls.Generators; namespace Beutl.Controls; @@ -65,7 +63,7 @@ static BcTabView() if (sender.TransitionIsEnabled && sender._gridHost != null) { - await sender._animation.RunAsync(sender._gridHost, null); + await sender._animation.RunAsync(sender._gridHost); sender._gridHost.Opacity = 1; } } @@ -79,8 +77,12 @@ protected void AdderButtonClicked(object sender, RoutedEventArgs e) e_.Handled = true; } - protected override IItemContainerGenerator CreateItemContainerGenerator() - => new BcTabItemContainerGenerator(this, ContentControl.ContentProperty, ContentControl.ContentTemplateProperty); + protected override Control CreateContainerForItemOverride(object item, int index, object recycleKey) + { + return new BcTabItem(); + } + + protected override void PrepareContainerForItemOverride(Control element, object item, int index) => base.PrepareContainerForItemOverride(element, item, index); protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { @@ -93,11 +95,11 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang double d = ((double)ItemCount / 2); if (_lastselectindex < d & ItemCount != 0) { - SelectedItem = (Items as IList).OfType().FirstOrDefault(); + SelectedItem = (ItemsSource as IList).OfType().FirstOrDefault(); } else if (_lastselectindex >= d & ItemCount != 0) { - SelectedItem = (Items as IList).OfType().LastOrDefault(); + SelectedItem = (ItemsSource as IList).OfType().LastOrDefault(); } } } diff --git a/src/Beutl.Controls/Behaviors/GenericDragBehavior.cs b/src/Beutl.Controls/Behaviors/GenericDragBehavior.cs index abd695a4a..dfeb31bda 100644 --- a/src/Beutl.Controls/Behaviors/GenericDragBehavior.cs +++ b/src/Beutl.Controls/Behaviors/GenericDragBehavior.cs @@ -17,7 +17,7 @@ namespace Beutl.Controls.Behaviors; #nullable enable -public class GenericDragBehavior : Behavior +public class GenericDragBehavior : Behavior { private bool _enableDrag; private bool _dragStarted; @@ -25,7 +25,7 @@ public class GenericDragBehavior : Behavior private int _draggedIndex; private int _targetIndex; private ItemsControl? _itemsControl; - private IControl? _draggedContainer; + private Control? _draggedContainer; public static readonly StyledProperty OrientationProperty = AvaloniaProperty.Register(nameof(Orientation)); @@ -160,9 +160,9 @@ private void Released() if (_itemsControl is { }) { - foreach (ItemContainerInfo? container in _itemsControl.ItemContainerGenerator.Containers) + foreach (Control container in _itemsControl.GetRealizedContainers()) { - SetDraggingPseudoClasses(container.ContainerControl, true); + SetDraggingPseudoClasses(container, true); } } @@ -174,9 +174,9 @@ private void Released() if (_itemsControl is { }) { - foreach (ItemContainerInfo container in _itemsControl.ItemContainerGenerator.Containers) + foreach (Control container in _itemsControl.GetRealizedContainers()) { - SetDraggingPseudoClasses(container.ContainerControl, false); + SetDraggingPseudoClasses(container, false); } } @@ -205,7 +205,7 @@ private static void AddTransforms(ItemsControl? itemsControl) foreach (object? _ in itemsControl.Items) { - IControl? container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i); + Control? container = itemsControl.ContainerFromIndex(i); if (container is not null) { SetTranslateTransform(container, 0, 0); @@ -226,7 +226,7 @@ private static void RemoveTransforms(ItemsControl? itemsControl) foreach (object? _ in itemsControl.Items) { - IControl? container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i); + Control? container = itemsControl.ContainerFromIndex(i); if (container is not null) { SetTranslateTransform(container, 0, 0); @@ -309,7 +309,7 @@ private void Moved(object? sender, PointerEventArgs e) SetTranslateTransform(_draggedContainer, 0, delta); } - _draggedIndex = _itemsControl.ItemContainerGenerator.IndexFromContainer(_draggedContainer); + _draggedIndex = _itemsControl.IndexFromContainer(_draggedContainer); _targetIndex = -1; Rect draggedBounds = _draggedContainer.Bounds; @@ -328,7 +328,7 @@ private void Moved(object? sender, PointerEventArgs e) foreach (object? _ in _itemsControl.Items) { - IControl? targetContainer = _itemsControl.ItemContainerGenerator.ContainerFromIndex(i); + Control? targetContainer = _itemsControl.ContainerFromIndex(i); if (targetContainer?.RenderTransform is null || ReferenceEquals(targetContainer, _draggedContainer)) { i++; @@ -343,7 +343,7 @@ private void Moved(object? sender, PointerEventArgs e) ? targetBounds.X + targetBounds.Width / 2 : targetBounds.Y + targetBounds.Height / 2; - int targetIndex = _itemsControl.ItemContainerGenerator.IndexFromContainer(targetContainer); + int targetIndex = _itemsControl.IndexFromContainer(targetContainer); if (targetStart > draggedStart && draggedDeltaEnd >= targetMid) { @@ -390,7 +390,7 @@ private void Moved(object? sender, PointerEventArgs e) } } - private static void SetDraggingPseudoClasses(IControl control, bool isDragging) + private static void SetDraggingPseudoClasses(Control control, bool isDragging) { if (isDragging) { @@ -402,7 +402,7 @@ private static void SetDraggingPseudoClasses(IControl control, bool isDragging) } } - private static void SetTranslateTransform(IControl control, double x, double y) + private static void SetTranslateTransform(Control control, double x, double y) { var transformBuilder = new TransformOperations.Builder(1); transformBuilder.AppendTranslate(x, y); diff --git a/src/Beutl.Controls/Behaviors/ItemDragBehavior.cs b/src/Beutl.Controls/Behaviors/ItemDragBehavior.cs index dc36728b5..7bb807b2f 100644 --- a/src/Beutl.Controls/Behaviors/ItemDragBehavior.cs +++ b/src/Beutl.Controls/Behaviors/ItemDragBehavior.cs @@ -13,7 +13,7 @@ namespace Beutl.Controls.Behaviors; -public class ItemDragBehavior : Behavior +public class ItemDragBehavior : Behavior { public static readonly StyledProperty OrientationProperty = AvaloniaProperty.Register(nameof(Orientation)); @@ -23,7 +23,7 @@ public class ItemDragBehavior : Behavior private int _draggedIndex; private int _targetIndex; private ItemsControl _itemsControl; - private IControl _draggedContainer; + private Control _draggedContainer; public Orientation Orientation { @@ -65,7 +65,7 @@ private void Pressed(object sender, PointerPressedEventArgs e) } _enableDrag = true; - _start = e.GetPosition(AssociatedObject.Parent); + _start = e.GetPosition(AssociatedObject.Parent as Visual); _draggedIndex = -1; _targetIndex = -1; _itemsControl = AssociatedObject.Parent as ItemsControl; @@ -105,7 +105,7 @@ private static void AddTransforms(ItemsControl itemsControl) foreach (object _ in itemsControl.Items) { - IControl container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i); + Control container = itemsControl.ContainerFromIndex(i); if (container != null) { container.RenderTransform = new TranslateTransform(); @@ -126,7 +126,7 @@ private static void RemoveTransforms(ItemsControl itemsControl) foreach (object _ in itemsControl.Items) { - IControl container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i); + Control container = itemsControl.ContainerFromIndex(i); if (container != null) { container.RenderTransform = null; @@ -180,7 +180,7 @@ private void Moved(object sender, PointerEventArgs e) ((TranslateTransform)_draggedContainer.RenderTransform).Y = delta; } - _draggedIndex = _itemsControl.ItemContainerGenerator.IndexFromContainer(_draggedContainer); + _draggedIndex = _itemsControl.IndexFromContainer(_draggedContainer); _targetIndex = -1; Rect draggedBounds = _draggedContainer.Bounds; @@ -198,7 +198,7 @@ private void Moved(object sender, PointerEventArgs e) foreach (object _ in _itemsControl.Items) { - IControl targetContainer = _itemsControl.ItemContainerGenerator.ContainerFromIndex(i); + Control targetContainer = _itemsControl.ContainerFromIndex(i); if (targetContainer?.RenderTransform is null || ReferenceEquals(targetContainer, _draggedContainer)) { i++; @@ -213,7 +213,7 @@ private void Moved(object sender, PointerEventArgs e) double targetMid = orientation == Orientation.Horizontal ? targetBounds.X + targetBounds.Width / 2 : targetBounds.Y + targetBounds.Height / 2; - int targetIndex = _itemsControl.ItemContainerGenerator.IndexFromContainer(targetContainer); + int targetIndex = _itemsControl.IndexFromContainer(targetContainer); if (targetStart > draggedStart && draggedDeltaEnd >= targetMid) { diff --git a/src/Beutl.Controls/Behaviors/SharedContentTemplate.cs b/src/Beutl.Controls/Behaviors/SharedContentTemplate.cs index f8c091e3a..d1b9fdff9 100644 --- a/src/Beutl.Controls/Behaviors/SharedContentTemplate.cs +++ b/src/Beutl.Controls/Behaviors/SharedContentTemplate.cs @@ -13,16 +13,16 @@ public class SharedContentTemplate : ITemplate public SharedContent Build() { - return (SharedContent)Load(Content!).Control; + return (SharedContent)Load(Content!).Result; } object ITemplate.Build() => Build().Content; - private static ControlTemplateResult Load(object templateContent) + private static TemplateResult Load(object templateContent) { if (templateContent is Func direct) { - return (ControlTemplateResult)direct(null!); + return (TemplateResult)direct(null!); } throw new ArgumentException(null, nameof(templateContent)); diff --git a/src/Beutl.Controls/Beutl.Controls.csproj b/src/Beutl.Controls/Beutl.Controls.csproj index c2a2a5677..9d8fe0230 100644 --- a/src/Beutl.Controls/Beutl.Controls.csproj +++ b/src/Beutl.Controls/Beutl.Controls.csproj @@ -9,7 +9,6 @@ - diff --git a/src/Beutl.Controls/DirectoryTreeView.cs b/src/Beutl.Controls/DirectoryTreeView.cs index 6f245fb83..fe2057645 100644 --- a/src/Beutl.Controls/DirectoryTreeView.cs +++ b/src/Beutl.Controls/DirectoryTreeView.cs @@ -5,8 +5,10 @@ using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.LogicalTree; +using Avalonia.Platform.Storage; using Avalonia.Styling; using Avalonia.Threading; @@ -16,7 +18,7 @@ namespace Beutl.Controls; -public sealed class DirectoryTreeView : TreeView, IStyleable +public sealed class DirectoryTreeView : TreeView { private readonly FileSystemWatcher _watcher; private readonly AvaloniaList _items = new(); @@ -34,7 +36,7 @@ public DirectoryTreeView(FileSystemWatcher watcher, Func context _watcher = watcher; _directoryInfo = new DirectoryInfo(watcher.Path); _contextFactory = contextFactory; - Items = _items; + ItemsSource = _items; InitSubDirectory(); _watcher.Renamed += Watcher_Renamed; @@ -103,7 +105,7 @@ public DirectoryTreeView(FileSystemWatcher watcher, Func context new MenuItem { Header = Strings.CreateNew, - Items = new object[] + Items = { _addfolder, }, @@ -132,10 +134,10 @@ public DirectoryTreeView(FileSystemWatcher watcher, Func context ContextMenu = new ContextMenu { - Items = _menuItem + ItemsSource = _menuItem }; - ContextMenu.ContextMenuOpening += ContextMenu_ContextMenuOpening; + ContextMenu.Opening += ContextMenu_ContextMenuOpening; } //private void PluginFileMenu_Click(object sender, RoutedEventArgs e) @@ -173,7 +175,7 @@ private void ContextMenu_ContextMenuOpening(object sender, System.ComponentModel //} } - Type IStyleable.StyleKey => typeof(TreeView); + protected override Type StyleKeyOverride => typeof(TreeView); private bool CanOpen() { @@ -197,18 +199,21 @@ private void Open(object sender, RoutedEventArgs e) private async void Copy(object sender, RoutedEventArgs e) { - if (SelectedItem is DirectoryTreeItem directoryTree) - { - await Application.Current.Clipboard.SetTextAsync(directoryTree.Info.FullName); - } - else if (SelectedItem is FileTreeItem fileTree) + if (TopLevel.GetTopLevel(this) is { Clipboard: IClipboard clipboard }) { - var data = new DataObject(); - data.Set(DataFormats.FileNames, new string[] + if (SelectedItem is DirectoryTreeItem directoryTree) { - fileTree.Info.FullName - }); - await Application.Current.Clipboard.SetDataObjectAsync(data); + await clipboard.SetTextAsync(directoryTree.Info.FullName); + } + else if (SelectedItem is FileTreeItem fileTree) + { + var data = new DataObject(); + data.Set(DataFormats.Files, new string[] + { + fileTree.Info.FullName + }); + await clipboard.SetDataObjectAsync(data); + } } } @@ -367,7 +372,7 @@ private void Watcher_Renamed(object sender, RenamedEventArgs e) private void OnDragOver(object sender, DragEventArgs e) { - if (e.Data.Contains(DataFormats.FileNames)) + if (e.Data.Contains(DataFormats.Files)) { e.DragEffects = DragDropEffects.Copy; } @@ -375,7 +380,7 @@ private void OnDragOver(object sender, DragEventArgs e) private void OnDrop(object sender, DragEventArgs e) { - if (e.Data.Contains(DataFormats.FileNames) && e.Source is ILogical logical) + if (e.Data.Contains(DataFormats.Files) && e.Source is ILogical logical) { e.DragEffects = DragDropEffects.Copy; @@ -387,12 +392,16 @@ private void OnDrop(object sender, DragEventArgs e) else if (treeViewItem is FileTreeItem fileTree && fileTree.Info.DirectoryName != null) baseDir = fileTree.Info.DirectoryName; - foreach (string src in e.Data.GetFileNames() ?? Enumerable.Empty()) + foreach (IStorageItem src in e.Data.GetFiles() ?? Enumerable.Empty()) { - string dst = Path.Combine(baseDir, Path.GetFileName(src)); - if (!File.Exists(dst)) + if (src is IStorageFile + && src.TryGetLocalPath() is string localPath) { - File.Copy(src, dst); + string dst = Path.Combine(baseDir, Path.GetFileName(localPath)); + if (!File.Exists(dst)) + { + File.Copy(localPath, dst); + } } } } @@ -457,7 +466,7 @@ static string Func(TreeViewItem item) } } -public sealed class FileTreeItem : TreeViewItem, IStyleable +public sealed class FileTreeItem : TreeViewItem { private FileInfo _info; // 名前を変更中 @@ -480,7 +489,7 @@ public FileInfo Info } } - Type IStyleable.StyleKey => typeof(TreeViewItem); + protected override Type StyleKeyOverride => typeof(TreeViewItem); public void Refresh() { @@ -582,7 +591,7 @@ protected override void OnPointerPressed(PointerPressedEventArgs e) Refresh(); var dataObject = new DataObject(); - dataObject.Set(DataFormats.FileNames, new string[] { Info.FullName }); + dataObject.Set(DataFormats.Files, new string[] { Info.FullName }); // ドラッグ開始 DragDrop.DoDragDrop(e, dataObject, DragDropEffects.Copy).ConfigureAwait(false); @@ -599,7 +608,7 @@ private void FileTreeItem_DoubleTapped(object sender, RoutedEventArgs e) } } -public sealed class DirectoryTreeItem : TreeViewItem, IStyleable +public sealed class DirectoryTreeItem : TreeViewItem { private readonly AvaloniaList _items = new(); private readonly FileSystemWatcher _watcher; @@ -614,7 +623,7 @@ public DirectoryTreeItem(DirectoryInfo info, FileSystemWatcher watcher, Func typeof(TreeViewItem); + protected override Type StyleKeyOverride => typeof(TreeViewItem); //サブフォルダツリー追加 private void InitSubDirectory() diff --git a/src/Beutl.Controls/FileInputArea.cs b/src/Beutl.Controls/FileInputArea.cs index d575ce211..ffb743d9f 100644 --- a/src/Beutl.Controls/FileInputArea.cs +++ b/src/Beutl.Controls/FileInputArea.cs @@ -27,16 +27,15 @@ public static readonly StyledProperty OpenOptionsProperty nameof(OpenOptions), validate: x => x?.AllowMultiple != true); - public static readonly DirectProperty TextProperty - = TextBlock.TextProperty.AddOwner(o => o.Text, (o, v) => o.Text = v, DefaultTextValue); + public static readonly StyledProperty TextProperty + = TextBlock.TextProperty.AddOwner(new StyledPropertyMetadata(DefaultTextValue)); private const string DefaultTextValue = "To open the file, drop it here or click here."; private static readonly FilePickerOpenOptions s_defaultOptions = new(); - private string _text = DefaultTextValue; private Button _button; private TextBlock _selectedFileDisplay; private List _patternContexts; - private FileInfo _matchResult; + private IStorageFile _matchResult; public IStorageFile SelectedFile { @@ -52,8 +51,8 @@ public FilePickerOpenOptions OpenOptions public string Text { - get => _text; - set => SetAndRaise(TextProperty, ref _text, value); + get => GetValue(TextProperty); + set => SetValue(TextProperty, value); } protected override void OnApplyTemplate(TemplateAppliedEventArgs e) @@ -78,7 +77,7 @@ private void OnDrop(object sender, DragEventArgs e) if (e.DragEffects != DragDropEffects.None && _matchResult != null) { - SelectedFile = new BclStorageFile(_matchResult); + SelectedFile = _matchResult; } } finally @@ -96,7 +95,7 @@ private void OnDragLeave(object sender, DragEventArgs e) private void OnDragEnter(object sender, DragEventArgs e) { if (_patternContexts != null - && e.Data.GetFileNames() is { } files) + && e.Data.GetFiles() is { } files) { _matchResult = Match(_patternContexts, files); if (_matchResult != null) @@ -187,23 +186,26 @@ private void OnOpenOptionsChanged() } } - private static FileInfo Match(List patternContexts, IEnumerable files) + private static IStorageFile Match(List patternContexts, IEnumerable files) { - foreach (string file in files) + foreach (IStorageItem item in files) { - var fi = new FileInfo(file); - var fiWrapper = new FileInfoWrapper(fi); - var diWrapper = new DirectoryInfoWrapper(fi.Directory); - - foreach (IPatternContext item in patternContexts) + if (item is IStorageFile file && file.TryGetLocalPath() is string path) { - item.PushDirectory(diWrapper); - if (item.Test(fiWrapper).IsSuccessful) + var fi = new FileInfo(path); + var fiWrapper = new FileInfoWrapper(fi); + var diWrapper = new DirectoryInfoWrapper(fi.Directory); + + foreach (IPatternContext ctx in patternContexts) { - item.PopDirectory(); - return fi; + ctx.PushDirectory(diWrapper); + if (ctx.Test(fiWrapper).IsSuccessful) + { + ctx.PopDirectory(); + return file; + } + ctx.PopDirectory(); } - item.PopDirectory(); } } diff --git a/src/Beutl.Controls/Generators/BcTabItemContainerGenerator.cs b/src/Beutl.Controls/Generators/BcTabItemContainerGenerator.cs index 49772b8fe..bc36b5cbb 100644 --- a/src/Beutl.Controls/Generators/BcTabItemContainerGenerator.cs +++ b/src/Beutl.Controls/Generators/BcTabItemContainerGenerator.cs @@ -1,4 +1,5 @@ -using Avalonia; +#if false +using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Generators; using Avalonia.Controls.Primitives; @@ -122,3 +123,4 @@ private void OwnerChanged(ILogical c) } } } +#endif diff --git a/src/Beutl.Controls/NavItemHelper.cs b/src/Beutl.Controls/NavItemHelper.cs index 34d8f5861..bf8112713 100644 --- a/src/Beutl.Controls/NavItemHelper.cs +++ b/src/Beutl.Controls/NavItemHelper.cs @@ -33,19 +33,6 @@ public IconSource FilledIcon protected override void OnAttached() { base.OnAttached(); - _regular = new IconSourceElement() - { - IconSource = RegularIcon, - Width = 40, - Height = 40 - }; - _filled = new IconSourceElement() - { - IconSource = FilledIcon, - Width = 40, - Height = 40 - }; - SetFontSize(RegularIcon); SetFontSize(FilledIcon); _disposable = AssociatedObject.GetPropertyChangedObservable(ListBoxItem.IsSelectedProperty) @@ -97,11 +84,11 @@ private void SelectionChanged(NavigationViewItem sender) { if (sender.IsSelected) { - sender.Icon = _filled; + sender.IconSource = FilledIcon; } else { - sender.Icon = _regular; + sender.IconSource = RegularIcon; } } } diff --git a/src/Beutl.Controls/OptionsDisplayItem.cs b/src/Beutl.Controls/OptionsDisplayItem.cs index 24e76a60b..98868fe49 100644 --- a/src/Beutl.Controls/OptionsDisplayItem.cs +++ b/src/Beutl.Controls/OptionsDisplayItem.cs @@ -29,8 +29,8 @@ public class OptionsDisplayItem : TemplatedControl public static readonly StyledProperty NavigatesProperty = AvaloniaProperty.Register(nameof(Navigates)); - public static readonly StyledProperty ActionButtonProperty = - AvaloniaProperty.Register(nameof(ActionButton)); + public static readonly StyledProperty ActionButtonProperty = + AvaloniaProperty.Register(nameof(ActionButton)); public static readonly StyledProperty ExpandsProperty = AvaloniaProperty.Register(nameof(Expands)); @@ -38,9 +38,8 @@ public class OptionsDisplayItem : TemplatedControl public static readonly StyledProperty ContentProperty = ContentControl.ContentProperty.AddOwner(); - public static readonly DirectProperty IsExpandedProperty = - Expander.IsExpandedProperty.AddOwner(x => x.IsExpanded, - (x, v) => x.IsExpanded = v); + public static readonly StyledProperty IsExpandedProperty = + Expander.IsExpandedProperty.AddOwner(); public static readonly StyledProperty NavigationCommandProperty = AvaloniaProperty.Register(nameof(NavigationCommand)); @@ -75,7 +74,7 @@ public bool Navigates set => SetValue(NavigatesProperty, value); } - public IControl ActionButton + public Control ActionButton { get => GetValue(ActionButtonProperty); set => SetValue(ActionButtonProperty, value); @@ -95,8 +94,8 @@ public object Content public bool IsExpanded { - get => _isExpanded; - set => SetAndRaise(IsExpandedProperty, ref _isExpanded, value); + get => GetValue(IsExpandedProperty); + set => SetValue(IsExpandedProperty, value); } public ICommand NavigationCommand @@ -210,7 +209,7 @@ private void OnLayoutRootPointerPressed(object sender, PointerPressedEventArgs e private void OnLayoutRootPointerReleased(object sender, PointerReleasedEventArgs e) { - var pt = e.GetCurrentPoint(this); + PointerPoint pt = e.GetCurrentPoint(this); if (_isPressed && pt.Properties.PointerUpdateKind == PointerUpdateKind.LeftButtonReleased) { _isPressed = false; @@ -236,7 +235,6 @@ private void OnLayoutRootPointerCaptureLost(object sender, PointerCaptureLostEve } private bool _isPressed; - private bool _isExpanded; private Border _layoutRoot; private CancellationTokenSource _lastTransitionCts; } diff --git a/src/Beutl.Controls/PropertyEditors/AlignmentXEditor.cs b/src/Beutl.Controls/PropertyEditors/AlignmentXEditor.cs index e9bb24dc8..97ee4df17 100644 --- a/src/Beutl.Controls/PropertyEditors/AlignmentXEditor.cs +++ b/src/Beutl.Controls/PropertyEditors/AlignmentXEditor.cs @@ -3,13 +3,12 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Metadata; -using Avalonia.Controls.Mixins; -using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Interactivity; using Beutl.Media; +using Beutl.Reactive; namespace Beutl.Controls.PropertyEditors; diff --git a/src/Beutl.Controls/PropertyEditors/AlignmentYEditor.cs b/src/Beutl.Controls/PropertyEditors/AlignmentYEditor.cs index c7a9a3fe3..c168803d4 100644 --- a/src/Beutl.Controls/PropertyEditors/AlignmentYEditor.cs +++ b/src/Beutl.Controls/PropertyEditors/AlignmentYEditor.cs @@ -3,13 +3,12 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Metadata; -using Avalonia.Controls.Mixins; -using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Interactivity; using Beutl.Media; +using Beutl.Reactive; namespace Beutl.Controls.PropertyEditors; diff --git a/src/Beutl.Controls/PropertyEditors/EnumEditor.cs b/src/Beutl.Controls/PropertyEditors/EnumEditor.cs index d3371c2b2..f41e794ff 100644 --- a/src/Beutl.Controls/PropertyEditors/EnumEditor.cs +++ b/src/Beutl.Controls/PropertyEditors/EnumEditor.cs @@ -10,15 +10,17 @@ namespace Beutl.Controls.PropertyEditors; -public class EnumEditor : EnumEditor, IStyleable +public class EnumEditor : EnumEditor where TEnum : struct, Enum { +#pragma warning disable AVP1002 // AvaloniaProperty objects should not be owned by a generic type public static readonly DirectProperty, TEnum> SelectedValueProperty = AvaloniaProperty.RegisterDirect, TEnum>( nameof(SelectedValue), o => o.SelectedValue, (o, v) => o.SelectedValue = v, defaultBindingMode: BindingMode.TwoWay); +#pragma warning restore AVP1002 // AvaloniaProperty objects should not be owned by a generic type private static readonly string[] s_enumStrings; private static readonly TEnum[] s_enumValues; @@ -59,7 +61,7 @@ public override int SelectedIndex } } - Type IStyleable.StyleKey => typeof(EnumEditor); + protected override Type StyleKeyOverride => typeof(EnumEditor); protected override void OnComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e) { diff --git a/src/Beutl.Controls/PropertyEditors/FontFamilyEditor.cs b/src/Beutl.Controls/PropertyEditors/FontFamilyEditor.cs index 62dc67524..7406528d2 100644 --- a/src/Beutl.Controls/PropertyEditors/FontFamilyEditor.cs +++ b/src/Beutl.Controls/PropertyEditors/FontFamilyEditor.cs @@ -100,7 +100,7 @@ protected override void OnApplyTemplate(TemplateAppliedEventArgs e) base.OnApplyTemplate(e); _textBox = e.NameScope.Get("PART_SearchTextBox"); _listBox = e.NameScope.Get("PART_ListBox"); - _listBox.Items = Media.FontManager.Instance.FontFamilies; + _listBox.ItemsSource = Media.FontManager.Instance.FontFamilies; _disposable = _textBox.GetObservable(TextBox.TextProperty).Subscribe(OnSearchTextBoxTextChanged); } @@ -108,7 +108,7 @@ private void OnSearchTextBoxTextChanged(string obj) { if (string.IsNullOrWhiteSpace(obj)) { - _listBox.Items = Media.FontManager.Instance.FontFamilies; + _listBox.ItemsSource = Media.FontManager.Instance.FontFamilies; } else { @@ -117,7 +117,7 @@ private void OnSearchTextBoxTextChanged(string obj) .Where(x => !string.IsNullOrWhiteSpace(x)) .ToArray(); - _listBox.Items = Media.FontManager.Instance.FontFamilies.Where(x => + _listBox.ItemsSource = Media.FontManager.Instance.FontFamilies.Where(x => { foreach (string item in segments) { diff --git a/src/Beutl.Controls/PropertyEditors/PropertyEditor.cs b/src/Beutl.Controls/PropertyEditors/PropertyEditor.cs index 539bfb093..d2177c01c 100644 --- a/src/Beutl.Controls/PropertyEditors/PropertyEditor.cs +++ b/src/Beutl.Controls/PropertyEditors/PropertyEditor.cs @@ -3,13 +3,13 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Metadata; -using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Interactivity; using Avalonia.LogicalTree; using Beutl.Framework; +using Beutl.Reactive; namespace Beutl.Controls.PropertyEditors; diff --git a/src/Beutl.Controls/PropertyEditors/PropertyEditorValueChangedEventArgs.cs b/src/Beutl.Controls/PropertyEditors/PropertyEditorValueChangedEventArgs.cs index f163b479b..1345ecfd4 100644 --- a/src/Beutl.Controls/PropertyEditors/PropertyEditorValueChangedEventArgs.cs +++ b/src/Beutl.Controls/PropertyEditors/PropertyEditorValueChangedEventArgs.cs @@ -9,7 +9,7 @@ protected PropertyEditorValueChangedEventArgs(RoutedEvent routedEvent) { } - protected PropertyEditorValueChangedEventArgs(RoutedEvent routedEvent, IInteractive source) + protected PropertyEditorValueChangedEventArgs(RoutedEvent routedEvent, Interactive source) : base(routedEvent, source) { } @@ -32,7 +32,7 @@ public PropertyEditorValueChangedEventArgs(TValue newValue, TValue oldValue, Rou OldValue = oldValue; } - public PropertyEditorValueChangedEventArgs(TValue newValue, TValue oldValue, RoutedEvent routedEvent, IInteractive source) + public PropertyEditorValueChangedEventArgs(TValue newValue, TValue oldValue, RoutedEvent routedEvent, Interactive source) : base(routedEvent, source) { NewValue = newValue; diff --git a/src/Beutl.Controls/PropertyEditors/StorageFileEditor.cs b/src/Beutl.Controls/PropertyEditors/StorageFileEditor.cs index 821c3887a..0e8193c2e 100644 --- a/src/Beutl.Controls/PropertyEditors/StorageFileEditor.cs +++ b/src/Beutl.Controls/PropertyEditors/StorageFileEditor.cs @@ -5,25 +5,23 @@ using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Platform.Storage; -using Avalonia.Platform.Storage.FileIO; -using Avalonia.Styling; namespace Beutl.Controls.PropertyEditors; -public class StorageFileEditor : StringEditor, IStyleable +public class StorageFileEditor : StringEditor { public static readonly StyledProperty OpenOptionsProperty = AvaloniaProperty.Register(nameof(OpenOptions)); - public static readonly DirectProperty ValueProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty ValueProperty = + AvaloniaProperty.RegisterDirect( nameof(Value), o => o.Value, (o, v) => o.Value = v, defaultBindingMode: BindingMode.TwoWay); - private IStorageFile _value; - private IStorageFile _oldValue; + private FileInfo _value; + private FileInfo _oldValue; private string _oldText; public StorageFileEditor() @@ -37,20 +35,19 @@ public FilePickerOpenOptions OpenOptions set => SetValue(OpenOptionsProperty, value); } - public IStorageFile Value + public FileInfo Value { get => _value; set { - string text = value?.TryGetUri(out Uri uri) == true ? uri.LocalPath : ""; if (SetAndRaise(ValueProperty, ref _value, value)) { - Text = text; + Text = value.FullName; } } } - Type IStyleable.StyleKey => typeof(StorageFileEditor); + protected override Type StyleKeyOverride => typeof(StorageFileEditor); protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { @@ -66,11 +63,11 @@ private async void OnButtonClick(object sender, RoutedEventArgs e) if (VisualRoot is TopLevel { StorageProvider: { } storage }) { IReadOnlyList result = await storage.OpenFilePickerAsync(OpenOptions); - if (result is [var file]) + if (result is [var file] && file.TryGetLocalPath() is string localPath) { - IStorageFile oldValue = Value; - Value = file; - RaiseEvent(new PropertyEditorValueChangedEventArgs(file, oldValue, ValueChangedEvent)); + FileInfo oldValue = Value; + Value = new FileInfo(localPath); + RaiseEvent(new PropertyEditorValueChangedEventArgs(Value, oldValue, ValueChangedEvent)); } } } @@ -91,7 +88,7 @@ protected override void OnTextBoxLostFocus(RoutedEventArgs e) Value = GetStorageFile(Text); if (Text != _oldText) { - RaiseEvent(new PropertyEditorValueChangedEventArgs(Value, _oldValue, ValueChangedEvent)); + RaiseEvent(new PropertyEditorValueChangedEventArgs(Value, _oldValue, ValueChangedEvent)); } } } @@ -113,26 +110,14 @@ private static bool FileExists(string value) return true; } - if (Uri.TryCreate(value, UriKind.Absolute, out Uri uri) - && uri.IsFile) - { - return File.Exists(uri.LocalPath); - } - return false; } - private static IStorageFile GetStorageFile(string value) + private FileInfo GetStorageFile(string value) { if (File.Exists(value)) { - return new BclStorageFile(value); - } - - if (Uri.TryCreate(value, UriKind.Absolute, out Uri uri) - && uri.IsFile) - { - return new BclStorageFile(uri.LocalPath); + return new FileInfo(value); } return null; diff --git a/src/Beutl.Controls/PropertyEditors/StringEditor.cs b/src/Beutl.Controls/PropertyEditors/StringEditor.cs index e23d9e15b..04387f98e 100644 --- a/src/Beutl.Controls/PropertyEditors/StringEditor.cs +++ b/src/Beutl.Controls/PropertyEditors/StringEditor.cs @@ -3,19 +3,17 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Metadata; -using Avalonia.Controls.Mixins; -using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Input; using Avalonia.Interactivity; -using Avalonia.Platform.Storage.FileIO; -using Avalonia.Styling; + +using Beutl.Reactive; namespace Beutl.Controls.PropertyEditors; [TemplatePart("PART_InnerTextBox", typeof(TextBox))] -public class StringEditor : PropertyEditor, IStyleable +public class StringEditor : PropertyEditor { public static readonly DirectProperty TextProperty = AvaloniaProperty.RegisterDirect( @@ -37,7 +35,7 @@ public string Text protected TextBox InnerTextBox => _innerTextBox; - Type IStyleable.StyleKey => typeof(StringEditor); + protected override Type StyleKeyOverride => typeof(StringEditor); protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { diff --git a/src/Beutl.Controls/PropertyEditors/Vector2Editor.cs b/src/Beutl.Controls/PropertyEditors/Vector2Editor.cs index 4f7f69d9c..3aa2497b6 100644 --- a/src/Beutl.Controls/PropertyEditors/Vector2Editor.cs +++ b/src/Beutl.Controls/PropertyEditors/Vector2Editor.cs @@ -204,7 +204,7 @@ private void OnInnerTextBoxPointerWheelChanged(object sender, PointerWheelEventA [TemplatePart("PART_InnerFirstTextBox", typeof(TextBox))] [TemplatePart("PART_InnerSecondTextBox", typeof(TextBox))] [TemplatePart("PART_BackgroundBorder", typeof(Border))] -public class Vector2Editor : PropertyEditor, IStyleable +public class Vector2Editor : PropertyEditor { public static readonly DirectProperty FirstTextProperty = Vector4Editor.FirstTextProperty.AddOwner( @@ -258,7 +258,7 @@ public string SecondHeader protected TextBox InnerSecondTextBox { get; private set; } - Type IStyleable.StyleKey => typeof(Vector2Editor); + protected override Type StyleKeyOverride => typeof(Vector2Editor); protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { diff --git a/src/Beutl.Controls/PropertyEditors/Vector3Editor.cs b/src/Beutl.Controls/PropertyEditors/Vector3Editor.cs index 7cf11400d..896e8f536 100644 --- a/src/Beutl.Controls/PropertyEditors/Vector3Editor.cs +++ b/src/Beutl.Controls/PropertyEditors/Vector3Editor.cs @@ -240,7 +240,7 @@ private void OnInnerTextBoxPointerWheelChanged(object sender, PointerWheelEventA [TemplatePart("PART_InnerSecondTextBox", typeof(TextBox))] [TemplatePart("PART_InnerThirdTextBox", typeof(TextBox))] [TemplatePart("PART_BackgroundBorder", typeof(Border))] -public class Vector3Editor : PropertyEditor, IStyleable +public class Vector3Editor : PropertyEditor { public static readonly DirectProperty FirstTextProperty = Vector4Editor.FirstTextProperty.AddOwner( @@ -318,7 +318,7 @@ public string ThirdHeader protected TextBox InnerThirdTextBox { get; private set; } - Type IStyleable.StyleKey => typeof(Vector3Editor); + protected override Type StyleKeyOverride => typeof(Vector3Editor); protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { diff --git a/src/Beutl.Controls/PropertyEditors/Vector4Editor.cs b/src/Beutl.Controls/PropertyEditors/Vector4Editor.cs index 587ad94cb..456b420e4 100644 --- a/src/Beutl.Controls/PropertyEditors/Vector4Editor.cs +++ b/src/Beutl.Controls/PropertyEditors/Vector4Editor.cs @@ -16,6 +16,7 @@ namespace Beutl.Controls.PropertyEditors; public class Vector4Editor : Vector4Editor where TElement : INumber { +#pragma warning disable AVP1002 // AvaloniaProperty objects should not be owned by a generic type public static readonly DirectProperty, TElement> FirstValueProperty = AvaloniaProperty.RegisterDirect, TElement>( nameof(FirstValue), @@ -43,6 +44,7 @@ public class Vector4Editor : Vector4Editor o => o.FourthValue, (o, v) => o.FourthValue = v, defaultBindingMode: BindingMode.TwoWay); +#pragma warning restore AVP1002 // AvaloniaProperty objects should not be owned by a generic type private TElement _firstValue; private TElement _oldFirstValue; @@ -286,7 +288,7 @@ private void OnInnerTextBoxPointerWheelChanged(object sender, PointerWheelEventA [TemplatePart("PART_InnerThirdTextBox", typeof(TextBox))] [TemplatePart("PART_InnerFourthTextBox", typeof(TextBox))] [TemplatePart("PART_BackgroundBorder", typeof(Border))] -public class Vector4Editor : PropertyEditor, IStyleable +public class Vector4Editor : PropertyEditor { public static readonly DirectProperty FirstTextProperty = AvaloniaProperty.RegisterDirect( @@ -396,7 +398,7 @@ public string FourthHeader protected TextBox InnerFourthTextBox { get; private set; } - Type IStyleable.StyleKey => typeof(Vector4Editor); + protected override Type StyleKeyOverride => typeof(Vector4Editor); protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { diff --git a/src/Beutl.Controls/Styling/AppWindowStyles.axaml b/src/Beutl.Controls/Styling/AppWindowStyles.axaml index 63e0da0fc..d72cd0076 100644 --- a/src/Beutl.Controls/Styling/AppWindowStyles.axaml +++ b/src/Beutl.Controls/Styling/AppWindowStyles.axaml @@ -1,7 +1,7 @@ + xmlns:wnd="using:FluentAvalonia.UI.Windowing"> Segoe Fluent Icons diff --git a/src/Beutl.Controls/Styling/BcTabView.axaml b/src/Beutl.Controls/Styling/BcTabView.axaml index 22cf58443..58828466c 100644 --- a/src/Beutl.Controls/Styling/BcTabView.axaml +++ b/src/Beutl.Controls/Styling/BcTabView.axaml @@ -68,8 +68,6 @@ Grid.Column="0" MaxWidth="{TemplateBinding MaxWidthOfItemsPresenter}" HorizontalAlignment="Left" - ItemTemplate="{TemplateBinding ItemTemplate}" - Items="{TemplateBinding Items}" ItemsPanel="{TemplateBinding ItemsPanel}"> @@ -181,8 +179,6 @@ Grid.Column="0" MaxWidth="{TemplateBinding MaxWidthOfItemsPresenter}" HorizontalAlignment="Left" - ItemTemplate="{TemplateBinding ItemTemplate}" - Items="{TemplateBinding Items}" ItemsPanel="{TemplateBinding ItemsPanel}"> @@ -251,8 +247,6 @@ MaxHeight="{TemplateBinding MaxWidthOfItemsPresenter}" HorizontalAlignment="Stretch" VerticalAlignment="Top" - ItemTemplate="{TemplateBinding ItemTemplate}" - Items="{TemplateBinding Items}" ItemsPanel="{TemplateBinding ItemsPanel}"> @@ -344,8 +338,6 @@ MaxHeight="{TemplateBinding MaxWidthOfItemsPresenter}" HorizontalAlignment="Stretch" VerticalAlignment="Top" - ItemTemplate="{TemplateBinding ItemTemplate}" - Items="{TemplateBinding Items}" ItemsPanel="{TemplateBinding ItemsPanel}"> diff --git a/src/Beutl.Controls/Styling/CommandBarStyles_WithoutCompiledBinding.axaml b/src/Beutl.Controls/Styling/CommandBarStyles_WithoutCompiledBinding.axaml index aa1fbe2f1..04917980a 100644 --- a/src/Beutl.Controls/Styling/CommandBarStyles_WithoutCompiledBinding.axaml +++ b/src/Beutl.Controls/Styling/CommandBarStyles_WithoutCompiledBinding.axaml @@ -133,7 +133,7 @@ + Symbol="More" /> diff --git a/src/Beutl.Controls/Styling/LiteNav.axaml b/src/Beutl.Controls/Styling/LiteNav.axaml index c2789b838..d46843313 100644 --- a/src/Beutl.Controls/Styling/LiteNav.axaml +++ b/src/Beutl.Controls/Styling/LiteNav.axaml @@ -34,10 +34,7 @@ BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="{TemplateBinding CornerRadius}"> - + diff --git a/src/Beutl.Controls/Styling/PropertyEditors/EnumEditor.axaml b/src/Beutl.Controls/Styling/PropertyEditors/EnumEditor.axaml index 3f88050e2..d8e3ca587 100644 --- a/src/Beutl.Controls/Styling/PropertyEditors/EnumEditor.axaml +++ b/src/Beutl.Controls/Styling/PropertyEditors/EnumEditor.axaml @@ -49,7 +49,7 @@ - - - - - From cceb6b54cc7d58718f16eedc9ef375382ef3fe3c Mon Sep 17 00:00:00 2001 From: indigo-san Date: Mon, 31 Jul 2023 01:09:45 +0900 Subject: [PATCH 82/84] update --- src/Beutl.Graphics/Audio/SoundNode.cs | 29 +- .../Graphics/BrushConstructor.cs | 302 ++++++++++++++ src/Beutl.Graphics/Graphics/Drawable.cs | 21 +- .../Graphics/Drawables/VideoFrame.cs | 12 +- src/Beutl.Graphics/Graphics/ICanvas.cs | 3 +- src/Beutl.Graphics/Graphics/IDrawable.cs | 27 -- .../Graphics/ImmediateCanvas.cs | 388 +----------------- .../Graphics/Rendering/BlendModeNode.cs | 4 - .../Graphics/Rendering/BrushDrawNode.cs | 6 + .../Graphics/Rendering/ClearNode.cs | 4 - .../Graphics/Rendering/ContainerNode.cs | 39 +- .../Graphics/Rendering/DeferradCanvas.cs | 41 +- .../Graphics/Rendering/DrawNode.cs | 30 +- .../Graphics/Rendering/DrawableNode.cs | 8 +- .../Graphics/Rendering/EllipseNode.cs | 4 - .../Graphics/Rendering/FilterEffectNode.cs | 4 +- .../Graphics/Rendering/GeometryClipNode.cs | 7 +- .../Graphics/Rendering/GeometryNode.cs | 3 +- .../Graphics/Rendering/INode.cs | 1 + .../{BitmapNode.cs => ImageSourceNode.cs} | 28 +- .../Graphics/Rendering/OpacityMaskNode.cs | 3 +- .../Graphics/Rendering/RectangleNode.cs | 4 - .../Graphics/Rendering/RenderSceneBrush.cs | 88 ++++ .../Graphics/Rendering/TextNode.cs | 4 - .../Graphics/Rendering/TransformNode.cs | 4 - src/Beutl.Graphics/Graphics/Size.cs | 6 + src/Beutl.Graphics/Graphics/SourceImage.cs | 7 +- .../Graphics/TileBrushCalculator.cs | 139 +++++++ .../Media/Brushes/DrawableBrush.cs | 15 +- .../Media/Brushes/IDrawableBrush.cs | 2 +- .../Media/Immutable/ImmutableDrawableBrush.cs | 5 +- .../Media/Source/BitmapSource.cs | 2 +- .../Rendering/Cache/RenderCache.cs | 72 +++- .../Rendering/Cache/RenderCacheContext.cs | 66 ++- .../Rendering/DeferredRenderer.cs | 358 ---------------- src/Beutl.Graphics/Rendering/IRenderable.cs | 15 - src/Beutl.Graphics/Rendering/IRenderer.cs | 18 +- src/Beutl.Graphics/Rendering/RenderLayer.cs | 14 + src/Beutl.Graphics/Rendering/RenderScene.cs | 3 +- src/Beutl.Graphics/Rendering/Renderable.cs | 23 +- .../{ImmediateRenderer.cs => Renderer.cs} | 28 +- .../Configure/ConfigureOperator.cs | 2 +- .../Nodes/Brushes/DrawableBrushNode.cs | 2 +- .../NodeTree/Nodes/Utilities/MeasureNode.cs | 2 +- .../Operation/ISourcePublisher.cs | 2 +- .../Operation/SourceStyler.cs | 6 +- .../Operation/StyledSourcePublisher.cs | 6 +- src/Beutl.ProjectSystem/SceneRenderer.cs | 6 +- src/Beutl/ViewModels/PlayerViewModel.cs | 6 +- 49 files changed, 925 insertions(+), 944 deletions(-) create mode 100644 src/Beutl.Graphics/Graphics/BrushConstructor.cs delete mode 100644 src/Beutl.Graphics/Graphics/IDrawable.cs rename src/Beutl.Graphics/Graphics/Rendering/{BitmapNode.cs => ImageSourceNode.cs} (55%) create mode 100644 src/Beutl.Graphics/Graphics/Rendering/RenderSceneBrush.cs create mode 100644 src/Beutl.Graphics/Graphics/TileBrushCalculator.cs delete mode 100644 src/Beutl.Graphics/Rendering/DeferredRenderer.cs delete mode 100644 src/Beutl.Graphics/Rendering/IRenderable.cs rename src/Beutl.Graphics/Rendering/{ImmediateRenderer.cs => Renderer.cs} (87%) diff --git a/src/Beutl.Graphics/Audio/SoundNode.cs b/src/Beutl.Graphics/Audio/SoundNode.cs index f4ebd0745..f45d84c48 100644 --- a/src/Beutl.Graphics/Audio/SoundNode.cs +++ b/src/Beutl.Graphics/Audio/SoundNode.cs @@ -2,13 +2,38 @@ namespace Beutl.Audio; -public sealed class SoundNode : INode +public class SoundNode : INode { - public SoundNode(Sound sound) => Sound = sound; + public SoundNode(Sound sound) + { + Sound = sound; + } + + ~SoundNode() + { + if (IsDisposed) + { + OnDispose(false); + IsDisposed = true; + } + } public Sound Sound { get; } + public bool IsDisposed { get; private set; } + public void Dispose() { + if (IsDisposed) + { + OnDispose(true); + IsDisposed = true; + GC.SuppressFinalize(this); + } } + + protected virtual void OnDispose(bool disposing) + { + } + } diff --git a/src/Beutl.Graphics/Graphics/BrushConstructor.cs b/src/Beutl.Graphics/Graphics/BrushConstructor.cs new file mode 100644 index 000000000..338c3f603 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/BrushConstructor.cs @@ -0,0 +1,302 @@ +using Beutl.Graphics.Rendering; +using Beutl.Media; +using Beutl.Media.Source; +using Beutl.Rendering; + +using SkiaSharp; + +namespace Beutl.Graphics; + +public readonly struct BrushConstructor +{ + public BrushConstructor(Size targetSize, IBrush? brush, BlendMode blendMode, IImmediateCanvasFactory factory) + { + TargetSize = targetSize; + Brush = brush; + BlendMode = blendMode; + Factory = factory; + } + + public Size TargetSize { get; } + + public IBrush? Brush { get; } + + public BlendMode BlendMode { get; } + + public IImmediateCanvasFactory Factory { get; } + + public void ConfigurePaint(SKPaint paint) + { + float opacity = Brush?.Opacity ?? 0; + paint.IsAntialias = true; + paint.BlendMode = (SKBlendMode)BlendMode; + + paint.Color = new SKColor(255, 255, 255, (byte)(255 * opacity)); + + if (Brush is ISolidColorBrush solid) + { + paint.Color = new SKColor(solid.Color.R, solid.Color.G, solid.Color.B, (byte)(solid.Color.A * opacity)); + } + else if (Brush is IGradientBrush gradient) + { + ConfigureGradientBrush(paint, gradient); + } + else if (Brush is ITileBrush tileBrush) + { + ConfigureTileBrush(paint, tileBrush); + } + else + { + paint.Color = new SKColor(255, 255, 255, 0); + } + } + + private void ConfigureGradientBrush(SKPaint paint, IGradientBrush gradientBrush) + { + var tileMode = gradientBrush.SpreadMethod.ToSKShaderTileMode(); + SKColor[] stopColors = gradientBrush.GradientStops.SelectArray(s => s.Color.ToSKColor()); + float[] stopOffsets = gradientBrush.GradientStops.SelectArray(s => s.Offset); + + switch (gradientBrush) + { + case ILinearGradientBrush linearGradient: + { + var start = linearGradient.StartPoint.ToPixels(TargetSize).ToSKPoint(); + var end = linearGradient.EndPoint.ToPixels(TargetSize).ToSKPoint(); + + if (linearGradient.Transform is null) + { + using (var shader = SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode)) + { + paint.Shader = shader; + } + } + else + { + Point transformOrigin = linearGradient.TransformOrigin.ToPixels(TargetSize); + var offset = Matrix.CreateTranslation(transformOrigin); + Matrix transform = (-offset) * linearGradient.Transform.Value * offset; + + using (var shader = SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode, transform.ToSKMatrix())) + { + paint.Shader = shader; + } + } + + break; + } + case IRadialGradientBrush radialGradient: + { + var center = radialGradient.Center.ToPixels(TargetSize).ToSKPoint(); + float radius = radialGradient.Radius * TargetSize.Width; + var origin = radialGradient.GradientOrigin.ToPixels(TargetSize).ToSKPoint(); + + if (origin.Equals(center)) + { + // when the origin is the same as the center the Skia RadialGradient acts the same as D2D + if (radialGradient.Transform is null) + { + using (var shader = SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode)) + { + paint.Shader = shader; + } + } + else + { + Point transformOrigin = radialGradient.TransformOrigin.ToPixels(TargetSize); + var offset = Matrix.CreateTranslation(transformOrigin); + Matrix transform = (-offset) * radialGradient.Transform.Value * (offset); + + using (var shader = SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode, transform.ToSKMatrix())) + { + paint.Shader = shader; + } + } + } + else + { + // when the origin is different to the center use a two point ConicalGradient to match the behaviour of D2D + + // reverse the order of the stops to match D2D + var reversedColors = new SKColor[stopColors.Length]; + Array.Copy(stopColors, reversedColors, stopColors.Length); + Array.Reverse(reversedColors); + + // and then reverse the reference point of the stops + float[] reversedStops = new float[stopOffsets.Length]; + for (int i = 0; i < stopOffsets.Length; i++) + { + reversedStops[i] = stopOffsets[i]; + if (reversedStops[i] > 0 && reversedStops[i] < 1) + { + reversedStops[i] = Math.Abs(1 - stopOffsets[i]); + } + } + + // compose with a background colour of the final stop to match D2D's behaviour of filling with the final color + if (radialGradient.Transform is null) + { + using (var shader = SKShader.CreateCompose( + SKShader.CreateColor(reversedColors[0]), + SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, reversedColors, reversedStops, tileMode) + )) + { + paint.Shader = shader; + } + } + else + { + + Point transformOrigin = radialGradient.TransformOrigin.ToPixels(TargetSize); + var offset = Matrix.CreateTranslation(transformOrigin); + Matrix transform = (-offset) * radialGradient.Transform.Value * (offset); + + using (var shader = SKShader.CreateCompose( + SKShader.CreateColor(reversedColors[0]), + SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, reversedColors, reversedStops, tileMode, transform.ToSKMatrix()) + )) + { + paint.Shader = shader; + } + } + } + + break; + } + case IConicGradientBrush conicGradient: + { + var center = conicGradient.Center.ToPixels(TargetSize).ToSKPoint(); + + // Skia's default is that angle 0 is from the right hand side of the center point + // but we are matching CSS where the vertical point above the center is 0. + float angle = conicGradient.Angle - 90; + var rotation = SKMatrix.CreateRotationDegrees(angle, center.X, center.Y); + + if (conicGradient.Transform is { }) + { + Point transformOrigin = conicGradient.TransformOrigin.ToPixels(TargetSize); + var offset = Matrix.CreateTranslation(transformOrigin); + Matrix transform = (-offset) * conicGradient.Transform.Value * (offset); + + rotation = rotation.PreConcat(transform.ToSKMatrix()); + } + + using (var shader = SKShader.CreateSweepGradient(center, stopColors, stopOffsets, rotation)) + { + paint.Shader = shader; + } + + break; + } + } + } + + private void ConfigureTileBrush(SKPaint paint, ITileBrush tileBrush) + { + SKSurface? surface = null; + SKBitmap? skbitmap = null; + PixelSize pixelSize; + + if (tileBrush is RenderSceneBrush sceneBrush) + { + RenderScene? scene = sceneBrush.Scene; + if(scene != null) + { + surface = Factory.CreateRenderTarget(scene.Size.Width, scene.Size.Height); + using (ImmediateCanvas icanvas = Factory.CreateCanvas(surface, true)) + using (icanvas.PushTransform(Matrix.CreateTranslation(-sceneBrush.Bounds.X, -sceneBrush.Bounds.Y))) + { + scene.Render(icanvas); + } + + pixelSize = scene.Size; + } + else + { + using (SKShader shader = SKShader.CreateEmpty()) + { + paint.Shader = shader; + return; + } + } + } + else if (tileBrush is IImageBrush imageBrush + && imageBrush.Source?.TryGetRef(out Ref? bitmap) == true) + { + using (bitmap) + { + skbitmap = bitmap.Value.ToSKBitmap(); + pixelSize = new(bitmap.Value.Width, bitmap.Value.Height); + } + } + else + { + throw new InvalidOperationException($"'{tileBrush.GetType().Name}' not supported."); + } + + if (surface == null && skbitmap == null) + return; + + try + { + var calc = new TileBrushCalculator(tileBrush, pixelSize.ToSize(1), TargetSize); + SKSizeI intermediateSize = calc.IntermediateSize.ToSKSize().ToSizeI(); + + using SKSurface intermediate = Factory.CreateRenderTarget(intermediateSize.Width, intermediateSize.Height); + SKCanvas canvas = intermediate.Canvas; + using var ipaint = new SKPaint(); + { + ipaint.FilterQuality = tileBrush.BitmapInterpolationMode.ToSKFilterQuality(); + + canvas.Clear(); + canvas.Save(); + canvas.ClipRect(calc.IntermediateClip.ToSKRect()); + canvas.SetMatrix(calc.IntermediateTransform.ToSKMatrix()); + + if (surface != null) + canvas.DrawSurface(surface, default, ipaint); + else if (skbitmap != null) + canvas.DrawBitmap(skbitmap, (SKPoint)default, ipaint); + + canvas.Restore(); + } + + SKMatrix tileTransform = tileBrush.TileMode != TileMode.None + ? SKMatrix.CreateTranslation(-calc.DestinationRect.X, -calc.DestinationRect.Y) + : SKMatrix.CreateIdentity(); + + SKShaderTileMode tileX = tileBrush.TileMode == TileMode.None + ? SKShaderTileMode.Decal + : tileBrush.TileMode == TileMode.FlipX || tileBrush.TileMode == TileMode.FlipXY + ? SKShaderTileMode.Mirror + : SKShaderTileMode.Repeat; + + SKShaderTileMode tileY = tileBrush.TileMode == TileMode.None + ? SKShaderTileMode.Decal + : tileBrush.TileMode == TileMode.FlipY || tileBrush.TileMode == TileMode.FlipXY + ? SKShaderTileMode.Mirror + : SKShaderTileMode.Repeat; + + + if (tileBrush.Transform is { }) + { + Point origin = tileBrush.TransformOrigin.ToPixels(TargetSize); + var offset = Matrix.CreateTranslation(origin); + Matrix transform = (-offset) * tileBrush.Transform.Value * offset; + + tileTransform = tileTransform.PreConcat(transform.ToSKMatrix()); + } + + using (SKImage skimage = intermediate.Snapshot()) + using (SKShader shader = skimage.ToShader(tileX, tileY, tileTransform)) + { + paint.Shader = shader; + } + } + finally + { + surface?.Dispose(); + skbitmap?.Dispose(); + } + } +} diff --git a/src/Beutl.Graphics/Graphics/Drawable.cs b/src/Beutl.Graphics/Graphics/Drawable.cs index e68eb401f..41a5c52b9 100644 --- a/src/Beutl.Graphics/Graphics/Drawable.cs +++ b/src/Beutl.Graphics/Graphics/Drawable.cs @@ -10,7 +10,7 @@ namespace Beutl.Graphics; -public abstract class Drawable : Renderable, IDrawable, IHierarchical +public abstract class Drawable : Renderable, IHierarchical { public static readonly CoreProperty TransformProperty; public static readonly CoreProperty FilterEffectProperty; @@ -136,25 +136,6 @@ public BlendMode BlendMode set => SetAndRaise(BlendModeProperty, ref _blendMode, value); } - public IBitmap ToBitmap() - { - Size size = MeasureCore(Size.Infinity); - int width = (int)size.Width; - int height = (int)size.Height; - if (width > 0 && height > 0) - { - using (var canvas = new ImmediateCanvas(width, height)) - { - OnDraw(canvas); - return canvas.GetBitmap(); - } - } - else - { - return new Bitmap(0, 0); - } - } - public void Measure(Size availableSize) { Size size = MeasureCore(availableSize); diff --git a/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs b/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs index 03979ccec..27383b287 100644 --- a/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs +++ b/src/Beutl.Graphics/Graphics/Drawables/VideoFrame.cs @@ -5,6 +5,7 @@ using Beutl.Animation; using Beutl.Media; using Beutl.Media.Decoding; +using Beutl.Media.Source; using Beutl.Rendering; using Beutl.Utilities; using Beutl.Validation; @@ -29,7 +30,7 @@ public class VideoFrame : Drawable private FileInfo? _sourceFile; private MediaReader? _mediaReader; private TimeSpan _requestedPosition; - private IBitmap? _previousBitmap; + private IImageSource? _previousBitmap; private double _previousFrame; static VideoFrame() @@ -152,15 +153,16 @@ protected override void OnDraw(ICanvas canvas) if (_previousBitmap?.IsDisposed == false && MathUtilities.AreClose(frameNum, _previousFrame)) { - canvas.DrawBitmap(_previousBitmap, Foreground, null); + canvas.DrawImageSource(_previousBitmap, Foreground, null); } else if (_mediaReader.ReadVideo((int)frameNum, out IBitmap? bmp) && bmp?.IsDisposed == false) { - canvas.DrawBitmap(bmp, Foreground, null); - _previousBitmap?.Dispose(); - _previousBitmap = bmp; + _previousBitmap = new BitmapSource(Ref.Create(bmp), "Temp"); + + canvas.DrawImageSource(_previousBitmap, Foreground, null); + _previousFrame = frameNum; } } diff --git a/src/Beutl.Graphics/Graphics/ICanvas.cs b/src/Beutl.Graphics/Graphics/ICanvas.cs index f4f8d79a6..3b8082436 100644 --- a/src/Beutl.Graphics/Graphics/ICanvas.cs +++ b/src/Beutl.Graphics/Graphics/ICanvas.cs @@ -1,6 +1,7 @@ using Beutl.Graphics.Effects; using Beutl.Media; using Beutl.Media.Pixel; +using Beutl.Media.Source; using Beutl.Media.TextFormatting; namespace Beutl.Graphics; @@ -19,7 +20,7 @@ public interface ICanvas : IDisposable void Clear(Color color); - void DrawBitmap(IBitmap bmp, IBrush? fill, IPen? pen); + void DrawImageSource(IImageSource source, IBrush? fill, IPen? pen); void DrawEllipse(Rect rect, IBrush? fill, IPen? pen); diff --git a/src/Beutl.Graphics/Graphics/IDrawable.cs b/src/Beutl.Graphics/Graphics/IDrawable.cs deleted file mode 100644 index 729b3e3d3..000000000 --- a/src/Beutl.Graphics/Graphics/IDrawable.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Beutl.Graphics.Effects; -using Beutl.Graphics.Transformation; -using Beutl.Media; -using Beutl.Rendering; - -namespace Beutl.Graphics; - -public interface IDrawable : IRenderable -{ - Rect Bounds { get; } - - IBrush? Foreground { get; set; } - - BlendMode BlendMode { get; set; } - - ITransform? Transform { get; set; } - - FilterEffect? FilterEffect { get; set; } - - AlignmentX AlignmentX { get; set; } - - AlignmentY AlignmentY { get; set; } - - RelativePoint TransformOrigin { get; set; } - - IBitmap ToBitmap(); -} diff --git a/src/Beutl.Graphics/Graphics/ImmediateCanvas.cs b/src/Beutl.Graphics/Graphics/ImmediateCanvas.cs index bfee0822e..62fef5ac2 100644 --- a/src/Beutl.Graphics/Graphics/ImmediateCanvas.cs +++ b/src/Beutl.Graphics/Graphics/ImmediateCanvas.cs @@ -234,6 +234,17 @@ public void DrawBitmap(IBitmap bmp, IBrush? fill, IPen? pen) } } + public void DrawImageSource(IImageSource source, IBrush? fill, IPen? pen) + { + if (source.TryGetRef(out Ref? bitmap)) + { + using (bitmap) + { + DrawBitmap(bitmap.Value, fill, pen); + } + } + } + public void DrawEllipse(Rect rect, IBrush? fill, IPen? pen) { VerifyAccess(); @@ -463,7 +474,7 @@ public PushedState PushOpacityMask(IBrush mask, Rect bounds, bool invert = false var paint = new SKPaint(); int count = _canvas.SaveLayer(paint); - ConfigurePaint(paint, bounds.Size, mask, (BlendMode)paint.BlendMode); + new BrushConstructor(bounds.Size, mask, (BlendMode)paint.BlendMode, this).ConfigurePaint(paint); _states.Push(new CanvasPushedState.MaskPushedState(count, invert, paint)); return new PushedState(this, _states.Count); } @@ -510,215 +521,6 @@ private void VerifyAccess() _dispatcher?.VerifyAccess(); } - private static void ConfigureGradientBrush(SKPaint paint, Size targetSize, IGradientBrush gradientBrush) - { - var tileMode = gradientBrush.SpreadMethod.ToSKShaderTileMode(); - SKColor[] stopColors = gradientBrush.GradientStops.SelectArray(s => s.Color.ToSKColor()); - float[] stopOffsets = gradientBrush.GradientStops.SelectArray(s => s.Offset); - - switch (gradientBrush) - { - case ILinearGradientBrush linearGradient: - { - var start = linearGradient.StartPoint.ToPixels(targetSize).ToSKPoint(); - var end = linearGradient.EndPoint.ToPixels(targetSize).ToSKPoint(); - - if (linearGradient.Transform is null) - { - using (var shader = SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode)) - { - paint.Shader = shader; - } - } - else - { - Point transformOrigin = linearGradient.TransformOrigin.ToPixels(targetSize); - var offset = Matrix.CreateTranslation(transformOrigin); - Matrix transform = (-offset) * linearGradient.Transform.Value * offset; - - using (var shader = SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode, transform.ToSKMatrix())) - { - paint.Shader = shader; - } - } - - break; - } - case IRadialGradientBrush radialGradient: - { - var center = radialGradient.Center.ToPixels(targetSize).ToSKPoint(); - float radius = radialGradient.Radius * targetSize.Width; - var origin = radialGradient.GradientOrigin.ToPixels(targetSize).ToSKPoint(); - - if (origin.Equals(center)) - { - // when the origin is the same as the center the Skia RadialGradient acts the same as D2D - if (radialGradient.Transform is null) - { - using (var shader = SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode)) - { - paint.Shader = shader; - } - } - else - { - Point transformOrigin = radialGradient.TransformOrigin.ToPixels(targetSize); - var offset = Matrix.CreateTranslation(transformOrigin); - Matrix transform = (-offset) * radialGradient.Transform.Value * (offset); - - using (var shader = SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode, transform.ToSKMatrix())) - { - paint.Shader = shader; - } - } - } - else - { - // when the origin is different to the center use a two point ConicalGradient to match the behaviour of D2D - - // reverse the order of the stops to match D2D - var reversedColors = new SKColor[stopColors.Length]; - Array.Copy(stopColors, reversedColors, stopColors.Length); - Array.Reverse(reversedColors); - - // and then reverse the reference point of the stops - float[] reversedStops = new float[stopOffsets.Length]; - for (int i = 0; i < stopOffsets.Length; i++) - { - reversedStops[i] = stopOffsets[i]; - if (reversedStops[i] > 0 && reversedStops[i] < 1) - { - reversedStops[i] = Math.Abs(1 - stopOffsets[i]); - } - } - - // compose with a background colour of the final stop to match D2D's behaviour of filling with the final color - if (radialGradient.Transform is null) - { - using (var shader = SKShader.CreateCompose( - SKShader.CreateColor(reversedColors[0]), - SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, reversedColors, reversedStops, tileMode) - )) - { - paint.Shader = shader; - } - } - else - { - - Point transformOrigin = radialGradient.TransformOrigin.ToPixels(targetSize); - var offset = Matrix.CreateTranslation(transformOrigin); - Matrix transform = (-offset) * radialGradient.Transform.Value * (offset); - - using (var shader = SKShader.CreateCompose( - SKShader.CreateColor(reversedColors[0]), - SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, reversedColors, reversedStops, tileMode, transform.ToSKMatrix()) - )) - { - paint.Shader = shader; - } - } - } - - break; - } - case IConicGradientBrush conicGradient: - { - var center = conicGradient.Center.ToPixels(targetSize).ToSKPoint(); - - // Skia's default is that angle 0 is from the right hand side of the center point - // but we are matching CSS where the vertical point above the center is 0. - float angle = conicGradient.Angle - 90; - var rotation = SKMatrix.CreateRotationDegrees(angle, center.X, center.Y); - - if (conicGradient.Transform is { }) - { - Point transformOrigin = conicGradient.TransformOrigin.ToPixels(targetSize); - var offset = Matrix.CreateTranslation(transformOrigin); - Matrix transform = (-offset) * conicGradient.Transform.Value * (offset); - - rotation = rotation.PreConcat(transform.ToSKMatrix()); - } - - using (var shader = SKShader.CreateSweepGradient(center, stopColors, stopOffsets, rotation)) - { - paint.Shader = shader; - } - - break; - } - } - } - - private static void ConfigureTileBrush(SKPaint paint, Size targetSize, ITileBrush tileBrush) - { - // Todo: DrawableBrush - Ref? bitmap; - if (tileBrush is IDrawableBrush { Drawable: { } } drawableBrush) - { - bitmap = Ref.Create(drawableBrush.Drawable.ToBitmap()); - } - else if ((tileBrush as IImageBrush)?.Source?.TryGetRef(out bitmap) == true) - { - } - else - { - throw new InvalidOperationException(); - } - - var calc = new TileBrushCalculator(tileBrush, new Size(bitmap.Value.Width, bitmap.Value.Height), targetSize); - SKSizeI intermediateSize = calc.IntermediateSize.ToSKSize().ToSizeI(); - - var intermediate = new SKBitmap(new SKImageInfo(intermediateSize.Width, intermediateSize.Height, SKColorType.Bgra8888)); - using (var canvas = new SKCanvas(intermediate)) - { - using var target = bitmap.Value.ToSKBitmap(); - using var ipaint = new SKPaint(); - ipaint.FilterQuality = tileBrush.BitmapInterpolationMode.ToSKFilterQuality(); - - canvas.Clear(); - canvas.Save(); - canvas.ClipRect(calc.IntermediateClip.ToSKRect()); - canvas.SetMatrix(calc.IntermediateTransform.ToSKMatrix()); - - canvas.DrawBitmap(target, (SKPoint)default, ipaint); - canvas.Restore(); - } - - bitmap.Dispose(); - - SKMatrix tileTransform = tileBrush.TileMode != TileMode.None - ? SKMatrix.CreateTranslation(-calc.DestinationRect.X, -calc.DestinationRect.Y) - : SKMatrix.CreateIdentity(); - - SKShaderTileMode tileX = tileBrush.TileMode == TileMode.None - ? SKShaderTileMode.Decal - : tileBrush.TileMode == TileMode.FlipX || tileBrush.TileMode == TileMode.FlipXY - ? SKShaderTileMode.Mirror - : SKShaderTileMode.Repeat; - - SKShaderTileMode tileY = tileBrush.TileMode == TileMode.None - ? SKShaderTileMode.Decal - : tileBrush.TileMode == TileMode.FlipY || tileBrush.TileMode == TileMode.FlipXY - ? SKShaderTileMode.Mirror - : SKShaderTileMode.Repeat; - - - if (tileBrush.Transform is { }) - { - Point origin = tileBrush.TransformOrigin.ToPixels(targetSize); - var offset = Matrix.CreateTranslation(origin); - Matrix transform = (-offset) * tileBrush.Transform.Value * offset; - - tileTransform = tileTransform.PreConcat(transform.ToSKMatrix()); - } - - using (SKShader shader = intermediate.ToShader(tileX, tileY, tileTransform)) - { - paint.Shader = shader; - } - } - private void ConfigureStrokePaint(Rect rect, IPen? pen) { _sharedStrokePaint.Reset(); @@ -769,175 +571,13 @@ private void ConfigureStrokePaint(Rect rect, IPen? pen) _sharedStrokePaint.PathEffect = pe; } - ConfigurePaint(_sharedStrokePaint, rect.Size, pen.Brush, BlendMode); + new BrushConstructor(rect.Size, pen.Brush, BlendMode, this).ConfigurePaint(_sharedStrokePaint); } } private void ConfigureFillPaint(Size targetSize, IBrush? brush) { _sharedFillPaint.Reset(); - ConfigurePaint(_sharedFillPaint, targetSize, brush, BlendMode); - } - - internal static void ConfigurePaint(SKPaint paint, Size targetSize, IBrush? brush, BlendMode blendMode) - { - float opacity = brush?.Opacity ?? 0; - paint.IsAntialias = true; - paint.BlendMode = (SKBlendMode)blendMode; - - paint.Color = new SKColor(255, 255, 255, (byte)(255 * opacity)); - - if (brush is ISolidColorBrush solid) - { - paint.Color = new SKColor(solid.Color.R, solid.Color.G, solid.Color.B, (byte)(solid.Color.A * opacity)); - } - else if (brush is IGradientBrush gradient) - { - ConfigureGradientBrush(paint, targetSize, gradient); - } - else if (brush is ITileBrush tileBrush) - { - ConfigureTileBrush(paint, targetSize, tileBrush); - } - else - { - paint.Color = new SKColor(255, 255, 255, 0); - } - } - - private readonly struct TileBrushCalculator - { - private readonly Size _imageSize; - private readonly Rect _drawRect; - - public TileBrushCalculator(ITileBrush brush, Size contentSize, Size targetSize) - : this( - brush.TileMode, - brush.Stretch, - brush.AlignmentX, - brush.AlignmentY, - brush.SourceRect, - brush.DestinationRect, - contentSize, - targetSize) - { - } - - public TileBrushCalculator( - TileMode tileMode, - Stretch stretch, - AlignmentX alignmentX, - AlignmentY alignmentY, - RelativeRect sourceRect, - RelativeRect destinationRect, - Size contentSize, - Size targetSize) - { - _imageSize = contentSize; - - SourceRect = sourceRect.ToPixels(_imageSize); - DestinationRect = destinationRect.ToPixels(targetSize); - - Vector scale = stretch.CalculateScaling(DestinationRect.Size, SourceRect.Size); - Vector translate = CalculateTranslate(alignmentX, alignmentY, SourceRect, DestinationRect, scale); - - IntermediateSize = tileMode == TileMode.None ? targetSize : DestinationRect.Size; - IntermediateTransform = CalculateIntermediateTransform( - tileMode, - SourceRect, - DestinationRect, - scale, - translate, - out _drawRect); - } - - public Rect DestinationRect { get; } - - public Rect IntermediateClip => _drawRect; - - public Size IntermediateSize { get; } - - public Matrix IntermediateTransform { get; } - - public bool NeedsIntermediate - { - get - { - if (IntermediateTransform != Matrix.Identity) - return true; - if (SourceRect.Position != default) - return true; - if (SourceRect.Size.AspectRatio == _imageSize.AspectRatio) - return false; - if (SourceRect.Width != _imageSize.Width || - SourceRect.Height != _imageSize.Height) - return true; - return false; - } - } - - public Rect SourceRect { get; } - - public static Vector CalculateTranslate( - AlignmentX alignmentX, - AlignmentY alignmentY, - Rect sourceRect, - Rect destinationRect, - Vector scale) - { - float x = 0.0f; - float y = 0.0f; - Size size = sourceRect.Size * scale; - - switch (alignmentX) - { - case AlignmentX.Center: - x += (destinationRect.Width - size.Width) / 2; - break; - case AlignmentX.Right: - x += destinationRect.Width - size.Width; - break; - } - - switch (alignmentY) - { - case AlignmentY.Center: - y += (destinationRect.Height - size.Height) / 2; - break; - case AlignmentY.Bottom: - y += destinationRect.Height - size.Height; - break; - } - - return new Vector(x, y); - } - - public static Matrix CalculateIntermediateTransform( - TileMode tileMode, - Rect sourceRect, - Rect destinationRect, - Vector scale, - Vector translate, - out Rect drawRect) - { - Matrix transform = Matrix.CreateTranslation(-sourceRect.Position) * - Matrix.CreateScale(scale) * - Matrix.CreateTranslation(translate); - Rect dr; - - if (tileMode == TileMode.None) - { - dr = destinationRect; - transform *= Matrix.CreateTranslation(destinationRect.Position); - } - else - { - dr = new Rect(destinationRect.Size); - } - - drawRect = dr; - - return transform; - } + new BrushConstructor(targetSize, brush, BlendMode, this).ConfigurePaint(_sharedFillPaint); } } diff --git a/src/Beutl.Graphics/Graphics/Rendering/BlendModeNode.cs b/src/Beutl.Graphics/Graphics/Rendering/BlendModeNode.cs index ea2bdc5b6..0d2409289 100644 --- a/src/Beutl.Graphics/Graphics/Rendering/BlendModeNode.cs +++ b/src/Beutl.Graphics/Graphics/Rendering/BlendModeNode.cs @@ -14,10 +14,6 @@ public bool Equals(BlendMode blendMode) return BlendMode == blendMode; } - public override void Dispose() - { - } - public override void Render(ImmediateCanvas canvas) { using (canvas.PushBlendMode(BlendMode)) diff --git a/src/Beutl.Graphics/Graphics/Rendering/BrushDrawNode.cs b/src/Beutl.Graphics/Graphics/Rendering/BrushDrawNode.cs index 1432da1d7..5cf426610 100644 --- a/src/Beutl.Graphics/Graphics/Rendering/BrushDrawNode.cs +++ b/src/Beutl.Graphics/Graphics/Rendering/BrushDrawNode.cs @@ -14,4 +14,10 @@ protected BrushDrawNode(IBrush? fill, IPen? pen, Rect bounds) public IBrush? Fill { get; } public IPen? Pen { get; } + + protected override void OnDispose(bool disposing) + { + base.OnDispose(disposing); + (Fill as IDisposable)?.Dispose(); + } } diff --git a/src/Beutl.Graphics/Graphics/Rendering/ClearNode.cs b/src/Beutl.Graphics/Graphics/Rendering/ClearNode.cs index 6a447ed22..3c8783b8f 100644 --- a/src/Beutl.Graphics/Graphics/Rendering/ClearNode.cs +++ b/src/Beutl.Graphics/Graphics/Rendering/ClearNode.cs @@ -22,10 +22,6 @@ public override void Render(ImmediateCanvas canvas) canvas.Clear(Color); } - public override void Dispose() - { - } - public override bool HitTest(Point point) { return false; diff --git a/src/Beutl.Graphics/Graphics/Rendering/ContainerNode.cs b/src/Beutl.Graphics/Graphics/Rendering/ContainerNode.cs index 5927a69a5..98307c36f 100644 --- a/src/Beutl.Graphics/Graphics/Rendering/ContainerNode.cs +++ b/src/Beutl.Graphics/Graphics/Rendering/ContainerNode.cs @@ -1,4 +1,6 @@ -namespace Beutl.Graphics.Rendering; +using System.Runtime.InteropServices; + +namespace Beutl.Graphics.Rendering; public class ContainerNode : IGraphicNode { @@ -6,6 +8,15 @@ public class ContainerNode : IGraphicNode private bool _isBoundsDirty = true; private Rect _originalBounds; + ~ContainerNode() + { + if (!IsDisposed) + { + OnDispose(false); + IsDisposed = true; + } + } + public Rect OriginalBounds { get @@ -30,10 +41,7 @@ public Rect OriginalBounds public IReadOnlyList Children => _children; - public virtual void Dispose() - { - GC.SuppressFinalize(this); - } + public bool IsDisposed { get; private set; } public virtual bool HitTest(Point point) { @@ -81,6 +89,7 @@ public void RemoveRange(int index, int count) public void SetChild(int index, IGraphicNode item) { + _children[index]?.Dispose(); _children[index] = item; _isBoundsDirty = true; } @@ -93,4 +102,24 @@ public void BringFrom(ContainerNode containerNode) containerNode._children.Clear(); _isBoundsDirty = true; } + + public void Dispose() + { + if (!IsDisposed) + { + OnDispose(true); + IsDisposed = true; + GC.SuppressFinalize(this); + } + } + + protected virtual void OnDispose(bool disposing) + { + foreach (IGraphicNode? item in CollectionsMarshal.AsSpan(_children)) + { + item.Dispose(); + } + + _children.Clear(); + } } diff --git a/src/Beutl.Graphics/Graphics/Rendering/DeferradCanvas.cs b/src/Beutl.Graphics/Graphics/Rendering/DeferradCanvas.cs index d8e8b458a..fd4c6c062 100644 --- a/src/Beutl.Graphics/Graphics/Rendering/DeferradCanvas.cs +++ b/src/Beutl.Graphics/Graphics/Rendering/DeferradCanvas.cs @@ -1,7 +1,9 @@ using Beutl.Graphics.Effects; using Beutl.Media; using Beutl.Media.Pixel; +using Beutl.Media.Source; using Beutl.Media.TextFormatting; +using Beutl.Rendering; namespace Beutl.Graphics.Rendering; @@ -96,13 +98,13 @@ public void Clear(Color color) ++_drawOperationindex; } - public void DrawBitmap(IBitmap bitmap, IBrush? fill, IPen? pen) + public void DrawImageSource(IImageSource source, IBrush? fill, IPen? pen) { - BitmapNode? next = Next(); + ImageSourceNode? next = Next(); - if (next == null || !next.Equals(bitmap, fill, pen)) + if (next == null || !next.Equals(source, fill, pen)) { - Add(new BitmapNode(bitmap, fill, pen)); + Add(new ImageSourceNode(source, ConvertBrush(fill), pen)); } ++_drawOperationindex; @@ -114,7 +116,7 @@ public void DrawEllipse(Rect rect, IBrush? fill, IPen? pen) if (next == null || !next.Equals(rect, fill, pen)) { - Add(new EllipseNode(rect, fill, pen)); + Add(new EllipseNode(rect, ConvertBrush(fill), pen)); } ++_drawOperationindex; @@ -126,7 +128,7 @@ public void DrawGeometry(Geometry geometry, IBrush? fill, IPen? pen) if (next == null || !next.Equals(geometry, fill, pen)) { - Add(new GeometryNode(geometry, fill, pen)); + Add(new GeometryNode(geometry, ConvertBrush(fill), pen)); } ++_drawOperationindex; @@ -138,7 +140,7 @@ public void DrawRectangle(Rect rect, IBrush? fill, IPen? pen) if (next == null || !next.Equals(rect, fill, pen)) { - Add(new RectangleNode(rect, fill, pen)); + Add(new RectangleNode(rect, ConvertBrush(fill), pen)); } ++_drawOperationindex; @@ -150,7 +152,7 @@ public void DrawText(FormattedText text, IBrush? fill, IPen? pen) if (next == null || !next.Equals(text, fill, pen)) { - Add(new TextNode(text, fill, pen)); + Add(new TextNode(text, ConvertBrush(fill), pen)); } ++_drawOperationindex; @@ -300,4 +302,27 @@ public PushedState PushTransform(Matrix matrix, TransformOperator transformOpera return new(this, _nodes.Count); } + + private static IBrush? ConvertBrush(IBrush? brush) + { + if (brush is IDrawableBrush drawableBrush) + { + RenderScene? scene = null; + Rect bounds = default; + if (drawableBrush is { Drawable: { IsVisible: true } drawable }) + { + drawable.Measure(Graphics.Size.Infinity); + + bounds = drawable.Bounds; + scene = new RenderScene(bounds.Size.Ceiling()); + scene[0].UpdateAll(new[] { drawable }); + } + + return new RenderSceneBrush(drawableBrush, scene, bounds); + } + else + { + return brush; + } + } } diff --git a/src/Beutl.Graphics/Graphics/Rendering/DrawNode.cs b/src/Beutl.Graphics/Graphics/Rendering/DrawNode.cs index e0bd50da4..b74190597 100644 --- a/src/Beutl.Graphics/Graphics/Rendering/DrawNode.cs +++ b/src/Beutl.Graphics/Graphics/Rendering/DrawNode.cs @@ -1,4 +1,6 @@ -namespace Beutl.Graphics.Rendering; +using System.Diagnostics; + +namespace Beutl.Graphics.Rendering; public abstract class DrawNode : IGraphicNode { @@ -9,11 +11,35 @@ public DrawNode(Rect bounds) Bounds = bounds; } + ~DrawNode() + { + Debug.WriteLine("GC発生"); + if (!IsDisposed) + { + OnDispose(false); + IsDisposed = true; + } + } + public Rect Bounds { get; } + public bool IsDisposed { get; private set; } + public abstract void Render(ImmediateCanvas canvas); - public abstract void Dispose(); + public void Dispose() + { + if (!IsDisposed) + { + OnDispose(true); + IsDisposed = true; + GC.SuppressFinalize(this); + } + } + + protected virtual void OnDispose(bool disposing) + { + } public abstract bool HitTest(Point point); } diff --git a/src/Beutl.Graphics/Graphics/Rendering/DrawableNode.cs b/src/Beutl.Graphics/Graphics/Rendering/DrawableNode.cs index 84a87378e..9c954a7db 100644 --- a/src/Beutl.Graphics/Graphics/Rendering/DrawableNode.cs +++ b/src/Beutl.Graphics/Graphics/Rendering/DrawableNode.cs @@ -10,7 +10,7 @@ public DrawableNode(Drawable drawable) Drawable = drawable; } - public Drawable Drawable { get; } + public Drawable Drawable { get; private set; } public override void Render(ImmediateCanvas canvas) { @@ -18,4 +18,10 @@ public override void Render(ImmediateCanvas canvas) Rect bounds = Bounds.Inflate(10); canvas.DrawRectangle(bounds, null, new ImmutablePen(Brushes.White, null, 0, 5)); } + + protected override void OnDispose(bool disposing) + { + base.OnDispose(disposing); + Drawable = null!; + } } diff --git a/src/Beutl.Graphics/Graphics/Rendering/EllipseNode.cs b/src/Beutl.Graphics/Graphics/Rendering/EllipseNode.cs index 6f676baf5..ce5f69a9f 100644 --- a/src/Beutl.Graphics/Graphics/Rendering/EllipseNode.cs +++ b/src/Beutl.Graphics/Graphics/Rendering/EllipseNode.cs @@ -24,10 +24,6 @@ public override void Render(ImmediateCanvas canvas) canvas.DrawEllipse(Rect, Fill, Pen); } - public override void Dispose() - { - } - //https://github.com/AvaloniaUI/Avalonia/blob/release/0.10.21/src/Avalonia.Visuals/Rendering/SceneGraph/EllipseNode.cs public override bool HitTest(Point point) { diff --git a/src/Beutl.Graphics/Graphics/Rendering/FilterEffectNode.cs b/src/Beutl.Graphics/Graphics/Rendering/FilterEffectNode.cs index 2e27791a3..3a1c9c27e 100644 --- a/src/Beutl.Graphics/Graphics/Rendering/FilterEffectNode.cs +++ b/src/Beutl.Graphics/Graphics/Rendering/FilterEffectNode.cs @@ -17,9 +17,9 @@ public FilterEffectNode(FilterEffect filterEffect) public FilterEffect FilterEffect { get; } - public override void Dispose() + protected override void OnDispose(bool disposing) { - base.Dispose(); + base.OnDispose(disposing); _prevContext?.Dispose(); _prevContext = null; } diff --git a/src/Beutl.Graphics/Graphics/Rendering/GeometryClipNode.cs b/src/Beutl.Graphics/Graphics/Rendering/GeometryClipNode.cs index 3015e02b5..fb2860002 100644 --- a/src/Beutl.Graphics/Graphics/Rendering/GeometryClipNode.cs +++ b/src/Beutl.Graphics/Graphics/Rendering/GeometryClipNode.cs @@ -4,9 +4,12 @@ namespace Beutl.Graphics.Rendering; public sealed class GeometryClipNode : ContainerNode { + private readonly int _version; + public GeometryClipNode(Geometry clip, ClipOperation operation) { Clip = clip; + _version = clip.Version; Operation = operation; } @@ -17,6 +20,7 @@ public GeometryClipNode(Geometry clip, ClipOperation operation) public bool Equals(Geometry clip, ClipOperation operation) { return Clip == clip + && _version == clip.Version && Operation == operation; } @@ -28,8 +32,9 @@ public override void Render(ImmediateCanvas canvas) } } - public override void Dispose() + protected override void OnDispose(bool disposing) { + base.OnDispose(disposing); Clip = null!; } } diff --git a/src/Beutl.Graphics/Graphics/Rendering/GeometryNode.cs b/src/Beutl.Graphics/Graphics/Rendering/GeometryNode.cs index 01b4097ea..a78c9f923 100644 --- a/src/Beutl.Graphics/Graphics/Rendering/GeometryNode.cs +++ b/src/Beutl.Graphics/Graphics/Rendering/GeometryNode.cs @@ -28,8 +28,9 @@ public override void Render(ImmediateCanvas canvas) canvas.DrawGeometry(Geometry, Fill, Pen); } - public override void Dispose() + protected override void OnDispose(bool disposing) { + base.OnDispose(disposing); Geometry = null!; } diff --git a/src/Beutl.Graphics/Graphics/Rendering/INode.cs b/src/Beutl.Graphics/Graphics/Rendering/INode.cs index b3646e1d9..360de5ea3 100644 --- a/src/Beutl.Graphics/Graphics/Rendering/INode.cs +++ b/src/Beutl.Graphics/Graphics/Rendering/INode.cs @@ -2,4 +2,5 @@ public interface INode : IDisposable { + bool IsDisposed { get; } } diff --git a/src/Beutl.Graphics/Graphics/Rendering/BitmapNode.cs b/src/Beutl.Graphics/Graphics/Rendering/ImageSourceNode.cs similarity index 55% rename from src/Beutl.Graphics/Graphics/Rendering/BitmapNode.cs rename to src/Beutl.Graphics/Graphics/Rendering/ImageSourceNode.cs index db2e50a44..018b6f6c5 100644 --- a/src/Beutl.Graphics/Graphics/Rendering/BitmapNode.cs +++ b/src/Beutl.Graphics/Graphics/Rendering/ImageSourceNode.cs @@ -1,32 +1,40 @@ using Beutl.Media; +using Beutl.Media.Source; namespace Beutl.Graphics.Rendering; -public sealed class BitmapNode : BrushDrawNode +public sealed class ImageSourceNode : BrushDrawNode { - public BitmapNode(IBitmap bitmap, IBrush? fill, IPen? pen) - : base(fill, pen, PenHelper.GetBounds(new Rect(0, 0, bitmap.Width, bitmap.Height), pen)) + public ImageSourceNode(IImageSource source, IBrush? fill, IPen? pen) + : base(fill, pen, PenHelper.GetBounds(new Rect(default, source.FrameSize.ToSize(1)), pen)) { - Bitmap = bitmap; + Source = source.Clone(); } - public IBitmap Bitmap { get; } + public IImageSource Source { get; } - public bool Equals(IBitmap bitmap, IBrush? fill, IPen? pen) + public bool Equals(IImageSource source, IBrush? fill, IPen? pen) { - return Bitmap == bitmap + return EqualityComparer.Default.Equals(Source, source) && EqualityComparer.Default.Equals(Fill, fill) && EqualityComparer.Default.Equals(Pen, pen); } public override void Render(ImmediateCanvas canvas) { - canvas.DrawBitmap(Bitmap, Fill, Pen); + if (Source.TryGetRef(out Ref? bitmap)) + { + using (bitmap) + { + canvas.DrawBitmap(bitmap.Value, Fill, Pen); + } + } } - public override void Dispose() + protected override void OnDispose(bool disposing) { - Bitmap.Dispose(); + base.OnDispose(disposing); + Source.Dispose(); } public override bool HitTest(Point point) diff --git a/src/Beutl.Graphics/Graphics/Rendering/OpacityMaskNode.cs b/src/Beutl.Graphics/Graphics/Rendering/OpacityMaskNode.cs index af9567542..8cbaa4962 100644 --- a/src/Beutl.Graphics/Graphics/Rendering/OpacityMaskNode.cs +++ b/src/Beutl.Graphics/Graphics/Rendering/OpacityMaskNode.cs @@ -32,8 +32,9 @@ public override void Render(ImmediateCanvas canvas) } } - public override void Dispose() + protected override void OnDispose(bool disposing) { + base.OnDispose(disposing); Mask = null!; } } diff --git a/src/Beutl.Graphics/Graphics/Rendering/RectangleNode.cs b/src/Beutl.Graphics/Graphics/Rendering/RectangleNode.cs index 7ced09296..9b447199f 100644 --- a/src/Beutl.Graphics/Graphics/Rendering/RectangleNode.cs +++ b/src/Beutl.Graphics/Graphics/Rendering/RectangleNode.cs @@ -24,10 +24,6 @@ public override void Render(ImmediateCanvas canvas) canvas.DrawRectangle(Rect, Fill, Pen); } - public override void Dispose() - { - } - public override bool HitTest(Point point) { StrokeAlignment alignment = Pen?.StrokeAlignment ?? StrokeAlignment.Inside; diff --git a/src/Beutl.Graphics/Graphics/Rendering/RenderSceneBrush.cs b/src/Beutl.Graphics/Graphics/Rendering/RenderSceneBrush.cs new file mode 100644 index 000000000..31cdc105a --- /dev/null +++ b/src/Beutl.Graphics/Graphics/Rendering/RenderSceneBrush.cs @@ -0,0 +1,88 @@ +using Beutl.Graphics.Transformation; +using Beutl.Media; +using Beutl.Rendering; + +namespace Beutl.Graphics.Rendering; + +internal sealed class RenderSceneBrush : IDrawableBrush, IEquatable, IDisposable +{ + public RenderSceneBrush(IDrawableBrush @base, RenderScene? scene, Rect bounds) + { + Base = @base; + Scene = scene; + Bounds = bounds; + } + + public RenderScene? Scene { get; private set; } + + public Rect Bounds { get; } + + public IDrawableBrush Base { get; } + + public AlignmentX AlignmentX => Base.AlignmentX; + + public AlignmentY AlignmentY => Base.AlignmentY; + + public RelativeRect DestinationRect => Base.DestinationRect; + + public RelativeRect SourceRect => Base.SourceRect; + + public Stretch Stretch => Base.Stretch; + + public TileMode TileMode => Base.TileMode; + + public BitmapInterpolationMode BitmapInterpolationMode => Base.BitmapInterpolationMode; + + public float Opacity => Base.Opacity; + + public ITransform? Transform => Base.Transform; + + public RelativePoint TransformOrigin => Base.TransformOrigin; + + public Drawable? Drawable => Base.Drawable; + + public void Dispose() + { + Scene?.Dispose(); + Scene = null; + } + + public override bool Equals(object? obj) + { + return Equals(obj as IDrawableBrush); + } + + public bool Equals(IDrawableBrush? other) + { + return other is not null + && AlignmentX == other.AlignmentX + && AlignmentY == other.AlignmentY + && DestinationRect.Equals(other.DestinationRect) + && Opacity == other.Opacity + && EqualityComparer.Default.Equals(Transform, other.Transform) + && TransformOrigin.Equals(other.TransformOrigin) + && SourceRect.Equals(other.SourceRect) + && Stretch == other.Stretch + && TileMode == other.TileMode + && BitmapInterpolationMode == other.BitmapInterpolationMode + && ReferenceEquals(Drawable, other.Drawable) + && Drawable?.Version == other.Drawable?.Version; + } + + public override int GetHashCode() + { + var hash = new HashCode(); + hash.Add(AlignmentX); + hash.Add(AlignmentY); + hash.Add(DestinationRect); + hash.Add(Opacity); + hash.Add(Transform); + hash.Add(TransformOrigin); + hash.Add(SourceRect); + hash.Add(Stretch); + hash.Add(TileMode); + hash.Add(BitmapInterpolationMode); + hash.Add(Drawable); + return hash.ToHashCode(); + } +} diff --git a/src/Beutl.Graphics/Graphics/Rendering/TextNode.cs b/src/Beutl.Graphics/Graphics/Rendering/TextNode.cs index e6997fdf2..adb38a125 100644 --- a/src/Beutl.Graphics/Graphics/Rendering/TextNode.cs +++ b/src/Beutl.Graphics/Graphics/Rendering/TextNode.cs @@ -26,10 +26,6 @@ public override void Render(ImmediateCanvas canvas) canvas.DrawText(Text, Fill, Pen); } - public override void Dispose() - { - } - public override bool HitTest(Point point) { return Bounds.ContainsExclusive(point); diff --git a/src/Beutl.Graphics/Graphics/Rendering/TransformNode.cs b/src/Beutl.Graphics/Graphics/Rendering/TransformNode.cs index 0e639eb4f..64a537b4e 100644 --- a/src/Beutl.Graphics/Graphics/Rendering/TransformNode.cs +++ b/src/Beutl.Graphics/Graphics/Rendering/TransformNode.cs @@ -23,10 +23,6 @@ public bool Equals(Matrix transform, TransformOperator transformOperator) && TransformOperator == transformOperator; } - public override void Dispose() - { - } - public override void Render(ImmediateCanvas canvas) { using (canvas.PushTransform(Transform, TransformOperator)) diff --git a/src/Beutl.Graphics/Graphics/Size.cs b/src/Beutl.Graphics/Graphics/Size.cs index b57d96b02..50c843436 100644 --- a/src/Beutl.Graphics/Graphics/Size.cs +++ b/src/Beutl.Graphics/Graphics/Size.cs @@ -5,6 +5,7 @@ using System.Text.Json.Serialization; using Beutl.Converters; +using Beutl.Media; using Beutl.Utilities; namespace Beutl.Graphics; @@ -365,6 +366,11 @@ public Size WithHeight(float height) return new Size(Width, height); } + public PixelSize Ceiling() + { + return new PixelSize((int)MathF.Ceiling(Width), (int)MathF.Ceiling(Height)); + } + /// /// Returns the string representation of the size. /// diff --git a/src/Beutl.Graphics/Graphics/SourceImage.cs b/src/Beutl.Graphics/Graphics/SourceImage.cs index 06e722414..437731f01 100644 --- a/src/Beutl.Graphics/Graphics/SourceImage.cs +++ b/src/Beutl.Graphics/Graphics/SourceImage.cs @@ -38,12 +38,9 @@ protected override Size MeasureCore(Size availableSize) protected override void OnDraw(ICanvas canvas) { - if (_source?.Read(out IBitmap? bitmap) == true) + if (_source != null) { - using (bitmap) - { - canvas.DrawBitmap(bitmap, Brushes.White, null); - } + canvas.DrawImageSource(_source, Brushes.White, null); } } } diff --git a/src/Beutl.Graphics/Graphics/TileBrushCalculator.cs b/src/Beutl.Graphics/Graphics/TileBrushCalculator.cs new file mode 100644 index 000000000..9cee3e3d2 --- /dev/null +++ b/src/Beutl.Graphics/Graphics/TileBrushCalculator.cs @@ -0,0 +1,139 @@ +using Beutl.Media; + +namespace Beutl.Graphics; + +internal readonly struct TileBrushCalculator +{ + private readonly Size _imageSize; + private readonly Rect _drawRect; + + public TileBrushCalculator(ITileBrush brush, Size contentSize, Size targetSize) + : this( + brush.TileMode, + brush.Stretch, + brush.AlignmentX, + brush.AlignmentY, + brush.SourceRect, + brush.DestinationRect, + contentSize, + targetSize) + { + } + + public TileBrushCalculator( + TileMode tileMode, + Stretch stretch, + AlignmentX alignmentX, + AlignmentY alignmentY, + RelativeRect sourceRect, + RelativeRect destinationRect, + Size contentSize, + Size targetSize) + { + _imageSize = contentSize; + + SourceRect = sourceRect.ToPixels(_imageSize); + DestinationRect = destinationRect.ToPixels(targetSize); + + Vector scale = stretch.CalculateScaling(DestinationRect.Size, SourceRect.Size); + Vector translate = CalculateTranslate(alignmentX, alignmentY, SourceRect, DestinationRect, scale); + + IntermediateSize = tileMode == TileMode.None ? targetSize : DestinationRect.Size; + IntermediateTransform = CalculateIntermediateTransform( + tileMode, + SourceRect, + DestinationRect, + scale, + translate, + out _drawRect); + } + + public Rect DestinationRect { get; } + + public Rect IntermediateClip => _drawRect; + + public Size IntermediateSize { get; } + + public Matrix IntermediateTransform { get; } + + public bool NeedsIntermediate + { + get + { + if (IntermediateTransform != Matrix.Identity) + return true; + if (SourceRect.Position != default) + return true; + if (SourceRect.Size.AspectRatio == _imageSize.AspectRatio) + return false; + if (SourceRect.Width != _imageSize.Width || + SourceRect.Height != _imageSize.Height) + return true; + return false; + } + } + + public Rect SourceRect { get; } + + public static Vector CalculateTranslate( + AlignmentX alignmentX, + AlignmentY alignmentY, + Rect sourceRect, + Rect destinationRect, + Vector scale) + { + float x = 0.0f; + float y = 0.0f; + Size size = sourceRect.Size * scale; + + switch (alignmentX) + { + case AlignmentX.Center: + x += (destinationRect.Width - size.Width) / 2; + break; + case AlignmentX.Right: + x += destinationRect.Width - size.Width; + break; + } + + switch (alignmentY) + { + case AlignmentY.Center: + y += (destinationRect.Height - size.Height) / 2; + break; + case AlignmentY.Bottom: + y += destinationRect.Height - size.Height; + break; + } + + return new Vector(x, y); + } + + public static Matrix CalculateIntermediateTransform( + TileMode tileMode, + Rect sourceRect, + Rect destinationRect, + Vector scale, + Vector translate, + out Rect drawRect) + { + Matrix transform = Matrix.CreateTranslation(-sourceRect.Position) * + Matrix.CreateScale(scale) * + Matrix.CreateTranslation(translate); + Rect dr; + + if (tileMode == TileMode.None) + { + dr = destinationRect; + transform *= Matrix.CreateTranslation(destinationRect.Position); + } + else + { + dr = new Rect(destinationRect.Size); + } + + drawRect = dr; + + return transform; + } +} diff --git a/src/Beutl.Graphics/Media/Brushes/DrawableBrush.cs b/src/Beutl.Graphics/Media/Brushes/DrawableBrush.cs index b5e653a04..daa1e7925 100644 --- a/src/Beutl.Graphics/Media/Brushes/DrawableBrush.cs +++ b/src/Beutl.Graphics/Media/Brushes/DrawableBrush.cs @@ -7,14 +7,14 @@ namespace Beutl.Media; /// /// Paints an area with an . /// -public class DrawableBrush : TileBrush, IDrawableBrush +public class DrawableBrush : TileBrush, IDrawableBrush, IEquatable { - public static readonly CoreProperty DrawableProperty; - private IDrawable? _drawable; + public static readonly CoreProperty DrawableProperty; + private Drawable? _drawable; static DrawableBrush() { - DrawableProperty = ConfigureProperty(nameof(Drawable)) + DrawableProperty = ConfigureProperty(nameof(Drawable)) .Accessor(o => o.Drawable, (o, v) => o.Drawable = v) .Register(); @@ -32,7 +32,7 @@ public DrawableBrush() /// Initializes a new instance of the class. /// /// The drawable to draw. - public DrawableBrush(IDrawable drawable) + public DrawableBrush(Drawable drawable) { Drawable = drawable; } @@ -40,7 +40,7 @@ public DrawableBrush(IDrawable drawable) /// /// Gets or sets the visual to draw. /// - public IDrawable? Drawable + public Drawable? Drawable { get => _drawable; set => SetAndRaise(DrawableProperty, ref _drawable, value); @@ -70,7 +70,8 @@ public bool Equals(IDrawableBrush? other) && Stretch == other.Stretch && TileMode == other.TileMode && BitmapInterpolationMode == other.BitmapInterpolationMode - && EqualityComparer.Default.Equals(Drawable, other.Drawable); + && ReferenceEquals(Drawable, other.Drawable) + && Drawable?.Version == other.Drawable?.Version; } public override int GetHashCode() diff --git a/src/Beutl.Graphics/Media/Brushes/IDrawableBrush.cs b/src/Beutl.Graphics/Media/Brushes/IDrawableBrush.cs index bccb6985a..7d1441c58 100644 --- a/src/Beutl.Graphics/Media/Brushes/IDrawableBrush.cs +++ b/src/Beutl.Graphics/Media/Brushes/IDrawableBrush.cs @@ -10,5 +10,5 @@ public interface IDrawableBrush : ITileBrush /// /// Gets the drawable to draw. /// - IDrawable? Drawable { get; } + Drawable? Drawable { get; } } diff --git a/src/Beutl.Graphics/Media/Immutable/ImmutableDrawableBrush.cs b/src/Beutl.Graphics/Media/Immutable/ImmutableDrawableBrush.cs index 14b815458..76357662a 100644 --- a/src/Beutl.Graphics/Media/Immutable/ImmutableDrawableBrush.cs +++ b/src/Beutl.Graphics/Media/Immutable/ImmutableDrawableBrush.cs @@ -39,7 +39,7 @@ public ImmutableDrawableBrush(IDrawableBrush source) Drawable = source.Drawable; } - public IDrawable? Drawable { get; } + public Drawable? Drawable { get; } public override bool Equals(object? obj) { @@ -59,7 +59,8 @@ public bool Equals(IDrawableBrush? other) && Stretch == other.Stretch && TileMode == other.TileMode && BitmapInterpolationMode == other.BitmapInterpolationMode - && EqualityComparer.Default.Equals(Drawable, other.Drawable); + && ReferenceEquals(Drawable, other.Drawable) + && Drawable?.Version == other.Drawable?.Version; } public override int GetHashCode() diff --git a/src/Beutl.Graphics/Media/Source/BitmapSource.cs b/src/Beutl.Graphics/Media/Source/BitmapSource.cs index 1a38d8f84..1985a2a73 100644 --- a/src/Beutl.Graphics/Media/Source/BitmapSource.cs +++ b/src/Beutl.Graphics/Media/Source/BitmapSource.cs @@ -47,7 +47,7 @@ public override bool TryGetRef([NotNullWhen(true)] out Ref? bitmap) return false; } - bitmap = _bitmap; + bitmap = _bitmap.Clone(); return true; } diff --git a/src/Beutl.Graphics/Rendering/Cache/RenderCache.cs b/src/Beutl.Graphics/Rendering/Cache/RenderCache.cs index 0af1153a0..9c5481f9a 100644 --- a/src/Beutl.Graphics/Rendering/Cache/RenderCache.cs +++ b/src/Beutl.Graphics/Rendering/Cache/RenderCache.cs @@ -4,6 +4,7 @@ using Beutl.Graphics; using Beutl.Graphics.Rendering; using Beutl.Media.Source; +using Beutl.Threading; using SkiaSharp; @@ -15,12 +16,32 @@ public sealed class RenderCache : IDisposable private Ref? _cache; private Rect _cacheBounds; + private int _count; + + // キャッシュしたときの進捗の値 + private int _cachedAt = -1; + // 前回のフレームと比べたときに同じだった操作の数(進捗) + private FixedArrayAccessor? _accessor; + private int _denum; + public RenderCache(IGraphicNode node) { _node = new WeakReference(node); } - private int _count; + ~RenderCache() + { + if (!IsDisposed) + Dispose(); + } + + private FixedArrayAccessor Accessor => _accessor ??= new(); + + public bool IsCached => _cache != null; + + public DateTime LastAccessedTime { get; private set; } + + public bool IsDisposed { get; private set; } public void ReportRenderCount(int count) { @@ -32,14 +53,6 @@ public void IncrementRenderCount() _count++; } - // キャッシュしたときの進捗の値 - private int _cachedAt = -1; - // 前回のフレームと比べたときに同じだった操作の数(進捗) - private FixedArrayAccessor? _accessor; - private int _denum; - - private FixedArrayAccessor Accessor => _accessor ??= new(); - // 一つのノードで処理が別れている場合、どこまで同じかを報告する public void ReportSameNumber(int value, int count) { @@ -99,10 +112,13 @@ public bool CanCacheBoundary() public void Invalidate() { + RenderThread.Dispatcher.CheckAccess(); +#if DEBUG if (_cache != null) { Debug.WriteLine($"[RenderCache:Invalildated] '{(_node.TryGetTarget(out var node) ? node : null)}'"); } +#endif _cache?.Dispose(); _cache = null; @@ -112,12 +128,37 @@ public void Invalidate() public void Dispose() { - _cache?.Dispose(); - _cache = null; - _cacheBounds = Rect.Empty; - } + void DisposeOnRenderThread() + { + if (_cache != null) + { + Ref tmp = _cache; + _cache = null; + _cacheBounds = Rect.Empty; - public bool IsCached => _cache != null; + RenderThread.Dispatcher.Dispatch(tmp.Dispose, DispatchPriority.Low); + } + + IsDisposed = true; + } + + if (!IsDisposed) + { + if (RenderThread.Dispatcher.CheckAccess()) + { + _cache?.Dispose(); + _cache = null; + _cacheBounds = Rect.Empty; + IsDisposed = true; + } + else + { + DisposeOnRenderThread(); + } + + GC.SuppressFinalize(this); + } + } public Ref UseCache(out Rect bounds) { @@ -127,6 +168,7 @@ public Ref UseCache(out Rect bounds) } bounds = _cacheBounds; + LastAccessedTime = DateTime.UtcNow; return _cache.Clone(); } @@ -146,6 +188,8 @@ public void StoreCache(Ref surface, Rect bounds) { _cachedAt = 0; } + + LastAccessedTime = DateTime.UtcNow; } private unsafe class FixedArrayAccessor diff --git a/src/Beutl.Graphics/Rendering/Cache/RenderCacheContext.cs b/src/Beutl.Graphics/Rendering/Cache/RenderCacheContext.cs index ddc482824..d0d14c557 100644 --- a/src/Beutl.Graphics/Rendering/Cache/RenderCacheContext.cs +++ b/src/Beutl.Graphics/Rendering/Cache/RenderCacheContext.cs @@ -1,9 +1,9 @@ using System.Diagnostics; using System.Runtime.CompilerServices; -using Beutl.Collections.Pooled; using Beutl.Graphics; using Beutl.Graphics.Rendering; +using Beutl.Media; using Beutl.Media.Source; using SkiaSharp; @@ -13,6 +13,22 @@ namespace Beutl.Rendering.Cache; public sealed class RenderCacheContext : IDisposable { private readonly ConditionalWeakTable _table = new(); + private RenderCacheOptions _cacheOptions = new(); + + public RenderCacheOptions CacheOptions + { + get => _cacheOptions; + set + { + ArgumentNullException.ThrowIfNull(value); + if (_cacheOptions != value) + { + Dispose(); + } + + _cacheOptions = value; + } + } public RenderCache GetCache(IGraphicNode node) { @@ -65,15 +81,33 @@ public void ClearCache(IGraphicNode node, RenderCache cache) { foreach (IGraphicNode item in containerNode.Children) { - ClearCache(item, GetCache(item)); + ClearCache(item); } } } - // 再帰呼び出しだらけ - // O(N^2) ??? + public void ClearCache(IGraphicNode node) + { + if (_table.TryGetValue(node, out RenderCache? cache)) + { + cache.Invalidate(); + } + + if (node is ContainerNode containerNode) + { + foreach (IGraphicNode item in containerNode.Children) + { + ClearCache(item); + } + } + } + + // 再帰呼び出しだらけしてる public void MakeCache(IGraphicNode node, IImmediateCanvasFactory factory) { + if (!_cacheOptions.IsEnabled) + return; + RenderCache cache = GetCache(node); // ここでのnodeは途中まで、キャッシュしても良い // CanCacheRecursive内で再帰呼び出ししているのはすべてキャッシュできる必要がある @@ -94,7 +128,7 @@ public void MakeCache(IGraphicNode node, IImmediateCanvasFactory factory) } } - public void MakeCacheCore(IGraphicNode node, RenderCache cache, IImmediateCanvasFactory factory) + private void MakeCacheCore(IGraphicNode node, RenderCache cache, IImmediateCanvasFactory factory) { // nodeの子要素のキャッシュをすべて削除 ClearCache(node, cache); @@ -124,6 +158,11 @@ public void MakeCacheCore(IGraphicNode node, RenderCache cache, IImmediateCanvas } public void Dispose() + { + Clear(); + } + + public void Clear() { foreach (KeyValuePair item in _table) { @@ -133,3 +172,20 @@ public void Dispose() _table.Clear(); } } + +public record RenderCacheOptions( + bool IsEnabled = true, + RenderCacheRules Rules = new()); + +public readonly record struct RenderCacheRules( + int MaxWidth = int.MaxValue, + int MaxHeight = int.MaxValue, + int MinWidth = 0, + int MinHeight = 0) +{ + public bool Match(PixelSize size) + { + return (size.Width <= MaxWidth && size.Height <= MaxHeight) + || (size.Width >= MinWidth && size.Height >= MinHeight); + } +} diff --git a/src/Beutl.Graphics/Rendering/DeferredRenderer.cs b/src/Beutl.Graphics/Rendering/DeferredRenderer.cs deleted file mode 100644 index 093cc8533..000000000 --- a/src/Beutl.Graphics/Rendering/DeferredRenderer.cs +++ /dev/null @@ -1,358 +0,0 @@ -using Beutl.Animation; -using Beutl.Audio; -using Beutl.Graphics; -using Beutl.Media; -using Beutl.Threading; - -using SkiaSharp; - -namespace Beutl.Rendering; - -#if false -public class DeferredRenderer : IRenderer -{ - internal static readonly Dispatcher s_dispatcher = Dispatcher.Spawn(); - private readonly SortedDictionary _objects = new(); - private readonly List _clips = new(); - private readonly HashSet _dirtyItems = new(); - private readonly Canvas _graphics; - private readonly Audio.Audio _audio; - private readonly Rect _canvasBounds; - private readonly FpsText _fpsText = new(); - private readonly InstanceClock _instanceClock = new(); - private TimeSpan _lastTimeSpan; - //private int _lastAudioTime = -1; - - public DeferredRenderer(int width, int height) - { - _graphics = s_dispatcher.Invoke(() => new Canvas(width, height)); - _canvasBounds = new Rect(_graphics.Size.ToSize(1)); - _audio = new Audio.Audio(44100); - } - - ~DeferredRenderer() - { - Dispose(); - } - - public ICanvas Graphics => _graphics; - - public IAudio Audio => _audio; - - public Dispatcher Dispatcher => s_dispatcher; - - public bool IsDisposed { get; private set; } - - public bool IsGraphicsRendering { get; private set; } - - public bool IsAudioRendering { get; private set; } - - public bool DrawFps - { - get => _fpsText.DrawFps; - set => _fpsText.DrawFps = value; - } - - public IClock Clock => _instanceClock; - - public IRenderLayer? this[int index] - { - get => _objects.TryGetValue(index, out IRenderLayer? value) ? value : null; - set - { - if (value != null) - { - value.AttachToRenderer(this); - _objects[index] = value; - } - else if (_objects.TryGetValue(index, out IRenderLayer? oldLayer)) - { - oldLayer.DetachFromRenderer(); - _objects.Remove(index); - } - } - } - - public event EventHandler? RenderInvalidated; - - public virtual void Dispose() - { - if (IsDisposed) return; - - Graphics?.Dispose(); - GC.SuppressFinalize(this); - - IsDisposed = true; - } - - public IRenderer.RenderResult RenderGraphics(TimeSpan timeSpan) - { - Dispatcher.VerifyAccess(); - if (!IsGraphicsRendering) - { - IsGraphicsRendering = true; - _instanceClock.CurrentTime = timeSpan; - using (_fpsText.StartRender(this)) - { - RenderGraphicsCore(); - } - - IsGraphicsRendering = false; - - _lastTimeSpan = timeSpan; - - return new IRenderer.RenderResult(Graphics.GetBitmap()); - } - else - { - return default; - } - } - - protected virtual void RenderGraphicsCore() - { - var objects = new KeyValuePair[_objects.Count]; - _objects.CopyTo(objects, 0); - var timeSpan = Clock.CurrentTime; - Func(objects, 0, objects.Length, timeSpan); - - using (Graphics.PushCanvas()) - { - using var path = new SKPath(); - for (int i = 0; i < _clips.Count; i++) - { - Rect item = _clips[i]; - path.AddRect(SKRect.Create(item.X, item.Y, item.Width, item.Height)); - } - - Graphics.ClipPath(path); - - if (_clips.Count > 0) - { - Graphics.Clear(); - } - - foreach (KeyValuePair item in objects) - { - IRenderable? renderable = item.Value[timeSpan]?.Value; - if (renderable != null - && _dirtyItems.Contains(renderable)) - { - item.Value.RenderGraphics(); - } - } - - _clips.Clear(); - _dirtyItems.Clear(); - } - } - - // _clipsにrect1, rect2を追加する - private void AddDirtyRects(Rect rect1, Rect rect2) - { - if (!rect1.IsEmpty) - { - if (!_canvasBounds.Contains(rect1)) - { - rect1 = ClipToCanvasBounds(rect1); - } - - if (!_clips.Contains(rect1)) - { - _clips.Add(rect1); - } - } - if (!rect2.IsEmpty) - { - if (!_canvasBounds.Contains(rect2)) - { - rect2 = ClipToCanvasBounds(rect2); - } - - if (rect1 != rect2 && !_clips.Contains(rect2)) - { - _clips.Add(rect2); - } - } - } - - // 変更されているオブジェクトのBoundsを_clipsに追加して、 - // そのオブジェクトが影響を与えるオブジェクトも同様の処理をする - private void Func(ReadOnlySpan> items, int start, int length, TimeSpan timeSpan) - { - for (int i = length - 1; i >= start; i--) - { - KeyValuePair item = items[i]; - IRenderLayer context = item.Value; - RenderLayerSpan? layerNode = context[timeSpan]; - RenderLayerSpan? lastLayerNode = context[_lastTimeSpan]; - IRenderable? renderable = layerNode?.Value; - IRenderable? lastRenderable = lastLayerNode?.Value; - - if (layerNode != lastLayerNode) - { - if (lastRenderable is Drawable lastDrawable) - { - AddDirtyRect(lastDrawable.Bounds); - //lastDrawable.Invalidate(); - } - - if (renderable != null) - { - AddDirty(renderable); - } - } - - if (renderable is Drawable drawable) - { - Rect rect1 = drawable.Bounds; - drawable.Measure(_canvasBounds.Size); - Rect rect2 = drawable.Bounds; - - if (_dirtyItems.Contains(drawable)) - { - AddDirtyRects(rect1, rect2); - - //Func(items, 0, i); - Func(items, i + 1, items.Length, timeSpan); - } - else if (renderable.IsVisible && HitTestClips(rect1, rect2)) - { - AddDirty(renderable); - } - } - } - } - - // rectがcanvasのBoundsに丸める - private Rect ClipToCanvasBounds(Rect rect) - { - return new Rect( - new Point(Math.Max(rect.Left, 0), Math.Max(rect.Top, 0)), - new Point(Math.Min(rect.Right, _canvasBounds.Width), Math.Min(rect.Bottom, _canvasBounds.Height))); - } - - // _clipsがrect1またはrect2と交差する場合trueを返す。 - private bool HitTestClips(Rect rect1, Rect rect2) - { - for (int i = 0; i < _clips.Count; i++) - { - Rect item = _clips[i]; - if (!item.IsEmpty && - (item.Intersects(rect1) || item.Intersects(rect2))) - { - return true; - } - } - - return false; - } - - public async void Invalidate(TimeSpan timeSpan) - { - if (RenderInvalidated != null) - { - IRenderer.RenderResult result = await Dispatcher.InvokeAsync(() => RenderGraphics(timeSpan)); - RenderInvalidated.Invoke(this, result); - result.Bitmap?.Dispose(); - result.Audio?.Dispose(); - } - } - - private void AddDirtyRect(Rect rect) - { - if (!rect.IsEmpty) - { - if (!_canvasBounds.Contains(rect)) - { - rect = ClipToCanvasBounds(rect); - } - - if (!_clips.Contains(rect)) - { - _clips.Add(rect); - } - } - } - - void IRenderer.AddDirtyRect(Rect rect) - { - AddDirtyRect(rect); - } - - void IRenderer.AddDirtyRange(TimeRange timeRange) - { - - } - - protected virtual void RenderAudioCore() - { - //var range = new TimeRange(timeSpan, TimeSpan.FromSeconds(1)); - //var lastRange = new TimeRange(_lastTimeSpan, TimeSpan.FromSeconds(1)); - //if (range.Intersects(lastRange)) - //{ - // // 交差している場合その場所を再利用する - //} - - _audio.Clear(); - - foreach (KeyValuePair item in _objects) - { - item.Value.RenderAudio(); - } - - //_lastAudioTime = timeSpan; - } - - public IRenderer.RenderResult RenderAudio(TimeSpan timeSpan) - { - if (!IsAudioRendering) - { - IsAudioRendering = true; - _instanceClock.AudioStartTime = timeSpan; - RenderAudioCore(); - - IsAudioRendering = false; - return new IRenderer.RenderResult(Audio: Audio.GetPcm()); - } - else - { - return default; - } - } - - public IRenderer.RenderResult Render(TimeSpan timeSpan) - { - Dispatcher.VerifyAccess(); - if (!IsGraphicsRendering && !IsAudioRendering) - { - IsGraphicsRendering = true; - IsAudioRendering = true; - _instanceClock.CurrentTime = timeSpan; - _instanceClock.AudioStartTime = timeSpan; - using (_fpsText.StartRender(this)) - { - RenderGraphicsCore(); - RenderAudioCore(); - } - - IsGraphicsRendering = false; - IsAudioRendering = false; - _lastTimeSpan = timeSpan; - return new IRenderer.RenderResult(Graphics.GetBitmap(), Audio.GetPcm()); - } - else - { - return default; - } - } - - public void AddDirty(IRenderable renderable) - { - if (renderable is Drawable) - { - _dirtyItems.Add(renderable); - } - } -} -#endif diff --git a/src/Beutl.Graphics/Rendering/IRenderable.cs b/src/Beutl.Graphics/Rendering/IRenderable.cs deleted file mode 100644 index e5aa3256c..000000000 --- a/src/Beutl.Graphics/Rendering/IRenderable.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Beutl.Graphics; -using Beutl.Media; - -namespace Beutl.Rendering; - -public interface IRenderable -{ - bool IsVisible { get; set; } - - int ZIndex { get; set; } - - TimeRange TimeRange { get; set; } - - void Invalidate(); -} diff --git a/src/Beutl.Graphics/Rendering/IRenderer.cs b/src/Beutl.Graphics/Rendering/IRenderer.cs index e7f8cd1d0..05a0cfef5 100644 --- a/src/Beutl.Graphics/Rendering/IRenderer.cs +++ b/src/Beutl.Graphics/Rendering/IRenderer.cs @@ -1,32 +1,28 @@ using Beutl.Animation; -using Beutl.Audio; using Beutl.Graphics; using Beutl.Media; using Beutl.Media.Music; using Beutl.Media.Music.Samples; using Beutl.Media.Pixel; -using Beutl.Threading; namespace Beutl.Rendering; -public interface IRenderer : IDisposable +public interface IRenderer : IDisposable, IImmediateCanvasFactory { RenderScene RenderScene { get; } - ImmediateCanvas Canvas { get; } + PixelSize FrameSize { get; } - Audio.Audio Audio { get; } + int SampleRate { get; } IClock Clock { get; } - Dispatcher Dispatcher { get; } - bool DrawFps { get; set; } bool IsDisposed { get; } bool IsGraphicsRendering { get; } - + bool IsAudioRendering { get; } event EventHandler RenderInvalidated; @@ -39,11 +35,5 @@ public interface IRenderer : IDisposable void RaiseInvalidated(TimeSpan timeSpan); - //void AddDirty(IRenderable renderable); - - //void AddDirtyRect(Rect rect); - - //void AddDirtyRange(TimeRange timeRange); - public record struct RenderResult(Bitmap? Bitmap = null, Pcm? Audio = null); } diff --git a/src/Beutl.Graphics/Rendering/RenderLayer.cs b/src/Beutl.Graphics/Rendering/RenderLayer.cs index 4af348770..56b07a358 100644 --- a/src/Beutl.Graphics/Rendering/RenderLayer.cs +++ b/src/Beutl.Graphics/Rendering/RenderLayer.cs @@ -61,6 +61,7 @@ public void UpdateAll(IReadOnlyList elements) { _currentFrame.Clear(); _currentFrame.EnsureCapacity(elements.Count); + // Todo: Drawable, Renderableを統合する予定 foreach (Renderable element in elements) { @@ -109,6 +110,19 @@ public void UpdateAll(IReadOnlyList elements) } } + public void ClearAllNodeCache(RenderCacheContext? context) + { + foreach (KeyValuePair item in _cache) + { + if (item.Value.Node is IGraphicNode graphicNode) + context?.ClearCache(graphicNode); + + item.Value.Dispose(); + } + + _cache.Clear(); + } + public void Render(ImmediateCanvas canvas) { foreach (Entry? entry in CollectionsMarshal.AsSpan(_currentFrame)) diff --git a/src/Beutl.Graphics/Rendering/RenderScene.cs b/src/Beutl.Graphics/Rendering/RenderScene.cs index 43b86ad5c..60290ec6f 100644 --- a/src/Beutl.Graphics/Rendering/RenderScene.cs +++ b/src/Beutl.Graphics/Rendering/RenderScene.cs @@ -1,4 +1,5 @@ -using Beutl.Graphics; +using Beutl.Animation; +using Beutl.Graphics; using Beutl.Media; namespace Beutl.Rendering; diff --git a/src/Beutl.Graphics/Rendering/Renderable.cs b/src/Beutl.Graphics/Rendering/Renderable.cs index 63b201964..2a2f299a8 100644 --- a/src/Beutl.Graphics/Rendering/Renderable.cs +++ b/src/Beutl.Graphics/Rendering/Renderable.cs @@ -1,11 +1,9 @@ -using Beutl.Audio; -using Beutl.Graphics; -using Beutl.Media; +using Beutl.Media; using Beutl.Styling; namespace Beutl.Rendering; -public abstract class Renderable : Styleable, IRenderable, IAffectsRender +public abstract class Renderable : Styleable, IAffectsRender { public static readonly CoreProperty IsVisibleProperty; public static readonly CoreProperty ZIndexProperty; @@ -52,9 +50,11 @@ public TimeRange TimeRange set => SetAndRaise(TimeRangeProperty, ref _timeRange, value); } + internal int Version { get; private set; } + private void AffectsRender_Invalidated(object? sender, RenderInvalidatedEventArgs e) { - Invalidated?.Invoke(this, e); + RaiseInvalidated(e); } protected static void AffectsRender(params CoreProperty[] properties) @@ -66,7 +66,7 @@ protected static void AffectsRender(params CoreProperty[] properties) { if (e.Sender is T s) { - s.Invalidated?.Invoke(s, new RenderInvalidatedEventArgs(s, e.Property.Name)); + s.RaiseInvalidated(new RenderInvalidatedEventArgs(s, e.Property.Name)); if (e.OldValue is IAffectsRender oldAffectsRender) { @@ -84,6 +84,15 @@ protected static void AffectsRender(params CoreProperty[] properties) public void Invalidate() { - Invalidated?.Invoke(this, new RenderInvalidatedEventArgs(this)); + RaiseInvalidated(new RenderInvalidatedEventArgs(this)); + } + + protected void RaiseInvalidated(RenderInvalidatedEventArgs args) + { + Invalidated?.Invoke(this, args); + unchecked + { + Version++; + } } } diff --git a/src/Beutl.Graphics/Rendering/ImmediateRenderer.cs b/src/Beutl.Graphics/Rendering/Renderer.cs similarity index 87% rename from src/Beutl.Graphics/Rendering/ImmediateRenderer.cs rename to src/Beutl.Graphics/Rendering/Renderer.cs index 3df25b91b..846dbe6cf 100644 --- a/src/Beutl.Graphics/Rendering/ImmediateRenderer.cs +++ b/src/Beutl.Graphics/Rendering/Renderer.cs @@ -8,10 +8,8 @@ namespace Beutl.Rendering; -// 後で名前を変更 -public class ImmediateRenderer : IRenderer, IImmediateCanvasFactory +public class Renderer : IRenderer { - internal static readonly Dispatcher s_dispatcher = RenderThread.Dispatcher; private readonly ImmediateCanvas _immediateCanvas; private readonly SKSurface _surface; private readonly Audio.Audio _audio; @@ -19,10 +17,12 @@ public class ImmediateRenderer : IRenderer, IImmediateCanvasFactory private readonly InstanceClock _instanceClock = new(); private readonly RenderCacheContext _cacheContext = new(); - public ImmediateRenderer(int width, int height) + public Renderer(int width, int height) { - RenderScene = new RenderScene(new PixelSize(width, height)); - (_immediateCanvas, _surface) = Dispatcher.Invoke(() => + FrameSize = new PixelSize(width, height); + SampleRate = 44100; + RenderScene = new RenderScene(FrameSize); + (_immediateCanvas, _surface) = RenderThread.Dispatcher.Invoke(() => { var factory = (IImmediateCanvasFactory)this; SKSurface surface = factory.CreateRenderTarget(width, height); @@ -32,8 +32,6 @@ public ImmediateRenderer(int width, int height) _audio = new Audio.Audio(44100); } - public Dispatcher Dispatcher => s_dispatcher; - public bool IsDisposed { get; private set; } public bool IsGraphicsRendering { get; private set; } @@ -48,11 +46,11 @@ public bool DrawFps public IClock Clock => _instanceClock; - public RenderScene RenderScene { get; } + public PixelSize FrameSize { get; } - public ImmediateCanvas Canvas => _immediateCanvas; + public int SampleRate { get; } - public Audio.Audio Audio => _audio; + public RenderScene RenderScene { get; } public event EventHandler? RenderInvalidated; @@ -77,7 +75,7 @@ public void RaiseInvalidated(TimeSpan timeSpan) public IRenderer.RenderResult RenderGraphics(TimeSpan timeSpan) { - Dispatcher.VerifyAccess(); + RenderThread.Dispatcher.VerifyAccess(); if (!IsGraphicsRendering) { IsGraphicsRendering = true; @@ -125,7 +123,7 @@ public IRenderer.RenderResult RenderAudio(TimeSpan timeSpan) public IRenderer.RenderResult Render(TimeSpan timeSpan) { - Dispatcher.VerifyAccess(); + RenderThread.Dispatcher.VerifyAccess(); if (!IsGraphicsRendering && !IsAudioRendering) { IsGraphicsRendering = true; @@ -150,7 +148,7 @@ public IRenderer.RenderResult Render(TimeSpan timeSpan) ImmediateCanvas IImmediateCanvasFactory.CreateCanvas(SKSurface surface, bool leaveOpen) { - Dispatcher.VerifyAccess(); + RenderThread.Dispatcher.VerifyAccess(); return new ImmediateCanvas(surface, leaveOpen) { Factory = this @@ -159,7 +157,7 @@ ImmediateCanvas IImmediateCanvasFactory.CreateCanvas(SKSurface surface, bool lea SKSurface IImmediateCanvasFactory.CreateRenderTarget(int width, int height) { - Dispatcher.VerifyAccess(); + RenderThread.Dispatcher.VerifyAccess(); GRContext grcontext = SharedGRContext.GetOrCreate(); SKSurface? surface; diff --git a/src/Beutl.Operators/Configure/ConfigureOperator.cs b/src/Beutl.Operators/Configure/ConfigureOperator.cs index d3596ad1a..f31816ccb 100644 --- a/src/Beutl.Operators/Configure/ConfigureOperator.cs +++ b/src/Beutl.Operators/Configure/ConfigureOperator.cs @@ -7,7 +7,7 @@ namespace Beutl.Operators.Configure; public abstract class ConfigureOperator : StylingOperator, ISourceTransformer - where TTarget : IRenderable + where TTarget : Renderable where TValue : CoreObject, IAffectsRender, new() { private IStyleInstance? _instance; diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Brushes/DrawableBrushNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Brushes/DrawableBrushNode.cs index a781b17b3..2b75aa158 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Brushes/DrawableBrushNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Brushes/DrawableBrushNode.cs @@ -5,7 +5,7 @@ namespace Beutl.NodeTree.Nodes.Brushes; public class DrawableBrushNode : TileBrushNode { - private readonly InputSocket _drawableSocket; + private readonly InputSocket _drawableSocket; public DrawableBrushNode() { diff --git a/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/MeasureNode.cs b/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/MeasureNode.cs index f61c25ac2..0fa4987e0 100644 --- a/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/MeasureNode.cs +++ b/src/Beutl.ProjectSystem/NodeTree/Nodes/Utilities/MeasureNode.cs @@ -30,7 +30,7 @@ public override void Evaluate(NodeEvaluationContext context) base.Evaluate(context); if (_inputSocket.Value is Drawable drawable) { - drawable.Measure(context.Renderer.Canvas.Size.ToSize(1)); + drawable.Measure(context.Renderer.FrameSize.ToSize(1)); _xSocket.Value = drawable.Bounds.X; _ySocket.Value = drawable.Bounds.Y; _widthSocket.Value = drawable.Bounds.Width; diff --git a/src/Beutl.ProjectSystem/Operation/ISourcePublisher.cs b/src/Beutl.ProjectSystem/Operation/ISourcePublisher.cs index e565a4b6c..b4bbe3b96 100644 --- a/src/Beutl.ProjectSystem/Operation/ISourcePublisher.cs +++ b/src/Beutl.ProjectSystem/Operation/ISourcePublisher.cs @@ -5,5 +5,5 @@ namespace Beutl.Operation; public interface ISourcePublisher : ISourceOperator { - IRenderable? Publish(IClock clock); + Renderable? Publish(IClock clock); } diff --git a/src/Beutl.ProjectSystem/Operation/SourceStyler.cs b/src/Beutl.ProjectSystem/Operation/SourceStyler.cs index e50520caa..3f5685ed4 100644 --- a/src/Beutl.ProjectSystem/Operation/SourceStyler.cs +++ b/src/Beutl.ProjectSystem/Operation/SourceStyler.cs @@ -31,11 +31,11 @@ public virtual void Transform(IList value, IClock clock) } } - protected virtual void OnPreSelect(IRenderable? value) + protected virtual void OnPreSelect(Renderable? value) { } - protected virtual void OnPostSelect(IRenderable? value) + protected virtual void OnPostSelect(Renderable? value) { } @@ -61,7 +61,7 @@ protected virtual void OnPostSelect(IRenderable? value) } } - protected virtual void ApplyStyle(IStyleInstance instance, IRenderable value, IClock clock) + protected virtual void ApplyStyle(IStyleInstance instance, Renderable value, IClock clock) { instance.Begin(); instance.Apply(clock); diff --git a/src/Beutl.ProjectSystem/Operation/StyledSourcePublisher.cs b/src/Beutl.ProjectSystem/Operation/StyledSourcePublisher.cs index 11ed04866..72a2a2c30 100644 --- a/src/Beutl.ProjectSystem/Operation/StyledSourcePublisher.cs +++ b/src/Beutl.ProjectSystem/Operation/StyledSourcePublisher.cs @@ -8,13 +8,13 @@ public abstract class StyledSourcePublisher : StylingOperator, ISourcePublisher { public IStyleInstance? Instance { get; private set; } - public virtual IRenderable? Publish(IClock clock) + public virtual Renderable? Publish(IClock clock) { - IRenderable? renderable = Instance?.Target as IRenderable; + Renderable? renderable = Instance?.Target as Renderable; if (!ReferenceEquals(Style, Instance?.Source) || Instance?.Target == null) { - renderable = Activator.CreateInstance(Style.TargetType) as IRenderable; + renderable = Activator.CreateInstance(Style.TargetType) as Renderable; if (renderable is ICoreObject coreObj) { Instance?.Dispose(); diff --git a/src/Beutl.ProjectSystem/SceneRenderer.cs b/src/Beutl.ProjectSystem/SceneRenderer.cs index fa7b7f57f..c75838e61 100644 --- a/src/Beutl.ProjectSystem/SceneRenderer.cs +++ b/src/Beutl.ProjectSystem/SceneRenderer.cs @@ -8,7 +8,7 @@ namespace Beutl; internal sealed class SceneRenderer : - ImmediateRenderer + Renderer //DeferredRenderer { private readonly Scene _scene; @@ -38,8 +38,10 @@ protected override void RenderGraphicsCore() foreach (Element item in exited) { - RenderScene[item.ZIndex].Clear(); ExitSourceOperators(item); + RenderLayer layer = RenderScene[item.ZIndex]; + layer.Clear(); + layer.ClearAllNodeCache(GetCacheContext()); } foreach (Element item in entered) diff --git a/src/Beutl/ViewModels/PlayerViewModel.cs b/src/Beutl/ViewModels/PlayerViewModel.cs index 32905a3be..fbb5f1720 100644 --- a/src/Beutl/ViewModels/PlayerViewModel.cs +++ b/src/Beutl/ViewModels/PlayerViewModel.cs @@ -208,7 +208,7 @@ private static void Swap(ref T x, ref T y) private async Task PlayWithXA2(XAudioContext audioContext) { IRenderer renderer = Scene.Renderer; - int sampleRate = renderer.Audio.SampleRate; + int sampleRate = renderer.SampleRate; TimeSpan cur = Scene.CurrentFrame; var fmt = new WaveFormat(sampleRate, 32, 2); var source = new XAudioSource(audioContext); @@ -330,7 +330,7 @@ private void Render(IRenderer renderer, TimeSpan timeSpan) if (renderer.IsGraphicsRendering) return; - renderer.Dispatcher.Dispatch(() => + RenderThread.Dispatcher.Dispatch(() => { if (renderer.RenderGraphics(timeSpan).Bitmap is { } bitmap) { @@ -374,7 +374,7 @@ private void Renderer_RenderInvalidated(object? sender, TimeSpan e) { if (sender is IRenderer { IsGraphicsRendering: false } renderer) { - renderer.Dispatcher.Dispatch(() => + RenderThread.Dispatcher.Dispatch(() => { IRenderer.RenderResult result = renderer.RenderGraphics(Scene.CurrentFrame); if (result.Bitmap is { } bitmap) From 0af2a74eea8cd0b1709dd92cf9801e0b885773b9 Mon Sep 17 00:00:00 2001 From: indigo-san Date: Mon, 31 Jul 2023 01:41:33 +0900 Subject: [PATCH 83/84] chore --- .../Graphics/Rendering/DrawableNode.cs | 12 +----- src/Beutl.Graphics/Rendering/Renderer.cs | 1 + .../ProjectSystem/SceneFile.cs | 8 ++-- src/Beutl.ProjectSystem/SceneRenderer.cs | 4 +- src/Beutl/ViewModels/OutputViewModel.cs | 38 ++++++++++++++----- 5 files changed, 35 insertions(+), 28 deletions(-) diff --git a/src/Beutl.Graphics/Graphics/Rendering/DrawableNode.cs b/src/Beutl.Graphics/Graphics/Rendering/DrawableNode.cs index 9c954a7db..82d9be3d3 100644 --- a/src/Beutl.Graphics/Graphics/Rendering/DrawableNode.cs +++ b/src/Beutl.Graphics/Graphics/Rendering/DrawableNode.cs @@ -1,7 +1,4 @@ -using Beutl.Media; -using Beutl.Media.Immutable; - -namespace Beutl.Graphics.Rendering; +namespace Beutl.Graphics.Rendering; public class DrawableNode : ContainerNode { @@ -12,13 +9,6 @@ public DrawableNode(Drawable drawable) public Drawable Drawable { get; private set; } - public override void Render(ImmediateCanvas canvas) - { - base.Render(canvas); - Rect bounds = Bounds.Inflate(10); - canvas.DrawRectangle(bounds, null, new ImmutablePen(Brushes.White, null, 0, 5)); - } - protected override void OnDispose(bool disposing) { base.OnDispose(disposing); diff --git a/src/Beutl.Graphics/Rendering/Renderer.cs b/src/Beutl.Graphics/Rendering/Renderer.cs index 846dbe6cf..add2a2f56 100644 --- a/src/Beutl.Graphics/Rendering/Renderer.cs +++ b/src/Beutl.Graphics/Rendering/Renderer.cs @@ -60,6 +60,7 @@ public virtual void Dispose() _immediateCanvas.Dispose(); _audio.Dispose(); + _cacheContext.Dispose(); GC.SuppressFinalize(this); IsDisposed = true; diff --git a/src/Beutl.ProjectSystem/ProjectSystem/SceneFile.cs b/src/Beutl.ProjectSystem/ProjectSystem/SceneFile.cs index 78a1c9d9c..d8d0e67ca 100644 --- a/src/Beutl.ProjectSystem/ProjectSystem/SceneFile.cs +++ b/src/Beutl.ProjectSystem/ProjectSystem/SceneFile.cs @@ -11,16 +11,16 @@ public SceneFile(string fileName) JsonNode json = JsonNode.Parse(File.ReadAllText(fileName))!; - Width = json["width"]?.AsValue()?.GetValue() ?? 0; - Height = json["height"]?.AsValue()?.GetValue() ?? 0; + Width = json[nameof(Width)]?.AsValue()?.GetValue() ?? 0; + Height = json[nameof(Height)]?.AsValue()?.GetValue() ?? 0; - if (json["duration"]?.AsValue()?.GetValue() is { } durationStr + if (json[nameof(Duration)]?.AsValue()?.GetValue() is { } durationStr && TimeSpan.TryParse(durationStr, out var duration)) { Duration = duration; } - if (json["currentFrame"]?.AsValue()?.GetValue() is { } currentStr + if (json[nameof(CurrentFrame)]?.AsValue()?.GetValue() is { } currentStr && TimeSpan.TryParse(currentStr, out var current)) { CurrentFrame = current; diff --git a/src/Beutl.ProjectSystem/SceneRenderer.cs b/src/Beutl.ProjectSystem/SceneRenderer.cs index c75838e61..425cbd688 100644 --- a/src/Beutl.ProjectSystem/SceneRenderer.cs +++ b/src/Beutl.ProjectSystem/SceneRenderer.cs @@ -7,9 +7,7 @@ namespace Beutl; -internal sealed class SceneRenderer : - Renderer -//DeferredRenderer +internal sealed class SceneRenderer : Renderer { private readonly Scene _scene; private readonly List _entered = new(); diff --git a/src/Beutl/ViewModels/OutputViewModel.cs b/src/Beutl/ViewModels/OutputViewModel.cs index 3569c8994..c01a919fb 100644 --- a/src/Beutl/ViewModels/OutputViewModel.cs +++ b/src/Beutl/ViewModels/OutputViewModel.cs @@ -16,6 +16,7 @@ using System.Text.Json.Nodes; using System.Text.Json; using Microsoft.Extensions.DependencyInjection; +using Beutl.Rendering.Cache; namespace Beutl.ViewModels; @@ -452,20 +453,37 @@ await RenderThread.Dispatcher.InvokeAsync(() => } IRenderer renderer = scene.Renderer; - for (double i = 0; i < frames; i++) + RenderCacheContext? cacheContext = renderer.GetCacheContext(); + RenderCacheOptions? restoreCacheOptions = null; + if (cacheContext != null) { - if (_lastCts.IsCancellationRequested) - break; + restoreCacheOptions = cacheContext.CacheOptions; + cacheContext.CacheOptions = new RenderCacheOptions(false); + } + try + { + for (double i = 0; i < frames; i++) + { + if (_lastCts.IsCancellationRequested) + break; - var ts = TimeSpan.FromSeconds(i / frameRateD); - IRenderer.RenderResult result = renderer.RenderGraphics(ts); + var ts = TimeSpan.FromSeconds(i / frameRateD); + IRenderer.RenderResult result = renderer.RenderGraphics(ts); - writer.AddVideo(result.Bitmap!); - result.Bitmap!.Dispose(); + writer.AddVideo(result.Bitmap!); + result.Bitmap!.Dispose(); - ProgressValue.Value++; - _progress.Value = ProgressValue.Value / ProgressMax.Value; - ProgressText.Value = $"動画を出力: {ts:hh\\:mm\\:ss\\.ff}"; + ProgressValue.Value++; + _progress.Value = ProgressValue.Value / ProgressMax.Value; + ProgressText.Value = $"動画を出力: {ts:hh\\:mm\\:ss\\.ff}"; + } + } + finally + { + if (cacheContext != null && restoreCacheOptions != null) + { + cacheContext.CacheOptions = restoreCacheOptions; + } } for (double i = 0; i < samples; i++) From 5b0be01dd281b3d491a5b48d5ef8fa2b3c3b3b66 Mon Sep 17 00:00:00 2001 From: indigo-san <66758394+indigo-san@users.noreply.github.com> Date: Mon, 31 Jul 2023 15:43:26 +0900 Subject: [PATCH 84/84] Fix SharedGRContext.cs --- .../Rendering/SharedGRContext.cs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/Beutl.Graphics/Rendering/SharedGRContext.cs b/src/Beutl.Graphics/Rendering/SharedGRContext.cs index 62b106a82..617585add 100644 --- a/src/Beutl.Graphics/Rendering/SharedGRContext.cs +++ b/src/Beutl.Graphics/Rendering/SharedGRContext.cs @@ -18,7 +18,8 @@ public static GRContext GetOrCreate() { RenderThread.Dispatcher.VerifyAccess(); GLFW.Init(); - ThrowGLFWError(); + if (GLFW.GetError(out _) is not ErrorCode.NoError) + return null; GLFW.DefaultWindowHints(); GLFW.WindowHint(WindowHintOpenGlProfile.OpenGlProfile, OpenGlProfile.Core); @@ -27,9 +28,10 @@ public static GRContext GetOrCreate() GLFW.WindowHint(WindowHintInt.ContextVersionMinor, 3); GLFW.WindowHint(WindowHintBool.Visible, false); s_window = GLFW.CreateWindow(1, 1, string.Empty, null, null); - + GLFW.MakeContextCurrent(s_window); - ThrowGLFWError(); + if (GLFW.GetError(out _) is not ErrorCode.NoError) + return null; GRContext = GRContext.CreateGl(); } @@ -49,14 +51,4 @@ public static void Shutdown() } }); } - - private static void ThrowGLFWError() - { - var result = GLFW.GetError(out var description); - - if (result is not ErrorCode.NoError) - { - throw new GraphicsException(description); - } - } }