Skip to content
Merged
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 27 additions & 7 deletions src/Controls/src/Core/Shapes/Shape.cs
Original file line number Diff line number Diff line change
Expand Up @@ -326,14 +326,34 @@ internal void TransformPathForBounds(PathF path, Graphics.Rect viewBounds)

if (Aspect == Stretch.None)
{
bool requireAdjustX = viewBounds.Left > pathBounds.Left;
bool requireAdjustY = viewBounds.Top > pathBounds.Top;
float translateX = 0;
float translateY = 0;

if (requireAdjustX || requireAdjustY)
// Check if path needs to be shifted right to fit within view bounds (left edge)
if (viewBounds.Left > pathBounds.Left)
{
transform = Matrix3x2.CreateTranslation(
(float)(pathBounds.X + viewBounds.Left - pathBounds.Left),
(float)(pathBounds.Y + viewBounds.Top - pathBounds.Top));
translateX = (float)(viewBounds.Left - pathBounds.Left);
}
// Check if path needs to be shifted left to fit within view bounds (right edge)
else if (pathBounds.Right > viewBounds.Right)
{
translateX = (float)(viewBounds.Right - pathBounds.Right);
}

// Check if path needs to be shifted down to fit within view bounds (top edge)
if (viewBounds.Top > pathBounds.Top)
{
translateY = (float)(viewBounds.Top - pathBounds.Top);
}
// Check if path needs to be shifted up to fit within view bounds (bottom edge)
else if (pathBounds.Bottom > viewBounds.Bottom)
{
translateY = (float)(viewBounds.Bottom - pathBounds.Bottom);
}

if (translateX != 0 || translateY != 0)
{
transform = Matrix3x2.CreateTranslation(translateX, translateY);
}
else
{
Expand Down Expand Up @@ -428,7 +448,7 @@ protected override Size MeasureOverride(double widthConstraint, double heightCon
}
else
{
pathBounds = this.GetPath().GetBoundsByFlattening(1);
pathBounds = this.GetPath().GetBoundsByFlattening(1);
}

SizeF boundsByFlattening = pathBounds.Size;
Expand Down
109 changes: 109 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue11404.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using Microsoft.Maui.Controls.Shapes;
using Microsoft.Maui.Graphics;

namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 11404, "Line coordinates not computed correctly", PlatformAffected.All)]
public class Issue11404 : ContentPage
{
readonly Line _line1;
readonly Line _line2;
readonly Label _resultLabel;

public Issue11404()
{
Title = "Issue11404";

_line1 = new Line
{
X1 = 0,
Y1 = 0,
X2 = 100,
Y2 = 100,
Stroke = new SolidColorBrush(Colors.Red),
StrokeThickness = 10
};

_line2 = new Line
{
X1 = 200,
Y1 = 0,
X2 = 100,
Y2 = 100,
Stroke = new SolidColorBrush(Colors.Red),
StrokeThickness = 10
};

var line3 = new Line
{
X1 = 200,
Y1 = 0,
X2 = 100,
Y2 = 100,
Stroke = new SolidColorBrush(Colors.Black),
StrokeThickness = 1
};

var grid = new Grid
{
WidthRequest = 200,
HeightRequest = 200,
BackgroundColor = Colors.LightGray
};

grid.Children.Add(_line1);
grid.Children.Add(_line2);
grid.Children.Add(line3);

_resultLabel = new Label
{
Text = "Checking...",
AutomationId = "SymmetryResult",
Margin = new Thickness(10),
HorizontalOptions = LayoutOptions.Center
};

var descriptionLabel = new Label
{
Text = "Two thick red lines should form a symmetric V shape. SymmetryResult should show 'Pass'.",
Margin = new Thickness(10),
HorizontalOptions = LayoutOptions.Center,
AutomationId = "DescriptionLabel"
};

Content = new VerticalStackLayout
{
Children = { grid, _resultLabel, descriptionLabel }
};

SizeChanged += OnPageSizeChanged;
}

void OnPageSizeChanged(object sender, EventArgs e)
{
SizeChanged -= OnPageSizeChanged;
UpdateSymmetryResult();
}

void UpdateSymmetryResult()
{
// Use the fixed view bounds matching the grid size
var viewBounds = new Rect(0, 0, 200, 200);

var path1 = ((IShape)_line1).PathForBounds(viewBounds);
var path2 = ((IShape)_line2).PathForBounds(viewBounds);

var bounds1 = path1.GetBoundsByFlattening(1);
var bounds2 = path2.GetBoundsByFlattening(1);

// line1 (0,0)→(100,100) and line2 (200,0)→(100,100) are symmetric around X=100.
// After correct transformation: bounds1.Left + bounds2.Right ≈ 200
// and bounds1.Right + bounds2.Left ≈ 200.
const float tolerance = 2f;
bool isSymmetric =
Math.Abs(bounds1.Left + bounds2.Right - 200f) < tolerance &&
Math.Abs(bounds1.Right + bounds2.Left - 200f) < tolerance;

_resultLabel.Text = isSymmetric ? "Pass" : "Fail";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues
{
public class Issue11404 : _IssuesUITest
{
public Issue11404(TestDevice device) : base(device)
{
}

public override string Issue => "Line coordinates not computed correctly";

[Test]
[Category(UITestCategories.Shape)]
public void LineWithReversedCoordinatesShouldRenderSymmetrical()
{
App.WaitForElement("DescriptionLabel");

// Wait for the symmetry check to complete (SizeChanged fires after layout)
App.WaitForElement("SymmetryResult");

// The HostApp computes path bounds for both lines and checks symmetry.
// "Pass" means bounds1.Left + bounds2.Right ≈ 200 and bounds1.Right + bounds2.Left ≈ 200,
// proving the path coordinates are symmetric around the center axis (X=100).
var result = App.FindElement("SymmetryResult").GetText();
Comment thread
NirmalKumarYuvaraj marked this conversation as resolved.
Assert.That(result, Is.EqualTo("Pass"),
"Line paths should be symmetric: path coordinates for mirror-image lines should mirror around center axis X=100");
}
}
}
Loading