// 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();
}
}
}
}