Skip to content

Commit

Permalink
Merge pull request #17505 from Gillibald/customers/codice
Browse files Browse the repository at this point in the history
Backport TextBlock fixes
  • Loading branch information
grokys authored Nov 15, 2024
2 parents 205c51d + e0e7afa commit 8ffd557
Show file tree
Hide file tree
Showing 10 changed files with 204 additions and 28 deletions.
58 changes: 39 additions & 19 deletions src/Avalonia.Controls/TextBlock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ public class TextBlock : Control, IInlineHost
nameof(Inlines), t => t.Inlines, (t, v) => t.Inlines = v);

private TextLayout? _textLayout;
protected Size _constraint;
protected Size _constraint = Size.Infinity;
protected IReadOnlyList<TextRun>? _textRuns;
private InlineCollection? _inlines;

Expand Down Expand Up @@ -653,7 +653,7 @@ protected virtual TextLayout CreateTextLayout(string? text)
TextDecorations,
Foreground);

var paragraphProperties = new GenericTextParagraphProperties(FlowDirection, TextAlignment, true, false,
var paragraphProperties = new GenericTextParagraphProperties(FlowDirection, IsMeasureValid ? TextAlignment : TextAlignment.Left, true, false,
defaultProperties, TextWrapping, LineHeight, 0, LetterSpacing)
{
LineSpacing = LineSpacing
Expand Down Expand Up @@ -701,13 +701,19 @@ protected override Size MeasureOverride(Size availableSize)
{
var scale = LayoutHelper.GetLayoutScale(this);
var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale);
var deflatedSize = availableSize.Deflate(padding);

_constraint = availableSize.Deflate(padding);

//Reset TextLayout otherwise constraint might be outdated.
_textLayout?.Dispose();
_textLayout = null;
if (_constraint != deflatedSize)
{
//Reset TextLayout when the constraint is not matching.
_textLayout?.Dispose();
_textLayout = null;
_constraint = deflatedSize;

//Force arrange so text will be properly alligned.
InvalidateArrange();
}

var inlines = Inlines;

if (HasComplexContent)
Expand All @@ -722,29 +728,39 @@ protected override Size MeasureOverride(Size availableSize)
_textRuns = textRuns;
}

var width = TextLayout.OverhangLeading + TextLayout.WidthIncludingTrailingWhitespace + TextLayout.OverhangTrailing;
//This implicitly recreated the TextLayout with a new constraint if we previously reset it.
var textLayout = TextLayout;

var width = textLayout.OverhangLeading + textLayout.WidthIncludingTrailingWhitespace + textLayout.OverhangTrailing;

var size = LayoutHelper.RoundLayoutSizeUp(new Size(width, textLayout.Height).Inflate(padding), 1, 1);

return new Size(width, TextLayout.Height).Inflate(padding);
return size;
}

protected override Size ArrangeOverride(Size finalSize)
{
var scale = LayoutHelper.GetLayoutScale(this);
var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale);

//Fixes: #11019
if (finalSize.Width < _constraint.Width)
{
_textLayout?.Dispose();
_textLayout = null;
_constraint = finalSize.Deflate(padding);
}
var availableSize = finalSize.Deflate(padding);

//ToDo: Introduce a text run cache to be able to reuse shaped runs etc.
_textLayout?.Dispose();
_textLayout = null;
_constraint = availableSize;

//This implicitly recreated the TextLayout with a new constraint.
var textLayout = TextLayout;

if (HasComplexContent)
{
{
//Clear visual children before complex run arrangement
VisualChildren.Clear();

var currentY = padding.Top;

foreach (var textLine in TextLayout.TextLines)
foreach (var textLine in textLayout.TextLines)
{
var currentX = padding.Left + textLine.Start;

Expand All @@ -755,6 +771,10 @@ protected override Size ArrangeOverride(Size finalSize)
if (drawable is EmbeddedControlRun controlRun
&& controlRun.Control is Control control)
{
//Add again to prevent clipping
//Fixes: #17194
VisualChildren.Add(control);

control.Arrange(
new Rect(new Point(currentX, currentY),
new Size(control.DesiredSize.Width, textLine.Height)));
Expand Down Expand Up @@ -946,6 +966,6 @@ public InlinesTextSource(IReadOnlyList<TextRun> textRuns, IReadOnlyList<ValueSpa

return new TextEndOfParagraph();
}
}
}
}
}
64 changes: 55 additions & 9 deletions tests/Avalonia.Controls.UnitTests/TextBlockTests.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
using System;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Rendering;
using Avalonia.UnitTests;
using Moq;
using Xunit;

namespace Avalonia.Controls.UnitTests
Expand All @@ -31,26 +27,76 @@ public void Default_Text_Value_Should_Be_Null()
}

[Fact]
public void Calling_Measure_Should_Update_Constraint_And_TextLayout()
public void Calling_Measure_Should_Update_TextLayout()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var textBlock = new TestTextBlock { Text = "Hello World" };

Assert.Equal(Size.Infinity, textBlock.Constraint);

textBlock.Measure(new Size(100, 100));

var textLayout = textBlock.TextLayout;

Assert.Equal(new Size(100,100), textBlock.Constraint);

textBlock.Measure(new Size(50, 100));

Assert.Equal(new Size(50, 100), textBlock.Constraint);
Assert.NotEqual(textLayout, textBlock.TextLayout);
}
}

