// 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.Media;
using Avalonia.Platform;
using Avalonia.Utilities;
using Avalonia.Visuals.Media.Imaging;
using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
{
///
/// A drawing context which builds a scene graph.
///
internal class DeferredDrawingContextImpl : IDrawingContextImpl
{
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)
{
Contract.Requires(node != null);
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, Rect rect, double radiusX = 0, double radiusY = 0)
{
var next = NextDrawAs();
if (next == null || !next.Item.Equals(Transform, brush, pen, rect, radiusX, radiusY))
{
Add(new RectangleNode(Transform, brush, pen, rect, radiusX, radiusY, 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 DrawText(IBrush foreground, Point origin, IFormattedTextImpl text)
{
var next = NextDrawAs();
if (next == null || !next.Item.Equals(Transform, foreground, origin, text))
{
Add(new TextNode(Transform, foreground, origin, text, CreateChildScene(foreground)));
}
else
{
++_drawOperationindex;
}
}
///
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun, Point baselineOrigin)
{
var next = NextDrawAs();
if (next == null || !next.Item.Equals(Transform, foreground, glyphRun))
{
Add(new GlyphRunNode(Transform, foreground, glyphRun, baselineOrigin, CreateChildScene(foreground)));
}
else
{
++_drawOperationindex;
}
}
public IRenderTargetBitmapImpl 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 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(clip))
{
Add(new ClipNode(clip));
}
else
{
++_drawOperationindex;
}
}
///
public void PushGeometryClip(IGeometryImpl clip)
{
var next = NextDrawAs();
if (next == null || !next.Item.Equals(clip))
{
Add(new GeometryClipNode(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 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;
foreach (var operation in Owner._node.DrawOperations)
{
dirty.Add(operation.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(IDrawOperation node)
{
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 IDictionary 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 new Dictionary { { visualBrush.Visual, scene } };
}
}
return null;
}
}
}