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.
 
 
 

477 lines
14 KiB

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
{
/// <summary>
/// A drawing context which builds a scene graph.
/// </summary>
internal class DeferredDrawingContextImpl : IDrawingContextImpl, IDrawingContextWithAcrylicLikeSupport
{
private readonly ISceneBuilder _sceneBuilder;
private VisualNode _node;
private int _childIndex;
private int _drawOperationindex;
/// <summary>
/// Initializes a new instance of the <see cref="DeferredDrawingContextImpl"/> class.
/// </summary>
/// <param name="sceneBuilder">
/// A scene builder used for constructing child scenes for visual brushes.
/// </param>
/// <param name="layers">The scene layers.</param>
public DeferredDrawingContextImpl(ISceneBuilder sceneBuilder, SceneLayers layers)
{
_sceneBuilder = sceneBuilder;
Layers = layers;
}
/// <inheritdoc/>
public Matrix Transform { get; set; } = Matrix.Identity;
/// <summary>
/// Gets the layers in the scene being built.
/// </summary>
public SceneLayers Layers { get; }
/// <summary>
/// Informs the drawing context of the visual node that is about to be rendered.
/// </summary>
/// <param name="node">The visual node.</param>
/// <returns>
/// An object which when disposed will commit the changes to visual node.
/// </returns>
public UpdateState BeginUpdate(VisualNode node)
{
Contract.Requires<ArgumentNullException>(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;
}
/// <inheritdoc/>
public void Clear(Color color)
{
// Cannot clear a deferred scene.
}
/// <inheritdoc/>
public void Dispose()
{
// Nothing to do here since we allocate no unmanaged resources.
}
/// <summary>
/// Removes any remaining drawing operations from the visual node.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public void TrimChildren()
{
_node.TrimChildren(_childIndex);
}
/// <inheritdoc/>
public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
{
var next = NextDrawAs<GeometryNode>();
if (next == null || !next.Item.Equals(Transform, brush, pen, geometry))
{
Add(new GeometryNode(Transform, brush, pen, geometry, CreateChildScene(brush)));
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
{
var next = NextDrawAs<ImageNode>();
if (next == null || !next.Item.Equals(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode))
{
Add(new ImageNode(Transform, source, opacity, sourceRect, destRect, bitmapInterpolationMode));
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void DrawBitmap(IRef<IBitmapImpl> 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();
}
/// <inheritdoc/>
public void DrawLine(IPen pen, Point p1, Point p2)
{
var next = NextDrawAs<LineNode>();
if (next == null || !next.Item.Equals(Transform, pen, p1, p2))
{
Add(new LineNode(Transform, pen, p1, p2, CreateChildScene(pen.Brush)));
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void DrawRectangle(IBrush brush, IPen pen, RoundedRect rect,
BoxShadows boxShadows = default)
{
var next = NextDrawAs<RectangleNode>();
if (next == null || !next.Item.Equals(Transform, brush, pen, rect, boxShadows))
{
Add(new RectangleNode(Transform, brush, pen, rect, boxShadows, CreateChildScene(brush)));
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void DrawRectangle(IExperimentalAcrylicMaterial material, RoundedRect rect)
{
var next = NextDrawAs<ExperimentalAcrylicNode>();
if (next == null || !next.Item.Equals(Transform, material, rect))
{
Add(new ExperimentalAcrylicNode(Transform, material, rect));
}
else
{
++_drawOperationindex;
}
}
public void Custom(ICustomDrawOperation custom)
{
var next = NextDrawAs<CustomDrawOperation>();
if (next == null || !next.Item.Equals(Transform, custom))
Add(new CustomDrawOperation(custom, Transform));
else
++_drawOperationindex;
}
/// <inheritdoc/>
public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text)
{
var next = NextDrawAs<TextNode>();
if (next == null || !next.Item.Equals(Transform, foreground, origin, text))
{
Add(new TextNode(Transform, foreground, origin, text, CreateChildScene(foreground)));
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{
var next = NextDrawAs<GlyphRunNode>();
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");
}
/// <inheritdoc/>
public void PopClip()
{
var next = NextDrawAs<ClipNode>();
if (next == null || !next.Item.Equals(null))
{
Add(new ClipNode());
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void PopGeometryClip()
{
var next = NextDrawAs<GeometryClipNode>();
if (next == null || !next.Item.Equals(null))
{
Add((new GeometryClipNode()));
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void PopBitmapBlendMode()
{
var next = NextDrawAs<BitmapBlendModeNode>();
if (next == null || !next.Item.Equals(null))
{
Add(new BitmapBlendModeNode());
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void PopOpacity()
{
var next = NextDrawAs<OpacityNode>();
if (next == null || !next.Item.Equals(null))
{
Add(new OpacityNode());
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void PopOpacityMask()
{
var next = NextDrawAs<OpacityMaskNode>();
if (next == null || !next.Item.Equals(null, null))
{
Add(new OpacityMaskNode());
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void PushClip(Rect clip)
{
var next = NextDrawAs<ClipNode>();
if (next == null || !next.Item.Equals(Transform, clip))
{
Add(new ClipNode(Transform, clip));
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc />
public void PushClip(RoundedRect clip)
{
var next = NextDrawAs<ClipNode>();
if (next == null || !next.Item.Equals(Transform, clip))
{
Add(new ClipNode(Transform, clip));
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void PushGeometryClip(IGeometryImpl clip)
{
var next = NextDrawAs<GeometryClipNode>();
if (next == null || !next.Item.Equals(Transform, clip))
{
Add(new GeometryClipNode(Transform, clip));
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void PushOpacity(double opacity)
{
var next = NextDrawAs<OpacityNode>();
if (next == null || !next.Item.Equals(opacity))
{
Add(new OpacityNode(opacity));
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void PushOpacityMask(IBrush mask, Rect bounds)
{
var next = NextDrawAs<OpacityMaskNode>();
if (next == null || !next.Item.Equals(mask, bounds))
{
Add(new OpacityMaskNode(mask, bounds, CreateChildScene(mask)));
}
else
{
++_drawOperationindex;
}
}
/// <inheritdoc/>
public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
{
var next = NextDrawAs<BitmapBlendModeNode>();
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>(T node) where T : class, IDrawOperation
{
using (var refCounted = RefCountable.Create(node))
{
Add(refCounted);
}
}
private void Add(IRef<IDrawOperation> node)
{
if (_drawOperationindex < _node.DrawOperations.Count)
{
_node.ReplaceDrawOperation(_drawOperationindex, node);
}
else
{
_node.AddDrawOperation(node);
}
++_drawOperationindex;
}
private IRef<T> NextDrawAs<T>() where T : class, IDrawOperation
{
return _drawOperationindex < _node.DrawOperations.Count ? _node.DrawOperations[_drawOperationindex] as IRef<T> : null;
}
private IDictionary<IVisual, Scene> 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<IVisual, Scene> { { visualBrush.Visual, scene } };
}
}
return null;
}
}
}