diff --git a/samples/RenderDemo/Pages/AnimationsPage.xaml b/samples/RenderDemo/Pages/AnimationsPage.xaml
index c5ad1fbfc8..5f8b408622 100644
--- a/samples/RenderDemo/Pages/AnimationsPage.xaml
+++ b/samples/RenderDemo/Pages/AnimationsPage.xaml
@@ -134,6 +134,23 @@
+
@@ -152,6 +169,8 @@
+
+
diff --git a/src/Avalonia.Controls/Border.cs b/src/Avalonia.Controls/Border.cs
index 993528a12a..d60a41c543 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 BoxShadow 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);
}
///
diff --git a/src/Avalonia.Controls/Presenters/ContentPresenter.cs b/src/Avalonia.Controls/Presenters/ContentPresenter.cs
index 55ed91893c..6f3aa90d09 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 BoxShadow 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);
}
///
diff --git a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
index 126f3e35ca..63f0dc1015 100644
--- a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
+++ b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
@@ -1,5 +1,6 @@
using System;
using Avalonia.Media;
+using Avalonia.Platform;
namespace Avalonia.Controls.Utils
{
@@ -67,14 +68,17 @@ 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 size, Thickness borders, CornerRadius radii, IBrush background,
+ IBrush borderBrush, BoxShadow boxShadow)
{
if (_useComplexRendering)
{
var backgroundGeometry = _backgroundGeometryCache;
if (backgroundGeometry != null)
{
- context.DrawGeometry(background, null, backgroundGeometry);
+ // We are using platform impl here because I'm not sure if we should
+ // make the box shadow for geometries to be public API
+ context.PlatformImpl.DrawGeometry(background, null, backgroundGeometry.PlatformImpl, boxShadow);
}
var borderGeometry = _borderGeometryCache;
@@ -97,7 +101,7 @@ namespace Avalonia.Controls.Utils
var rect = new Rect(top, top, size.Width - borderThickness, size.Height - borderThickness);
- context.DrawRectangle(background, pen, rect, radii.TopLeft, radii.TopLeft);
+ context.DrawRectangle(background, pen, rect, radii.TopLeft, radii.TopLeft, boxShadow);
}
}
diff --git a/src/Avalonia.Visuals/Animation/Animators/BoxShadowAnimator.cs b/src/Avalonia.Visuals/Animation/Animators/BoxShadowAnimator.cs
new file mode 100644
index 0000000000..fced815b85
--- /dev/null
+++ b/src/Avalonia.Visuals/Animation/Animators/BoxShadowAnimator.cs
@@ -0,0 +1,21 @@
+using Avalonia.Media;
+
+namespace Avalonia.Animation.Animators
+{
+ public class BoxShadowAnimator : Animator
+ {
+ static ColorAnimator s_colorAnimator = new ColorAnimator();
+ static DoubleAnimator s_doubleAnimator = new DoubleAnimator();
+ 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)
+ };
+ }
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/BoxShadow.cs b/src/Avalonia.Visuals/Media/BoxShadow.cs
new file mode 100644
index 0000000000..2b0b17595c
--- /dev/null
+++ b/src/Avalonia.Visuals/Media/BoxShadow.cs
@@ -0,0 +1,88 @@
+using System;
+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; }
+
+ static BoxShadow()
+ {
+ Animation.Animation.RegisterAnimator(prop =>
+ typeof(BoxShadow).IsAssignableFrom(prop.PropertyType));
+ }
+
+ public bool Equals(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;
+
+ public static unsafe BoxShadow Parse(string s)
+ {
+ if (s == "none")
+ return default;
+
+ var separatorCount = 0;
+ var separators = stackalloc char[4];
+ for(var c = 0; c 4)
+ throw new FormatException("Invalid box-shadow format");
+
+ var tokenizer = new StringTokenizer(s);
+ var offsetX = tokenizer.ReadDouble(separators[0]);
+ var offsetY = tokenizer.ReadDouble(separators[1]);
+ double blur = 0;
+ double spread = 0;
+ if (separatorCount > 2)
+ blur = tokenizer.ReadDouble(separators[2]);
+ if (separatorCount > 3)
+ spread = tokenizer.ReadDouble(separators[3]);
+ var color = Media.Color.Parse(tokenizer.ReadString());
+ return new BoxShadow
+ {
+ OffsetX = offsetX,
+ OffsetY = offsetY,
+ Blur = blur,
+ Spread = spread,
+ Color = color
+ };
+ }
+
+ public Rect TransformBounds(in Rect rect)
+ => rect.Translate(new Vector(OffsetX, OffsetY)).Inflate(Spread + Blur);
+ }
+}
diff --git a/src/Avalonia.Visuals/Media/Color.cs b/src/Avalonia.Visuals/Media/Color.cs
index b9a812be80..2264fc327e 100644
--- a/src/Avalonia.Visuals/Media/Color.cs
+++ b/src/Avalonia.Visuals/Media/Color.cs
@@ -118,6 +118,50 @@ namespace Avalonia.Media
throw new FormatException($"Invalid color string: '{s}'.");
}
+ ///
+ /// Parses a color string.
+ ///
+ /// The color string.
+ /// The parsed color
+ /// The status of the operation.
+ public static bool TryParse(Span 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 4045b92c0c..49690adbed 100644
--- a/src/Avalonia.Visuals/Media/DrawingContext.cs
+++ b/src/Avalonia.Visuals/Media/DrawingContext.cs
@@ -125,7 +125,7 @@ namespace Avalonia.Media
if (brush != null || PenIsVisible(pen))
{
- PlatformImpl.DrawGeometry(brush, pen, geometry.PlatformImpl);
+ PlatformImpl.DrawGeometry(brush, pen, geometry.PlatformImpl, default);
}
}
@@ -141,11 +141,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))
{
@@ -162,7 +164,7 @@ namespace Avalonia.Media
radiusY = Math.Min(radiusY, rect.Height / 2);
}
- PlatformImpl.DrawRectangle(brush, pen, rect, radiusX, radiusY);
+ PlatformImpl.DrawRectangle(brush, pen, rect, radiusX, radiusY, boxShadow);
}
///
diff --git a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
index b89039698e..143348a463 100644
--- a/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
+++ b/src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
@@ -55,7 +55,8 @@ namespace Avalonia.Platform
/// The fill brush.
/// The stroke pen.
/// The geometry.
- void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry);
+ /// The box shadow parameters
+ void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry, BoxShadow boxShadow);
///
/// Draws a rectangle with the specified Brush and Pen.
@@ -69,11 +70,13 @@ namespace Avalonia.Platform
/// 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, Rect rect, double radiusX = 0, double radiusY = 0,
+ BoxShadow boxShadow = default);
///
/// Draws text.
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
index bfa1c08034..c5989459a9 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
@@ -97,13 +97,13 @@ namespace Avalonia.Rendering.SceneGraph
}
///
- public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
+ public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry, BoxShadow boxShadow)
{
var next = NextDrawAs();
- if (next == null || !next.Item.Equals(Transform, brush, pen, geometry))
+ if (next == null || !next.Item.Equals(Transform, brush, pen, geometry, boxShadow))
{
- Add(new GeometryNode(Transform, brush, pen, geometry, CreateChildScene(brush)));
+ Add(new GeometryNode(Transform, brush, pen, geometry, boxShadow, CreateChildScene(brush)));
}
else
{
@@ -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, Rect rect, double radiusX = 0D, double radiusY = 0D,
+ BoxShadow boxShadow = 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, radiusX, radiusY, boxShadow))
{
- Add(new RectangleNode(Transform, brush, pen, rect, radiusX, radiusY, CreateChildScene(brush)));
+ Add(new RectangleNode(Transform, brush, pen, rect, radiusX, radiusY, boxShadow, CreateChildScene(brush)));
}
else
{
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
index 2e5ca973ec..f93417a0f1 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
@@ -19,18 +19,20 @@ 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,
+ BoxShadow boxShadow,
IDictionary childScenes = null)
- : base(geometry.GetRenderBounds(pen), transform, null)
+ : base(boxShadow.TransformBounds(geometry.GetRenderBounds(pen)), transform, null)
{
Transform = transform;
Brush = brush?.ToImmutable();
Pen = pen?.ToImmutable();
Geometry = geometry;
+ BoxShadow = boxShadow;
ChildScenes = childScenes;
}
@@ -54,6 +56,8 @@ namespace Avalonia.Rendering.SceneGraph
///
public IGeometryImpl Geometry { get; }
+ public BoxShadow BoxShadow { get; }
+
///
public override IDictionary ChildScenes { get; }
@@ -64,24 +68,27 @@ 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
/// allocation of a not-yet-constructed draw operation object.
///
- public bool Equals(Matrix transform, IBrush brush, IPen pen, IGeometryImpl geometry)
+ public bool Equals(Matrix transform, IBrush brush, IPen pen, IGeometryImpl geometry,
+ BoxShadow boxShadow)
{
return transform == Transform &&
- Equals(brush, Brush) &&
- Equals(Pen, pen) &&
- Equals(geometry, Geometry);
+ BoxShadow.Equals(boxShadow, boxShadow) &&
+ Equals(brush, Brush) &&
+ Equals(Pen, pen) &&
+ Equals(geometry, Geometry);
}
///
public override void Render(IDrawingContextImpl context)
{
context.Transform = Transform;
- context.DrawGeometry(Brush, Pen, Geometry);
+ context.DrawGeometry(Brush, Pen, Geometry, BoxShadow);
}
///
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
index e48bad6433..aef7f7f260 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
@@ -29,8 +29,9 @@ namespace Avalonia.Rendering.SceneGraph
Rect rect,
double radiusX,
double radiusY,
+ BoxShadow boxShadow,
IDictionary childScenes = null)
- : base(rect, transform, pen)
+ : base(boxShadow.TransformBounds(rect), transform, pen)
{
Transform = transform;
Brush = brush?.ToImmutable();
@@ -39,6 +40,7 @@ namespace Avalonia.Rendering.SceneGraph
RadiusX = radiusX;
RadiusY = radiusY;
ChildScenes = childScenes;
+ BoxShadow = boxShadow;
}
///
@@ -70,6 +72,11 @@ namespace Avalonia.Rendering.SceneGraph
/// The radius in the Y dimension of the rounded corners.
///
public double RadiusY { get; }
+
+ ///
+ /// The parameters for the box-shadow effect
+ ///
+ public BoxShadow BoxShadow { get; }
///
public override IDictionary ChildScenes { get; }
@@ -88,11 +95,12 @@ namespace Avalonia.Rendering.SceneGraph
/// 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, Rect rect, double radiusX, double radiusY, BoxShadow boxShadow)
{
return transform == Transform &&
Equals(brush, Brush) &&
Equals(Pen, pen) &&
+ Media.BoxShadow.Equals(BoxShadow, boxShadow) &&
rect == Rect &&
Math.Abs(radiusX - RadiusX) < double.Epsilon &&
Math.Abs(radiusY - RadiusY) < double.Epsilon;
@@ -103,7 +111,7 @@ namespace Avalonia.Rendering.SceneGraph
{
context.Transform = Transform;
- context.DrawRectangle(Brush, Pen, Rect, RadiusX, RadiusY);
+ context.DrawRectangle(Brush, Pen, Rect, RadiusX, RadiusY, BoxShadow);
}
///
diff --git a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
index 9f99ed3cef..33f6e8ec33 100644
--- a/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
+++ b/src/Skia/Avalonia.Skia/DrawingContextImpl.cs
@@ -164,11 +164,21 @@ namespace Avalonia.Skia
}
///
- public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
+ public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry, BoxShadow boxShadow)
{
var impl = (GeometryImpl) geometry;
var size = geometry.Bounds.Size;
+ if(!boxShadow.IsEmpty)
+ using (var shadow = BoxShadowFilter.Create(boxShadow, _currentOpacity, false))
+ {
+ Canvas.Save();
+ Canvas.ClipPath(impl.EffectivePath, SKClipOperation.Difference);
+ Canvas.DrawPath(impl.EffectivePath, shadow.Paint);
+ Canvas.Restore();
+
+ }
+
using (var fill = brush != null ? CreatePaint(_fillPaint, brush, size) : default(PaintWrapper))
using (var stroke = pen?.Brush != null ? CreatePaint(_strokePaint, pen, size) : default(PaintWrapper))
{
@@ -184,12 +194,65 @@ namespace Avalonia.Skia
}
}
+ struct BoxShadowFilter : IDisposable
+ {
+ public SKPaint Paint;
+ private SKImageFilter _filter;
+ private SKImageFilter _dilate;
+
+ public static BoxShadowFilter Create(BoxShadow shadow, double opacity, bool skipDilate)
+ {
+ var ac = shadow.Color;
+ var spread = (int)shadow.Spread;
+ var dilate = !skipDilate && spread != 0 ? SKImageFilter.CreateDilate(spread, spread) : null;
+ 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, dilate);
+ var paint = new SKPaint { Color = SKColors.White, ImageFilter = filter };
+ return new BoxShadowFilter { Paint = paint, _filter = filter, _dilate = dilate };
+ }
+
+ public void Dispose()
+ {
+ Paint.Dispose();
+ _filter.Dispose();
+ _dilate?.Dispose();
+ }
+ }
+
///
- public void DrawRectangle(IBrush brush, IPen pen, Rect rect, double radiusX, double radiusY)
+ public void DrawRectangle(IBrush brush, IPen pen, Rect rect, double radiusX = 0D, double radiusY = 0D,
+ BoxShadow boxShadow = default)
{
var rc = rect.ToSKRect();
var isRounded = Math.Abs(radiusX) > double.Epsilon || Math.Abs(radiusY) > double.Epsilon;
+ if (!boxShadow.IsEmpty)
+ {
+ using(var shadow = BoxShadowFilter.Create(boxShadow, _currentOpacity, true))
+ {
+ var shadowRect = rc;
+ shadowRect.Inflate((float)boxShadow.Spread, (float)boxShadow.Spread);
+ Canvas.Save();
+ if (isRounded)
+ {
+ Canvas.ClipRoundRect(new SKRoundRect(rc, (float)radiusX, (float)radiusY),
+ SKClipOperation.Difference, true);
+ Canvas.DrawRoundRect(shadowRect, (float)radiusX, (float)radiusY, shadow.Paint);
+ }
+ else
+ {
+ Canvas.ClipRect(shadowRect, SKClipOperation.Difference);
+ Canvas.DrawRect(shadowRect, shadow.Paint);
+ }
+ Canvas.Restore();
+ }
+ }
+
if (brush != null)
{
using (var paint = CreatePaint(_fillPaint, brush, rect.Size))
diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
index a2e529395c..78f0f64b5f 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
@@ -199,7 +199,8 @@ namespace Avalonia.Direct2D1.Media
/// The fill brush.
/// The stroke pen.
/// The geometry.
- public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
+ public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry,
+ BoxShadow boxShadow)
{
if (brush != null)
{
@@ -228,7 +229,8 @@ namespace Avalonia.Direct2D1.Media
}
///
- public void DrawRectangle(IBrush brush, IPen pen, Rect rect, double radiusX, double radiusY)
+ public void DrawRectangle(IBrush brush, IPen pen, Rect rect, double radiusX = 0D, double radiusY = 0D,
+ BoxShadow boxShadow = default)
{
var rc = rect.ToDirect2D();
var isRounded = Math.Abs(radiusX) > double.Epsilon || Math.Abs(radiusY) > double.Epsilon;
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
index fffd36d30a..216527cf8a 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), 0, 0, 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), 0, 0, 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), 0, 0, 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), 0, 0, 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..e334fc74cd 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), 0, 0, 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), 0, 0, 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), 0, 0, 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), 0, 0, 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());
}