Skip to content

Commit

Permalink
fix(Shapes): Match UWP StrokeThickness behaviour
Browse files Browse the repository at this point in the history
  • Loading branch information
kazo0 committed Jan 22, 2021
1 parent b47cf58 commit 63da357
Show file tree
Hide file tree
Showing 12 changed files with 312 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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}";
}

}
}
7 changes: 7 additions & 0 deletions src/SamplesApp/UITests.Shared/UITests.Shared.projitems
Original file line number Diff line number Diff line change
Expand Up @@ -3757,6 +3757,10 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Shapes\Shapes_Default_StrokeThickness.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Shapes\StretchPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
Expand Down Expand Up @@ -5983,6 +5987,9 @@
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Shapes\ShapeControlsPage.xaml.cs">
<DependentUpon>ShapeControlsPage.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Shapes\Shapes_Default_StrokeThickness.xaml.cs">
<DependentUpon>Shapes_Default_StrokeThickness.xaml</DependentUpon>
</Compile>
<Compile Include="$(MSBuildThisFileDirectory)Windows_UI_Xaml_Shapes\StretchPage.xaml.cs">
<DependentUpon>StretchPage.xaml</DependentUpon>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<UserControl x:Class="UITests.Windows_UI_Xaml_Shapes.Shapes_Default_StrokeThickness"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UITests.Windows_UI_Xaml_Shapes"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:uc="using:Uno.UI.Samples.Converters"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<UserControl.Resources>
<uc:FromNullableBoolToVisibilityConverter x:Key="FromNullableBoolToVisibilityConverter"
VisibilityIfTrue="Visible" />
</UserControl.Resources>
<StackPanel Margin="20"
HorizontalAlignment="Center">
<StackPanel>
<StackPanel Orientation="Horizontal">
<RadioButton Content="MyLine"
x:Name="MyLineSelector"
GroupName="ShapeSelector" />
<RadioButton Content="MyRect"
x:Name="MyRectSelector"
GroupName="ShapeSelector" />
<RadioButton Content="MyPolyline"
x:Name="MyPolylineSelector"
GroupName="ShapeSelector" />
</StackPanel>
<StackPanel Orientation="Horizontal">
<RadioButton Content="MyPolygon"
x:Name="MyPolygonSelector"
GroupName="ShapeSelector" />
<RadioButton Content="MyEllipse"
x:Name="MyEllipseSelector"
GroupName="ShapeSelector" />
<RadioButton Content="MyPath"
x:Name="MyPathSelector"
GroupName="ShapeSelector" />
</StackPanel>
<Button x:Name="StrokeThicknessButton"
Content="Set StrokeThickness" />
</StackPanel>

<Grid BorderBrush="HotPink"
BorderThickness="1"
Background="White"
Width="300"
Height="300"
x:Name="TestZone">
<Grid x:Name="MyLineGrid"
Height="10"
Width="100"
Visibility="{Binding IsChecked, ElementName=MyLineSelector, Converter={StaticResource FromNullableBoolToVisibilityConverter}}">
<Line X1="0"
Y1="5"
X2="100"
Y2="5"
x:Name="MyLine"
Stroke="Red" />
<Border BorderBrush="Green"
BorderThickness="1"
x:Name="MyLineTarget"
Height="10"
Width="10" />
</Grid>
<Grid x:Name="MyRectGrid"
Width="100"
Height="100"
Visibility="{Binding IsChecked, ElementName=MyRectSelector, Converter={StaticResource FromNullableBoolToVisibilityConverter}}">
<Rectangle Fill="Blue"
Width="100"
x:Name="MyRect"
Stroke="Red"
Height="100" />
</Grid>
<Grid x:Name="MyPolylineGrid"
Width="101"
Height="101"
Visibility="{Binding IsChecked, ElementName=MyPolylineSelector, Converter={StaticResource FromNullableBoolToVisibilityConverter}}">
<Polyline Points="2,2, 100,2, 100,100, 2,100, 2,2"

x:Name="MyPolyline"
Stroke="Red"/>
</Grid>

<Grid x:Name="MyPolygonGrid"
Width="101"
Height="101"
Visibility="{Binding IsChecked, ElementName=MyPolygonSelector, Converter={StaticResource FromNullableBoolToVisibilityConverter}}">
<Polygon
Stroke="Red"
x:Name="MyPolygon"
Points="2,2, 100,2, 100,100, 2,100, 2,2"/>
</Grid>

<Grid x:Name="MyEllipseGrid"
Width="100"
Height="100"
Visibility="{Binding IsChecked, ElementName=MyEllipseSelector, Converter={StaticResource FromNullableBoolToVisibilityConverter}}">
<Ellipse
x:Name="MyEllipse"
Stroke="Red"
Height="100"
Width="100" />
</Grid>

<Grid x:Name="MyPathGrid"
Visibility="{Binding IsChecked, ElementName=MyPathSelector, Converter={StaticResource FromNullableBoolToVisibilityConverter}}">
<Path Stroke="Red"
x:Name="MyPath"
Data="M 10,100 C 10,300 100,-200 300,100" />
<Border BorderBrush="Green"
BorderThickness="1"
x:Name="MyPathTarget"
HorizontalAlignment="Left"
Margin="20, -3, 0, 0"
Width="3"
Height="3" />
</Grid>
</Grid>
</StackPanel>
</UserControl>
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
}
2 changes: 1 addition & 1 deletion src/Uno.UI/UI/Xaml/Shapes/ArbitraryShapeBase.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ private IDisposable BuildDrawableLayer()
}

// Draw the contour
if (stroke != null)
if (HasStroke)
{
using (var strokeBrush = new Paint(stroke.GetStrokePaint(drawArea)))
{
Expand Down
6 changes: 4 additions & 2 deletions src/Uno.UI/UI/Xaml/Shapes/ArbitraryShapeBase.wasm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion src/Uno.UI/UI/Xaml/Shapes/Shape.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/Uno.UI/UI/Xaml/Shapes/Shape.Skia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ private void UpdateStrokeThickness()
{
if (_pathSpriteShape != null)
{
_pathSpriteShape.StrokeThickness = (float)StrokeThickness;
_pathSpriteShape.StrokeThickness = (float)ActualStrokeThickness;
}
}

Expand Down
14 changes: 7 additions & 7 deletions src/Uno.UI/UI/Xaml/Shapes/Shape.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ public abstract partial class Shape : FrameworkElement
private readonly SerialDisposable _brushChanged = new SerialDisposable();

/// <summary>
/// Returns StrokeThickness or 0.0 if Stroke is <c>null</c>
/// Returns 0.0 if Stroke is <c>null</c>, otherwise, StrokeThickness
/// </summary>
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);
}
/// <remarks>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
///</remarks>
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)
Expand Down
4 changes: 2 additions & 2 deletions src/Uno.UI/UI/Xaml/Shapes/Shape.layout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 63da357

Please sign in to comment.