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 Label Multilinetruncation (MaxLines / LineBreakMode) #14918

Merged
merged 1 commit into from
Jan 18, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,13 @@
<Label
LineBreakMode ="TailTruncation"
Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." />
<Label
Text="TailTruncation (with 2 MaxLines)"
Style="{StaticResource Headline}" />
<Label
MaxLines="2"
LineBreakMode ="TailTruncation"
Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." />
<Label
Text="CharacterWrap"
Style="{StaticResource Headline}" />
Expand Down
1 change: 0 additions & 1 deletion src/Controls/src/Core/Label/Label.Android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ private protected override void OnHandlerChangedCore()
public static void MapText(LabelHandler handler, Label label) => MapText((ILabelHandler)handler, label);
public static void MapLineBreakMode(LabelHandler handler, Label label) => MapLineBreakMode((ILabelHandler)handler, label);


public static void MapText(ILabelHandler handler, Label label)
{
Platform.TextViewExtensions.UpdateText(handler.PlatformView, label);
Expand Down
5 changes: 2 additions & 3 deletions src/Controls/src/Core/Label/Label.Windows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,16 @@ public partial class Label
public static void MapDetectReadingOrderFromContent(LabelHandler handler, Label label) => MapDetectReadingOrderFromContent((ILabelHandler)handler, label);
public static void MapText(LabelHandler handler, Label label) => MapText((ILabelHandler)handler, label);


public static void MapDetectReadingOrderFromContent(ILabelHandler handler, Label label) =>
Platform.TextBlockExtensions.UpdateDetectReadingOrderFromContent(handler.PlatformView, label);

public static void MapText(ILabelHandler handler, Label label) =>
Platform.TextBlockExtensions.UpdateText(handler.PlatformView, label);

public static void MapLineBreakMode(ILabelHandler handler, Label label) =>
handler.PlatformView?.UpdateLineBreakMode(label.LineBreakMode);
handler.PlatformView?.UpdateLineBreakMode(label);

public static void MapMaxLines(ILabelHandler handler, Label label) =>
handler.PlatformView?.UpdateMaxLines(label);
}
}
}
2 changes: 1 addition & 1 deletion src/Controls/src/Core/Label/Label.iOS.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#nullable disable
#nullable disable
using System;
using Microsoft.Maui.Controls.Platform;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System;
using Android.Text;
using Android.Widget;
using AndroidX.AppCompat.Widget;
using Microsoft.Maui.Controls.Internals;

namespace Microsoft.Maui.Controls.Platform
Expand Down Expand Up @@ -31,14 +32,21 @@ public static void UpdateLineBreakMode(this TextView textView, Label label)

public static void UpdateMaxLines(this TextView textView, Label label)
{
// Linebreak mode also handles settng MaxLines
// Linebreak mode also handles setting MaxLines
textView.SetLineBreakMode(label.LineBreakMode, label.MaxLines);
}

