using System;
using System.Collections.Generic;
using System.Numerics;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Utilities;
using Avalonia.Media.Imaging;
using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
{
///
/// A drawing context which builds a scene graph.
///
internal class DeferredDrawingContextImpl : IDrawingContextImpl, IDrawingContextWithAcrylicLikeSupport
{
private readonly ISceneBuilder _sceneBuilder;
private VisualNode? _node;
private int _childIndex;
private int _drawOperationindex;
///
/// Initializes a new instance of the class.
///
///
/// A scene builder used for constructing child scenes for visual brushes.
///
/// The scene layers.
public DeferredDrawingContextImpl(ISceneBuilder sceneBuilder, SceneLayers layers)
{
_sceneBuilder = sceneBuilder;
Layers = layers;
}
///
public Matrix Transform { get; set; } = Matrix.Identity;
///
/// Gets the layers in the scene being built.
///
public SceneLayers Layers { get; }
///
/// Informs the drawing context of the visual node that is about to be rendered.
///
/// The visual node.
///
/// An object which when disposed will commit the changes to visual node.
///
public UpdateState BeginUpdate(VisualNode node)
{
_ = node ?? throw new ArgumentNullException(nameof(node));
if (_node != null)
{
if (_childIndex < _node.Children.Count)
{
_node.ReplaceChild(_childIndex, node);
}
else
{
_node.AddChild(node);
}
++_childIndex;
}
var state = new UpdateState(this, _node, _childIndex, _drawOperationindex);
_node = node;
_childIndex = _drawOperationindex = 0;
return state;
}
///
public void Clear(Color color)
{
// Cannot clear a deferred scene.
}
///
public void Dispose()
{
// Nothing to do here since we allocate no unmanaged resources.
}
///
/// Removes any remaining drawing operations from the visual node.
///
///
/// Drawing operations are updated in place, overwriting existing drawing operations if
/// they are different. Once drawing has completed for the current visual node, it is
/// possible that there are stale drawing operations at the end of the list. This method
/// trims these stale drawing operations.
///
public void TrimChildren()
{
_node!.TrimChildren(_childIndex);
}
///
public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry)
{
var next = NextDrawAs();
if (next == null || !next.Item.Equals(Transform, brush, pen, geometry))
{
Add(new GeometryNode(Transform, brush, pen, geometry, CreateChildScene(brush)));
}
else
{
++_drawOperationindex;
}
}
///
public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
{
var next = NextDrawAs();
if (next == null || !next.Item.Equals(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode))
{
Add(new ImageNode(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode));
}
else
{
++_drawOperationindex;
}
}
///
public void DrawBitmap(IRef source, IBrush opacityMask, Rect opacityMaskRect, Rect sourceRect)
{
// This method is currently only used to composite layers so shouldn't be called here.
throw new NotSupportedException();
}
///
public void DrawLine(IPen pen, Point p1, Point p2)
{
var next = NextDrawAs();
if (next == null || !next.Item.Equals(Transform, pen, p1, p2))
{
Add(new LineNode(Transform, pen, p1, p2, CreateChildScene(pen.Brush)));
}
else
{
++_drawOperationindex;
}
}
///
public void DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rect,
BoxShadows boxShadows = default)
{
var next = NextDrawAs();
if (next == null || !next.Item.Equals(Transform, brush, pen, rect, boxShadows))
{
Add(new RectangleNode(Transform, brush, pen, rect, boxShadows, CreateChildScene(brush)));
}
else
{
++_drawOperationindex;
}
}
///
public void DrawRectangle(IExperimentalAcrylicMaterial material, RoundedRect rect)
{
var next = NextDrawAs();
if (next == null || !next.Item.Equals(Transform, material, rect))
{
Add(new ExperimentalAcrylicNode(Transform, material, rect));
}
else
{
++_drawOperationindex;
}
}
public void DrawEllipse(IBrush? brush, IPen? pen, Rect rect)
{
var next = NextDrawAs();
if (next == null || !next.Item.Equals(Transform, brush, pen, rect))
{
Add(new EllipseNode(Transform, brush, pen, rect, CreateChildScene(brush)));
}
else
{
++_drawOperationindex;
}
}
public void Custom(ICustomDrawOperation custom)
{
var next = NextDrawAs();
if (next == null || !next.Item.Equals(Transform, custom))
Add(new CustomDrawOperation(custom, Transform));
else
++_drawOperationindex;
}
///
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{
var next = NextDrawAs();
if (next == null || !next.Item.Equals(Transform, foreground, glyphRun))
{
Add(new GlyphRunNode(Transform, foreground, glyphRun, CreateChildScene(foreground)));
}
else
{
++_drawOperationindex;
}
}
public IDrawingContextLayerImpl CreateLayer(Size size)
{
throw new NotSupportedException("Creating layers on a deferred drawing context not supported");
}
///
public void PopClip()
{
var next = NextDrawAs();
if (next == null || !next.Item.Equals(null))
{
Add(new ClipNode());
}
else
{
++_drawOperationindex;
}
}
///
public void PopGeometryClip()
{
var next = NextDrawAs();
if (next == null || !next.Item.Equals(null))
{
Add(new GeometryClipNode());
}
else
{
++_drawOperationindex;
}
}
///
public void PopBitmapBlendMode()
{
var next = NextDrawAs();
if (next == null || !next.Item.Equals(null))
{
Add(new BitmapBlendModeNode());
}
else
{
++_drawOperationindex;
}
}
///
public void PopOpacity()
{
var next = NextDrawAs();
if (next == null || !next.Item.Equals(null))
{
Add(new OpacityNode());
}
else
{
++_drawOperationindex;
}
}
///
public void PopOpacityMask()
{
var next = NextDrawAs();
if (next == null || !next.Item.Equals(null, null))
{
Add(new OpacityMaskNode());
}
else
{
++_drawOperationindex;
}
}
///
public void PushClip(Rect clip)
{
var next = NextDrawAs();
if (next == null || !next.Item.Equals(Transform, clip))
{
Add(new ClipNode(Transform, clip));
}
else
{
++_drawOperationindex;
}
}
///
public void PushClip(RoundedRect clip)
{
var next = NextDrawAs();
if (next == null || !next.Item.Equals(Transform, clip))
{
Add(new ClipNode(Transform, clip));
}
else
{
++_drawOperationindex;
}
}
///
public void PushGeometryClip(IGeometryImpl? clip)
{
if (clip is null)
return;
var next = NextDrawAs();
if (next == null || !next.Item.Equals(Transform, clip))
{
Add(new GeometryClipNode(Transform, clip));
}
else
{
++_drawOperationindex;
}
}
///
public void PushOpacity(double opacity)
{
var next = NextDrawAs();
if (next == null || !next.Item.Equals(opacity))
{
Add(new OpacityNode(opacity));
}
else
{
++_drawOperationindex;
}
}
///
public void PushOpacityMask(IBrush mask, Rect bounds)
{
var next = NextDrawAs();
if (next == null || !next.Item.Equals(mask, bounds))
{
Add(new OpacityMaskNode(mask, bounds, CreateChildScene(mask)));
}
else
{
++_drawOperationindex;
}
}
///
public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
{
var next = NextDrawAs();
if (next == null || !next.Item.Equals(blendingMode))
{
Add(new BitmapBlendModeNode(blendingMode));
}
else
{
++_drawOperationindex;
}
}
public readonly struct UpdateState : IDisposable
{
public UpdateState(
DeferredDrawingContextImpl owner,
VisualNode? node,
int childIndex,
int drawOperationIndex)
{
Owner = owner;
Node = node;
ChildIndex = childIndex;
DrawOperationIndex = drawOperationIndex;
}
public void Dispose()
{
Owner._node!.TrimDrawOperations(Owner._drawOperationindex);
var dirty = Owner.Layers.GetOrAdd(Owner._node.LayerRoot!).Dirty;
var drawOperations = Owner._node.DrawOperations;
var drawOperationsCount = drawOperations.Count;
for (var i = 0; i < drawOperationsCount; i++)
{
dirty.Add(drawOperations[i].Item.Bounds);
}
Owner._node = Node;
Owner._childIndex = ChildIndex;
Owner._drawOperationindex = DrawOperationIndex;
}
public DeferredDrawingContextImpl Owner { get; }
public VisualNode? Node { get; }
public int ChildIndex { get; }
public int DrawOperationIndex { get; }
}
private void Add(T node) where T : class, IDrawOperation
{
using (var refCounted = RefCountable.Create(node))
{
Add(refCounted);
}
}
private void Add(IRef node)
{
if (_drawOperationindex < _node!.DrawOperations.Count)
{
_node.ReplaceDrawOperation(_drawOperationindex, node);
}
else
{
_node.AddDrawOperation(node);
}
++_drawOperationindex;
}
private IRef? NextDrawAs() where T : class, IDrawOperation
{
return _drawOperationindex < _node!.DrawOperations.Count ? _node.DrawOperations[_drawOperationindex] as IRef : null;
}
private IDisposable? CreateChildScene(IBrush? brush)
{
var visualBrush = brush as VisualBrush;
if (visualBrush != null)
{
var visual = visualBrush.Visual;
if (visual != null)
{
(visual as IVisualBrushInitialize)?.EnsureInitialized();
var scene = new Scene(visual);
_sceneBuilder.UpdateAll(scene);
return scene;
}
}
return null;
}
}
}