diff --git a/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Shapes/Shapes_Tests.cs b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Shapes/Shapes_Tests.cs
index ad70de97361b..27cf7fbb4bd6 100644
--- a/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Shapes/Shapes_Tests.cs
+++ b/src/SamplesApp/SamplesApp.UITests/Windows_UI_Xaml_Shapes/Shapes_Tests.cs
@@ -9,6 +9,7 @@
using SamplesApp.UITests.TestFramework;
using Uno.UITest.Helpers;
using Uno.UITest.Helpers.Queries;
+using Uno.UITests.Helpers;
namespace SamplesApp.UITests.Windows_UI_Xaml_Shapes
{
@@ -130,5 +131,102 @@ public void Check_Bound_Color()
ImageAssert.HasColorAt(screenshot, bounds.CenterX, bounds.CenterY, Color.Blue);
}
+
+ [Test]
+ [AutoRetry]
+ public void Default_StrokeThickness()
+ {
+ const string red = "#FF0000";
+ const string reddish = "#FF8080";
+
+ var shapeExpectations = new[]
+ {
+ new ShapeExpectation
+ {
+ Name = "MyLine",
+ Offsets = new [] {0, 0, 0, 0},
+ Colors = red,
+ },
+ new ShapeExpectation
+ {
+ Name = "MyRect",
+ Offsets = new [] {0, 0, -1, -1},
+ Colors = red,
+ },
+ new ShapeExpectation
+ {
+ Name = "MyPolyline",
+ Offsets = new [] {2, 2, -1, -1},
+ Colors = AppInitializer.GetLocalPlatform() == Platform.Browser ? reddish : red,
+ },
+ new ShapeExpectation
+ {
+ Name = "MyPolygon",
+ Offsets = new [] {2, 2, -1, -1},
+ Colors = AppInitializer.GetLocalPlatform() == Platform.Browser ? reddish : red,
+ },
+ new ShapeExpectation
+ {
+ Name = "MyEllipse",
+ Offsets = new [] {0, 0, -1, -1},
+ Colors = red,
+ },
+ new ShapeExpectation
+ {
+ Name = "MyPath",
+ Offsets = new [] {0, 0, 0, 0},
+ Colors = red,
+ },
+ };
+ Run("UITests.Windows_UI_Xaml_Shapes.Shapes_Default_StrokeThickness");
+
+ _app.WaitForElement("TestZone");
+
+ foreach(var expectation in shapeExpectations)
+ {
+ _app.Marked($"{expectation.Name}Selector").FastTap();
+
+ using var screenshot = TakeScreenshot($"{expectation}");
+ if (expectation.Name == "MyLine" || expectation.Name == "MyPath")
+ {
+ var targetRect = _app.GetPhysicalRect($"{expectation.Name}Target");
+ ImageAssert.DoesNotHaveColorAt(screenshot, targetRect.CenterX, targetRect.CenterY, Color.White);
+
+ _app.Marked("StrokeThicknessButton").FastTap();
+
+ using var zeroStrokeThicknessScreenshot = TakeScreenshot($"{expectation.Name}_0_StrokeThickness");
+ ImageAssert.HasColorAt(zeroStrokeThicknessScreenshot, targetRect.CenterX, targetRect.CenterY, Color.White);
+ }
+ else
+ {
+ var shapeContainer = _app.GetPhysicalRect($"{expectation}Grid");
+
+ ImageAssert.HasColorAt(screenshot, shapeContainer.X + expectation.Offsets[0], shapeContainer.CenterY, expectation.Colors, tolerance: 15);
+ ImageAssert.HasColorAt(screenshot, shapeContainer.CenterX, shapeContainer.Y + expectation.Offsets[1], expectation.Colors, tolerance: 15);
+ ImageAssert.HasColorAt(screenshot, shapeContainer.Right + expectation.Offsets[2], shapeContainer.CenterY, expectation.Colors, tolerance: 15);
+ ImageAssert.HasColorAt(screenshot, shapeContainer.CenterX, shapeContainer.Bottom + expectation.Offsets[3], expectation.Colors, tolerance: 15);
+
+ _app.Marked("StrokeThicknessButton").FastTap();
+
+ using var zeroStrokeThicknessScreenshot = TakeScreenshot($"{expectation.Name}_0_StrokeThickness");
+
+ ImageAssert.DoesNotHaveColorAt(zeroStrokeThicknessScreenshot, shapeContainer.X + expectation.Offsets[0], shapeContainer.CenterY, expectation.Colors);
+ ImageAssert.DoesNotHaveColorAt(zeroStrokeThicknessScreenshot, shapeContainer.CenterX, shapeContainer.Y + expectation.Offsets[1], expectation.Colors);
+ ImageAssert.DoesNotHaveColorAt(zeroStrokeThicknessScreenshot, shapeContainer.Right + expectation.Offsets[2], shapeContainer.CenterY, expectation.Colors);
+ ImageAssert.DoesNotHaveColorAt(zeroStrokeThicknessScreenshot, shapeContainer.CenterX, shapeContainer.Bottom + expectation.Offsets[3], expectation.Colors);
+ }
+ }
+
+ }
+
+ private struct ShapeExpectation
+ {
+ public string Name { get; set; }
+ public int[] Offsets { get; set; }
+ public string Colors { get; set; }
+
+ public override string ToString() => $"{Name}";
+ }
+
}
}
diff --git a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems
index f79711c4d4c9..64374c65776a 100644
--- a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems
+++ b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems
@@ -3757,6 +3757,10 @@
Designer
MSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+
Designer
MSBuild:Compile
@@ -5983,6 +5987,9 @@
ShapeControlsPage.xaml
+
+ Shapes_Default_StrokeThickness.xaml
+
StretchPage.xaml
diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Shapes/Shapes_Default_StrokeThickness.xaml b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Shapes/Shapes_Default_StrokeThickness.xaml
new file mode 100644
index 000000000000..36e5d4f050e5
--- /dev/null
+++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Shapes/Shapes_Default_StrokeThickness.xaml
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Shapes/Shapes_Default_StrokeThickness.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Shapes/Shapes_Default_StrokeThickness.xaml.cs
new file mode 100644
index 000000000000..3db86b82b515
--- /dev/null
+++ b/src/SamplesApp/UITests.Shared/Windows_UI_Xaml_Shapes/Shapes_Default_StrokeThickness.xaml.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices.WindowsRuntime;
+using Uno.UI.Samples.Controls;
+using Windows.Foundation;
+using Windows.Foundation.Collections;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Controls.Primitives;
+using Windows.UI.Xaml.Data;
+using Windows.UI.Xaml.Input;
+using Windows.UI.Xaml.Media;
+using Windows.UI.Xaml.Navigation;
+
+// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236
+
+namespace UITests.Windows_UI_Xaml_Shapes
+{
+ [Sample("Shapes", "Default_StrokeThickness")]
+ public sealed partial class Shapes_Default_StrokeThickness : UserControl
+ {
+ public double MyStrokeThickness { get; set; } = 0d;
+
+ public Shapes_Default_StrokeThickness()
+ {
+ this.InitializeComponent();
+ StrokeThicknessButton.Click += StrokeThicknessButton_Click;
+ }
+
+ private void StrokeThicknessButton_Click(object sender, RoutedEventArgs e)
+ {
+ if (MyLineSelector.IsChecked ?? false)
+ {
+ MyLine.StrokeThickness = MyStrokeThickness;
+ }
+ if (MyRectSelector.IsChecked ?? false)
+ {
+ MyRect.StrokeThickness = MyStrokeThickness;
+ }
+ if (MyPolylineSelector.IsChecked ?? false)
+ {
+ MyPolyline.StrokeThickness = MyStrokeThickness;
+ }
+ if (MyPolygonSelector.IsChecked ?? false)
+ {
+ MyPolygon.StrokeThickness = MyStrokeThickness;
+ }
+ if (MyEllipseSelector.IsChecked ?? false)
+ {
+ MyEllipse.StrokeThickness = MyStrokeThickness;
+ }
+ if (MyPathSelector.IsChecked ?? false)
+ {
+ MyPath.StrokeThickness = MyStrokeThickness;
+ }
+ }
+ }
+}
diff --git a/src/Uno.UI/UI/Xaml/Shapes/ArbitraryShapeBase.Android.cs b/src/Uno.UI/UI/Xaml/Shapes/ArbitraryShapeBase.Android.cs
index 18879287dea2..5b9c23a580c8 100644
--- a/src/Uno.UI/UI/Xaml/Shapes/ArbitraryShapeBase.Android.cs
+++ b/src/Uno.UI/UI/Xaml/Shapes/ArbitraryShapeBase.Android.cs
@@ -134,7 +134,7 @@ private IDisposable BuildDrawableLayer()
}
// Draw the contour
- if (stroke != null)
+ if (HasStroke)
{
using (var strokeBrush = new Paint(stroke.GetStrokePaint(drawArea)))
{
diff --git a/src/Uno.UI/UI/Xaml/Shapes/ArbitraryShapeBase.wasm.cs b/src/Uno.UI/UI/Xaml/Shapes/ArbitraryShapeBase.wasm.cs
index c8cee33a30c0..6d0bdeeea2db 100644
--- a/src/Uno.UI/UI/Xaml/Shapes/ArbitraryShapeBase.wasm.cs
+++ b/src/Uno.UI/UI/Xaml/Shapes/ArbitraryShapeBase.wasm.cs
@@ -174,12 +174,14 @@ private Rect GetBBoxOfChildrenWithStrokeThickness()
private Rect GetBBoxWithStrokeThickness(UIElement element)
{
var bbox = element.GetBBox();
- if (Stroke == null || StrokeThickness < double.Epsilon)
+ var strokeThickness = ActualStrokeThickness;
+
+ if (Stroke == null || strokeThickness < double.Epsilon)
{
return bbox;
}
- var halfStrokeThickness = StrokeThickness / 2;
+ var halfStrokeThickness = strokeThickness / 2;
var x = Math.Min(bbox.X, bbox.Left - halfStrokeThickness);
var y = Math.Min(bbox.Y, bbox.Top - halfStrokeThickness);
diff --git a/src/Uno.UI/UI/Xaml/Shapes/Shape.Android.cs b/src/Uno.UI/UI/Xaml/Shapes/Shape.Android.cs
index ff34d184ac82..47942346b5ae 100644
--- a/src/Uno.UI/UI/Xaml/Shapes/Shape.Android.cs
+++ b/src/Uno.UI/UI/Xaml/Shapes/Shape.Android.cs
@@ -14,7 +14,7 @@ public partial class Shape
{
protected bool HasStroke
{
- get { return StrokeThickness > 0 && Stroke != null; }
+ get { return Stroke != null && ActualStrokeThickness > 0; }
}
internal double PhysicalStrokeThickness
diff --git a/src/Uno.UI/UI/Xaml/Shapes/Shape.Skia.cs b/src/Uno.UI/UI/Xaml/Shapes/Shape.Skia.cs
index 17544d45d81a..547024b72763 100644
--- a/src/Uno.UI/UI/Xaml/Shapes/Shape.Skia.cs
+++ b/src/Uno.UI/UI/Xaml/Shapes/Shape.Skia.cs
@@ -93,7 +93,7 @@ private void UpdateStrokeThickness()
{
if (_pathSpriteShape != null)
{
- _pathSpriteShape.StrokeThickness = (float)StrokeThickness;
+ _pathSpriteShape.StrokeThickness = (float)ActualStrokeThickness;
}
}
diff --git a/src/Uno.UI/UI/Xaml/Shapes/Shape.cs b/src/Uno.UI/UI/Xaml/Shapes/Shape.cs
index dccaf630183a..746fbfcff69f 100644
--- a/src/Uno.UI/UI/Xaml/Shapes/Shape.cs
+++ b/src/Uno.UI/UI/Xaml/Shapes/Shape.cs
@@ -20,14 +20,14 @@ public abstract partial class Shape : FrameworkElement
private readonly SerialDisposable _brushChanged = new SerialDisposable();
///
- /// Returns StrokeThickness or 0.0 if Stroke is null
+ /// Returns 0.0 if Stroke is null, otherwise, StrokeThickness
///
- private protected double ActualStrokeThickness
- {
- //Path does not need to define a stroke, in that case StrokeThickness should just return 0
- //Other shapes like Ellipse and Polygon will not draw if Stroke is null so returning 0 will have no effect
- get => Stroke == null ? DefaultStrokeThicknessWhenNoStrokeDefined : LayoutRound(StrokeThickness);
- }
+ /// Path does not need to define a stroke, in that case StrokeThickness should just return 0.
+ /// Other shapes like Ellipse and Polygon will not draw if Stroke is null so returning 0 will have no effect
+ ///
+ private protected double ActualStrokeThickness => Stroke == null
+ ? DefaultStrokeThicknessWhenNoStrokeDefined
+ : LayoutRound(StrokeThickness);
#region Fill Dependency Property
//This field is never accessed. It just exists to create a reference, because the DP causes issues with ImageBrush of the backing bitmap being prematurely garbage-collected. (Bug with ConditionalWeakTable? https://bugzilla.xamarin.com/show_bug.cgi?id=21620)
diff --git a/src/Uno.UI/UI/Xaml/Shapes/Shape.layout.cs b/src/Uno.UI/UI/Xaml/Shapes/Shape.layout.cs
index fdf7dc0c3e55..20a47188be72 100644
--- a/src/Uno.UI/UI/Xaml/Shapes/Shape.layout.cs
+++ b/src/Uno.UI/UI/Xaml/Shapes/Shape.layout.cs
@@ -220,7 +220,7 @@ private protected Size MeasureAbsoluteShape(Size availableSize, NativePath path)
var stretch = Stretch;
var userSize = GetUserSizes();
var (userMinSize, userMaxSize) = GetMinMax(userSize);
- var strokeThickness = StrokeThickness;
+ var strokeThickness = ActualStrokeThickness;
var pathBounds = GetPathBoundingBox(path); // The BoundingBox does also contains bezier anchors even if out of geometry
var pathSize = (Size)pathBounds.Size;
@@ -335,7 +335,7 @@ private protected Size ArrangeAbsoluteShape(Size finalSize, NativePath path)
var vertical = VerticalAlignment;
var stretch = Stretch;
var userSize = GetUserSizes();
- var strokeThickness = StrokeThickness;
+ var strokeThickness = ActualStrokeThickness;
var halfStrokeThickness = strokeThickness / 2.0;
var pathBounds = GetPathBoundingBox(path); // The BoundingBox does also contains bezier anchors even if out of geometry
var pathSize = (Size)pathBounds.Size;
diff --git a/src/Uno.UI/UI/Xaml/Shapes/Shape.wasm.cs b/src/Uno.UI/UI/Xaml/Shapes/Shape.wasm.cs
index 26b48cd778ac..c2497ac1a97a 100644
--- a/src/Uno.UI/UI/Xaml/Shapes/Shape.wasm.cs
+++ b/src/Uno.UI/UI/Xaml/Shapes/Shape.wasm.cs
@@ -160,16 +160,20 @@ partial void OnStrokeUpdatedPartial()
partial void OnStrokeThicknessUpdatedPartial()
{
var svgElement = GetMainSvgElement();
+ var strokeThickness = ActualStrokeThickness;
- var strokeThickness = StrokeThickness;
- if (strokeThickness == default)
+ if (Stroke == null)
{
- svgElement.ResetStyle("stroke-width");
+ svgElement.SetStyle("stroke-width", $"{DefaultStrokeThicknessWhenNoStrokeDefined}px");
}
- else
+ else if (strokeThickness != 1.0d)
{
svgElement.SetStyle("stroke-width", $"{strokeThickness}px");
}
+ else
+ {
+ svgElement.ResetStyle("stroke-width");
+ }
}
partial void OnStrokeDashArrayUpdatedPartial()
diff --git a/src/Uno.UWP/UI/Composition/CompositionSpriteShape.Skia.cs b/src/Uno.UWP/UI/Composition/CompositionSpriteShape.Skia.cs
index 0b28d18e5830..80412de3d761 100644
--- a/src/Uno.UWP/UI/Composition/CompositionSpriteShape.Skia.cs
+++ b/src/Uno.UWP/UI/Composition/CompositionSpriteShape.Skia.cs
@@ -23,7 +23,7 @@ internal override void Render(SKSurface surface, SKImageInfo info)
surface.Canvas.DrawPath(geometrySource.Geometry, _fillPaint);
}
- if (StrokeBrush != null)
+ if (StrokeBrush != null && StrokeThickness > 0)
{
var strokePaint = TryCreateStrokePaint();