Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Android TextView being truncated under some conditions #27179

Merged
merged 11 commits into from
Feb 4, 2025
2 changes: 1 addition & 1 deletion src/Controls/src/Core/VisualElement/VisualElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1908,7 +1908,7 @@ Size IView.Arrange(Rect bounds)
/// </summary>
/// <param name="bounds">The new bounds of the element.</param>
/// <returns>The resulting size of this element's frame by the platform.</returns>
/// <remarks>Subclasses will stil want to call <see cref="ArrangeOverride"/> on the base class or call <see cref="IViewHandler.PlatformArrange"/> on the <see cref="Handler"/> .</remarks>
/// <remarks>Subclasses will still want to call <see cref="ArrangeOverride"/> on the base class or call <see cref="IViewHandler.PlatformArrange"/> on the <see cref="Handler"/> .</remarks>
protected virtual Size ArrangeOverride(Rect bounds)
{
Frame = this.ComputeFrame(bounds);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Android.Views;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Maui;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Handlers;
using Microsoft.Maui.DeviceTests.Stubs;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Platform;
using Xunit;
using Xunit.Sdk;

namespace Microsoft.Maui.DeviceTests
{
public partial class ViewTests
{
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
[InlineData(4)]
[InlineData(5)]
[InlineData(6)]
[InlineData(7)]
[InlineData(8)]
[InlineData(9)]
[InlineData(10)]
[InlineData(11)]
public async Task ArrangesContentWithoutOverlapAndWithProperSize(int columnCount)
{
EnsureHandlerCreated(builder =>
{
builder.ConfigureMauiHandlers(handlers =>
{
handlers.AddHandler<ContentView, ContentViewHandler>();
handlers.AddHandler<Layout, LayoutHandler>();
});
});

await InvokeOnMainThreadAsync(async () =>
{
var grid = new Grid();
grid.WidthRequest = 293;
grid.ColumnDefinitions = new ColumnDefinitionCollection(
Enumerable.Range(0, columnCount)
.Select(_ => new ColumnDefinition())
.ToArray());

for (int i = 0; i < columnCount; i++)
{
var content = new ContentView();
content.BackgroundColor = new Color(255 / (i + 1), 255 / (i + 1), 255 / (i + 1));
content.HeightRequest = 50;
grid.Add(content, i);
}

await CreateHandlerAndAddToWindow(grid, (LayoutHandler handler) =>
{
var platformView = (ViewGroup)handler.PlatformView;
var childrenPlatformViews = Enumerable.Range(0, platformView.ChildCount)
.Select(n => platformView.GetChildAt(n)!);
var arrangedFrames = childrenPlatformViews
.Select(v => (v.Left, v.Top, v.Right, v.Bottom, v.Width, v.Height))
.ToArray();
var context = platformView.Context;

int lastRight = 0;
for (int i = 0; i < arrangedFrames.Length; i++)
{
var childVirtualView = grid[i];
var dpFrame = childVirtualView.Frame;
var pxFrame = arrangedFrames[i];
var expectedWidth = context.ToPixels(dpFrame.Width);

// This fails sometimes due to the way we arrange the content based on coordinates instead of size
// Assert.Equal(expectedWidth, pxFrame.Width);
Assert.True(pxFrame.Left == lastRight);

lastRight = pxFrame.Right;
}
});
});
}
}
}
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.
20 changes: 20 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue17884.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.Issue17884">
<Label Text="CHAT MISSING_WORD"
TextColor="Black"
AutomationId="StubLabel"
x:Name="StubLabel"
FontSize="16"
FontFamily="MontserratBold"
Shadow="{Shadow Offset='0,1', Radius=1, Brush=Aqua}"
MaxLines="1"
LineBreakMode="NoWrap"
VerticalOptions="Center"
HorizontalOptions="Center">
<Label.GestureRecognizers>
<TapGestureRecognizer Tapped="OnStubLabelTapped"/>
</Label.GestureRecognizers>
</Label>
</ContentPage>
17 changes: 17 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue17884.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

namespace Maui.Controls.Sample.Issues
{
[Issue(IssueTracker.Github, 17884, "[Android] Entire words omitted & letters truncated from Label display", PlatformAffected.Android)]
public partial class Issue17884 : ContentPage
{
public Issue17884()
{
InitializeComponent();
}

private void OnStubLabelTapped(object sender, EventArgs e)
{
++StubLabel.FontSize;
}
}
}
49 changes: 49 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue22853.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Maui.Controls.Sample.Issues.Issue22853">
<Grid
Margin="24"
ColumnDefinitions="*,auto,auto"
HorizontalOptions="Center"
VerticalOptions="Start"
ColumnSpacing="2">

<Label
AutomationId="WaitForStubControl"
BackgroundColor="DarkSlateGray"
Grid.Column="0"
HorizontalOptions="Start"
LineBreakMode="WordWrap"
LineHeight="1"
FontSize="Caption"
Text="Svorna, Mikeondoko Nananom Dadadadodo Septop"
TextColor="White"
VerticalOptions="Start"
VerticalTextAlignment="Start" />

<Label
BackgroundColor="DarkGreen"
Grid.Column="1"
Text=" • "
TextColor="White"
LineHeight="1"
FontSize="Caption"
VerticalOptions="Start"
HorizontalOptions="Center"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Start" />

<Label
BackgroundColor="DarkRed"
Grid.Column="2"
LineHeight="1"
FontSize="Caption"
Text="May 32-Jun 33, 2069"
TextColor="White"
VerticalOptions="Start"
VerticalTextAlignment="Start" />

</Grid>
</ContentPage>
12 changes: 12 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue22853.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Maui.Controls.Sample.Issues
{
[Issue(IssueTracker.Github, 22853, "WordWrap LineBreakMode leaves extra space on Android", PlatformAffected.Android
)]
public partial class Issue22853 : ContentPage
{
public Issue22853()
{
InitializeComponent();
}
}
}
1 change: 1 addition & 0 deletions src/Controls/tests/TestCases.HostApp/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public static MauiApp CreateMauiApp()
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("FontAwesome.ttf", "FA");
fonts.AddFont("ionicons.ttf", "Ion");
fonts.AddFont("Montserrat-Bold.otf", "MontserratBold");
})
.RenderingPerformanceAddMappers()
.Issue21109AddMappers()
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#if ANDROID
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

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

