Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 10 additions & 4 deletions src/Controls/src/Core/Shapes/Shape.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,10 +280,16 @@ internal void TransformPathForBounds(PathF path, Graphics.Rect viewBounds)
// since default GetBoundsByFlattening(0.001) returns incorrect results for curves
RectF pathBounds = path.GetBoundsByFlattening(1);

viewBounds.X += StrokeThickness / 2;
viewBounds.Y += StrokeThickness / 2;
viewBounds.Width -= StrokeThickness;
viewBounds.Height -= StrokeThickness;
// Only apply stroke inset if there is an actual stroke.
// For shapes with no stroke shrinking the bounds by StrokeThickness was
// effectively collapsing very small heights into a barely visible line.
if (Stroke is not null && StrokeThickness > 0)
{
viewBounds.X += StrokeThickness / 2;
viewBounds.Y += StrokeThickness / 2;
viewBounds.Width -= StrokeThickness;
viewBounds.Height -= StrokeThickness;
}

Matrix3x2 transform;

Expand Down
88 changes: 48 additions & 40 deletions src/Controls/tests/DeviceTests/Elements/Border/BorderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,53 +130,61 @@ public async Task RoundedRectangleBorderLayoutIsCorrect()
await CreateHandlerAsync<BorderHandler>(border);
await CreateHandlerAsync<LayoutHandler>(grid);

Point[] corners = new Point[4]
{
new Point(0, 0), // upper-left corner
new Point(100, 0), // upper-right corner
new Point(0, 100), // lower-left corner
new Point(100, 100) // lower-right corner
};
// This test validates both PR #17087 (Android RoundRectangle fix) and Issue #31330 fix:
// - PR #17087: RoundRectangle.GetPath() should NOT compensate for stroke; callers handle it
// - Issue #31330: Shape.TransformPathForBounds only applies stroke inset when Shape HAS its own stroke
//
// With Issue #31330 fix, RoundRectangle (which has NO stroke) fills entire 100x100 cell.
// Border renders its 4px stroke on top of this path.
//
// Note: Before Issue #31330, the shape was incorrectly applying stroke inset even without
// its own stroke, which shifted corners inward by strokeThickness/2 = 2px. The test points
// need to account for this change.

var points = new Point[16];
var colors = new Color[16];
int index = 0;

// To calculate the x and y offsets (from the center) for a 45-45-90 triangle, we can use the radius as the hypotenuse
// which means that the x and y offsets would be radius / sqrt(2).
var xy = radius - (radius / Math.Sqrt(2));
// For rounded corners, test along the 45-degree diagonal
// At 45°, corner curve is at: radius - (radius / sqrt(2)) ≈ 5.86 for radius=20
var outerXY = radius - (radius / Math.Sqrt(2));
var innerXY = outerXY + strokeThickness;

// Test all 4 corners with 4 test points each
Point[] corners = new Point[4]
{
new Point(0, 0), // upper-left
new Point(100, 0), // upper-right
new Point(0, 100), // lower-left
new Point(100, 100) // lower-right
};

