|
|
@@ -1,6 +1,10 @@
|
|
|
-using Avalonia.Collections;
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using Avalonia.Media.Imaging;
|
|
|
using Avalonia.Metadata;
|
|
|
using Avalonia.Platform;
|
|
|
+using Avalonia.Rendering.SceneGraph;
|
|
|
+using Avalonia.Utilities;
|
|
|
|
|
|
namespace Avalonia.Media
|
|
|
{
|
|
|
@@ -18,6 +22,14 @@ namespace Avalonia.Media
|
|
|
public static readonly StyledProperty<IBrush> OpacityMaskProperty =
|
|
|
AvaloniaProperty.Register<DrawingGroup, IBrush>(nameof(OpacityMask));
|
|
|
|
|
|
+ public static readonly DirectProperty<DrawingGroup, DrawingCollection> ChildrenProperty =
|
|
|
+ AvaloniaProperty.RegisterDirect<DrawingGroup, DrawingCollection>(
|
|
|
+ nameof(Children),
|
|
|
+ o => o.Children,
|
|
|
+ (o, v) => o.Children = v);
|
|
|
+
|
|
|
+ private DrawingCollection _children = new DrawingCollection();
|
|
|
+
|
|
|
public double Opacity
|
|
|
{
|
|
|
get => GetValue(OpacityProperty);
|
|
|
@@ -42,8 +54,23 @@ namespace Avalonia.Media
|
|
|
set => SetValue(OpacityMaskProperty, value);
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the collection that contains the child geometries.
|
|
|
+ /// </summary>
|
|
|
[Content]
|
|
|
- public AvaloniaList<Drawing> Children { get; } = new AvaloniaList<Drawing>();
|
|
|
+ public DrawingCollection Children
|
|
|
+ {
|
|
|
+ get => _children;
|
|
|
+ set
|
|
|
+ {
|
|
|
+ SetAndRaise(ChildrenProperty, ref _children, value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public DrawingContext Open()
|
|
|
+ {
|
|
|
+ return new DrawingContext(new DrawingGroupDrawingContext(this));
|
|
|
+ }
|
|
|
|
|
|
public override void Draw(DrawingContext context)
|
|
|
{
|
|
|
@@ -75,5 +102,394 @@ namespace Avalonia.Media
|
|
|
|
|
|
return rect;
|
|
|
}
|
|
|
+
|
|
|
+ private class DrawingGroupDrawingContext : IDrawingContextImpl
|
|
|
+ {
|
|
|
+ private readonly DrawingGroup _drawingGroup;
|
|
|
+ private readonly IPlatformRenderInterface _platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
|
|
|
+
|
|
|
+ private Matrix _transform;
|
|
|
+
|
|
|
+ private bool _disposed;
|
|
|
+
|
|
|
+ // Root drawing created by this DrawingContext.
|
|
|
+ //
|
|
|
+ // If there is only a single child of the root DrawingGroup, _rootDrawing
|
|
|
+ // will reference the single child, and the root _currentDrawingGroup
|
|
|
+ // value will be null. Otherwise, _rootDrawing will reference the
|
|
|
+ // root DrawingGroup, and be the same value as the root _currentDrawingGroup.
|
|
|
+ //
|
|
|
+ // Either way, _rootDrawing always references the root drawing.
|
|
|
+ protected Drawing? _rootDrawing;
|
|
|
+
|
|
|
+ // Current DrawingGroup that new children are added to
|
|
|
+ protected DrawingGroup? _currentDrawingGroup;
|
|
|
+
|
|
|
+ // Previous values of _currentDrawingGroup
|
|
|
+ private Stack<DrawingGroup?>? _previousDrawingGroupStack;
|
|
|
+
|
|
|
+ public DrawingGroupDrawingContext(DrawingGroup drawingGroup)
|
|
|
+ {
|
|
|
+ _drawingGroup = drawingGroup;
|
|
|
+ }
|
|
|
+
|
|
|
+ public Matrix Transform
|
|
|
+ {
|
|
|
+ get => _transform;
|
|
|
+ set
|
|
|
+ {
|
|
|
+ _transform = value;
|
|
|
+ PushTransform(new MatrixTransform(value));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void DrawEllipse(IBrush? brush, IPen? pen, Rect rect)
|
|
|
+ {
|
|
|
+ if ((brush == null) && (pen == null))
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Instantiate the geometry
|
|
|
+ var geometry = _platformRenderInterface.CreateEllipseGeometry(rect);
|
|
|
+
|
|
|
+ // Add Drawing to the Drawing graph
|
|
|
+ AddNewGeometryDrawing(brush, pen, new PlatformGeometry(geometry));
|
|
|
+ }
|
|
|
+
|
|
|
+ public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry)
|
|
|
+ {
|
|
|
+ if (((brush == null) && (pen == null)) || (geometry == null))
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ AddNewGeometryDrawing(brush, pen, new PlatformGeometry(geometry));
|
|
|
+ }
|
|
|
+
|
|
|
+ public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
|
|
|
+ {
|
|
|
+ if (foreground == null || glyphRun == null)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Add a GlyphRunDrawing to the Drawing graph
|
|
|
+ GlyphRunDrawing glyphRunDrawing = new GlyphRunDrawing
|
|
|
+ {
|
|
|
+ Foreground = foreground,
|
|
|
+ GlyphRun = glyphRun,
|
|
|
+ };
|
|
|
+
|
|
|
+ // Add Drawing to the Drawing graph
|
|
|
+ AddDrawing(glyphRunDrawing);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void DrawLine(IPen pen, Point p1, Point p2)
|
|
|
+ {
|
|
|
+ if (pen == null)
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Instantiate the geometry
|
|
|
+ var geometry = _platformRenderInterface.CreateLineGeometry(p1, p2);
|
|
|
+
|
|
|
+ // Add Drawing to the Drawing graph
|
|
|
+ AddNewGeometryDrawing(null, pen, new PlatformGeometry(geometry));
|
|
|
+ }
|
|
|
+
|
|
|
+ public void DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rect, BoxShadows boxShadows = default)
|
|
|
+ {
|
|
|
+ if ((brush == null) && (pen == null))
|
|
|
+ {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Instantiate the geometry
|
|
|
+ var geometry = _platformRenderInterface.CreateRectangleGeometry(rect.Rect);
|
|
|
+
|
|
|
+ // Add Drawing to the Drawing graph
|
|
|
+ AddNewGeometryDrawing(brush, pen, new PlatformGeometry(geometry));
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Clear(Color color)
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ public IDrawingContextLayerImpl CreateLayer(Size size)
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Custom(ICustomDrawOperation custom)
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void PopBitmapBlendMode()
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void PopClip()
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void PopGeometryClip()
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void PopOpacity()
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void PopOpacityMask()
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void PushBitmapBlendMode(BitmapBlendingMode blendingMode)
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void PushClip(Rect clip)
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void PushClip(RoundedRect clip)
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void PushGeometryClip(IGeometryImpl clip)
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void PushOpacity(double opacity)
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void PushOpacityMask(IBrush mask, Rect bounds)
|
|
|
+ {
|
|
|
+ throw new NotImplementedException();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Dispose()
|
|
|
+ {
|
|
|
+ // Dispose may be called multiple times without throwing
|
|
|
+ // an exception.
|
|
|
+ if (!_disposed)
|
|
|
+ {
|
|
|
+ // Match any outstanding Push calls with a Pop
|
|
|
+ if (_previousDrawingGroupStack != null)
|
|
|
+ {
|
|
|
+ int stackCount = _previousDrawingGroupStack.Count;
|
|
|
+ for (int i = 0; i < stackCount; i++)
|
|
|
+ {
|
|
|
+ Pop();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Call CloseCore with the root DrawingGroup's children
|
|
|
+ DrawingCollection rootChildren;
|
|
|
+
|
|
|
+ if (_currentDrawingGroup != null)
|
|
|
+ {
|
|
|
+ // If we created a root DrawingGroup because multiple elements
|
|
|
+ // exist at the root level, provide it's Children collection
|
|
|
+ // directly.
|
|
|
+ rootChildren = _currentDrawingGroup.Children;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Create a new DrawingCollection if we didn't create a
|
|
|
+ // root DrawingGroup because the root level only contained
|
|
|
+ // a single child.
|
|
|
+ //
|
|
|
+ // This collection is needed by DrawingGroup.Open because
|
|
|
+ // Open always replaces it's Children collection. It isn't
|
|
|
+ // strictly needed for Append, but always using a collection
|
|
|
+ // simplifies the TransactionalAppend implementation (i.e.,
|
|
|
+ // a seperate implemention isn't needed for a single element)
|
|
|
+ rootChildren = new DrawingCollection();
|
|
|
+
|
|
|
+ //
|
|
|
+ // We may need to opt-out of inheritance through the new Freezable.
|
|
|
+ // This is controlled by this.CanBeInheritanceContext.
|
|
|
+ //
|
|
|
+ if (_rootDrawing != null)
|
|
|
+ {
|
|
|
+ rootChildren.Add(_rootDrawing);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Inform our derived classes that Close was called
|
|
|
+ _drawingGroup.Children = rootChildren;
|
|
|
+
|
|
|
+ _disposed = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Pop
|
|
|
+ /// </summary>
|
|
|
+ private void Pop()
|
|
|
+ {
|
|
|
+ // Verify that Pop hasn't been called too many times
|
|
|
+ if ((_previousDrawingGroupStack == null) || (_previousDrawingGroupStack.Count == 0))
|
|
|
+ {
|
|
|
+ throw new InvalidOperationException("DrawingGroupStack count missmatch.");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Restore the previous value of the current drawing group
|
|
|
+ _currentDrawingGroup = _previousDrawingGroupStack.Pop();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// PushTransform -
|
|
|
+ /// Push a Transform which will apply to all drawing operations until the corresponding
|
|
|
+ /// Pop.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="transform"> The Transform to push. </param>
|
|
|
+ private void PushTransform(Transform transform)
|
|
|
+ {
|
|
|
+ // Instantiate a new drawing group and set it as the _currentDrawingGroup
|
|
|
+ var drawingGroup = PushNewDrawingGroup();
|
|
|
+
|
|
|
+ // Set the transform on the new DrawingGroup
|
|
|
+ drawingGroup.Transform = transform;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Creates a new DrawingGroup for a Push* call by setting the
|
|
|
+ /// _currentDrawingGroup to a newly instantiated DrawingGroup,
|
|
|
+ /// and saving the previous _currentDrawingGroup value on the
|
|
|
+ /// _previousDrawingGroupStack.
|
|
|
+ /// </summary>
|
|
|
+ private DrawingGroup PushNewDrawingGroup()
|
|
|
+ {
|
|
|
+ // Instantiate a new drawing group
|
|
|
+ DrawingGroup drawingGroup = new DrawingGroup();
|
|
|
+
|
|
|
+ // Add it to the drawing graph, like any other Drawing
|
|
|
+ AddDrawing(drawingGroup);
|
|
|
+
|
|
|
+ // Lazily allocate the stack when it is needed because many uses
|
|
|
+ // of DrawingDrawingContext will have a depth of one.
|
|
|
+ if (null == _previousDrawingGroupStack)
|
|
|
+ {
|
|
|
+ _previousDrawingGroupStack = new Stack<DrawingGroup?>(2);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Save the previous _currentDrawingGroup value.
|
|
|
+ //
|
|
|
+ // If this is the first call, the value of _currentDrawingGroup
|
|
|
+ // will be null because AddDrawing doesn't create a _currentDrawingGroup
|
|
|
+ // for the first drawing. Having null on the stack is valid, and simply
|
|
|
+ // denotes that this new DrawingGroup is the first child in the root
|
|
|
+ // DrawingGroup. It is also possible for the first value on the stack
|
|
|
+ // to be non-null, which means that the root DrawingGroup has other
|
|
|
+ // children.
|
|
|
+ _previousDrawingGroupStack.Push(_currentDrawingGroup);
|
|
|
+
|
|
|
+ // Set this drawing group as the current one so that subsequent drawing's
|
|
|
+ // are added as it's children until Pop is called.
|
|
|
+ _currentDrawingGroup = drawingGroup;
|
|
|
+
|
|
|
+ return drawingGroup;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Contains the functionality common to GeometryDrawing operations of
|
|
|
+ /// instantiating the GeometryDrawing, setting it's Freezable state,
|
|
|
+ /// and Adding it to the Drawing Graph.
|
|
|
+ /// </summary>
|
|
|
+ private void AddNewGeometryDrawing(IBrush? brush, IPen? pen, Geometry? geometry)
|
|
|
+ {
|
|
|
+ if (geometry == null)
|
|
|
+ {
|
|
|
+ throw new ArgumentNullException(nameof(geometry));
|
|
|
+ }
|
|
|
+
|
|
|
+ // Instantiate the GeometryDrawing
|
|
|
+ GeometryDrawing geometryDrawing = new GeometryDrawing
|
|
|
+ {
|
|
|
+ // We may need to opt-out of inheritance through the new Freezable.
|
|
|
+ // This is controlled by this.CanBeInheritanceContext.
|
|
|
+ Brush = brush,
|
|
|
+ Pen = pen,
|
|
|
+ Geometry = geometry
|
|
|
+ };
|
|
|
+
|
|
|
+ // Add it to the drawing graph
|
|
|
+ AddDrawing(geometryDrawing);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Adds a new Drawing to the DrawingGraph.
|
|
|
+ ///
|
|
|
+ /// This method avoids creating a DrawingGroup for the common case
|
|
|
+ /// where only a single child exists in the root DrawingGroup.
|
|
|
+ /// </summary>
|
|
|
+ private void AddDrawing(Drawing newDrawing)
|
|
|
+ {
|
|
|
+ if (newDrawing == null)
|
|
|
+ {
|
|
|
+ throw new ArgumentNullException(nameof(newDrawing));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (_rootDrawing == null)
|
|
|
+ {
|
|
|
+ // When a DrawingGroup is set, it should be made the root if
|
|
|
+ // a root drawing didnt exist.
|
|
|
+ Contract.Requires<NotSupportedException>(_currentDrawingGroup == null);
|
|
|
+
|
|
|
+ // If this is the first Drawing being added, avoid creating a DrawingGroup
|
|
|
+ // and set this drawing as the root drawing. This optimizes the common
|
|
|
+ // case where only a single child exists in the root DrawingGroup.
|
|
|
+ _rootDrawing = newDrawing;
|
|
|
+ }
|
|
|
+ else if (_currentDrawingGroup == null)
|
|
|
+ {
|
|
|
+ // When the second drawing is added at the root level, set a
|
|
|
+ // DrawingGroup as the root and add both drawings to it.
|
|
|
+
|
|
|
+ // Instantiate the DrawingGroup
|
|
|
+ _currentDrawingGroup = new DrawingGroup();
|
|
|
+
|
|
|
+ // Add both Children
|
|
|
+ _currentDrawingGroup.Children.Add(_rootDrawing);
|
|
|
+ _currentDrawingGroup.Children.Add(newDrawing);
|
|
|
+
|
|
|
+ // Set the new DrawingGroup as the current
|
|
|
+ _rootDrawing = _currentDrawingGroup;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // If there already is a current drawing group, then simply add
|
|
|
+ // the new drawing too it.
|
|
|
+ _currentDrawingGroup.Children.Add(newDrawing);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|