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; } } }