Skip to content

Commit

Permalink
Rework how TextBlock skips redundant measure and arrange calls (#17271)
Browse files Browse the repository at this point in the history
* Rework how TextBlock skips redundant measure and arrange calls
Add some tests

* Adjust tests

* Try this

* Make sure the TextBlock is arranged after it has been measured with a different availableSize

* Make it more clear that we are resetting and recreating the TextLayout

* Capture textLayout after inlines have been processed
  • Loading branch information
Gillibald authored and maxkatz6 committed Oct 27, 2024
1 parent 0dbf5c2 commit 4f79c1b
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 18 deletions.
40 changes: 29 additions & 11 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 @@ -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,41 @@ 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);
_constraint = size;

return size;
}

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

var availableSize = finalSize.Deflate(padding);

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

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

if (HasComplexContent)
{
var currentY = padding.Top;

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

Expand Down
64 changes: 57 additions & 7 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 Down Expand Up @@ -37,20 +33,74 @@ public void Calling_Measure_Should_Update_Constraint_And_TextLayout()
{
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);
Assert.Equal(new Size(110, 10), 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);

Assert.Equal(constraint, textBlock.Constraint);

textBlock.Arrange(new Rect(constraint));

Assert.Equal(constraint, textBlock.Constraint);

Assert.Equal(textLayout, textBlock.TextLayout);

textBlock.Measure(constraint);

Assert.Equal(textLayout, textBlock.TextLayout);

constraint += new Size(50, 0);

textBlock.Arrange(new Rect(constraint));

Assert.Equal(constraint, textBlock.Constraint);

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;

Assert.Equal(new Size(110, 10), textBlock.Constraint);

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

0 comments on commit 4f79c1b

Please sign in to comment.