using System;
using System.Collections.Generic;
using System.Numerics;
using Avalonia.Media;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using Avalonia.Rendering.Composition.Drawing;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Utilities;
using Avalonia.VisualTree;
namespace Avalonia.Rendering.Composition;
///
/// An IDrawingContextImpl implementation that builds
///
internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextWithAcrylicLikeSupport
{
private CompositionDrawListBuilder _builder = new();
private int _drawOperationIndex;
///
public Matrix Transform { get; set; } = Matrix.Identity;
///
public void Clear(Color color)
{
// Cannot clear a deferred scene.
}
///
public void Dispose()
{
// Nothing to do here since we allocate no unmanaged resources.
}
public void BeginUpdate(CompositionDrawList? list)
{
_builder.Reset(list);
_drawOperationIndex = 0;
}
public CompositionDrawList EndUpdate()
{
_builder.TrimTo(_drawOperationIndex);
return _builder.DrawOperations!;
}
///
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;
}
}
private void Add(T node) where T : class, IDrawOperation
{
if (_drawOperationIndex < _builder.Count)
{
_builder.ReplaceDrawOperation(_drawOperationIndex, node);
}
else
{
_builder.AddDrawOperation(node);
}
++_drawOperationIndex;
}
private IRef? NextDrawAs() where T : class, IDrawOperation
{
return _drawOperationIndex < _builder.Count
? _builder.DrawOperations![_drawOperationIndex] as IRef
: null;
}
private IDisposable? CreateChildScene(IBrush? brush)
{
if (brush is VisualBrush visualBrush)
{
var visual = visualBrush.Visual;
if (visual != null)
{
// TODO: This is a temporary solution to make visual brush to work like it does with DeferredRenderer
// We should directly reference the corresponding CompositionVisual (which should
// be attached to the same composition target) like UWP does.
// Render-able visuals shouldn't be dangling unattached
(visual as IVisualBrushInitialize)?.EnsureInitialized();
var recorder = new CompositionDrawingContext();
recorder.BeginUpdate(null);
ImmediateRenderer.Render(visual, new DrawingContext(recorder));
var drawList = recorder.EndUpdate();
drawList.Size = visual.Bounds.Size;
return drawList;
}
}
return null;
}
}