Browse Source

Merge remote-tracking branch 'origin/box-shadow' into feature/validate-draw-calls

# Conflicts:
#	src/Avalonia.Visuals/Media/DrawingContext.cs
test-log-box-shadow
Dan Walmsley 6 years ago
parent
commit
0d085b9a3f
  1. 28
      samples/RenderDemo/Pages/AnimationsPage.xaml
  2. 25
      src/Avalonia.Controls/Border.cs
  3. 21
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  4. 46
      src/Avalonia.Controls/Utils/BorderRenderHelper.cs
  5. 23
      src/Avalonia.Visuals/Animation/Animators/BoxShadowAnimator.cs
  6. 40
      src/Avalonia.Visuals/Animation/Animators/BoxShadowsAnimator.cs
  7. 133
      src/Avalonia.Visuals/Media/BoxShadow.cs
  8. 137
      src/Avalonia.Visuals/Media/BoxShadows.cs
  9. 44
      src/Avalonia.Visuals/Media/Color.cs
  10. 6
      src/Avalonia.Visuals/Media/DrawingContext.cs
  11. 10
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  12. 2
      src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs
  13. 10
      src/Avalonia.Visuals/Rect.cs
  14. 7
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  15. 10
      src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
  16. 44
      src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs
  17. 136
      src/Avalonia.Visuals/RoundedRect.cs
  18. 145
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  19. 2
      src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
  20. 5
      src/Skia/Avalonia.Skia/SkiaSharpExtensions.cs
  21. 2
      src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
  22. 9
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  23. 2
      tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
  24. 2
      tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
  25. 45
      tests/Avalonia.Visuals.UnitTests/Media/BoxShadowTests.cs
  26. 8
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
  27. 8
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DeferredDrawingContextImplTests.cs
  28. 2
      tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/DrawOperationTests.cs
  29. 2
      tests/Avalonia.Visuals.UnitTests/VisualTree/MockRenderInterface.cs

28
samples/RenderDemo/Pages/AnimationsPage.xaml

@ -134,6 +134,32 @@
</Animation>
</Style.Animations>
</Style>
<Style Selector="Border.Shadow">
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
<Style.Animations>
<Animation Duration="0:0:3"
IterationCount="Infinite"
PlaybackDirection="Alternate">
<KeyFrame Cue="0%">
<Setter Property="BoxShadow" Value="inset 0 0 0 2 Red, -15 -15 Green"/>
</KeyFrame>
<KeyFrame Cue="35%">
<Setter Property="BoxShadow" Value="inset 0 0 20 2 Blue, -15 20 0 0 Blue"/>
</KeyFrame>
<KeyFrame Cue="70%">
<Setter Property="BoxShadow" Value="inset 0 0 20 30 Green, 20 20 20 0 Red"/>
</KeyFrame>
<KeyFrame Cue="85%">
<Setter Property="BoxShadow" Value="inset 30 0 20 30 Green, 20 20 20 10 Red"/>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="BoxShadow" Value="inset 30 30 20 30 Green, 20 40 20 10 Red"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Styles>
</UserControl.Styles>
<Grid>
@ -152,6 +178,8 @@
<Border Classes="Test Rect4" Background="Navy"/>
<Border Classes="Test Rect5" Background="SeaGreen"/>
<Border Classes="Test Rect6" Background="Red"/>
<Border Classes="Test Shadow" CornerRadius="10" Child="{x:Null}" />
<Border Classes="Test Shadow" CornerRadius="0 30 60 0" Child="{x:Null}" />
</WrapPanel>
</StackPanel>
</Grid>

25
src/Avalonia.Controls/Border.cs

@ -33,6 +33,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
AvaloniaProperty.Register<Border, CornerRadius>(nameof(CornerRadius));
/// <summary>
/// Defines the <see cref="BoxShadow"/> property.
/// </summary>
public static readonly StyledProperty<BoxShadows> BoxShadowProperty =
AvaloniaProperty.Register<Border, BoxShadows>(nameof(BoxShadow));
private readonly BorderRenderHelper _borderRenderHelper = new BorderRenderHelper();
/// <summary>
@ -44,7 +50,8 @@ namespace Avalonia.Controls
BackgroundProperty,
BorderBrushProperty,
BorderThicknessProperty,
CornerRadiusProperty);
CornerRadiusProperty,
BoxShadowProperty);
AffectsMeasure<Border>(BorderThicknessProperty);
}
@ -83,14 +90,24 @@ namespace Avalonia.Controls
get { return GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
/// <summary>
/// Gets or sets the box shadow effect parameters
/// </summary>
public BoxShadows BoxShadow
{
get => GetValue(BoxShadowProperty);
set => SetValue(BoxShadowProperty, value);
}
/// <summary>
/// Renders the control.
/// </summary>
/// <param name="context">The drawing context.</param>
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);
}
/// <summary>
@ -110,8 +127,6 @@ namespace Avalonia.Controls
/// <returns>The space taken.</returns>
protected override Size ArrangeOverride(Size finalSize)
{
_borderRenderHelper.Update(finalSize, BorderThickness, CornerRadius);
return LayoutHelper.ArrangeChild(Child, finalSize, Padding, BorderThickness);
}
}