public override string Issue => "[Android] Entire words omitted & letters truncated from Label display";

[Test]
[Category(UITestCategories.Visual)]
public void VerifyTextIsNotMissing()
{
App.WaitForElement("StubLabel");
VerifyScreenshot();
}
}
}
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#if TEST_FAILS_ON_MACCATALYST //https://github.com/dotnet/maui/pull/27531
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

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

public override string Issue => "WordWrap LineBreakMode leaves extra space on Android";

[Test]
[Category(UITestCategories.Label)]
public void WordWrapLineBreakModeNoExtraSpace()
{
App.WaitForElement("WaitForStubControl");
VerifyScreenshot();
}
}
}
#endif
24 changes: 11 additions & 13 deletions src/Core/src/Handlers/ViewHandlerExtensions.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Runtime.CompilerServices;
using Android.Content;
using Android.Views;
using Android.Widget;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Platform;
using static Android.Views.View;
Expand Down Expand Up @@ -77,35 +78,35 @@ internal static Size MeasureVirtualView(

internal static Size GetDesiredSizeFromHandler(this IViewHandler viewHandler, double widthConstraint, double heightConstraint)
{
var Context = viewHandler.MauiContext?.Context;
var context = viewHandler.MauiContext?.Context;
var platformView = viewHandler.ToPlatform();
var virtualView = viewHandler.VirtualView;

if (platformView == null || virtualView == null || Context == null)
if (platformView == null || virtualView == null || context == null)
{
return Size.Zero;
}

// Create a spec to handle the native measure
var widthSpec = Context.CreateMeasureSpec(widthConstraint, virtualView.Width, virtualView.MinimumWidth, virtualView.MaximumWidth);
var heightSpec = Context.CreateMeasureSpec(heightConstraint, virtualView.Height, virtualView.MinimumHeight, virtualView.MaximumHeight);
var widthSpec = context.CreateMeasureSpec(widthConstraint, virtualView.Width, virtualView.MinimumWidth, virtualView.MaximumWidth);
var heightSpec = context.CreateMeasureSpec(heightConstraint, virtualView.Height, virtualView.MinimumHeight, virtualView.MaximumHeight);

var packed = PlatformInterop.MeasureAndGetWidthAndHeight(platformView, widthSpec, heightSpec);
var measuredWidth = (int)(packed >> 32);
var measuredHeight = (int)(packed & 0xffffffffL);

// Convert back to xplat sizes for the return value
return Context.FromPixels(measuredWidth, measuredHeight);
return context.FromPixels(measuredWidth, measuredHeight);
}

internal static void PlatformArrangeHandler(this IViewHandler viewHandler, Rect frame)
{
var platformView = viewHandler.ToPlatform();

var Context = viewHandler.MauiContext?.Context;
var MauiContext = viewHandler.MauiContext;
var context = viewHandler.MauiContext?.Context;
var mauiContext = viewHandler.MauiContext;

if (platformView == null || MauiContext == null || Context == null)
if (platformView == null || mauiContext == null || context == null)
{
return;
}
Expand All @@ -116,10 +117,7 @@ internal static void PlatformArrangeHandler(this IViewHandler viewHandler, Rect
return;
}

var left = Context.ToPixels(frame.Left);
var top = Context.ToPixels(frame.Top);
var bottom = Context.ToPixels(frame.Bottom);
var right = Context.ToPixels(frame.Right);
var (left, top, right, bottom) = context.ToPixels(frame);

var viewParent = platformView.Parent;
if (viewParent?.LayoutDirection == LayoutDirection.Rtl && viewParent is View parentView)
Expand All @@ -130,7 +128,7 @@ internal static void PlatformArrangeHandler(this IViewHandler viewHandler, Rect
right = left + width;
}

platformView.Layout((int)left, (int)top, (int)right, (int)bottom);
platformView.Layout(left, top, right, bottom);

viewHandler.Invoke(nameof(IView.Frame), frame);
}
Expand Down
Loading
Loading