for (int i = 0; i < corners.Length; i++)
{
int xdir = i == 0 || i == 2 ? 1 : -1;
int ydir = i == 0 || i == 1 ? 1 : -1;

// This marks the outside edge of the rounded corner.
var outerX = corners[i].X + (xdir * xy);
var outerY = corners[i].Y + (ydir * xy);

// Add stroke thickness to find the inner edge of the rounded corner.
var innerX = outerX + (xdir * strokeThickness);
var innerY = outerY + (ydir * strokeThickness);

// Verify that the color outside of the rounded corner is the parent's color (White)
points[index] = new Point(outerX - (xdir * 0.25), outerY - (ydir * 0.25));
colors[index] = Colors.White;
index++;

// Verify that the rounded corner stroke is where we expect it to be
points[index] = new Point(outerX + (xdir * 1.25), outerY + (ydir * 1.25));
colors[index] = stroke;
index++;

points[index] = new Point(innerX - (xdir * 1.25), innerY - (ydir * 1.25));
colors[index] = stroke;
index++;

// Verify that the background color starts where we'd expect it to start
points[index] = new Point(innerX + (xdir * 0.25), innerY + (ydir * 0.25));
colors[index] = border.BackgroundColor;
index++;
int xdir = (i == 0 || i == 2) ? 1 : -1; // +1 for left, -1 for right
int ydir = (i == 0 || i == 1) ? 1 : -1; // +1 for top, -1 for bottom

var baseX = corners[i].X;
var baseY = corners[i].Y;

// With the new fix, corners are 2px further out than before (no incorrect inset)
// Adjust test points to account for the shape now filling the entire cell
// Original test used: outside at -0.25, stroke at +1.25, stroke at -1.25 from inner, bg at +0.25 from inner

// Point 1: Outside corner (in grid's white background)
points[i * 4] = new Point(baseX + xdir * (outerXY - 2), baseY + ydir * (outerXY - 2));
colors[i * 4] = Colors.White;

// Point 2: In stroke, near outer edge
points[i * 4 + 1] = new Point(baseX + xdir * (outerXY + 0.25), baseY + ydir * (outerXY + 0.25));
colors[i * 4 + 1] = stroke;

// Point 3: In stroke, near inner edge
points[i * 4 + 2] = new Point(baseX + xdir * (outerXY + 2), baseY + ydir * (outerXY + 2));
colors[i * 4 + 2] = stroke;

// Point 4: Inside border (in red background)
points[i * 4 + 3] = new Point(baseX + xdir * (innerXY + 0.25), baseY + ydir * (innerXY + 0.25));
colors[i * 4 + 3] = border.BackgroundColor;
}

await AssertColorsAtPoints(grid, typeof(LayoutHandler), colors, points);
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
121 changes: 121 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using Microsoft.Maui.Controls.Shapes;

namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 31330, "Rectangle renders as thin line instead of filled shape for small height values", PlatformAffected.Android | PlatformAffected.iOS)]
public class Issue31330 : ContentPage
{
public Issue31330()
{
var scrollView = new ScrollView
{
Orientation = ScrollOrientation.Both,
VerticalScrollBarVisibility = ScrollBarVisibility.Always,
HorizontalScrollBarVisibility = ScrollBarVisibility.Always,
};

var grid = new Grid
{
WidthRequest = 800,
HeightRequest = 600,
BackgroundColor = Colors.LightGray,
RowSpacing = 10,
Padding = 20
};

grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Star });

// Instructions
var instructions = new Label
{
Text = "Test passes if:\n1. Red BoxView (height 1.2) is visible as a filled rectangle\n2. Blue Rectangle (height 1.2) is visible as a filled rectangle (not a thin line)\n3. Both should have similar appearance",
FontAttributes = FontAttributes.Bold,
AutomationId = "Instructions"
};
Grid.SetRow(instructions, 0);
grid.Children.Add(instructions);

// BoxView with small height (reference for correct rendering)
var boxViewLabel = new Label { Text = "BoxView (height 1.2):" };
Grid.SetRow(boxViewLabel, 1);
grid.Children.Add(boxViewLabel);

var boxView = new BoxView
{
Color = Colors.Red,
WidthRequest = 50,
HeightRequest = 1.2,
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.Start,
AutomationId = "TestBoxView"
};
Grid.SetRow(boxView, 1);
grid.Children.Add(boxView);

// Rectangle with small height (should render like BoxView, not as a line)
var rectangleLabel = new Label { Text = "Rectangle (height 1.2, Fill only, no Stroke):" };
Grid.SetRow(rectangleLabel, 2);
grid.Children.Add(rectangleLabel);

