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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
194 changes: 194 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue20834.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 20834, "[Android] GraphicsView can also be visible outside the canvas", PlatformAffected.Android)]
public class Issue20834 : TestContentPage
{
readonly Issue20834_Drawable drawable = new();
GraphicsView graphicsView;
Label overlayLabel;

protected override void Init()
{
var rootLayout = new Grid()
{
RowDefinitions =
{
new RowDefinition() { Height = 300 }, // Canvas row
new RowDefinition() { Height = 60 } // Button row
}
};

// First row: Canvas with overlapping label
var canvasGrid = new Grid()
{
BackgroundColor = Color.FromArgb("#1a2033"),
HeightRequest = 300,
WidthRequest = 300,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
};

// Create the GraphicsView (canvas) with specific dimensions
graphicsView = new GraphicsView()
{
HeightRequest = 300,
WidthRequest = 300,
BackgroundColor = Colors.LightGray,
AutomationId = "GraphicsCanvas"
};

// Create an overlapping label
overlayLabel = new Label()
{
Text = "Overlay Label",
TextColor = Colors.Red,
FontSize = 16,
FontAttributes = FontAttributes.Bold,
HorizontalOptions = LayoutOptions.Start,
VerticalOptions = LayoutOptions.Start,
Margin = new Thickness(10, 10, 0, 0),
AutomationId = "OverlayLabel"
};

graphicsView.Drawable = drawable;

// Add both canvas and label to the same grid cell (overlapping)
canvasGrid.Add(graphicsView);
canvasGrid.Add(overlayLabel);

// Button row: Reset and Move buttons
var buttonRow = new StackLayout()
{
Orientation = StackOrientation.Horizontal,
HorizontalOptions = LayoutOptions.Center,
Spacing = 10
};

var resetButton = new Button()
{
Text = "Reset Circles",
AutomationId = "ResetButton"
};
resetButton.Clicked += ResetButton_Clicked;

var moveButton = new Button()
{
Text = "Move to Edge Positions",
AutomationId = "MoveCirclesButton"
};
moveButton.Clicked += MoveButton_Clicked;

buttonRow.Children.Add(resetButton);
buttonRow.Children.Add(moveButton);

// Add all rows to the root layout
rootLayout.Add(canvasGrid, 0, 0);
rootLayout.Add(buttonRow, 0, 1);

Content = rootLayout;
}

void ResetButton_Clicked(object sender, EventArgs e)
{
// Reset all circles to initial positions
drawable.ResetPositions();
graphicsView.Invalidate();
}

void MoveButton_Clicked(object sender, EventArgs e)
{
// Move circles to edge positions to test clipping
drawable.MoveToEdgePositions();
graphicsView.Invalidate();
}

class Issue20834_Drawable : IDrawable
{
// Circle data structure
class Issue20834_Circle
{
public float X { get; set; }
public float Y { get; set; }
public Color Color { get; set; }
public float InitialX { get; set; }
public float InitialY { get; set; }

public Issue20834_Circle(float x, float y, Color color)
{
X = x;
Y = y;
InitialX = x;
InitialY = y;
Color = color;
}
}

private readonly List<Issue20834_Circle> _circles;
private const float CircleDiameter = 100f;

public Issue20834_Drawable()
{
// Initialize 5 circles with different colors at initial positions
_circles = new List<Issue20834_Circle>
{
new Issue20834_Circle(50f, 50f, Colors.Blue),
new Issue20834_Circle(50f, 50f, Colors.Red),
new Issue20834_Circle(50f, 50f, Colors.Green),
new Issue20834_Circle(50f, 50f, Colors.Orange),
new Issue20834_Circle(50f, 50f, Colors.Purple)
};
}


public void ResetPositions()
{
foreach (var circle in _circles)
{
circle.X = circle.InitialX;
circle.Y = circle.InitialY;
}
}

public void MoveToEdgePositions()
{

if (_circles.Count >= 5)
Copy link

Copilot AI Aug 8, 2025

Choose a reason for hiding this comment

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

Magic number 5 should be replaced with a named constant or use _circles.Count directly since exactly 5 circles are initialized in the constructor.

Suggested change
if (_circles.Count >= 5)
if (_circles.Count >= RequiredCircleCount)

Copilot uses AI. Check for mistakes.
{

_circles[0].X = 250f; // Right
_circles[0].Y = 150f;

_circles[1].Y = 250f; // Bottom
_circles[1].X = 150f;

_circles[2].X = -50f; //left
_circles[2].Y = 150f;

_circles[3].Y = -50f; // top
_circles[3].X = 150f;

_circles[4].X = -50f;
_circles[4].Y = -50f;
}
}

public void Draw(ICanvas canvas, RectF dirtyRect)
{
Copy link

Copilot AI Aug 8, 2025

Choose a reason for hiding this comment

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

Hard-coded position values (250f, -50f, etc.) should be calculated based on canvas dimensions or defined as constants to improve maintainability and make the edge positioning logic clearer.

Suggested change
{
// Use the latest canvas size to position circles at the edges
if (_circles.Count >= 5)
{
// Right edge (circle just inside right edge)
_circles[0].X = _canvasWidth - CircleDiameter;
_circles[0].Y = _canvasHeight / 2 - CircleDiameter / 2;
// Bottom edge
_circles[1].X = _canvasWidth / 2 - CircleDiameter / 2;
_circles[1].Y = _canvasHeight - CircleDiameter;
// Left edge (circle just outside left edge)
_circles[2].X = -CircleDiameter / 2;
_circles[2].Y = _canvasHeight / 2 - CircleDiameter / 2;
// Top edge (circle just outside top edge)
_circles[3].X = _canvasWidth / 2 - CircleDiameter / 2;
_circles[3].Y = -CircleDiameter / 2;
// Top-left corner (circle just outside both top and left)
_circles[4].X = -CircleDiameter / 2;
_circles[4].Y = -CircleDiameter / 2;
}
}
public void Draw(ICanvas canvas, RectF dirtyRect)
{
// Store the latest canvas size for edge calculations
_canvasWidth = dirtyRect.Width;
_canvasHeight = dirtyRect.Height;

Copilot uses AI. Check for mistakes.
canvas.SaveState();

// Draw a border around the canvas to visualize the bounds
canvas.StrokeColor = Colors.Black;
canvas.StrokeSize = 2;
canvas.DrawRectangle(0, 0, dirtyRect.Width, dirtyRect.Height);

// Draw all circles
foreach (var circle in _circles)
{
canvas.FillColor = circle.Color;
canvas.FillEllipse(circle.X, circle.Y, CircleDiameter, CircleDiameter);
}

canvas.RestoreState();
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues;

public class Issue20834 : _IssuesUITest
{
public Issue20834(TestDevice testDevice) : base(testDevice)
{
}

public override string Issue => "[Android] GraphicsView can also be visible outside the canvas";
protected override bool ResetAfterEachTest => true;

[Test]
[Category(UITestCategories.GraphicsView)]
public void GraphicsViewShouldClipCirclesAtEdgePositions()
{
App.WaitForElement("MoveCirclesButton");
App.Tap("MoveCirclesButton");
VerifyScreenshot();
}
}
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.
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ public override void Draw(Canvas androidCanvas)

var dirtyRect = new RectF(0, 0, _width, _height);

// Save the canvas state and clip to view bounds to prevent drawing outside
androidCanvas.Save();
androidCanvas.ClipRect(0, 0, _width, _height);

_canvas.Canvas = androidCanvas;
if (_backgroundColor != null)
{
Expand All @@ -72,6 +76,8 @@ public override void Draw(Canvas androidCanvas)
dirtyRect.Height /= _scale;
dirtyRect.Width /= _scale;
_drawable.Draw(_scalingCanvas, dirtyRect);

androidCanvas.Restore();
_canvas.Canvas = null;
}

Expand Down
Loading