// Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; using System.Collections.Generic; using System.Linq; using System.Reactive.Disposables; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Utilities; using Avalonia.VisualTree; namespace Avalonia.Rendering.SceneGraph { /// /// A node in the low-level scene graph representing an . /// internal class VisualNode : IVisualNode { private static readonly IReadOnlyList EmptyChildren = new IVisualNode[0]; private static readonly IReadOnlyList> EmptyDrawOperations = new IRef[0]; private Rect? _bounds; private double _opacity; private List _children; private List> _drawOperations; private IRef _drawOperationsRefCounter; private bool _drawOperationsCloned; private Matrix transformRestore; /// /// Initializes a new instance of the class. /// /// The visual that this node represents. /// The parent scene graph node, if any. public VisualNode(IVisual visual, IVisualNode parent) { Contract.Requires(visual != null); Visual = visual; Parent = parent; HasAncestorGeometryClip = parent != null && (parent.HasAncestorGeometryClip || parent.GeometryClip != null); } /// public IVisual Visual { get; } /// public IVisualNode Parent { get; } /// public Matrix Transform { get; set; } /// public Rect Bounds => _bounds ?? CalculateBounds(); /// public Rect ClipBounds { get; set; } /// public bool ClipToBounds { get; set; } /// public IGeometryImpl GeometryClip { get; set; } /// public bool HasAncestorGeometryClip { get; } /// public double Opacity { get { return _opacity; } set { if (_opacity != value) { _opacity = value; OpacityChanged = true; } } } /// /// Gets or sets the opacity mask for the scnee graph node. /// public IBrush OpacityMask { get; set; } /// /// Gets a value indicating whether this node in the scene graph has already /// been updated in the current update pass. /// public bool SubTreeUpdated { get; set; } /// /// Gets a value indicating whether the property has changed. /// public bool OpacityChanged { get; private set; } public IVisual LayerRoot { get; set; } /// public IReadOnlyList Children => _children ?? EmptyChildren; /// public IReadOnlyList> DrawOperations => _drawOperations ?? EmptyDrawOperations; /// /// Adds a child to the collection. /// /// The child to add. public void AddChild(IVisualNode child) { if (child.Disposed) { throw new ObjectDisposedException("Visual node for {node.Visual}"); } EnsureChildrenCreated(); _children.Add(child); } /// /// Adds an operation to the collection. /// /// The operation to add. public void AddDrawOperation(IRef operation) { EnsureDrawOperationsCreated(); _drawOperations.Add(operation.Clone()); } /// /// Removes a child from the collection. /// /// The child to remove. public void RemoveChild(IVisualNode child) { EnsureChildrenCreated(); _children.Remove(child); } /// /// Replaces a child in the collection. /// /// The child to be replaced. /// The child to add. public void ReplaceChild(int index, IVisualNode node) { if (node.Disposed) { throw new ObjectDisposedException("Visual node for {node.Visual}"); } EnsureChildrenCreated(); _children[index] = node; } /// /// Replaces an item in the collection. /// /// The opeation to be replaced. /// The operation to add. public void ReplaceDrawOperation(int index, IRef operation) { EnsureDrawOperationsCreated(); var old = _drawOperations[index]; _drawOperations[index] = operation.Clone(); old.Dispose(); } /// /// Removes items in the collection from the specified index /// to the end. /// /// The index of the first child to be removed. public void TrimChildren(int first) { if (first < _children?.Count) { EnsureChildrenCreated(); for (int i = first; i < _children.Count - first; i++) { _children[i].Dispose(); } _children.RemoveRange(first, _children.Count - first); } } /// /// Removes items in the collection from the specified index /// to the end. /// /// The index of the first operation to be removed. public void TrimDrawOperations(int first) { if (first < _drawOperations?.Count) { EnsureDrawOperationsCreated(); for (int i = first; i < _drawOperations.Count; i++) { _drawOperations[i].Dispose(); } _drawOperations.RemoveRange(first, _drawOperations.Count - first); } } /// /// Makes a copy of the node /// /// The new parent node. /// A cloned node. public VisualNode Clone(IVisualNode parent) { return new VisualNode(Visual, parent) { Transform = Transform, ClipBounds = ClipBounds, ClipToBounds = ClipToBounds, GeometryClip = GeometryClip, _opacity = Opacity, OpacityMask = OpacityMask, _drawOperations = _drawOperations, _drawOperationsRefCounter = _drawOperationsRefCounter?.Clone(), _drawOperationsCloned = true, LayerRoot= LayerRoot, }; } /// public bool HitTest(Point p) { foreach (var operation in DrawOperations) { if (operation.Item.HitTest(p) == true) { return true; } } return false; } /// public void BeginRender(IDrawingContextImpl context, bool skipOpacity) { transformRestore = context.Transform; if (ClipToBounds) { context.Transform = Matrix.Identity; context.PushClip(ClipBounds); } context.Transform = Transform; if (Opacity != 1 && !skipOpacity) { context.PushOpacity(Opacity); } if (GeometryClip != null) { context.PushGeometryClip(GeometryClip); } if (OpacityMask != null) { context.PushOpacityMask(OpacityMask, ClipBounds); } } /// public void EndRender(IDrawingContextImpl context, bool skipOpacity) { if (OpacityMask != null) { context.PopOpacityMask(); } if (GeometryClip != null) { context.PopGeometryClip(); } if (Opacity != 1 && !skipOpacity) { context.PopOpacity(); } if (ClipToBounds) { context.Transform = Matrix.Identity; context.PopClip(); } context.Transform = transformRestore; } private Rect CalculateBounds() { var result = new Rect(); foreach (var operation in DrawOperations) { result = result.Union(operation.Item.Bounds); } _bounds = result; return result; } private void EnsureChildrenCreated() { if (_children == null) { _children = new List(); } } private void EnsureDrawOperationsCreated() { if (_drawOperations == null) { _drawOperations = new List>(); _drawOperationsRefCounter = RefCountable.Create(Disposable.Create(DisposeDrawOperations)); _drawOperationsCloned = false; } else if (_drawOperationsCloned) { _drawOperations = new List>(_drawOperations.Select(op => op.Clone())); _drawOperationsRefCounter.Dispose(); _drawOperationsRefCounter = RefCountable.Create(Disposable.Create(DisposeDrawOperations)); _drawOperationsCloned = false; } } public bool Disposed { get; } public void Dispose() { _drawOperationsRefCounter?.Dispose(); } private void DisposeDrawOperations() { foreach (var operation in DrawOperations) { operation.Dispose(); } } } }