var rectangle = new Rectangle
{
WidthRequest = 50,
HeightRequest = 1.2,
Fill = Colors.Blue,
Stroke = null, // Explicitly no stroke
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.Start,
AutomationId = "TestRectangle"
};
Grid.SetRow(rectangle, 2);
grid.Children.Add(rectangle);

// AbsoluteLayout test (from original issue report)
var absoluteLayout = new AbsoluteLayout
{
BackgroundColor = Colors.White,
AutomationId = "AbsoluteLayoutTest"
};
Grid.SetRow(absoluteLayout, 3);
grid.Children.Add(absoluteLayout);

double shapeWidth = 20;
double shapeHeight = 1.2;
double centerX = 400;
double centerY = 200;

// BoxView in AbsoluteLayout (reference)
var absBoxView = new BoxView
{
BackgroundColor = Colors.Red
};
AbsoluteLayout.SetLayoutBounds(absBoxView, new Rect(
centerX - shapeWidth - 30,
centerY - (shapeHeight / 2),
shapeWidth,
shapeHeight
));
AbsoluteLayout.SetLayoutFlags(absBoxView, AbsoluteLayoutFlags.None);

Check failure on line 101 in src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs

View check run for this annotation

Azure Pipelines / maui-pr-uitests (Build UITests CoreCLR Sample App Build Sample App)

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs#L101

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs(101,51): Error CS0103: The name 'AbsoluteLayoutFlags' does not exist in the current context

Check failure on line 101 in src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs

View check run for this annotation

Azure Pipelines / maui-pr-uitests (Build UITests Sample App Build Sample App)

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs#L101

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs(101,51): Error CS0103: The name 'AbsoluteLayoutFlags' does not exist in the current context

Check failure on line 101 in src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs

View check run for this annotation

Azure Pipelines / maui-pr-uitests (Build UITests Sample App Build Sample App)

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs#L101

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs(101,51): Error CS0103: The name 'AbsoluteLayoutFlags' does not exist in the current context

Check failure on line 101 in src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs

View check run for this annotation

Azure Pipelines / maui-pr-uitests (Build UITests Sample App Build Sample App)

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs#L101

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs(101,51): Error CS0103: The name 'AbsoluteLayoutFlags' does not exist in the current context

Check failure on line 101 in src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs

View check run for this annotation

Azure Pipelines / maui-pr-uitests

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs#L101

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs(101,51): Error CS0103: The name 'AbsoluteLayoutFlags' does not exist in the current context

Check failure on line 101 in src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs

View check run for this annotation

Azure Pipelines / maui-pr (Build .NET MAUI Build macOS (Release))

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs#L101

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs(101,51): error CS0103: (NETCORE_ENGINEERING_TELEMETRY=Build) The name 'AbsoluteLayoutFlags' does not exist in the current context

Check failure on line 101 in src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs

View check run for this annotation

Azure Pipelines / maui-pr (Build .NET MAUI Build macOS (Release))

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs#L101

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs(101,51): error CS0103: (NETCORE_ENGINEERING_TELEMETRY=Build) The name 'AbsoluteLayoutFlags' does not exist in the current context

Check failure on line 101 in src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs

View check run for this annotation

Azure Pipelines / maui-pr (Build .NET MAUI Build macOS (Release))

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs#L101

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs(101,51): error CS0103: (NETCORE_ENGINEERING_TELEMETRY=Build) The name 'AbsoluteLayoutFlags' does not exist in the current context

Check failure on line 101 in src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs

View check run for this annotation

Azure Pipelines / maui-pr

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs#L101

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs(101,51): error CS0103: (NETCORE_ENGINEERING_TELEMETRY=Build) The name 'AbsoluteLayoutFlags' does not exist in the current context
absoluteLayout.Children.Add(absBoxView);

