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
pull/17322/head
Benedikt Stebner 1 year ago
committed by GitHub
parent
commit
82b048d58e
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  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);
private TextLayout? _textLayout;
protected Size _constraint;
protected Size _constraint = Size.Infinity;
protected IReadOnlyList<TextRun>? _textRuns;
private InlineCollection? _inlines;
@ -701,13 +701,19 @@ namespace Avalonia.Controls
{
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)
@ -722,9 +728,16 @@ namespace Avalonia.Controls
_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)
@ -732,19 +745,24 @@ namespace Avalonia.Controls
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;

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

@ -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
@ -37,20 +33,74 @@ namespace Avalonia.Controls.UnitTests
{
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()
{

Loading…
Cancel
Save