21
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -40,7 +40,12 @@ namespace Avalonia.Controls.Presenters
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
Border.CornerRadiusProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="BoxShadow"/> property.
/// </summary>
public static readonly StyledProperty<BoxShadows> BoxShadowProperty =
Border.BoxShadowProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="Child"/> property.
/// </summary>
@ -132,6 +137,15 @@ namespace Avalonia.Controls.Presenters
set { SetValue(CornerRadiusProperty, value); }
}
/// <summary>
/// Gets or sets the box shadow effect parameters
/// </summary>
public BoxShadows BoxShadow
{
get => GetValue(BoxShadowProperty);
set => SetValue(BoxShadowProperty, value);
}
/// <summary>
/// Gets the control displayed by the presenter.
/// </summary>
@ -274,7 +288,8 @@ namespace Avalonia.Controls.Presenters
/// <inheritdoc/>
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);
}
/// <summary>
@ -321,8 +336,6 @@ namespace Avalonia.Controls.Presenters
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
_borderRenderer.Update(finalSize, BorderThickness, CornerRadius);
return ArrangeOverrideImpl(finalSize, new Vector());
}

46
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<IPlatformRenderInterface>()
.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);
}
}

23
src/Avalonia.Visuals/Animation/Animators/BoxShadowAnimator.cs

@ -0,0 +1,23 @@
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
public class BoxShadowAnimator : Animator<BoxShadow>
{
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)
};
}
}
}

40
src/Avalonia.Visuals/Animation/Animators/BoxShadowsAnimator.cs

@ -0,0 +1,40 @@
using Avalonia.Media;
namespace Avalonia.Animation.Animators
{
public class BoxShadowsAnimator : Animator<BoxShadows>
{
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);
}
}
}

133
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<BoxShadowAnimator>(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);
}
}

137
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<BoxShadowsAnimator>(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<Count ; c++)
if (!this[c].Equals(other[c]))
return false;
return true;
}
public override bool Equals(object obj)
{
return obj is BoxShadows other && Equals(other);
}
public override int GetHashCode()
{
unchecked
{
int hashCode = 0;
foreach (var s in this)
hashCode = (hashCode * 397) ^ s.GetHashCode();
return hashCode;
}
}
}
}

44
src/Avalonia.Visuals/Media/Color.cs

@ -118,6 +118,50 @@ namespace Avalonia.Media
throw new FormatException($"Invalid color string: '{s}'.");
}
/// <summary>
/// Parses a color string.
/// </summary>
/// <param name="s">The color string.</param>
/// <param name="color">The parsed color</param>
/// <returns>The status of the operation.</returns>
public static bool TryParse(ReadOnlySpan<char> 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;
}
/// <summary>
/// Returns the string representation of the color.
/// </summary>

6
src/Avalonia.Visuals/Media/DrawingContext.cs

@ -155,11 +155,13 @@ namespace Avalonia.Media
/// <param name="radiusY">The radius in the Y dimension of the rounded corners.
/// This value will be clamped to the range of 0 to Height/2
/// </param>
/// <param name="boxShadow">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, 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
{

10
src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs

@ -63,17 +63,13 @@ namespace Avalonia.Platform
/// <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="rect">The rectangle bounds.</param>
/// <param name="radiusX">The radius in the X dimension of the rounded corners.
/// This value will be clamped to the range of 0 to Width/2
/// </param>
/// <param name="radiusY">The radius in the Y dimension of the rounded corners.
/// This value will be clamped to the range of 0 to Height/2
/// </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>
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);
/// <summary>
/// Draws text.

2
src/Avalonia.Visuals/Platform/IPlatformRenderInterface.cs

@ -116,5 +116,7 @@ namespace Avalonia.Platform
/// <param name="width">The glyph run's width.</param>
/// <returns></returns>
IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun, out double width);
bool SupportsIndividualRoundRects { get; }
}
}

10
src/Avalonia.Visuals/Rect.cs

@ -132,6 +132,16 @@ namespace Avalonia
/// Gets the bottom position of the rectangle.
/// </summary>
public double Bottom => _y + _height;
/// <summary>
/// Gets the left position.
/// </summary>
public double Left => _x;
/// <summary>
/// Gets the top position.
/// </summary>
public double Top => _y;
/// <summary>
/// Gets the top left point of the rectangle.

