csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
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.
434 lines
17 KiB
434 lines
17 KiB
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
|
|
{
|
|
public class DrawingGroup : Drawing
|
|
{
|
|
public static readonly StyledProperty<double> OpacityProperty =
|
|
AvaloniaProperty.Register<DrawingGroup, double>(nameof(Opacity), 1);
|
|
|
|
public static readonly StyledProperty<Transform?> TransformProperty =
|
|
AvaloniaProperty.Register<DrawingGroup, Transform?>(nameof(Transform));
|
|
|
|
public static readonly StyledProperty<Geometry?> ClipGeometryProperty =
|
|
AvaloniaProperty.Register<DrawingGroup, Geometry?>(nameof(ClipGeometry));
|
|
|
|
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);
|
|
set => SetValue(OpacityProperty, value);
|
|
}
|
|
|
|
public Transform? Transform
|
|
{
|
|
get => GetValue(TransformProperty);
|
|
set => SetValue(TransformProperty, value);
|
|
}
|
|
|
|
public Geometry? ClipGeometry
|
|
{
|
|
get => GetValue(ClipGeometryProperty);
|
|
set => SetValue(ClipGeometryProperty, value);
|
|
}
|
|
|
|
public IBrush? OpacityMask
|
|
{
|
|
get => GetValue(OpacityMaskProperty);
|
|
set => SetValue(OpacityMaskProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the collection that contains the child geometries.
|
|
/// </summary>
|
|
[Content]
|
|
public DrawingCollection Children
|
|
{
|
|
get => _children;
|
|
set
|
|
{
|
|
SetAndRaise(ChildrenProperty, ref _children, value);
|
|
}
|
|
}
|
|
|
|
public DrawingContext Open() => new DrawingGroupDrawingContext(this);
|
|
|
|
public override void Draw(DrawingContext context)
|
|
{
|
|
var bounds = GetBounds();
|
|
|
|
using (context.PushTransform(Transform?.Value ?? Matrix.Identity))
|
|
using (context.PushOpacity(Opacity, bounds))
|
|
using (ClipGeometry != null ? context.PushGeometryClip(ClipGeometry) : default)
|
|
using (OpacityMask != null ? context.PushOpacityMask(OpacityMask, bounds) : default)
|
|
{
|
|
foreach (var drawing in Children)
|
|
{
|
|
drawing.Draw(context);
|
|
}
|
|
}
|
|
}
|
|
|
|
public override Rect GetBounds()
|
|
{
|
|
var rect = new Rect();
|
|
|
|
foreach (var drawing in Children)
|
|
{
|
|
rect = rect.Union(drawing.GetBounds());
|
|
}
|
|
|
|
if (Transform != null)
|
|
{
|
|
rect = rect.TransformToAABB(Transform.Value);
|
|
}
|
|
|
|
return rect;
|
|
}
|
|
|
|
private sealed class DrawingGroupDrawingContext : DrawingContext
|
|
{
|
|
private readonly DrawingGroup _drawingGroup;
|
|
private readonly IPlatformRenderInterface _platformRenderInterface = AvaloniaLocator.Current.GetRequiredService<IPlatformRenderInterface>();
|
|
|
|
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.
|
|
private Drawing? _rootDrawing;
|
|
|
|
// Current DrawingGroup that new children are added to
|
|
private DrawingGroup? _currentDrawingGroup;
|
|
|
|
// Previous values of _currentDrawingGroup
|
|
private Stack<DrawingGroup?>? _previousDrawingGroupStack;
|
|
|
|
public DrawingGroupDrawingContext(DrawingGroup drawingGroup)
|
|
{
|
|
_drawingGroup = drawingGroup;
|
|
}
|
|
|
|
protected override void DrawEllipseCore(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));
|
|
}
|
|
|
|
protected override void DrawGeometryCore(IBrush? brush, IPen? pen, IGeometryImpl geometry)
|
|
{
|
|
if ((brush == null) && (pen == null))
|
|
{
|
|
return;
|
|
}
|
|
|
|
AddNewGeometryDrawing(brush, pen, new PlatformGeometry(geometry));
|
|
}
|
|
|
|
public override void DrawGlyphRun(IBrush? foreground, GlyphRun glyphRun)
|
|
{
|
|
if (foreground == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
GlyphRunDrawing glyphRunDrawing = new GlyphRunDrawing
|
|
{
|
|
Foreground = foreground,
|
|
GlyphRun = glyphRun
|
|
};
|
|
|
|
// Add Drawing to the Drawing graph
|
|
AddDrawing(glyphRunDrawing);
|
|
}
|
|
|
|
protected override void PushClipCore(RoundedRect rect)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
protected override void PushClipCore(Rect rect)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
protected override void PushGeometryClipCore(Geometry clip)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
protected override void PushOpacityCore(double opacity, Rect bounds)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
protected override void PushOpacityMaskCore(IBrush mask, Rect bounds)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
internal override void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
protected override void DrawLineCore(IPen pen, Point p1, Point p2)
|
|
{
|
|
// Instantiate the geometry
|
|
var geometry = _platformRenderInterface.CreateLineGeometry(p1, p2);
|
|
|
|
// Add Drawing to the Drawing graph
|
|
AddNewGeometryDrawing(null, pen, new PlatformGeometry(geometry));
|
|
}
|
|
|
|
protected override void DrawRectangleCore(IBrush? brush, IPen? pen, RoundedRect rrect, BoxShadows boxShadows = default)
|
|
{
|
|
// Instantiate the geometry
|
|
var geometry = _platformRenderInterface.CreateRectangleGeometry(rrect.Rect);
|
|
|
|
// Add Drawing to the Drawing graph
|
|
AddNewGeometryDrawing(brush, pen, new PlatformGeometry(geometry));
|
|
}
|
|
|
|
public override void Custom(ICustomDrawOperation custom) => throw new NotSupportedException();
|
|
|
|
protected override void DisposeCore()
|
|
{
|
|
// 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="matrix"> The transform to push. </param>
|
|
protected override void PushTransformCore(Matrix matrix)
|
|
{
|
|
// Instantiate a new drawing group and set it as the _currentDrawingGroup
|
|
var drawingGroup = PushNewDrawingGroup();
|
|
|
|
// Set the transform on the new DrawingGroup
|
|
drawingGroup.Transform = new MatrixTransform(matrix);
|
|
}
|
|
|
|
protected override void PopClipCore() => Pop();
|
|
|
|
protected override void PopGeometryClipCore() => Pop();
|
|
|
|
protected override void PopOpacityCore() => Pop();
|
|
|
|
protected override void PopOpacityMaskCore() => Pop();
|
|
|
|
protected override void PopTransformCore() => Pop();
|
|
|
|
/// <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)
|
|
{
|
|
if (_currentDrawingGroup != null)
|
|
{
|
|
throw new NotSupportedException("When a DrawingGroup is set, it should be made the root if a root drawing didnt exist.");
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|