internal static void SetLineBreakMode(this TextView textView, LineBreakMode lineBreakMode, int? maxLines = null)
{
if (!maxLines.HasValue || maxLines <= 0)
maxLines = int.MaxValue;
{
// Without setting the MaxLines property, to equalize behaviors across platforms
// we set the max to 1.
if (lineBreakMode == LineBreakMode.TailTruncation)
maxLines = 1;
else
maxLines = int.MaxValue;
}
jsuarezruiz marked this conversation as resolved.
Show resolved Hide resolved

bool singleLine = false;
bool shouldSetSingleLine = !OperatingSystem.IsAndroidVersionAtLeast(23);
Expand Down Expand Up @@ -67,12 +75,11 @@ internal static void SetLineBreakMode(this TextView textView, LineBreakMode line
break;
case LineBreakMode.TailTruncation:

// Leaving this in for now to preserve existing behavior
mattleibow marked this conversation as resolved.
Show resolved Hide resolved
// Technically, we don't _need_ this for Labels; they will handle Ellipsization at the end just fine, even with multiple lines
// But we don't have a mechanism for setting MaxLines on other controls (e.g., Button) right now, so we need to force it here or
// they will potentially exceed a single line. Also, changing this behavior the for Labels would technically be breaking (though
// possibly less surprising than what happens currently).
maxLines = 1;
// We don't have a mechanism for setting MaxLines on other controls (e.g., Button) right now, so we need to force it here or
// they will potentially exceed a single line.
if (textView is AppCompatButton)
maxLines = 1;

textView.Ellipsize = TextUtils.TruncateAt.End;
break;
case LineBreakMode.MiddleTruncation:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,44 +12,11 @@ namespace Microsoft.Maui.Controls.Platform
internal static class TextBlockExtensions
{
public static void UpdateLineBreakMode(this TextBlock textBlock, Label label) =>
textBlock.UpdateLineBreakMode(label.LineBreakMode);
textBlock.SetLineBreakMode(label.LineBreakMode, label.MaxLines);

public static void UpdateLineBreakMode(this TextBlock textBlock, LineBreakMode lineBreakMode)
{
if (textBlock == null)
return;

switch (lineBreakMode)
{
case LineBreakMode.NoWrap:
textBlock.TextTrimming = TextTrimming.Clip;
textBlock.TextWrapping = TextWrapping.NoWrap;
break;
case LineBreakMode.WordWrap:
textBlock.TextTrimming = TextTrimming.None;
textBlock.TextWrapping = TextWrapping.Wrap;
break;
case LineBreakMode.CharacterWrap:
textBlock.TextTrimming = TextTrimming.WordEllipsis;
textBlock.TextWrapping = TextWrapping.Wrap;
break;
case LineBreakMode.HeadTruncation:
// TODO: This truncates at the end.
textBlock.TextTrimming = TextTrimming.WordEllipsis;
DetermineTruncatedTextWrapping(textBlock);
break;
case LineBreakMode.TailTruncation:
textBlock.TextTrimming = TextTrimming.CharacterEllipsis;
DetermineTruncatedTextWrapping(textBlock);
break;
case LineBreakMode.MiddleTruncation:
// TODO: This truncates at the end.
textBlock.TextTrimming = TextTrimming.WordEllipsis;
DetermineTruncatedTextWrapping(textBlock);
break;
default:
throw new ArgumentOutOfRangeException();
}
textBlock.SetLineBreakMode(lineBreakMode, null);
}

static void DetermineTruncatedTextWrapping(TextBlock textBlock) =>
Expand Down Expand Up @@ -87,10 +54,8 @@ public static double FindDefaultLineHeight(this TextBlock control, Inline inline

public static void UpdateMaxLines(this TextBlock platformControl, Label label)
{
if (label.MaxLines >= 0)
platformControl.MaxLines = label.MaxLines;
else
platformControl.MaxLines = 0;
// Linebreak mode also handles setting MaxLines
platformControl.SetLineBreakMode(label.LineBreakMode, label.MaxLines);
}

public static void UpdateDetectReadingOrderFromContent(this TextBlock platformControl, Label label)
Expand All @@ -99,6 +64,46 @@ public static void UpdateDetectReadingOrderFromContent(this TextBlock platformCo
platformControl.SetTextReadingOrder(label.OnThisPlatform().GetDetectReadingOrderFromContent());
}

internal static void SetLineBreakMode(this TextBlock textBlock, LineBreakMode lineBreakMode, int? maxLines = null)
mattleibow marked this conversation as resolved.
Show resolved Hide resolved
{
if (maxLines.HasValue && maxLines >= 0)
textBlock.MaxLines = maxLines.Value;
else
textBlock.MaxLines = 0;

switch (lineBreakMode)
{
case LineBreakMode.NoWrap:
textBlock.TextTrimming = TextTrimming.Clip;
textBlock.TextWrapping = TextWrapping.NoWrap;
break;
case LineBreakMode.WordWrap:
textBlock.TextTrimming = TextTrimming.None;
textBlock.TextWrapping = TextWrapping.Wrap;
break;
case LineBreakMode.CharacterWrap:
textBlock.TextTrimming = TextTrimming.WordEllipsis;
textBlock.TextWrapping = TextWrapping.Wrap;
break;
case LineBreakMode.HeadTruncation:
// TODO: This truncates at the end.
textBlock.TextTrimming = TextTrimming.WordEllipsis;
DetermineTruncatedTextWrapping(textBlock);
break;
case LineBreakMode.TailTruncation:
textBlock.TextTrimming = TextTrimming.CharacterEllipsis;
DetermineTruncatedTextWrapping(textBlock);
break;
case LineBreakMode.MiddleTruncation:
// TODO: This truncates at the end.
textBlock.TextTrimming = TextTrimming.WordEllipsis;
DetermineTruncatedTextWrapping(textBlock);
break;
default:
throw new ArgumentOutOfRangeException();
}
}

internal static void SetTextReadingOrder(this TextBlock platformControl, bool detectReadingOrderFromContent) =>
platformControl.TextReadingOrder = detectReadingOrderFromContent
? TextReadingOrder.DetectFromContent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,14 @@ public static void UpdateMaxLines(this UILabel platformLabel, Label label)
internal static void SetLineBreakMode(this UILabel platformLabel, Label label)
{
int maxLines = label.MaxLines;

if (maxLines < 0)
maxLines = 0;
{
if (label.LineBreakMode == LineBreakMode.TailTruncation)
maxLines = 1;
else
maxLines = 0;
}
jsuarezruiz marked this conversation as resolved.
Show resolved Hide resolved

switch (label.LineBreakMode)
{
Expand All @@ -97,7 +103,6 @@ internal static void SetLineBreakMode(this UILabel platformLabel, Label label)
break;
case LineBreakMode.TailTruncation:
platformLabel.LineBreakMode = UILineBreakMode.TailTruncation;
maxLines = 1;
break;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,28 @@ public async Task VerticalTextAlignedWhenRtlIsFalse()
Assert.True(platformLabel.Gravity.HasFlag(GravityFlags.CenterVertical), "Label should only have the CenterVertical flag.");
}

// https://github.com/dotnet/maui/issues/18059
[Fact(DisplayName = "Using TailTruncation LineBreakMode with 2 MaxLines")]
public async Task UsingTailTruncationWith2MaxLines()
{
var label = new Label()
{
Text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
LineBreakMode = LineBreakMode.TailTruncation,
MaxLines = 2
};

var handler = await CreateHandlerAsync<LabelHandler>(label);

var platformLabel = GetPlatformLabel(handler);

await InvokeOnMainThreadAsync((System.Action)(() =>
{
Assert.Equal(2, GetPlatformMaxLines(handler));
Assert.Equal(LineBreakMode.TailTruncation.ToPlatform(), GetPlatformLineBreakMode(handler));
}));
}

TextView GetPlatformLabel(LabelHandler labelHandler) =>
labelHandler.PlatformView;

Expand Down
60 changes: 59 additions & 1 deletion src/Controls/tests/DeviceTests/Elements/Label/LabelTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
#if __IOS__
using Foundation;
#endif
Expand Down Expand Up @@ -26,6 +27,37 @@ void SetupBuilder()
});
});
}

