Browse Source

Rework how TextBlock skips redundant measure and arrange calls (#17271)

* 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
release/11.2.0-rc2
Benedikt Stebner 1 year ago
committed by Max Katz
parent
commit
4f79c1b939
  1. 40
      src/Avalonia.Controls/TextBlock.cs
  2. 64
      tests/Avalonia.Controls.UnitTests/TextBlockTests.cs

40
src/Avalonia.Controls/TextBlock.cs

@ -162,7 +162,7 @@ namespace Avalonia.Controls
nameof(Inlines), t => t.Inlines, (t, v) => t.Inlines = v); nameof(Inlines), t => t.Inlines, (t, v) => t.Inlines = v);
private TextLayout? _textLayout; private TextLayout? _textLayout;
protected Size _constraint; protected Size _constraint = Size.Infinity;
protected IReadOnlyList<TextRun>? _textRuns; protected IReadOnlyList<TextRun>? _textRuns;
private InlineCollection? _inlines; private InlineCollection? _inlines;
@ -701,13 +701,19 @@ namespace Avalonia.Controls
{ {
var scale = LayoutHelper.GetLayoutScale(this); var scale = LayoutHelper.GetLayoutScale(this);
var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale); var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale);
var deflatedSize = availableSize.Deflate(padding);
_constraint = availableSize.Deflate(padding); if(_constraint != deflatedSize)
{
//Reset TextLayout otherwise constraint might be outdated. //Reset TextLayout when the constraint is not matching.
_textLayout?.Dispose(); _textLayout?.Dispose();
_textLayout = null; _textLayout = null;
_constraint = deflatedSize;
//Force arrange so text will be properly alligned.
InvalidateArrange();
}
var inlines = Inlines; var inlines = Inlines;
if (HasComplexContent) if (HasComplexContent)
@ -722,9 +728,16 @@ namespace Avalonia.Controls
_textRuns = textRuns; _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) protected override Size ArrangeOverride(Size finalSize)
@ -732,19 +745,24 @@ namespace Avalonia.Controls
var scale = LayoutHelper.GetLayoutScale(this); var scale = LayoutHelper.GetLayoutScale(this);
var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale); var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale);
var availableSize = finalSize.Deflate(padding);
//Fixes: #11019 //Fixes: #11019
if (finalSize.Width < _constraint.Width) if (availableSize != _constraint)
{ {
_textLayout?.Dispose(); _textLayout?.Dispose();
_textLayout = null; _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) if (HasComplexContent)
{ {
var currentY = padding.Top; var currentY = padding.Top;
foreach (var textLine in TextLayout.TextLines) foreach (var textLine in textLayout.TextLines)
{ {
var currentX = padding.Left + textLine.Start; var currentX = padding.Left + textLine.Start;

64
tests/Avalonia.Controls.UnitTests/TextBlockTests.cs

@ -1,13 +1,9 @@
using System;
using Avalonia.Controls.Documents; using Avalonia.Controls.Documents;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input; using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Rendering;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Moq;
using Xunit; using Xunit;
namespace Avalonia.Controls.UnitTests namespace Avalonia.Controls.UnitTests
@ -37,20 +33,74 @@ namespace Avalonia.Controls.UnitTests
{ {
var textBlock = new TestTextBlock { Text = "Hello World" }; var textBlock = new TestTextBlock { Text = "Hello World" };
Assert.Equal(Size.Infinity, textBlock.Constraint);
textBlock.Measure(new Size(100, 100)); textBlock.Measure(new Size(100, 100));
var textLayout = textBlock.TextLayout; 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)); 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); 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] [Fact]
public void Changing_InlinesCollection_Should_Invalidate_Measure() public void Changing_InlinesCollection_Should_Invalidate_Measure()
{ {

Loading…
Cancel
Save