@ -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 _d rawingGroup ;
private readonly IPlatformRenderInterface _ platformRenderInterface = AvaloniaLocator . Current . GetRequiredService < IPlatformRenderInterface > ( ) ;
private Matrix _ transform ;
private bool _d isposed ;
// 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 )
{
_d rawingGroup = 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 ( ! _d isposed )
{
// 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
_d rawingGroup . Children = rootChildren ;
_d isposed = 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 ) ;
}
}
}
}
}