|
|
|
@ -8,83 +8,45 @@ using Avalonia.Media.Imaging; |
|
|
|
|
|
|
|
namespace Avalonia.Media |
|
|
|
{ |
|
|
|
public sealed class DrawingContext : IDisposable |
|
|
|
public abstract class DrawingContext : IDisposable |
|
|
|
{ |
|
|
|
private readonly bool _ownsImpl; |
|
|
|
private int _currentLevel; |
|
|
|
private static ThreadSafeObjectPool<Stack<RestoreState>> StateStackPool { get; } = |
|
|
|
ThreadSafeObjectPool<Stack<RestoreState>>.Default; |
|
|
|
|
|
|
|
private Stack<RestoreState>? _states; |
|
|
|
|
|
|
|
private static ThreadSafeObjectPool<Stack<PushedState>> StateStackPool { get; } = |
|
|
|
ThreadSafeObjectPool<Stack<PushedState>>.Default; |
|
|
|
|
|
|
|
private static ThreadSafeObjectPool<Stack<TransformContainer>> TransformStackPool { get; } = |
|
|
|
ThreadSafeObjectPool<Stack<TransformContainer>>.Default; |
|
|
|
|
|
|
|
private Stack<PushedState>? _states = StateStackPool.Get(); |
|
|
|
|
|
|
|
private Stack<TransformContainer>? _transformContainers = TransformStackPool.Get(); |
|
|
|
|
|
|
|
readonly struct TransformContainer |
|
|
|
{ |
|
|
|
public readonly Matrix LocalTransform; |
|
|
|
public readonly Matrix ContainerTransform; |
|
|
|
|
|
|
|
public TransformContainer(Matrix localTransform, Matrix containerTransform) |
|
|
|
{ |
|
|
|
LocalTransform = localTransform; |
|
|
|
ContainerTransform = containerTransform; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public DrawingContext(IDrawingContextImpl impl) |
|
|
|
internal DrawingContext() |
|
|
|
{ |
|
|
|
PlatformImpl = impl; |
|
|
|
_ownsImpl = true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
public DrawingContext(IDrawingContextImpl impl, bool ownsImpl) |
|
|
|
{ |
|
|
|
_ownsImpl = ownsImpl; |
|
|
|
PlatformImpl = impl; |
|
|
|
} |
|
|
|
|
|
|
|
public IDrawingContextImpl PlatformImpl { get; } |
|
|
|
|
|
|
|
private Matrix _currentTransform = Matrix.Identity; |
|
|
|
|
|
|
|
private Matrix _currentContainerTransform = Matrix.Identity; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Gets the current transform of the drawing context.
|
|
|
|
/// </summary>
|
|
|
|
public Matrix CurrentTransform |
|
|
|
public void Dispose() |
|
|
|
{ |
|
|
|
get { return _currentTransform; } |
|
|
|
private set |
|
|
|
if (_states != null) |
|
|
|
{ |
|
|
|
_currentTransform = value; |
|
|
|
var transform = _currentTransform * _currentContainerTransform; |
|
|
|
PlatformImpl.Transform = transform; |
|
|
|
} |
|
|
|
} |
|
|
|
while (_states.Count > 0) |
|
|
|
_states.Pop().Dispose(); |
|
|
|
|
|
|
|
//HACK: This is a temporary hack that is used in the render loop
|
|
|
|
//to update TransformedBounds property
|
|
|
|
[Obsolete("HACK for render loop, don't use")] |
|
|
|
public Matrix CurrentContainerTransform => _currentContainerTransform; |
|
|
|
StateStackPool.ReturnAndSetNull(ref _states); |
|
|
|
} |
|
|
|
|
|
|
|
DisposeCore(); |
|
|
|
} |
|
|
|
|
|
|
|
protected abstract void DisposeCore(); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Draws an image.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="source">The image.</param>
|
|
|
|
/// <param name="rect">The rect in the output to draw to.</param>
|
|
|
|
public void DrawImage(IImage source, Rect rect) |
|
|
|
public virtual void DrawImage(IImage source, Rect rect) |
|
|
|
{ |
|
|
|
_ = source ?? throw new ArgumentNullException(nameof(source)); |
|
|
|
|
|
|
|
DrawImage(source, new Rect(source.Size), rect); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Draws an image.
|
|
|
|
/// </summary>
|
|
|
|
@ -92,12 +54,22 @@ namespace Avalonia.Media |
|
|
|
/// <param name="sourceRect">The rect in the image to draw.</param>
|
|
|
|
/// <param name="destRect">The rect in the output to draw to.</param>
|
|
|
|
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
|
|
|
|
public void DrawImage(IImage source, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = default) |
|
|
|
public virtual void DrawImage(IImage source, Rect sourceRect, Rect destRect, |
|
|
|
BitmapInterpolationMode bitmapInterpolationMode = default) |
|
|
|
{ |
|
|
|
_ = source ?? throw new ArgumentNullException(nameof(source)); |
|
|
|
|
|
|
|
source.Draw(this, sourceRect, destRect, bitmapInterpolationMode); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Draws a platform-specific bitmap impl.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="source">The bitmap image.</param>
|
|
|
|
/// <param name="opacity">The opacity to draw with.</param>
|
|
|
|
/// <param name="sourceRect">The rect in the image to draw.</param>
|
|
|
|
/// <param name="destRect">The rect in the output to draw to.</param>
|
|
|
|
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
|
|
|
|
internal abstract void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Draws a line.
|
|
|
|
@ -108,11 +80,11 @@ namespace Avalonia.Media |
|
|
|
public void DrawLine(IPen pen, Point p1, Point p2) |
|
|
|
{ |
|
|
|
if (PenIsVisible(pen)) |
|
|
|
{ |
|
|
|
PlatformImpl.DrawLine(pen, p1, p2); |
|
|
|
} |
|
|
|
DrawLineCore(pen, p1, p2); |
|
|
|
} |
|
|
|
|
|
|
|
protected abstract void DrawLineCore(IPen pen, Point p1, Point p2); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Draws a geometry.
|
|
|
|
/// </summary>
|
|
|
|
@ -121,10 +93,10 @@ namespace Avalonia.Media |
|
|
|
/// <param name="geometry">The geometry.</param>
|
|
|
|
public void DrawGeometry(IBrush? brush, IPen? pen, Geometry geometry) |
|
|
|
{ |
|
|
|
if (geometry.PlatformImpl is not null) |
|
|
|
DrawGeometry(brush, pen, geometry.PlatformImpl); |
|
|
|
if ((brush != null || PenIsVisible(pen)) && geometry.PlatformImpl != null) |
|
|
|
DrawGeometryCore(brush, pen, geometry.PlatformImpl); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Draws a geometry.
|
|
|
|
/// </summary>
|
|
|
|
@ -133,14 +105,12 @@ namespace Avalonia.Media |
|
|
|
/// <param name="geometry">The geometry.</param>
|
|
|
|
public void DrawGeometry(IBrush? brush, IPen? pen, IGeometryImpl geometry) |
|
|
|
{ |
|
|
|
_ = geometry ?? throw new ArgumentNullException(nameof(geometry)); |
|
|
|
|
|
|
|
if (brush != null || PenIsVisible(pen)) |
|
|
|
{ |
|
|
|
PlatformImpl.DrawGeometry(brush, pen, geometry); |
|
|
|
} |
|
|
|
if ((brush != null || PenIsVisible(pen))) |
|
|
|
DrawGeometryCore(brush, pen, geometry); |
|
|
|
} |
|
|
|
|
|
|
|
protected abstract void DrawGeometryCore(IBrush? brush, IPen? pen, IGeometryImpl geometry); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Draws a rectangle with the specified Brush and Pen.
|
|
|
|
/// </summary>
|
|
|
|
@ -158,14 +128,12 @@ namespace Avalonia.Media |
|
|
|
/// The brush and the pen can both be null. If the brush is null, then no fill is performed.
|
|
|
|
/// If the pen is null, then no stoke is performed. If both the pen and the brush are null, then the drawing is not visible.
|
|
|
|
/// </remarks>
|
|
|
|
public void DrawRectangle(IBrush? brush, IPen? pen, Rect rect, double radiusX = 0, double radiusY = 0, |
|
|
|
public void DrawRectangle(IBrush? brush, IPen? pen, Rect rect, |
|
|
|
double radiusX = 0, double radiusY = 0, |
|
|
|
BoxShadows boxShadows = default) |
|
|
|
{ |
|
|
|
if (brush == null && !PenIsVisible(pen)) |
|
|
|
{ |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (!MathUtilities.IsZero(radiusX)) |
|
|
|
{ |
|
|
|
radiusX = Math.Min(radiusX, rect.Width / 2); |
|
|
|
@ -175,20 +143,48 @@ namespace Avalonia.Media |
|
|
|
{ |
|
|
|
radiusY = Math.Min(radiusY, rect.Height / 2); |
|
|
|
} |
|
|
|
|
|
|
|
PlatformImpl.DrawRectangle(brush, pen, new RoundedRect(rect, radiusX, radiusY), boxShadows); |
|
|
|
|
|
|
|
DrawRectangleCore(brush, pen, new RoundedRect(rect, radiusX, radiusY), boxShadows); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Draws a rectangle with the specified Brush and Pen.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="brush">The brush used to fill the rectangle, or <c>null</c> for no fill.</param>
|
|
|
|
/// <param name="pen">The pen used to stroke the rectangle, or <c>null</c> for no stroke.</param>
|
|
|
|
/// <param name="rrect">The rectangle bounds.</param>
|
|
|
|
/// <param name="boxShadows">Box shadow effect parameters</param>
|
|
|
|
/// <remarks>
|
|
|
|
/// The brush and the pen can both be null. If the brush is null, then no fill is performed.
|
|
|
|
/// If the pen is null, then no stoke is performed. If both the pen and the brush are null, then the drawing is not visible.
|
|
|
|
/// </remarks>
|
|
|
|
public void DrawRectangle(IBrush? brush, IPen? pen, RoundedRect rrect, BoxShadows boxShadows = default) |
|
|
|
{ |
|
|
|
if (brush == null && !PenIsVisible(pen)) |
|
|
|
return; |
|
|
|
DrawRectangleCore(brush, pen, rrect, boxShadows); |
|
|
|
} |
|
|
|
|
|
|
|
protected abstract void DrawRectangleCore(IBrush? brush, IPen? pen, RoundedRect rrect, |
|
|
|
BoxShadows boxShadows = default); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Draws the outline of a rectangle.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="pen">The pen.</param>
|
|
|
|
/// <param name="rect">The rectangle bounds.</param>
|
|
|
|
/// <param name="cornerRadius">The corner radius.</param>
|
|
|
|
public void DrawRectangle(IPen pen, Rect rect, float cornerRadius = 0.0f) |
|
|
|
{ |
|
|
|
public void DrawRectangle(IPen pen, Rect rect, float cornerRadius = 0.0f) => |
|
|
|
DrawRectangle(null, pen, rect, cornerRadius, cornerRadius); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Draws a filled rectangle.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="brush">The brush.</param>
|
|
|
|
/// <param name="rect">The rectangle bounds.</param>
|
|
|
|
/// <param name="cornerRadius">The corner radius.</param>
|
|
|
|
public void FillRectangle(IBrush brush, Rect rect, float cornerRadius = 0.0f) => |
|
|
|
DrawRectangle(brush, null, rect, cornerRadius, cornerRadius); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Draws an ellipse with the specified Brush and Pen.
|
|
|
|
@ -204,35 +200,50 @@ namespace Avalonia.Media |
|
|
|
/// </remarks>
|
|
|
|
public void DrawEllipse(IBrush? brush, IPen? pen, Point center, double radiusX, double radiusY) |
|
|
|
{ |
|
|
|
if (brush == null && !PenIsVisible(pen)) |
|
|
|
if (brush != null || PenIsVisible(pen)) |
|
|
|
{ |
|
|
|
return; |
|
|
|
var originX = center.X - radiusX; |
|
|
|
var originY = center.Y - radiusY; |
|
|
|
var width = radiusX * 2; |
|
|
|
var height = radiusY * 2; |
|
|
|
DrawEllipseCore(brush, pen, new Rect(originX, originY, width, height)); |
|
|
|
} |
|
|
|
|
|
|
|
var originX = center.X - radiusX; |
|
|
|
var originY = center.Y - radiusY; |
|
|
|
var width = radiusX * 2; |
|
|
|
var height = radiusY * 2; |
|
|
|
|
|
|
|
PlatformImpl.DrawEllipse(brush, pen, new Rect(originX, originY, width, height)); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Draws an ellipse with the specified Brush and Pen.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="brush">The brush used to fill the ellipse, or <c>null</c> for no fill.</param>
|
|
|
|
/// <param name="pen">The pen used to stroke the ellipse, or <c>null</c> for no stroke.</param>
|
|
|
|
/// <param name="rect">The bounding rect.</param>
|
|
|
|
/// <remarks>
|
|
|
|
/// The brush and the pen can both be null. If the brush is null, then no fill is performed.
|
|
|
|
/// If the pen is null, then no stoke is performed. If both the pen and the brush are null, then the drawing is not visible.
|
|
|
|
/// </remarks>
|
|
|
|
public void DrawEllipse(IBrush? brush, IPen? pen, Rect rect) |
|
|
|
{ |
|
|
|
if (brush != null || PenIsVisible(pen)) |
|
|
|
DrawEllipseCore(brush, pen, rect); |
|
|
|
} |
|
|
|
|
|
|
|
protected abstract void DrawEllipseCore(IBrush? brush, IPen? pen, Rect rect); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Draws a custom drawing operation
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="custom">custom operation</param>
|
|
|
|
public void Custom(ICustomDrawOperation custom) => PlatformImpl.Custom(custom); |
|
|
|
public abstract void Custom(ICustomDrawOperation custom); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Draws text.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="origin">The upper-left corner of the text.</param>
|
|
|
|
/// <param name="text">The text.</param>
|
|
|
|
public void DrawText(FormattedText text, Point origin) |
|
|
|
public virtual void DrawText(FormattedText text, Point origin) |
|
|
|
{ |
|
|
|
_ = text ?? throw new ArgumentNullException(nameof(text)); |
|
|
|
|
|
|
|
text.Draw(this, origin); |
|
|
|
text.Draw(this, origin); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -240,30 +251,31 @@ namespace Avalonia.Media |
|
|
|
/// </summary>
|
|
|
|
/// <param name="foreground">The foreground brush.</param>
|
|
|
|
/// <param name="glyphRun">The glyph run.</param>
|
|
|
|
public void DrawGlyphRun(IBrush? foreground, GlyphRun glyphRun) |
|
|
|
public abstract void DrawGlyphRun(IBrush? foreground, GlyphRun glyphRun); |
|
|
|
|
|
|
|
public record struct PushedState : IDisposable |
|
|
|
{ |
|
|
|
_ = glyphRun ?? throw new ArgumentNullException(nameof(glyphRun)); |
|
|
|
private readonly DrawingContext _context; |
|
|
|
private readonly int _level; |
|
|
|
|
|
|
|
if (foreground != null) |
|
|
|
public PushedState(DrawingContext context) |
|
|
|
{ |
|
|
|
PlatformImpl.DrawGlyphRun(foreground, glyphRun.PlatformImpl); |
|
|
|
_context = context; |
|
|
|
_level = _context._states!.Count; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Draws a filled rectangle.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="brush">The brush.</param>
|
|
|
|
/// <param name="rect">The rectangle bounds.</param>
|
|
|
|
/// <param name="cornerRadius">The corner radius.</param>
|
|
|
|
public void FillRectangle(IBrush brush, Rect rect, float cornerRadius = 0.0f) |
|
|
|
{ |
|
|
|
DrawRectangle(brush, null, rect, cornerRadius, cornerRadius); |
|
|
|
public void Dispose() |
|
|
|
{ |
|
|
|
if(_context?._states == null) |
|
|
|
return; |
|
|
|
if(_context._states.Count != _level) |
|
|
|
throw new InvalidOperationException("Wrong Push/Pop state order"); |
|
|
|
_context._states.Pop().Dispose(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public readonly record struct PushedState : IDisposable |
|
|
|
|
|
|
|
private readonly record struct RestoreState : IDisposable |
|
|
|
{ |
|
|
|
private readonly int _level; |
|
|
|
private readonly DrawingContext _context; |
|
|
|
private readonly Matrix _matrix; |
|
|
|
private readonly PushedStateType _type; |
|
|
|
@ -271,62 +283,56 @@ namespace Avalonia.Media |
|
|
|
public enum PushedStateType |
|
|
|
{ |
|
|
|
None, |
|
|
|
Matrix, |
|
|
|
Transform, |
|
|
|
Opacity, |
|
|
|
Clip, |
|
|
|
MatrixContainer, |
|
|
|
GeometryClip, |
|
|
|
OpacityMask, |
|
|
|
BitmapBlendMode |
|
|
|
} |
|
|
|
|
|
|
|
public PushedState(DrawingContext context, PushedStateType type, Matrix matrix = default) |
|
|
|
public RestoreState(DrawingContext context, PushedStateType type) |
|
|
|
{ |
|
|
|
if (context._states is null) |
|
|
|
throw new ObjectDisposedException(nameof(DrawingContext)); |
|
|
|
|
|
|
|
_context = context; |
|
|
|
_type = type; |
|
|
|
_matrix = matrix; |
|
|
|
_level = context._currentLevel += 1; |
|
|
|
context._states.Push(this); |
|
|
|
} |
|
|
|
|
|
|
|
public void Dispose() |
|
|
|
{ |
|
|
|
if (_type == PushedStateType.None) |
|
|
|
return; |
|
|
|
if (_context._states is null || _context._transformContainers is null) |
|
|
|
if (_context._states is null) |
|
|
|
throw new ObjectDisposedException(nameof(DrawingContext)); |
|
|
|
if (_context._currentLevel != _level) |
|
|
|
throw new InvalidOperationException("Wrong Push/Pop state order"); |
|
|
|
_context._currentLevel--; |
|
|
|
_context._states.Pop(); |
|
|
|
if (_type == PushedStateType.Matrix) |
|
|
|
_context.CurrentTransform = _matrix; |
|
|
|
if (_type == PushedStateType.Transform) |
|
|
|
_context.PopTransformCore(); |
|
|
|
else if (_type == PushedStateType.Clip) |
|
|
|
_context.PlatformImpl.PopClip(); |
|
|
|
_context.PopClipCore(); |
|
|
|
else if (_type == PushedStateType.Opacity) |
|
|
|
_context.PlatformImpl.PopOpacity(); |
|
|
|
_context.PopOpacityCore(); |
|
|
|
else if (_type == PushedStateType.GeometryClip) |
|
|
|
_context.PlatformImpl.PopGeometryClip(); |
|
|
|
_context.PopGeometryClipCore(); |
|
|
|
else if (_type == PushedStateType.OpacityMask) |
|
|
|
_context.PlatformImpl.PopOpacityMask(); |
|
|
|
else if (_type == PushedStateType.MatrixContainer) |
|
|
|
{ |
|
|
|
var cont = _context._transformContainers.Pop(); |
|
|
|
_context._currentContainerTransform = cont.ContainerTransform; |
|
|
|
_context.CurrentTransform = cont.LocalTransform; |
|
|
|
} |
|
|
|
_context.PopOpacityMaskCore(); |
|
|
|
else if (_type == PushedStateType.BitmapBlendMode) |
|
|
|
_context.PopBitmapBlendModeCore(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Pushes a clip rectangle.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="clip">The clip rectangle.</param>
|
|
|
|
/// <returns>A disposable used to undo the clip rectangle.</returns>
|
|
|
|
public PushedState PushClip(RoundedRect clip) |
|
|
|
{ |
|
|
|
PlatformImpl.PushClip(clip); |
|
|
|
return new PushedState(this, PushedState.PushedStateType.Clip); |
|
|
|
PushClipCore(clip); |
|
|
|
_states ??= StateStackPool.Get(); |
|
|
|
_states.Push(new RestoreState(this, RestoreState.PushedStateType.Clip)); |
|
|
|
return new PushedState(this); |
|
|
|
} |
|
|
|
|
|
|
|
protected abstract void PushClipCore(RoundedRect rect); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Pushes a clip rectangle.
|
|
|
|
/// </summary>
|
|
|
|
@ -334,9 +340,13 @@ namespace Avalonia.Media |
|
|
|
/// <returns>A disposable used to undo the clip rectangle.</returns>
|
|
|
|
public PushedState PushClip(Rect clip) |
|
|
|
{ |
|
|
|
PlatformImpl.PushClip(clip); |
|
|
|
return new PushedState(this, PushedState.PushedStateType.Clip); |
|
|
|
PushClipCore(clip); |
|
|
|
_states ??= StateStackPool.Get(); |
|
|
|
_states.Push(new RestoreState(this, RestoreState.PushedStateType.Clip)); |
|
|
|
return new PushedState(this); |
|
|
|
} |
|
|
|
|
|
|
|
protected abstract void PushClipCore(Rect rect); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Pushes a clip geometry.
|
|
|
|
@ -345,17 +355,13 @@ namespace Avalonia.Media |
|
|
|
/// <returns>A disposable used to undo the clip geometry.</returns>
|
|
|
|
public PushedState PushGeometryClip(Geometry clip) |
|
|
|
{ |
|
|
|
_ = clip ?? throw new ArgumentNullException(nameof(clip)); |
|
|
|
|
|
|
|
// HACK: This check was added when nullable annotations pointed out that we're potentially
|
|
|
|
// pushing a null value for the clip here. Ideally we'd return an empty PushedState here but
|
|
|
|
// I don't want to make that change as part of adding nullable annotations.
|
|
|
|
if (clip.PlatformImpl is null) |
|
|
|
throw new InvalidOperationException("Cannot push empty geometry clip."); |
|
|
|
|
|
|
|
PlatformImpl.PushGeometryClip(clip.PlatformImpl); |
|
|
|
return new PushedState(this, PushedState.PushedStateType.GeometryClip); |
|
|
|
PushGeometryClipCore(clip); |
|
|
|
_states ??= StateStackPool.Get(); |
|
|
|
_states.Push(new RestoreState(this, RestoreState.PushedStateType.GeometryClip)); |
|
|
|
return new PushedState(this); |
|
|
|
} |
|
|
|
|
|
|
|
protected abstract void PushGeometryClipCore(Geometry clip); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Pushes an opacity value.
|
|
|
|
@ -364,11 +370,13 @@ namespace Avalonia.Media |
|
|
|
/// <param name="bounds">The bounds.</param>
|
|
|
|
/// <returns>A disposable used to undo the opacity.</returns>
|
|
|
|
public PushedState PushOpacity(double opacity, Rect bounds) |
|
|
|
//TODO: Eliminate platform-specific push opacity call
|
|
|
|
{ |
|
|
|
PlatformImpl.PushOpacity(opacity, bounds); |
|
|
|
return new PushedState(this, PushedState.PushedStateType.Opacity); |
|
|
|
PushOpacityCore(opacity, bounds); |
|
|
|
_states ??= StateStackPool.Get(); |
|
|
|
_states.Push(new RestoreState(this, RestoreState.PushedStateType.Opacity)); |
|
|
|
return new PushedState(this); |
|
|
|
} |
|
|
|
protected abstract void PushOpacityCore(double opacity, Rect bounds); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Pushes an opacity mask.
|
|
|
|
@ -380,70 +388,53 @@ namespace Avalonia.Media |
|
|
|
/// <returns>A disposable to undo the opacity mask.</returns>
|
|
|
|
public PushedState PushOpacityMask(IBrush mask, Rect bounds) |
|
|
|
{ |
|
|
|
PlatformImpl.PushOpacityMask(mask, bounds); |
|
|
|
return new PushedState(this, PushedState.PushedStateType.OpacityMask); |
|
|
|
PushOpacityMaskCore(mask, bounds); |
|
|
|
_states ??= StateStackPool.Get(); |
|
|
|
_states.Push(new RestoreState(this, RestoreState.PushedStateType.OpacityMask)); |
|
|
|
return new PushedState(this); |
|
|
|
} |
|
|
|
protected abstract void PushOpacityMaskCore(IBrush mask, Rect bounds); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Pushes a matrix post-transformation.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="matrix">The matrix</param>
|
|
|
|
/// <returns>A disposable used to undo the transformation.</returns>
|
|
|
|
public PushedState PushPostTransform(Matrix matrix) => PushSetTransform(CurrentTransform * matrix); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Pushes a matrix pre-transformation.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="matrix">The matrix</param>
|
|
|
|
/// <returns>A disposable used to undo the transformation.</returns>
|
|
|
|
public PushedState PushPreTransform(Matrix matrix) => PushSetTransform(matrix * CurrentTransform); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Sets the current matrix transformation.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="matrix">The matrix</param>
|
|
|
|
/// <returns>A disposable used to undo the transformation.</returns>
|
|
|
|
public PushedState PushSetTransform(Matrix matrix) |
|
|
|
public PushedState PushBitmapBlendMode(BitmapBlendingMode blendingMode) |
|
|
|
{ |
|
|
|
var oldMatrix = CurrentTransform; |
|
|
|
CurrentTransform = matrix; |
|
|
|
|
|
|
|
return new PushedState(this, PushedState.PushedStateType.Matrix, oldMatrix); |
|
|
|
PushBitmapBlendMode(blendingMode); |
|
|
|
_states ??= StateStackPool.Get(); |
|
|
|
_states.Push(new RestoreState(this, RestoreState.PushedStateType.BitmapBlendMode)); |
|
|
|
return new PushedState(this); |
|
|
|
} |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Pushes a new transform context.
|
|
|
|
/// </summary>
|
|
|
|
/// <returns>A disposable used to undo the transformation.</returns>
|
|
|
|
public PushedState PushTransformContainer() |
|
|
|
{ |
|
|
|
if (_transformContainers is null) |
|
|
|
throw new ObjectDisposedException(nameof(DrawingContext)); |
|
|
|
_transformContainers.Push(new TransformContainer(CurrentTransform, _currentContainerTransform)); |
|
|
|
_currentContainerTransform = CurrentTransform * _currentContainerTransform; |
|
|
|
_currentTransform = Matrix.Identity; |
|
|
|
return new PushedState(this, PushedState.PushedStateType.MatrixContainer); |
|
|
|
} |
|
|
|
protected abstract void PushBitmapBlendModeCore(BitmapBlendingMode blendingMode); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Disposes of any resources held by the <see cref="DrawingContext"/>.
|
|
|
|
/// Pushes a matrix transformation.
|
|
|
|
/// </summary>
|
|
|
|
public void Dispose() |
|
|
|
/// <param name="matrix">The matrix</param>
|
|
|
|
/// <returns>A disposable used to undo the transformation.</returns>
|
|
|
|
public PushedState PushTransform(Matrix matrix) |
|
|
|
{ |
|
|
|
if (_states is null || _transformContainers is null) |
|
|
|
throw new ObjectDisposedException(nameof(DrawingContext)); |
|
|
|
while (_states.Count != 0) |
|
|
|
_states.Peek().Dispose(); |
|
|
|
StateStackPool.Return(_states); |
|
|
|
_states = null; |
|
|
|
if (_transformContainers.Count != 0) |
|
|
|
throw new InvalidOperationException("Transform container stack is non-empty"); |
|
|
|
TransformStackPool.Return(_transformContainers); |
|
|
|
_transformContainers = null; |
|
|
|
if (_ownsImpl) |
|
|
|
PlatformImpl.Dispose(); |
|
|
|
PushTransformCore(matrix); |
|
|
|
_states ??= StateStackPool.Get(); |
|
|
|
_states.Push(new RestoreState(this, RestoreState.PushedStateType.Transform)); |
|
|
|
return new PushedState(this); |
|
|
|
} |
|
|
|
|
|
|
|
[Obsolete("Use PushTransform")] |
|
|
|
public PushedState PushPreTransform(Matrix matrix) => PushTransform(matrix); |
|
|
|
[Obsolete("Use PushTransform")] |
|
|
|
public PushedState PushPostTransform(Matrix matrix) => PushTransform(matrix); |
|
|
|
[Obsolete("Use PushTransform")] |
|
|
|
public PushedState PushTransformContainer() => PushTransform(Matrix.Identity); |
|
|
|
|
|
|
|
|
|
|
|
protected abstract void PushTransformCore(Matrix matrix); |
|
|
|
|
|
|
|
protected abstract void PopClipCore(); |
|
|
|
protected abstract void PopGeometryClipCore(); |
|
|
|
protected abstract void PopOpacityCore(); |
|
|
|
protected abstract void PopOpacityMaskCore(); |
|
|
|
protected abstract void PopBitmapBlendModeCore(); |
|
|
|
protected abstract void PopTransformCore(); |
|
|
|
|
|
|
|
private static bool PenIsVisible(IPen? pen) |
|
|
|
{ |
|
|
|
return pen?.Brush != null && pen.Thickness > 0; |
|
|
|
|