diff --git a/samples/RenderDemo/Pages/AnimationsPage.xaml b/samples/RenderDemo/Pages/AnimationsPage.xaml
index c5ad1fbfc8..12fb31ea59 100644
--- a/samples/RenderDemo/Pages/AnimationsPage.xaml
+++ b/samples/RenderDemo/Pages/AnimationsPage.xaml
@@ -134,6 +134,32 @@
+
@@ -152,6 +178,8 @@
+
+
diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs
index 993528a12a..b355310244 100644
--- a/src/Avalonia.Controls/Border.cs
+++ b/src/Avalonia.Controls/Border.cs
@@ -33,6 +33,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty CornerRadiusProperty =
AvaloniaProperty.Register(nameof(CornerRadius));
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty BoxShadowProperty =
+ AvaloniaProperty.Register(nameof(BoxShadow));
+
private readonly BorderRenderHelper _borderRenderHelper = new BorderRenderHelper();
///
@@ -44,7 +50,8 @@ namespace Avalonia.Controls
BackgroundProperty,
BorderBrushProperty,
BorderThicknessProperty,
- CornerRadiusProperty);
+ CornerRadiusProperty,
+ BoxShadowProperty);
AffectsMeasure(BorderThicknessProperty);
}
@@ -83,14 +90,24 @@ namespace Avalonia.Controls
get { return GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
-
+
+ ///
+ /// Gets or sets the box shadow effect parameters
+ ///
+ public BoxShadows BoxShadow
+ {
+ get => GetValue(BoxShadowProperty);
+ set => SetValue(BoxShadowProperty, value);
+ }
+
///
/// Renders the control.
///
/// The drawing context.
public override void Render(DrawingContext context)
{
- _borderRenderHelper.Render(context, Bounds.Size, BorderThickness, CornerRadius, Background, BorderBrush);
+ _borderRenderHelper.Render(context, Bounds.Size, BorderThickness, CornerRadius, Background, BorderBrush,
+ BoxShadow);
}
///
@@ -110,8 +127,6 @@ namespace Avalonia.Controls
/// The space taken.
protected override Size ArrangeOverride(Size finalSize)
{
- _borderRenderHelper.Update(finalSize, BorderThickness, CornerRadius);
-
return LayoutHelper.ArrangeChild(Child, finalSize, Padding, BorderThickness);
}
}
diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
index 55ed91893c..50aa8a9e71 100644
--- a/src/Avalonia.Controls/Presenters/ContentPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
@@ -40,7 +40,12 @@ namespace Avalonia.Controls.Presenters
public static readonly StyledProperty CornerRadiusProperty =
Border.CornerRadiusProperty.AddOwner();
-
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty BoxShadowProperty =
+ Border.BoxShadowProperty.AddOwner();
+
///
/// Defines the property.
///
@@ -132,6 +137,15 @@ namespace Avalonia.Controls.Presenters
set { SetValue(CornerRadiusProperty, value); }
}
+ ///
+ /// Gets or sets the box shadow effect parameters
+ ///
+ public BoxShadows BoxShadow
+ {
+ get => GetValue(BoxShadowProperty);
+ set => SetValue(BoxShadowProperty, value);
+ }
+
///
/// Gets the control displayed by the presenter.
///
@@ -274,7 +288,8 @@ namespace Avalonia.Controls.Presenters
///
public override void Render(DrawingContext context)
{
- _borderRenderer.Render(context, Bounds.Size, BorderThickness, CornerRadius, Background, BorderBrush);
+ _borderRenderer.Render(context, Bounds.Size, BorderThickness, CornerRadius, Background, BorderBrush,
+ BoxShadow);
}
///
@@ -321,8 +336,6 @@ namespace Avalonia.Controls.Presenters
///
protected override Size ArrangeOverride(Size finalSize)
{
- _borderRenderer.Update(finalSize, BorderThickness, CornerRadius);
-
return ArrangeOverrideImpl(finalSize, new Vector());
}
diff --git a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
index 126f3e35ca..cd0735d46f 100644
--- a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
+++ b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
@@ -1,17 +1,30 @@
using System;
using Avalonia.Media;
+using Avalonia.Platform;
namespace Avalonia.Controls.Utils
{
internal class BorderRenderHelper
{
private bool _useComplexRendering;
+ private bool? _backendSupportsIndividualCorners;
private StreamGeometry _backgroundGeometryCache;
private StreamGeometry _borderGeometryCache;
+ private Size _size;
+ private Thickness _borderThickness;
+ private CornerRadius _cornerRadius;
+ private bool _initialized;
- public void Update(Size finalSize, Thickness borderThickness, CornerRadius cornerRadius)
+ void Update(Size finalSize, Thickness borderThickness, CornerRadius cornerRadius)
{
- if (borderThickness.IsUniform && cornerRadius.IsUniform)
+ _backendSupportsIndividualCorners ??= AvaloniaLocator.Current.GetService()
+ .SupportsIndividualRoundRects;
+ _size = finalSize;
+ _borderThickness = borderThickness;
+ _cornerRadius = cornerRadius;
+ _initialized = true;
+
+ if (borderThickness.IsUniform && (cornerRadius.IsUniform || _backendSupportsIndividualCorners == true))
{
_backgroundGeometryCache = null;
_borderGeometryCache = null;
@@ -67,7 +80,19 @@ namespace Avalonia.Controls.Utils
}
}
- public void Render(DrawingContext context, Size size, Thickness borders, CornerRadius radii, IBrush background, IBrush borderBrush)
+ public void Render(DrawingContext context,
+ Size finalSize, Thickness borderThickness, CornerRadius cornerRadius,
+ IBrush background, IBrush borderBrush, BoxShadows boxShadows)
+ {
+ if (_size != finalSize
+ || _borderThickness != borderThickness
+ || _cornerRadius != cornerRadius
+ || !_initialized)
+ Update(finalSize, borderThickness, cornerRadius);
+ RenderCore(context, background, borderBrush, boxShadows);
+ }
+
+ void RenderCore(DrawingContext context, IBrush background, IBrush borderBrush, BoxShadows boxShadows)
{
if (_useComplexRendering)
{
@@ -76,7 +101,7 @@ namespace Avalonia.Controls.Utils
{
context.DrawGeometry(background, null, backgroundGeometry);
}
-
+
var borderGeometry = _borderGeometryCache;
if (borderGeometry != null)
{
@@ -85,9 +110,7 @@ namespace Avalonia.Controls.Utils
}
else
{
- var borderThickness = borders.Top;
- var top = borderThickness * 0.5;
-
+ var borderThickness = _borderThickness.Top;
IPen pen = null;
if (borderThickness > 0)
@@ -95,9 +118,14 @@ namespace Avalonia.Controls.Utils
pen = new Pen(borderBrush, borderThickness);
}
- var rect = new Rect(top, top, size.Width - borderThickness, size.Height - borderThickness);
+ var rrect = new RoundedRect(new Rect(_size), _cornerRadius.TopLeft, _cornerRadius.TopRight,
+ _cornerRadius.BottomRight, _cornerRadius.BottomLeft);
+ if (Math.Abs(borderThickness) > double.Epsilon)
+ {
+ rrect = rrect.Deflate(borderThickness * 0.5, borderThickness * 0.5);
+ }
- context.DrawRectangle(background, pen, rect, radii.TopLeft, radii.TopLeft);
+ context.PlatformImpl.DrawRectangle(background, pen, rrect, boxShadows);
}
}
diff --git a/src/Avalonia.Visuals/Animation/Animators/BoxShadowAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/BoxShadowAnimator.cs
new file mode 100644
index 0000000000..ff76902425
--- /dev/null
+++ b/src/Avalonia.Visuals/Animation/Animators/BoxShadowAnimator.cs
@@ -0,0 +1,23 @@
+using Avalonia.Media;
+
+namespace Avalonia.Animation.Animators
+{
+ public class BoxShadowAnimator : Animator
+ {
+ static ColorAnimator s_colorAnimator = new ColorAnimator();
+ static DoubleAnimator s_doubleAnimator = new DoubleAnimator();
+ static BoolAnimator s_boolAnimator = new BoolAnimator();
+ public override BoxShadow Interpolate(double progress, BoxShadow oldValue, BoxShadow newValue)
+ {
+ return new BoxShadow
+ {
+ OffsetX = s_doubleAnimator.Interpolate(progress, oldValue.OffsetX, newValue.OffsetX),
+ OffsetY = s_doubleAnimator.Interpolate(progress, oldValue.OffsetY, newValue.OffsetY),
+ Blur = s_doubleAnimator.Interpolate(progress, oldValue.Blur, newValue.Blur),
+ Spread = s_doubleAnimator.Interpolate(progress, oldValue.Spread, newValue.Spread),
+ Color = s_colorAnimator.Interpolate(progress, oldValue.Color, newValue.Color),
+ IsInset = s_boolAnimator.Interpolate(progress, oldValue.IsInset, newValue.IsInset)
+ };
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Animation/Animators/BoxShadowsAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/BoxShadowsAnimator.cs
new file mode 100644
index 0000000000..c6f96a2d0e
--- /dev/null
+++ b/src/Avalonia.Visuals/Animation/Animators/BoxShadowsAnimator.cs
@@ -0,0 +1,40 @@
+using Avalonia.Media;
+
+namespace Avalonia.Animation.Animators
+{
+ public class BoxShadowsAnimator : Animator
+ {
+ private static readonly BoxShadowAnimator s_boxShadowAnimator = new BoxShadowAnimator();
+ public override BoxShadows Interpolate(double progress, BoxShadows oldValue, BoxShadows newValue)
+ {
+ int cnt = progress >= 1d ? newValue.Count : oldValue.Count;
+ if (cnt == 0)
+ return new BoxShadows();
+
+ BoxShadow first;
+ if (oldValue.Count > 0 && newValue.Count > 0)
+ first = s_boxShadowAnimator.Interpolate(progress, oldValue[0], newValue[0]);
+ else if (oldValue.Count > 0)
+ first = oldValue[0];
+ else
+ first = newValue[0];
+
+ if (cnt == 1)
+ return new BoxShadows(first);
+
+ var rest = new BoxShadow[cnt - 1];
+ for (var c = 0; c < rest.Length; c++)
+ {
+ var idx = c + 1;
+ if (oldValue.Count > idx && newValue.Count > idx)
+ rest[c] = s_boxShadowAnimator.Interpolate(progress, oldValue[idx], newValue[idx]);
+ else if (oldValue.Count > idx)
+ rest[c] = oldValue[idx];
+ else
+ rest[c] = newValue[idx];
+ }
+
+ return new BoxShadows(first, rest);
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/BoxShadow.cs b/src/Avalonia.Visuals/Media/BoxShadow.cs
new file mode 100644
index 0000000000..69395fd3b8
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/BoxShadow.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Globalization;
+using Avalonia.Animation.Animators;
+using Avalonia.Utilities;
+
+namespace Avalonia.Media
+{
+ public struct BoxShadow
+ {
+ public double OffsetX { get; set; }
+ public double OffsetY { get; set; }
+ public double Blur { get; set; }
+ public double Spread { get; set; }
+ public Color Color { get; set; }
+ public bool IsInset { get; set; }
+
+ static BoxShadow()
+ {
+ Animation.Animation.RegisterAnimator(prop =>
+ typeof(BoxShadow).IsAssignableFrom(prop.PropertyType));
+ }
+
+ public bool Equals(in BoxShadow other)
+ {
+ return OffsetX.Equals(other.OffsetX) && OffsetY.Equals(other.OffsetY) && Blur.Equals(other.Blur) && Spread.Equals(other.Spread) && Color.Equals(other.Color);
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is BoxShadow other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hashCode = OffsetX.GetHashCode();
+ hashCode = (hashCode * 397) ^ OffsetY.GetHashCode();
+ hashCode = (hashCode * 397) ^ Blur.GetHashCode();
+ hashCode = (hashCode * 397) ^ Spread.GetHashCode();
+ hashCode = (hashCode * 397) ^ Color.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ public bool IsEmpty => OffsetX == 0 && OffsetY == 0 && Blur == 0 && Spread == 0;
+
+ private readonly static char[] s_Separator = new char[] { ' ', '\t' };
+
+ struct ArrayReader
+ {
+ private int _index;
+ private string[] _arr;
+
+ public ArrayReader(string[] arr)
+ {
+ _arr = arr;
+ _index = 0;
+ }
+
+ public bool TryReadString(out string s)
+ {
+ s = null;
+ if (_index >= _arr.Length)
+ return false;
+ s = _arr[_index];
+ _index++;
+ return true;
+ }
+
+ public string ReadString()
+ {
+ if(!TryReadString(out var rv))
+ throw new FormatException();
+ return rv;
+ }
+ }
+ public static unsafe BoxShadow Parse(string s)
+ {
+ if(s == null)
+ throw new ArgumentNullException();
+ if (s.Length == 0)
+ throw new FormatException();
+
+ var p = s.Split(s_Separator, StringSplitOptions.RemoveEmptyEntries);
+ if (p.Length == 1 && p[0] == "none")
+ return default;
+
+ if (p.Length < 3 || p.Length > 6)
+ throw new FormatException();
+
+ bool inset = false;
+
+ var tokenizer = new ArrayReader(p);
+
+ string firstToken = tokenizer.ReadString();
+ if (firstToken == "inset")
+ {
+ inset = true;
+ firstToken = tokenizer.ReadString();
+ }
+
+ var offsetX = double.Parse(firstToken, CultureInfo.InvariantCulture);
+ var offsetY = double.Parse(tokenizer.ReadString(), CultureInfo.InvariantCulture);
+ double blur = 0;
+ double spread = 0;
+
+
+ tokenizer.TryReadString(out var token3);
+ tokenizer.TryReadString(out var token4);
+ tokenizer.TryReadString(out var token5);
+
+ if (token4 != null)
+ blur = double.Parse(token3, CultureInfo.InvariantCulture);
+ if (token5 != null)
+ spread = double.Parse(token4, CultureInfo.InvariantCulture);
+
+ var color = Color.Parse(token5 ?? token4 ?? token3);
+ return new BoxShadow
+ {
+ IsInset = inset,
+ OffsetX = offsetX,
+ OffsetY = offsetY,
+ Blur = blur,
+ Spread = spread,
+ Color = color
+ };
+ }
+
+ public Rect TransformBounds(in Rect rect)
+ => IsInset ? rect : rect.Translate(new Vector(OffsetX, OffsetY)).Inflate(Spread + Blur);
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/BoxShadows.cs b/src/Avalonia.Visuals/Media/BoxShadows.cs
new file mode 100644
index 0000000000..fd187f6409
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/BoxShadows.cs
@@ -0,0 +1,137 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using Avalonia.Animation.Animators;
+
+namespace Avalonia.Media
+{
+ public struct BoxShadows
+ {
+ private readonly BoxShadow _first;
+ private readonly BoxShadow[] _list;
+ public int Count { get; }
+
+ static BoxShadows()
+ {
+ Animation.Animation.RegisterAnimator(prop =>
+ typeof(BoxShadows).IsAssignableFrom(prop.PropertyType));
+ }
+
+ public BoxShadows(BoxShadow shadow)
+ {
+ _first = shadow;
+ _list = null;
+ Count = 1;
+ }
+
+ public BoxShadows(BoxShadow first, BoxShadow[] rest)
+ {
+ _first = first;
+ _list = rest;
+ Count = 1 + (rest?.Length ?? 0);
+ }
+
+ public BoxShadow this[int c]
+ {
+ get
+ {
+ if (c< 0 || c >= Count)
+ throw new IndexOutOfRangeException();
+ if (c == 0)
+ return _first;
+ return _list[c - 1];
+ }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public struct BoxShadowsEnumerator
+ {
+ private int _index;
+ private BoxShadows _shadows;
+
+ public BoxShadowsEnumerator(BoxShadows shadows)
+ {
+ _shadows = shadows;
+ _index = -1;
+ }
+
+ public BoxShadow Current => _shadows[_index];
+
+ public bool MoveNext()
+ {
+ _index++;
+ return _index < _shadows.Count;
+ }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public BoxShadowsEnumerator GetEnumerator() => new BoxShadowsEnumerator(this);
+
+ private static readonly char[] s_Separators = new[] { ',' };
+ public static BoxShadows Parse(string s)
+ {
+ var sp = s.Split(s_Separators, StringSplitOptions.RemoveEmptyEntries);
+ if (sp.Length == 0
+ || (sp.Length == 1 &&
+ (string.IsNullOrWhiteSpace(sp[0])
+ || sp[0] == "none")))
+ return new BoxShadows();
+
+ var first = BoxShadow.Parse(sp[0]);
+ if (sp.Length == 1)
+ return new BoxShadows(first);
+
+ var rest = new BoxShadow[sp.Length - 1];
+ for (var c = 0; c < rest.Length; c++)
+ rest[c] = BoxShadow.Parse(sp[c + 1]);
+ return new BoxShadows(first, rest);
+ }
+
+ public Rect TransformBounds(in Rect rect)
+ {
+ var final = rect;
+ foreach (var shadow in this)
+ final = final.Union(shadow.TransformBounds(rect));
+ return final;
+ }
+
+ public bool HasInsetShadows
+ {
+ get
+ {
+ foreach(var boxShadow in this)
+ if (!boxShadow.IsEmpty && boxShadow.IsInset)
+ return true;
+ return false;
+ }
+ }
+
+ public static implicit operator BoxShadows(BoxShadow shadow) => new BoxShadows(shadow);
+
+ public bool Equals(BoxShadows other)
+ {
+ if (other.Count != Count)
+ return false;
+ for(var c=0; c
+ /// Parses a color string.
+ ///
+ /// The color string.
+ /// The parsed color
+ /// The status of the operation.
+ public static bool TryParse(ReadOnlySpan s, out Color color)
+ {
+ color = default;
+ if (s == null)
+ return false;
+ if (s.Length == 0)
+ return false;
+
+ if (s[0] == '#')
+ {
+ var or = 0u;
+
+ if (s.Length == 7)
+ {
+ or = 0xff000000;
+ }
+ else if (s.Length != 9)
+ {
+ return false;
+ }
+
+ if(!uint.TryParse(s.Slice(1).ToString(), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var parsed))
+ return false;
+ color = FromUInt32(parsed| or);
+ return true;
+ }
+
+ var knownColor = KnownColors.GetKnownColor(s.ToString());
+
+ if (knownColor != KnownColor.None)
+ {
+ color = knownColor.ToColor();
+ return true;
+ }
+
+ return false;
+ }
+
///
/// Returns the string representation of the color.
///
diff --git a/src/Avalonia.Visuals/Media/DrawingContext.cs b/src/Avalonia.Visuals/Media/DrawingContext.cs
index bb05d17967..aa7574a3ed 100644
--- a/src/Avalonia.Visuals/Media/DrawingContext.cs
+++ b/src/Avalonia.Visuals/Media/DrawingContext.cs
@@ -155,11 +155,13 @@ namespace Avalonia.Media
/// The radius in the Y dimension of the rounded corners.
/// This value will be clamped to the range of 0 to Height/2
///
+ /// Box shadow effect parameters
///
/// 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.
///
- 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,
+ BoxShadow boxShadow = default)
{
if (brush == null && !PenIsVisible(pen))
{
@@ -178,7 +180,7 @@ namespace Avalonia.Media
if ((pen is null || (pen != null && pen.IsRenderValid())) && rect.IsRenderValid() && radiusX.IsRenderValid() && radiusY.IsRenderValid())
{
- PlatformImpl.DrawRectangle(brush, pen, rect, radiusX, radiusY);
+ PlatformImpl.DrawRectangle(brush, pen, new RoundedRect(rect, radiusX, radiusY), boxShadow);
}
else
{
diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
index b89039698e..660d10c088 100644
--- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
+++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
@@ -63,17 +63,13 @@ namespace Avalonia.Platform
/// The brush used to fill the rectangle, or null for no fill.
/// The pen used to stroke the rectangle, or null for no stroke.
/// The rectangle bounds.
- /// The radius in the X dimension of the rounded corners.
- /// This value will be clamped to the range of 0 to Width/2
- ///
- /// The radius in the Y dimension of the rounded corners.
- /// This value will be clamped to the range of 0 to Height/2
- ///
+ /// Box shadow effect parameters
///
/// 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.
///
- void DrawRectangle(IBrush brush, IPen pen, Rect rect, double radiusX = 0, double radiusY = 0);
+ void DrawRectangle(IBrush brush, IPen pen, RoundedRect rect,
+ BoxShadows boxShadow = default);
///
/// Draws text.
diff --git a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
index bd569fe841..a0102a0f33 100644
--- a/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
+++ b/src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
@@ -116,5 +116,7 @@ namespace Avalonia.Platform
/// The glyph run's width.
///
IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun, out double width);
+
+ bool SupportsIndividualRoundRects { get; }
}
}
diff --git a/src/Avalonia.Visuals/Rect.cs b/src/Avalonia.Visuals/Rect.cs
index a7aa16684e..b0c4cb62eb 100644
--- a/src/Avalonia.Visuals/Rect.cs
+++ b/src/Avalonia.Visuals/Rect.cs
@@ -132,6 +132,16 @@ namespace Avalonia
/// Gets the bottom position of the rectangle.
///
public double Bottom => _y + _height;
+
+ ///
+ /// Gets the left position.
+ ///
+ public double Left => _x;
+
+ ///
+ /// Gets the top position.
+ ///
+ public double Top => _y;
///
/// Gets the top left point of the rectangle.
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
index bfa1c08034..b8658a7a26 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
@@ -149,13 +149,14 @@ namespace Avalonia.Rendering.SceneGraph
}
///
- public void DrawRectangle(IBrush brush, IPen pen, Rect rect, double radiusX = 0, double radiusY = 0)
+ 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, radiusX, radiusY))
+ if (next == null || !next.Item.Equals(Transform, brush, pen, rect, boxShadows))
{
- Add(new RectangleNode(Transform, brush, pen, rect, radiusX, radiusY, CreateChildScene(brush)));
+ Add(new RectangleNode(Transform, brush, pen, rect, boxShadows, CreateChildScene(brush)));
}
else
{
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
index 2e5ca973ec..cb7498f7b7 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
@@ -19,8 +19,7 @@ namespace Avalonia.Rendering.SceneGraph
/// The stroke pen.
/// The geometry.
/// Child scenes for drawing visual brushes.
- public GeometryNode(
- Matrix transform,
+ public GeometryNode(Matrix transform,
IBrush brush,
IPen pen,
IGeometryImpl geometry,
@@ -64,6 +63,7 @@ namespace Avalonia.Rendering.SceneGraph
/// The fill of the other draw operation.
/// The stroke of the other draw operation.
/// The geometry of the other draw operation.
+ /// The box shadow parameters
/// True if the draw operations are the same, otherwise false.
///
/// The properties of the other draw operation are passed in as arguments to prevent
@@ -72,9 +72,9 @@ namespace Avalonia.Rendering.SceneGraph
public bool Equals(Matrix transform, IBrush brush, IPen pen, IGeometryImpl geometry)
{
return transform == Transform &&
- Equals(brush, Brush) &&
- Equals(Pen, pen) &&
- Equals(geometry, Geometry);
+ Equals(brush, Brush) &&
+ Equals(Pen, pen) &&
+ Equals(geometry, Geometry);
}
///
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
index e48bad6433..633b1fc5f3 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
@@ -19,26 +19,23 @@ namespace Avalonia.Rendering.SceneGraph
/// The fill brush.
/// The stroke pen.
/// The rectangle to draw.
- /// The radius in the Y dimension of the rounded corners.
- /// The radius in the X dimension of the rounded corners.
+ /// The box shadow parameters
/// Child scenes for drawing visual brushes.
public RectangleNode(
Matrix transform,
IBrush brush,
IPen pen,
- Rect rect,
- double radiusX,
- double radiusY,
+ RoundedRect rect,
+ BoxShadows boxShadows,
IDictionary childScenes = null)
- : base(rect, transform, pen)
+ : base(boxShadows.TransformBounds(rect.Rect), transform, pen)
{
Transform = transform;
Brush = brush?.ToImmutable();
Pen = pen?.ToImmutable();
Rect = rect;
- RadiusX = radiusX;
- RadiusY = radiusY;
ChildScenes = childScenes;
+ BoxShadows = boxShadows;
}
///
@@ -59,17 +56,12 @@ namespace Avalonia.Rendering.SceneGraph
///
/// Gets the rectangle to draw.
///
- public Rect Rect { get; }
-
- ///
- /// The radius in the X dimension of the rounded corners.
- ///
- public double RadiusX { get; }
-
+ public RoundedRect Rect { get; }
+
///
- /// The radius in the Y dimension of the rounded corners.
+ /// The parameters for the box-shadow effect
///
- public double RadiusY { get; }
+ public BoxShadows BoxShadows { get; }
///
public override IDictionary ChildScenes { get; }
@@ -81,21 +73,19 @@ namespace Avalonia.Rendering.SceneGraph
/// The fill of the other draw operation.
/// The stroke of the other draw operation.
/// The rectangle of the other draw operation.
- ///
- ///
+ /// The box shadow parameters of the other draw operation
/// True if the draw operations are the same, otherwise false.
///
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
///
- public bool Equals(Matrix transform, IBrush brush, IPen pen, Rect rect, double radiusX, double radiusY)
+ public bool Equals(Matrix transform, IBrush brush, IPen pen, RoundedRect rect, BoxShadows boxShadows)
{
return transform == Transform &&
Equals(brush, Brush) &&
Equals(Pen, pen) &&
- rect == Rect &&
- Math.Abs(radiusX - RadiusX) < double.Epsilon &&
- Math.Abs(radiusY - RadiusY) < double.Epsilon;
+ Media.BoxShadows.Equals(BoxShadows, boxShadows) &&
+ rect.Equals(Rect);
}
///
@@ -103,7 +93,7 @@ namespace Avalonia.Rendering.SceneGraph
{
context.Transform = Transform;
- context.DrawRectangle(Brush, Pen, Rect, RadiusX, RadiusY);
+ context.DrawRectangle(Brush, Pen, Rect, BoxShadows);
}
///
@@ -116,13 +106,13 @@ namespace Avalonia.Rendering.SceneGraph
if (Brush != null)
{
- var rect = Rect.Inflate((Pen?.Thickness / 2) ?? 0);
+ var rect = Rect.Rect.Inflate((Pen?.Thickness / 2) ?? 0);
return rect.Contains(p);
}
else
{
- var borderRect = Rect.Inflate((Pen?.Thickness / 2) ?? 0);
- var emptyRect = Rect.Deflate((Pen?.Thickness / 2) ?? 0);
+ var borderRect = Rect.Rect.Inflate((Pen?.Thickness / 2) ?? 0);
+ var emptyRect = Rect.Rect.Deflate((Pen?.Thickness / 2) ?? 0);
return borderRect.Contains(p) && !emptyRect.Contains(p);
}
}
diff --git a/src/Avalonia.Visuals/RoundedRect.cs b/src/Avalonia.Visuals/RoundedRect.cs
new file mode 100644
index 0000000000..ad860240f2
--- /dev/null
+++ b/src/Avalonia.Visuals/RoundedRect.cs
@@ -0,0 +1,136 @@
+using System;
+
+namespace Avalonia
+{
+ public struct RoundedRect
+ {
+ public bool Equals(RoundedRect other)
+ {
+ return Rect.Equals(other.Rect) && RadiiTopLeft.Equals(other.RadiiTopLeft) && RadiiTopRight.Equals(other.RadiiTopRight) && RadiiBottomLeft.Equals(other.RadiiBottomLeft) && RadiiBottomRight.Equals(other.RadiiBottomRight);
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is RoundedRect other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hashCode = Rect.GetHashCode();
+ hashCode = (hashCode * 397) ^ RadiiTopLeft.GetHashCode();
+ hashCode = (hashCode * 397) ^ RadiiTopRight.GetHashCode();
+ hashCode = (hashCode * 397) ^ RadiiBottomLeft.GetHashCode();
+ hashCode = (hashCode * 397) ^ RadiiBottomRight.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ public Rect Rect { get; }
+ public Vector RadiiTopLeft { get; }
+ public Vector RadiiTopRight { get; }
+ public Vector RadiiBottomLeft { get; }
+ public Vector RadiiBottomRight { get; }
+
+ public RoundedRect(Rect rect, Vector radiiTopLeft, Vector radiiTopRight, Vector radiiBottomRight, Vector radiiBottomLeft)
+ {
+ Rect = rect;
+ RadiiTopLeft = radiiTopLeft;
+ RadiiTopRight = radiiTopRight;
+ RadiiBottomRight = radiiBottomRight;
+ RadiiBottomLeft = radiiBottomLeft;
+ }
+
+ public RoundedRect(Rect rect, double radiusTopLeft, double radiusTopRight, double radiusBottomRight,
+ double radiusBottomLeft)
+ : this(rect,
+ new Vector(radiusTopLeft, radiusTopLeft),
+ new Vector(radiusTopRight, radiusTopRight),
+ new Vector(radiusBottomRight, radiusBottomRight),
+ new Vector(radiusBottomLeft, radiusBottomLeft)
+ )
+ {
+
+ }
+
+ public RoundedRect(Rect rect, Vector radii) : this(rect, radii, radii, radii, radii)
+ {
+
+ }
+
+ public RoundedRect(Rect rect, double radiusX, double radiusY) : this(rect, new Vector(radiusX, radiusY))
+ {
+
+ }
+
+ public RoundedRect(Rect rect, double radius) : this(rect, radius, radius)
+ {
+
+ }
+
+ public RoundedRect(Rect rect) : this(rect, 0)
+ {
+
+ }
+
+ public static implicit operator RoundedRect(Rect r) => new RoundedRect(r);
+
+ public bool IsRounded => RadiiTopLeft != default || RadiiTopRight != default || RadiiBottomRight != default ||
+ RadiiBottomLeft != default;
+
+ public bool IsUniform =>
+ RadiiTopLeft.Equals(RadiiTopRight) &&
+ RadiiTopLeft.Equals(RadiiBottomRight) &&
+ RadiiTopLeft.Equals(RadiiBottomLeft);
+
+ public RoundedRect Inflate(double dx, double dy)
+ {
+ return Deflate(-dx, -dy);
+ }
+
+ public unsafe RoundedRect Deflate(double dx, double dy)
+ {
+ if (!IsRounded)
+ return new RoundedRect(Rect.Deflate(new Thickness(dx, dy)));
+
+ // Ported from SKRRect
+ var left = Rect.X + dx;
+ var top = Rect.Y + dy;
+ var right = left + Rect.Width - dx * 2;
+ var bottom = top + Rect.Height - dy * 2;
+ var radii = stackalloc Vector[4];
+ radii[0] = RadiiTopLeft;
+ radii[1] = RadiiTopRight;
+ radii[2] = RadiiBottomRight;
+ radii[3] = RadiiBottomLeft;
+
+ bool degenerate = false;
+ if (right <= left) {
+ degenerate = true;
+ left = right = (left + right)*0.5;
+ }
+ if (bottom <= top) {
+ degenerate = true;
+ top = bottom = (top + bottom) * 0.5;
+ }
+ if (degenerate)
+ {
+ return new RoundedRect(new Rect(left, top, right - left, bottom - top));
+ }
+
+ for (var c = 0; c < 4; c++)
+ {
+ var rx = Math.Max(0, radii[c].X - dx);
+ var ry = Math.Max(0, radii[c].Y - dy);
+ if (rx == 0 || ry == 0)
+ radii[c] = default;
+ else
+ radii[c] = new Vector(rx, ry);
+ }
+
+ return new RoundedRect(new Rect(left, top, right - left, bottom - top),
+ radii[0], radii[1], radii[2], radii[3]);
+ }
+ }
+}
diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
index 9f99ed3cef..570ed1ac65 100644
--- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
+++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
@@ -33,6 +33,7 @@ namespace Avalonia.Skia
private readonly SKPaint _strokePaint = new SKPaint();
private readonly SKPaint _fillPaint = new SKPaint();
+ private readonly SKPaint _boxShadowPaint = new SKPaint();
///
/// Context create info.
@@ -184,19 +185,124 @@ namespace Avalonia.Skia
}
}
+ struct BoxShadowFilter : IDisposable
+ {
+ public SKPaint Paint;
+ private SKImageFilter _filter;
+ public SKClipOperation ClipOperation;
+
+ public static BoxShadowFilter Create(SKPaint paint, BoxShadow shadow, double opacity)
+ {
+ var ac = shadow.Color;
+ var spread = (int)shadow.Spread;
+ if (shadow.IsInset)
+ spread = -spread;
+
+ var filter = SKImageFilter.CreateDropShadow(
+ (float)shadow.OffsetX,
+ (float)shadow.OffsetY,
+ (float)shadow.Blur / 2,
+ (float)shadow.Blur / 2,
+ new SKColor(ac.R, ac.G, ac.B, (byte)(ac.A * opacity)),
+ SKDropShadowImageFilterShadowMode.DrawShadowOnly, null);
+
+ paint.Reset();
+ paint.IsAntialias = true;
+ paint.Color= SKColors.White;
+ paint.ImageFilter = filter;
+
+ return new BoxShadowFilter
+ {
+ Paint = paint, _filter = filter,
+ ClipOperation = shadow.IsInset ? SKClipOperation.Intersect : SKClipOperation.Difference
+ };
+ }
+
+ public void Dispose()
+ {
+ Paint.Reset();
+ Paint = null;
+ _filter.Dispose();
+ }
+ }
+
+ SKRect AreaCastingShadowInHole(
+ SKRect hole_rect,
+ float shadow_blur,
+ float shadow_spread,
+ float offsetX, float offsetY)
+ {
+ // Adapted from Chromium
+ var bounds = hole_rect;
+
+ bounds.Inflate(shadow_blur, shadow_blur);
+
+ if (shadow_spread < 0)
+ bounds.Inflate(-shadow_spread, -shadow_spread);
+
+ var offset_bounds = bounds;
+ offset_bounds.Offset(-offsetX, -offsetY);
+ bounds.Union(offset_bounds);
+ return bounds;
+ }
+
+
///
- public void DrawRectangle(IBrush brush, IPen pen, Rect rect, double radiusX, double radiusY)
+ public void DrawRectangle(IBrush brush, IPen pen, RoundedRect rect, BoxShadows boxShadows = default)
{
- var rc = rect.ToSKRect();
- var isRounded = Math.Abs(radiusX) > double.Epsilon || Math.Abs(radiusY) > double.Epsilon;
+ var rc = rect.Rect.ToSKRect();
+ var isRounded = rect.IsRounded;
+ var needRoundRect = rect.IsRounded || (boxShadows.HasInsetShadows);
+ using var skRoundRect = needRoundRect ? new SKRoundRect() : null;
+ if (needRoundRect)
+ skRoundRect.SetRectRadii(rc,
+ new[]
+ {
+ rect.RadiiTopLeft.ToSKPoint(), rect.RadiiTopRight.ToSKPoint(),
+ rect.RadiiBottomRight.ToSKPoint(), rect.RadiiBottomLeft.ToSKPoint(),
+ });
+
+ foreach (var boxShadow in boxShadows)
+ {
+ if (!boxShadow.IsEmpty && !boxShadow.IsInset)
+ {
+ using (var shadow = BoxShadowFilter.Create(_boxShadowPaint, boxShadow, _currentOpacity))
+ {
+ var spread = (float)boxShadow.Spread;
+ if (boxShadow.IsInset)
+ spread = -spread;
+
+ Canvas.Save();
+ if (isRounded)
+ {
+ using var shadowRect = new SKRoundRect(skRoundRect);
+ if (spread != 0)
+ shadowRect.Inflate(spread, spread);
+ Canvas.ClipRoundRect(skRoundRect,
+ shadow.ClipOperation, true);
+ Canvas.DrawRoundRect(shadowRect, shadow.Paint);
+ }
+ else
+ {
+ var shadowRect = rc;
+ if (spread != 0)
+ shadowRect.Inflate(spread, spread);
+ Canvas.ClipRect(shadowRect, shadow.ClipOperation);
+ Canvas.DrawRect(shadowRect, shadow.Paint);
+ }
+
+ Canvas.Restore();
+ }
+ }
+ }
if (brush != null)
{
- using (var paint = CreatePaint(_fillPaint, brush, rect.Size))
+ using (var paint = CreatePaint(_fillPaint, brush, rect.Rect.Size))
{
if (isRounded)
{
- Canvas.DrawRoundRect(rc, (float)radiusX, (float)radiusY, paint.Paint);
+ Canvas.DrawRoundRect(skRoundRect, paint.Paint);
}
else
{
@@ -206,13 +312,38 @@ namespace Avalonia.Skia
}
}
+ foreach (var boxShadow in boxShadows)
+ {
+ if (!boxShadow.IsEmpty && boxShadow.IsInset)
+ {
+ using (var shadow = BoxShadowFilter.Create(_boxShadowPaint, boxShadow, _currentOpacity))
+ {
+ var spread = (float)boxShadow.Spread;
+ var offsetX = (float)boxShadow.OffsetX;
+ var offsetY = (float)boxShadow.OffsetY;
+ var outerRect = AreaCastingShadowInHole(rc, (float)boxShadow.Blur, spread, offsetX, offsetY);
+
+ Canvas.Save();
+ using var shadowRect = new SKRoundRect(skRoundRect);
+ if (spread != 0)
+ shadowRect.Deflate(spread, spread);
+ Canvas.ClipRoundRect(skRoundRect,
+ shadow.ClipOperation, true);
+ using (var outerRRect = new SKRoundRect(outerRect))
+ Canvas.DrawRoundRectDifference(outerRRect, shadowRect, shadow.Paint);
+
+ Canvas.Restore();
+ }
+ }
+ }
+
if (pen?.Brush != null)
{
- using (var paint = CreatePaint(_strokePaint, pen, rect.Size))
+ using (var paint = CreatePaint(_strokePaint, pen, rect.Rect.Size))
{
if (isRounded)
{
- Canvas.DrawRoundRect(rc, (float)radiusX, (float)radiusY, paint.Paint);
+ Canvas.DrawRoundRect(skRoundRect, paint.Paint);
}
else
{
diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
index d277267d5d..bfe24bb429 100644
--- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
+++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
@@ -255,5 +255,7 @@ namespace Avalonia.Skia
return new GlyphRunImpl(textBlob);
}
+
+ public bool SupportsIndividualRoundRects => true;
}
}
diff --git a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
index 1dd2310475..459486f784 100644
--- a/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
+++ b/src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
@@ -11,6 +11,11 @@ namespace Avalonia.Skia
{
return new SKPoint((float)p.X, (float)p.Y);
}
+
+ public static SKPoint ToSKPoint(this Vector p)
+ {
+ return new SKPoint((float)p.X, (float)p.Y);
+ }
public static SKRect ToSKRect(this Rect r)
{
diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
index b8bbed24f8..96bd96341c 100644
--- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
+++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
@@ -240,5 +240,7 @@ namespace Avalonia.Direct2D1
return new GlyphRunImpl(run);
}
+
+ public bool SupportsIndividualRoundRects => false;
}
}
diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
index a2e529395c..bbb45cf64c 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
@@ -228,9 +228,14 @@ namespace Avalonia.Direct2D1.Media
}
///
- public void DrawRectangle(IBrush brush, IPen pen, Rect rect, double radiusX, double radiusY)
+ public void DrawRectangle(IBrush brush, IPen pen, RoundedRect rrect, BoxShadows boxShadow = default)
{
- var rc = rect.ToDirect2D();
+ var rc = rrect.Rect.ToDirect2D();
+ var rect = rrect.Rect;
+ var radiusX = Math.Max(rrect.RadiiTopLeft.X,
+ Math.Max(rrect.RadiiTopRight.X, Math.Max(rrect.RadiiBottomRight.X, rrect.RadiiBottomLeft.X)));
+ var radiusY = Math.Max(rrect.RadiiTopLeft.Y,
+ Math.Max(rrect.RadiiTopRight.Y, Math.Max(rrect.RadiiBottomRight.Y, rrect.RadiiBottomLeft.Y)));
var isRounded = Math.Abs(radiusX) > double.Epsilon || Math.Abs(radiusY) > double.Epsilon;
if (brush != null)
diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
index 1f983069c2..5e29893946 100644
--- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
+++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
@@ -76,5 +76,7 @@ namespace Avalonia.Benchmarks
return new NullGlyphRun();
}
+
+ public bool SupportsIndividualRoundRects => true;
}
}
diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
index 23b6a00cc8..afdf95430b 100644
--- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
+++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
@@ -84,5 +84,7 @@ namespace Avalonia.UnitTests
width = 0;
return Mock.Of();
}
+
+ public bool SupportsIndividualRoundRects { get; set; }
}
}
diff --git a/tests/Avalonia.Visuals.UnitTests/Media/BoxShadowTests.cs b/tests/Avalonia.Visuals.UnitTests/Media/BoxShadowTests.cs
new file mode 100644
index 0000000000..e3b703baf2
--- /dev/null
+++ b/tests/Avalonia.Visuals.UnitTests/Media/BoxShadowTests.cs
@@ -0,0 +1,45 @@
+using Avalonia.Media;
+using Xunit;
+
+namespace Avalonia.Visuals.UnitTests.Media
+{
+ public class BoxShadowTests
+ {
+ [Fact]
+ public void BoxShadow_Should_Parse()
+ {
+ foreach (var extraSpaces in new[] { false, true })
+ foreach (var inset in new[] { false, true })
+ for (var componentCount = 2; componentCount < 5; componentCount++)
+ {
+ var s = (inset ? "inset " : "") + "10 20";
+ double blur = 0;
+ double spread = 0;
+ if (componentCount > 2)
+ {
+ s += " 30";
+ blur = 30;
+ }
+
+ if (componentCount > 3)
+ {
+ s += " 40";
+ spread = 40;
+ }
+
+ s += " red";
+
+ if (extraSpaces)
+ s = " " + s.Replace(" ", " ") + " ";
+
+ var parsed = BoxShadow.Parse(s);
+ Assert.Equal(inset, parsed.IsInset);
+ Assert.Equal(10, parsed.OffsetX);
+ Assert.Equal(20, parsed.OffsetY);
+ Assert.Equal(blur, parsed.Blur);
+ Assert.Equal(spread, parsed.Spread);
+ Assert.Equal(Colors.Red, parsed.Color);
+ }
+ }
+ }
+}
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
index fffd36d30a..767111b89b 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
@@ -473,7 +473,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
var animation = new BehaviorSubject(0.5);
context.Verify(x => x.PushOpacity(0.5), Times.Once);
- context.Verify(x => x.DrawRectangle(Brushes.Red, null, new Rect(0, 0, 100, 100), 0, 0), Times.Once);
+ context.Verify(x => x.DrawRectangle(Brushes.Red, null, new Rect(0, 0, 100, 100), default), Times.Once);
context.Verify(x => x.PopOpacity(), Times.Once);
}
}
@@ -503,7 +503,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
var animation = new BehaviorSubject(0.5);
context.Verify(x => x.PushOpacity(0.5), Times.Never);
- context.Verify(x => x.DrawRectangle(Brushes.Red, null, new Rect(0, 0, 100, 100), 0, 0), Times.Never);
+ context.Verify(x => x.DrawRectangle(Brushes.Red, null, new Rect(0, 0, 100, 100), default), Times.Never);
context.Verify(x => x.PopOpacity(), Times.Never);
}
}
@@ -528,7 +528,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
var animation = new BehaviorSubject(0.5);
context.Verify(x => x.PushOpacityMask(Brushes.Green, new Rect(0, 0, 100, 100)), Times.Once);
- context.Verify(x => x.DrawRectangle(Brushes.Red, null, new Rect(0, 0, 100, 100), 0, 0), Times.Once);
+ context.Verify(x => x.DrawRectangle(Brushes.Red, null, new Rect(0, 0, 100, 100), default), Times.Once);
context.Verify(x => x.PopOpacityMask(), Times.Once);
}
}
@@ -653,7 +653,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
var context = GetLayerContext(target, border);
context.Verify(x => x.PushOpacity(0.5), Times.Never);
- context.Verify(x => x.DrawRectangle(Brushes.Red, null, new Rect(0, 0, 100, 100), 0, 0), Times.Once);
+ context.Verify(x => x.DrawRectangle(Brushes.Red, null, new Rect(0, 0, 100, 100), default), Times.Once);
context.Verify(x => x.PopOpacity(), Times.Never);
}
}
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs
index 5fe92ba039..27aa0c55b5 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs
@@ -111,7 +111,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
public void Should_Not_Replace_Identical_DrawOperation()
{
var node = new VisualNode(new TestRoot(), null);
- var operation = RefCountable.Create(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), 0, 0));
+ var operation = RefCountable.Create(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), default));
var layers = new SceneLayers(node.Visual);
var target = new DeferredDrawingContextImpl(null, layers);
@@ -133,7 +133,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
public void Should_Replace_Different_DrawOperation()
{
var node = new VisualNode(new TestRoot(), null);
- var operation = RefCountable.Create(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), 0, 0));
+ var operation = RefCountable.Create(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), default));
var layers = new SceneLayers(node.Visual);
var target = new DeferredDrawingContextImpl(null, layers);
@@ -155,7 +155,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
public void Should_Update_DirtyRects()
{
var node = new VisualNode(new TestRoot(), null);
- var operation = new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), 0, 0);
+ var operation = new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), default);
var layers = new SceneLayers(node.Visual);
var target = new DeferredDrawingContextImpl(null, layers);
@@ -206,7 +206,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
public void Trimmed_DrawOperations_Releases_Reference()
{
var node = new VisualNode(new TestRoot(), null);
- var operation = RefCountable.Create(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), 0, 0));
+ var operation = RefCountable.Create(new RectangleNode(Matrix.Identity, Brushes.Red, null, new Rect(0, 0, 100, 100), default));
var layers = new SceneLayers(node.Visual);
var target = new DeferredDrawingContextImpl(null, layers);
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs
index 6a1f08a384..7787ac0871 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs
@@ -69,7 +69,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
new Matrix(),
Brushes.Black,
null,
- geometry);
+ geometry, default);
geometryNode.HitTest(new Point());
}
diff --git a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs
index 28304b674b..019eefe1a6 100644
--- a/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs
+++ b/tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs
@@ -56,6 +56,8 @@ namespace Avalonia.Visuals.UnitTests.VisualTree
throw new NotImplementedException();
}
+ public bool SupportsIndividualRoundRects { get; set; }
+
public IFontManagerImpl CreateFontManager()
{
return new MockFontManagerImpl();