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.
 
 
 

228 lines
6.0 KiB

using System;
using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Threading;
using Avalonia.Utilities;
namespace Avalonia.Rendering.Composition.Drawing.Nodes;
enum RenderDataPopNodeType
{
Transform,
Clip,
GeometryClip,
Opacity,
OpacityMask
}
interface IRenderDataServerResourcesCollector
{
void AddRenderDataServerResource(object? obj);
}
interface IRenderDataItemWithServerResources : IRenderDataItem
{
void Collect(IRenderDataServerResourcesCollector collector);
}
struct RenderDataNodeRenderContext : IDisposable
{
private Stack<Matrix>? _stack;
private static readonly ThreadSafeObjectPool<Stack<Matrix>> s_matrixStackPool = new();
public RenderDataNodeRenderContext(IDrawingContextImpl context)
{
Context = context;
}
public IDrawingContextImpl Context { get; }
public Stack<Matrix> MatrixStack
{
get => _stack ??= s_matrixStackPool.Get();
}
public void Dispose()
{
if (_stack != null)
{
_stack.Clear();
s_matrixStackPool.ReturnAndSetNull(ref _stack);
}
}
}
interface IRenderDataItem
{
/// <summary>
/// Renders the node to a drawing context.
/// </summary>
/// <param name="context">The drawing context.</param>
void Invoke(ref RenderDataNodeRenderContext context);
/// <summary>
/// Gets the bounds of the visible content in the node in global coordinates.
/// </summary>
Rect? Bounds { get; }
/// <summary>
/// Hit test the geometry in this node.
/// </summary>
/// <param name="p">The point in global coordinates.</param>
/// <returns>True if the point hits the node's geometry; otherwise false.</returns>
/// <remarks>
/// This method does not recurse to childs, if you want
/// to hit test children they must be hit tested manually.
/// </remarks>
bool HitTest(Point p);
}
class RenderDataCustomNode : IRenderDataItem
{
public ICustomDrawOperation? Operation { get; set; }
public bool HitTest(Point p) => Operation?.HitTest(p) ?? false;
public void Invoke(ref RenderDataNodeRenderContext context) => Operation?.Render(new(context.Context, false));
public Rect? Bounds => Operation?.Bounds;
}
abstract class RenderDataPushNode : IRenderDataItem, IDisposable
{
public PooledInlineList<IRenderDataItem> Children;
public abstract void Push(ref RenderDataNodeRenderContext context);
public abstract void Pop(ref RenderDataNodeRenderContext context);
public void Invoke(ref RenderDataNodeRenderContext context)
{
if (Children.Count == 0)
return;
Push(ref context);
foreach (var ch in Children)
ch.Invoke(ref context);
Pop(ref context);
}
public virtual Rect? Bounds
{
get
{
if (Children.Count == 0)
return null;
Rect? union = null;
foreach (var i in Children)
union = Rect.Union(union, i.Bounds);
return union;
}
}
public virtual bool HitTest(Point p)
{
if (Children.Count == 0)
return false;
foreach(var ch in Children)
if (ch.HitTest(p))
return true;
return false;
}
public void Dispose()
{
if (Children.Count > 0)
{
foreach(var ch in Children)
if (ch is RenderDataPushNode node)
node.Dispose();
Children.Dispose();
}
}
}
class RenderDataClipNode : RenderDataPushNode
{
public RoundedRect Rect { get; set; }
public override void Push(ref RenderDataNodeRenderContext context) =>
context.Context.PushClip(Rect);
public override void Pop(ref RenderDataNodeRenderContext context) =>
context.Context.PopClip();
public override bool HitTest(Point p)
{
if (!Rect.Rect.Contains(p))
return false;
return base.HitTest(p);
}
}
class RenderDataGeometryClipNode : RenderDataPushNode
{
public IGeometryImpl? Geometry { get; set; }
public bool Contains(Point p) => Geometry?.FillContains(p) ?? false;
public override void Push(ref RenderDataNodeRenderContext context)
{
if (Geometry != null)
context.Context.PushGeometryClip(Geometry);
}
public override void Pop(ref RenderDataNodeRenderContext context)
{
if (Geometry != null)
context.Context.PopGeometryClip();
}
public override bool HitTest(Point p)
{
if (Geometry != null && !Geometry.FillContains(p))
return false;
return base.HitTest(p);
}
}
class RenderDataOpacityNode : RenderDataPushNode
{
public double Opacity { get; set; }
public override void Push(ref RenderDataNodeRenderContext context)
{
if (Opacity != 1)
context.Context.PushOpacity(Opacity, null);
}
public override void Pop(ref RenderDataNodeRenderContext context)
{
if (Opacity != 1)
context.Context.PopOpacity();
}
}
abstract class RenderDataBrushAndPenNode : IRenderDataItemWithServerResources
{
public IBrush? ServerBrush { get; set; }
public IPen? ServerPen { get; set; }
public IPen? ClientPen { get; set; }
public void Collect(IRenderDataServerResourcesCollector collector)
{
collector.AddRenderDataServerResource(ServerBrush);
collector.AddRenderDataServerResource(ServerPen);
}
public abstract void Invoke(ref RenderDataNodeRenderContext context);
public abstract Rect? Bounds { get; }
public abstract bool HitTest(Point p);
}
class RenderDataRenderOptionsNode : RenderDataPushNode
{
public RenderOptions RenderOptions { get; set; }
public override void Push(ref RenderDataNodeRenderContext context)
{
context.Context.PushRenderOptions(RenderOptions);
}
public override void Pop(ref RenderDataNodeRenderContext context)
{
context.Context.PopRenderOptions();
}
}