[Fact]
public void Calling_Arrange_With_Different_Size_Should_Update_Constraint_And_TextLayout()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var textBlock = new TestTextBlock { Text = "Hello World" };

textBlock.Measure(Size.Infinity);

var textLayout = textBlock.TextLayout;

var constraint = LayoutHelper.RoundLayoutSizeUp(new Size(textLayout.WidthIncludingTrailingWhitespace, textLayout.Height), 1, 1);

textBlock.Arrange(new Rect(constraint));

//TextLayout is recreated after arrange
textLayout = textBlock.TextLayout;

Assert.Equal(constraint, textBlock.Constraint);

textBlock.Measure(constraint);

Assert.Equal(textLayout, textBlock.TextLayout);

constraint += new Size(50, 0);

textBlock.Arrange(new Rect(constraint));

Assert.Equal(constraint, textBlock.Constraint);

//TextLayout is recreated after arrange
Assert.NotEqual(textLayout, textBlock.TextLayout);
}
}

[Fact]
public void Calling_Measure_With_Infinite_Space_Should_Set_DesiredSize()
{
using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
{
var textBlock = new TestTextBlock { Text = "Hello World" };

textBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));

var textLayout = textBlock.TextLayout;

var constraint = LayoutHelper.RoundLayoutSizeUp(new Size(textLayout.WidthIncludingTrailingWhitespace, textLayout.Height), 1, 1);

Assert.Equal(constraint, textBlock.DesiredSize);
}
}

[Fact]
public void Changing_InlinesCollection_Should_Invalidate_Measure()
{
Expand Down
101 changes: 101 additions & 0 deletions tests/Avalonia.RenderTests/Controls/TextBlockTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Avalonia.Layout;
using Avalonia.Media;
using Xunit;
using static System.Net.Mime.MediaTypeNames;

#if AVALONIA_SKIA
namespace Avalonia.Skia.RenderTests
Expand Down Expand Up @@ -145,5 +146,105 @@ Control text(VerticalAlignment verticalAlignment, bool clip = true, bool restric
await RenderToFile(target);
CompareImages();
}

[InlineData(150, 200, TextWrapping.NoWrap)]
[InlineData(44, 200, TextWrapping.NoWrap)]
[InlineData(44, 400, TextWrapping.Wrap)]
[Win32Theory("Has text")]
public async Task Should_Measure_Arrange_TextBlock(double width, double height, TextWrapping textWrapping)
{
var text = "Hello World";

var target = new StackPanel { Width = 200, Height = height };

target.Children.Add(new TextBlock
{
Text = text,
Background = Brushes.Red,
HorizontalAlignment = HorizontalAlignment.Left,
TextAlignment = TextAlignment.Left,
Width = width,
TextWrapping = textWrapping
});
target.Children.Add(new TextBlock
{
Text = text,
Background = Brushes.Red,
HorizontalAlignment = HorizontalAlignment.Left,
TextAlignment = TextAlignment.Center,
Width = width,
TextWrapping = textWrapping
});
target.Children.Add(new TextBlock
{
Text = text,
Background = Brushes.Red,
HorizontalAlignment = HorizontalAlignment.Left,
TextAlignment = TextAlignment.Right,
Width = width,
TextWrapping = textWrapping
});

target.Children.Add(new TextBlock
{
Text = text,
Background = Brushes.Red,
HorizontalAlignment = HorizontalAlignment.Center,
TextAlignment = TextAlignment.Left,
Width = width,
TextWrapping = textWrapping
});
target.Children.Add(new TextBlock
{
Text = text,
Background = Brushes.Red,
HorizontalAlignment = HorizontalAlignment.Center,
TextAlignment = TextAlignment.Center,
Width = width,
TextWrapping = textWrapping
});
target.Children.Add(new TextBlock
{
Text = text,
Background = Brushes.Red,
HorizontalAlignment = HorizontalAlignment.Center,
TextAlignment = TextAlignment.Right,
Width = width,
TextWrapping = textWrapping
});

target.Children.Add(new TextBlock
{
Text = text,
Background = Brushes.Red,
HorizontalAlignment = HorizontalAlignment.Right,
TextAlignment = TextAlignment.Left,
Width = width,
TextWrapping = textWrapping
});
target.Children.Add(new TextBlock
{
Text = text,
Background = Brushes.Red,
HorizontalAlignment = HorizontalAlignment.Right,
TextAlignment = TextAlignment.Center,
Width = width,
TextWrapping = textWrapping
});
target.Children.Add(new TextBlock
{
Text = text,
Background = Brushes.Red,
HorizontalAlignment = HorizontalAlignment.Right,
TextAlignment = TextAlignment.Right,
Width = width,
TextWrapping = textWrapping
});

var testName = $"Should_Measure_Arrange_TextBlock_{width}_{textWrapping}";

await RenderToFile(target, testName);
CompareImages(testName);
}
}
}
9 changes: 9 additions & 0 deletions tests/Avalonia.RenderTests/TestSkip.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,14 @@ public Win32Fact(string message)
Skip = message;
}
}

public class Win32Theory : TheoryAttribute
{
public Win32Theory(string message)
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
Skip = message;
}
}
}

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.

0 comments on commit 8ffd557

Please sign in to comment.