7
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@ -149,13 +149,14 @@ namespace Avalonia.Rendering.SceneGraph
}
/// <inheritdoc/>
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<RectangleNode>();
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
{

10
src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs

@ -19,8 +19,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="pen">The stroke pen.</param>
/// <param name="geometry">The geometry.</param>
/// <param name="childScenes">Child scenes for drawing visual brushes.</param>
public GeometryNode(
Matrix transform,
public GeometryNode(Matrix transform,
IBrush brush,
IPen pen,
IGeometryImpl geometry,
@ -64,6 +63,7 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="brush">The fill of the other draw operation.</param>
/// <param name="pen">The stroke of the other draw operation.</param>
/// <param name="geometry">The geometry of the other draw operation.</param>
/// <param name="boxShadow">The box shadow parameters</param>
/// <returns>True if the draw operations are the same, otherwise false.</returns>
/// <remarks>
/// 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);
}
/// <inheritdoc/>

44
src/Avalonia.Visuals/Rendering/SceneGraph/RectangleNode.cs

@ -19,26 +19,23 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="brush">The fill brush.</param>
/// <param name="pen">The stroke pen.</param>
/// <param name="rect">The rectangle to draw.</param>
/// <param name="radiusY">The radius in the Y dimension of the rounded corners.</param>
/// <param name="radiusX">The radius in the X dimension of the rounded corners.</param>
/// <param name="boxShadow">The box shadow parameters</param>
/// <param name="childScenes">Child scenes for drawing visual brushes.</param>
public RectangleNode(
Matrix transform,
IBrush brush,
IPen pen,
Rect rect,
double radiusX,
double radiusY,
RoundedRect rect,
BoxShadows boxShadows,
IDictionary<IVisual, Scene> 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;
}
/// <summary>
@ -59,17 +56,12 @@ namespace Avalonia.Rendering.SceneGraph
/// <summary>
/// Gets the rectangle to draw.
/// </summary>
public Rect Rect { get; }
/// <summary>
/// The radius in the X dimension of the rounded corners.
/// </summary>
public double RadiusX { get; }
public RoundedRect Rect { get; }
/// <summary>
/// The radius in the Y dimension of the rounded corners.
/// The parameters for the box-shadow effect
/// </summary>
public double RadiusY { get; }
public BoxShadows BoxShadows { get; }
/// <inheritdoc/>
public override IDictionary<IVisual, Scene> ChildScenes { get; }
@ -81,21 +73,19 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="brush">The fill of the other draw operation.</param>
/// <param name="pen">The stroke of the other draw operation.</param>
/// <param name="rect">The rectangle of the other draw operation.</param>
/// <param name="radiusX"></param>
/// <param name="radiusY"></param>
/// <param name="boxShadow">The box shadow parameters of the other draw operation</param>
/// <returns>True if the draw operations are the same, otherwise false.</returns>
/// <remarks>
/// The properties of the other draw operation are passed in as arguments to prevent
/// allocation of a not-yet-constructed draw operation object.
/// </remarks>
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);
}
/// <inheritdoc/>
@ -103,7 +93,7 @@ namespace Avalonia.Rendering.SceneGraph
{
context.Transform = Transform;
context.DrawRectangle(Brush, Pen, Rect, RadiusX, RadiusY);
context.DrawRectangle(Brush, Pen, Rect, BoxShadows);
}
/// <inheritdoc/>
@ -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);
}
}

136
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]);
}
}
}

145
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();
/// <summary>
/// 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;
}
/// <inheritdoc />
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
{

2
src/Skia/Avalonia.Skia/PlatformRenderInterface.cs

@ -255,5 +255,7 @@ namespace Avalonia.Skia
return new GlyphRunImpl(textBlob);
}
public bool SupportsIndividualRoundRects => true;
}
}

5
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)
{

2
src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs

@ -240,5 +240,7 @@ namespace Avalonia.Direct2D1
return new GlyphRunImpl(run);
}
public bool SupportsIndividualRoundRects => false;
}
}

9
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -228,9 +228,14 @@ namespace Avalonia.Direct2D1.Media
}
/// <inheritdoc />
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)

2
tests/Avalonia.Benchmarks/NullRenderingPlatform.cs

@ -76,5 +76,7 @@ namespace Avalonia.Benchmarks
return new NullGlyphRun();
}
public bool SupportsIndividualRoundRects => true;
}
}

2
tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs

@ -84,5 +84,7 @@ namespace Avalonia.UnitTests
width = 0;
return Mock.Of<IGlyphRunImpl>();
}
public bool SupportsIndividualRoundRects { get; set; }
}
}

45
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);
}
}
}
}

8
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

@ -473,7 +473,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
var animation = new BehaviorSubject<double>(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<double>(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<double>(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);
}
}

8
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);

2
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());
}

2
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();

Loading…
Cancel
Save