A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

184 lines
5.7 KiB

// 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 Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
{
/// <summary>
/// Represents a scene graph used by the <see cref="DeferredRenderer"/>.
/// </summary>
public class Scene
{
private Dictionary<IVisual, IVisualNode> _index;
/// <summary>
/// Initializes a new instance of the <see cref="Scene"/> class.
/// </summary>
/// <param name="rootVisual">The root visual to draw.</param>
public Scene(IVisual rootVisual)
: this(
new VisualNode(rootVisual, null),
new Dictionary<IVisual, IVisualNode>(),
new SceneLayers(rootVisual),
0)
{
_index.Add(rootVisual, Root);
}
private Scene(VisualNode root, Dictionary<IVisual, IVisualNode> index, SceneLayers layers, int id)
{
Contract.Requires<ArgumentNullException>(root != null);
var renderRoot = root.Visual as IRenderRoot;
_index = index;
Root = root;
Layers = layers;
Id = id;
root.LayerRoot = root.Visual;
}
/// <summary>
/// Gets an ID identifying the scene. This is incremented each time the scene is cloned.
/// </summary>
public int Id { get; }
/// <summary>
/// Gets the layers for the scene.
/// </summary>
public SceneLayers Layers { get; }
/// <summary>
/// Gets the root node of the scene graph.
/// </summary>
public IVisualNode Root { get; }
/// <summary>
/// Gets or sets the size of the scene in device independent pixels.
/// </summary>
public Size Size { get; set; }
/// <summary>
/// Gets or sets the scene scaling.
/// </summary>
public double Scaling { get; set; } = 1;
/// <summary>
/// Adds a node to the scene index.
/// </summary>
/// <param name="node">The node.</param>
public void Add(IVisualNode node)
{
Contract.Requires<ArgumentNullException>(node != null);
_index.Add(node.Visual, node);
}
/// <summary>
/// Clones the scene.
/// </summary>
/// <returns>The cloned scene.</returns>
public Scene Clone()
{
var index = new Dictionary<IVisual, IVisualNode>();
var root = Clone((VisualNode)Root, null, index);
var result = new Scene(root, index, Layers.Clone(), Id + 1)
{
Size = Size,
Scaling = Scaling,
};
return result;
}
/// <summary>
/// Tries to find a node in the scene graph representing the specified visual.
/// </summary>
/// <param name="visual">The visual.</param>
/// <returns>
/// The node representing the visual or null if it could not be found.
/// </returns>
public IVisualNode FindNode(IVisual visual)
{
IVisualNode node;
_index.TryGetValue(visual, out node);
return node;
}
/// <summary>
/// Gets the visuals at a point in the scene.
/// </summary>
/// <param name="p">The point.</param>
/// <param name="filter">A filter. May be null.</param>
/// <returns>The visuals at the specified point.</returns>
public IEnumerable<IVisual> HitTest(Point p, Func<IVisual, bool> filter)
{
return HitTest(Root, p, null, filter);
}
/// <summary>
/// Removes a node from the scene index.
/// </summary>
/// <param name="node">The node.</param>
public void Remove(IVisualNode node)
{
Contract.Requires<ArgumentNullException>(node != null);
_index.Remove(node.Visual);
}
private VisualNode Clone(VisualNode source, IVisualNode parent, Dictionary<IVisual, IVisualNode> index)
{
var result = source.Clone(parent);
index.Add(result.Visual, result);
foreach (var child in source.Children)
{
result.AddChild(Clone((VisualNode)child, result, index));
}
return result;
}
private IEnumerable<IVisual> HitTest(IVisualNode node, Point p, Rect? clip, Func<IVisual, bool> filter)
{
if (filter?.Invoke(node.Visual) != false)
{
var clipped = false;
if (node.ClipToBounds)
{
clip = clip == null ? node.ClipBounds : clip.Value.Intersect(node.ClipBounds);
clipped = !clip.Value.Contains(p);
}
if (node.GeometryClip != null)
{
var controlPoint = Root.Visual.TranslatePoint(p, node.Visual);
clipped = !node.GeometryClip.FillContains(controlPoint);
}
if (!clipped)
{
for (var i = node.Children.Count - 1; i >= 0; --i)
{
foreach (var h in HitTest(node.Children[i], p, clip, filter))
{
yield return h;
}
}
if (node.HitTest(p))
{
yield return node.Visual;
}
}
}
}
}
}