// Rectangle in AbsoluteLayout (should match BoxView appearance)
var absRectangle = new Rectangle
{
Fill = Colors.Blue
};
AbsoluteLayout.SetLayoutBounds(absRectangle, new Rect(
centerX + 30,
centerY - (shapeHeight / 2),
shapeWidth,
shapeHeight
));
AbsoluteLayout.SetLayoutFlags(absRectangle, AbsoluteLayoutFlags.None);

Check failure on line 115 in src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs

View check run for this annotation

Azure Pipelines / maui-pr-uitests (Build UITests CoreCLR Sample App Build Sample App)

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs#L115

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs(115,53): Error CS0103: The name 'AbsoluteLayoutFlags' does not exist in the current context

Check failure on line 115 in src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs

View check run for this annotation

Azure Pipelines / maui-pr-uitests (Build UITests Sample App Build Sample App)

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs#L115

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs(115,53): Error CS0103: The name 'AbsoluteLayoutFlags' does not exist in the current context

Check failure on line 115 in src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs

View check run for this annotation

Azure Pipelines / maui-pr-uitests (Build UITests Sample App Build Sample App)

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs#L115

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs(115,53): Error CS0103: The name 'AbsoluteLayoutFlags' does not exist in the current context

Check failure on line 115 in src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs

View check run for this annotation

Azure Pipelines / maui-pr-uitests

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs#L115

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs(115,53): Error CS0103: The name 'AbsoluteLayoutFlags' does not exist in the current context

Check failure on line 115 in src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs

View check run for this annotation

Azure Pipelines / maui-pr (Build .NET MAUI Build macOS (Release))

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs#L115

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs(115,53): error CS0103: (NETCORE_ENGINEERING_TELEMETRY=Build) The name 'AbsoluteLayoutFlags' does not exist in the current context

Check failure on line 115 in src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs

View check run for this annotation

Azure Pipelines / maui-pr (Build .NET MAUI Build macOS (Release))

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs#L115

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs(115,53): error CS0103: (NETCORE_ENGINEERING_TELEMETRY=Build) The name 'AbsoluteLayoutFlags' does not exist in the current context

Check failure on line 115 in src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs

View check run for this annotation

Azure Pipelines / maui-pr

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs#L115

src/Controls/tests/TestCases.HostApp/Issues/Issue31330.cs(115,53): error CS0103: (NETCORE_ENGINEERING_TELEMETRY=Build) The name 'AbsoluteLayoutFlags' does not exist in the current context
absoluteLayout.Children.Add(absRectangle);

scrollView.Content = grid;
Content = scrollView;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue31330 : _IssuesUITest
{
public Issue31330(TestDevice testDevice) : base(testDevice)
{
}
public override string Issue => "Rectangle renders as thin line instead of filled shape for small height values";

[Test]
[Category(UITestCategories.Shape)]
public void UpdateSizeOnlyWhenStrokeExists()
{
App.WaitForElement("Issue31330BoxView");
VerifyScreenshot();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running a build, pending snapshot on some platforms.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test failing on Windows:

  at UITest.Appium.HelperExtensions.Wait(Func`1 query, Func`2 satisfactory, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2530
   at UITest.Appium.HelperExtensions.WaitForAtLeastOne(Func`1 query, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 2547
   at UITest.Appium.HelperExtensions.WaitForElement(IApp app, String marked, String timeoutMessage, Nullable`1 timeout, Nullable`1 retryFrequency, Nullable`1 postTimeout) in /_/src/TestUtils/src/UITest.Appium/HelperExtensions.cs:line 743
   at Microsoft.Maui.TestCases.Tests.Issues.Issue31330.UpdateSizeOnlyWhenStrokeExists() in /_/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/Issue31330.cs:line 18
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr)

Copy link
Contributor

@jsuarezruiz jsuarezruiz Sep 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pending snapshot on Mac:
image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jsuarezruiz, Added pending snapshots for Android, Mac, and Windows platforms. Re-saved images for failed cases due to recent changes related to shapes.

}
}
Loading
Loading