[Fact(DisplayName = "Does Not Leak")]
public async Task DoesNotLeak()
{
SetupBuilder();

WeakReference viewReference = null;
WeakReference platformViewReference = null;
WeakReference handlerReference = null;

await InvokeOnMainThreadAsync(() =>
{
var layout = new Grid();

var label = new Label
{
Text = "Test"
};

layout.Add(label);
var handler = CreateHandler<LayoutHandler>(layout);
viewReference = new WeakReference(label);
handlerReference = new WeakReference(label.Handler);
platformViewReference = new WeakReference(label.Handler.PlatformView);
});

await AssertionExtensions.WaitForGC(viewReference, handlerReference, platformViewReference);
Assert.False(viewReference.IsAlive, "Label should not be alive!");
Assert.False(handlerReference.IsAlive, "Handler should not be alive!");
Assert.False(platformViewReference.IsAlive, "PlatformView should not be alive!");
}

[Theory]
[ClassData(typeof(TextTransformCases))]
Expand Down Expand Up @@ -204,6 +236,32 @@ await InvokeOnMainThreadAsync((System.Action)(() =>
}));
}

[Fact(DisplayName = "LineBreakMode TailTruncation does not affect MaxLines")]
public async Task TailTruncationDoesNotAffectMaxLines()
{
var label = new Label()
{
Text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
MaxLines = 3,
LineBreakMode = LineBreakMode.TailTruncation,
};

var handler = await CreateHandlerAsync<LabelHandler>(label);
var platformLabel = GetPlatformLabel(handler);

await InvokeOnMainThreadAsync(() =>
{
Assert.Equal(3, GetPlatformMaxLines(handler));
Assert.Equal(LineBreakMode.TailTruncation.ToPlatform(), GetPlatformLineBreakMode(handler));

label.LineBreakMode = LineBreakMode.CharacterWrap;
platformLabel.UpdateLineBreakMode(label);

Assert.Equal(3, GetPlatformMaxLines(handler));
Assert.Equal(LineBreakMode.CharacterWrap.ToPlatform(), GetPlatformLineBreakMode(handler));
});
}

[Fact(DisplayName = "MaxLines Initializes Correctly")]
public async Task MaxLinesInitializesCorrectly()
{
Expand Down
26 changes: 22 additions & 4 deletions src/Controls/tests/DeviceTests/Elements/Label/LabelTests.iOS.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Handlers;
Expand All @@ -12,6 +9,27 @@ namespace Microsoft.Maui.DeviceTests
{
public partial class LabelTests
{

[Fact(DisplayName = "Using TailTruncation LineBreakMode changes MaxLines")]
public async Task UsingTailTruncationSetMaxLines()
{
var label = new Label()
{
Text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
LineBreakMode = LineBreakMode.TailTruncation,
};

var handler = await CreateHandlerAsync<LabelHandler>(label);

var platformLabel = GetPlatformLabel(handler);

await InvokeOnMainThreadAsync((System.Action)(() =>
{
Assert.Equal(1, GetPlatformMaxLines(handler));
Assert.Equal(LineBreakMode.TailTruncation.ToPlatform(), GetPlatformLineBreakMode(handler));
}));
}

UILabel GetPlatformLabel(LabelHandler labelHandler) =>
(UILabel)labelHandler.PlatformView;

Expand Down
Loading