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

Fixes TextBox measure logic for MaxLines scenario #12589

Merged
merged 2 commits into from
Aug 19, 2023
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
34 changes: 33 additions & 1 deletion src/Avalonia.Controls/TextBox.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1879,6 +1879,37 @@ private string GetSelection()
return text.Substring(start, end - start);
}

/// <summary>
/// Returns the sum of any vertical whitespace added between the <see cref="ScrollViewer"/> and <see cref="TextPresenter"/> in the control template.
/// </summary>
/// <returns>The total vertical whitespace.</returns>
private double GetVerticalSpaceBetweenScrollViewerAndPresenter()
{
var verticalSpace = 0.0;
if (_presenter != null)
{
Visual? visual = _presenter;
while ((visual != null) && (visual != this))
{
if (visual == _scrollViewer)
{
// ScrollViewer is a stopping point and should only include the Padding
verticalSpace += _scrollViewer.Padding.Top + _scrollViewer.Padding.Bottom;
break;
}

var margin = visual.GetValue<Thickness>(Layoutable.MarginProperty);
var padding = visual.GetValue<Thickness>(Decorator.PaddingProperty);

verticalSpace += margin.Top + padding.Top + padding.Bottom + margin.Bottom;

visual = visual.VisualParent;
}
}

return verticalSpace;
}

/// <summary>
/// Raises both the <see cref="TextChanging"/> and <see cref="TextChanged"/> events.
/// </summary>
Expand Down Expand Up @@ -2032,8 +2063,9 @@ protected override Size MeasureOverride(Size availableSize)
var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
var paragraphProperties = TextLayout.CreateTextParagraphProperties(typeface, fontSize, null, default, default, null, default, LineHeight, default);
var textLayout = new TextLayout(new MaxLinesTextSource(MaxLines), paragraphProperties);
var verticalSpace = GetVerticalSpaceBetweenScrollViewerAndPresenter();

maxHeight = Math.Ceiling(textLayout.Height);
maxHeight = Math.Ceiling(textLayout.Height + verticalSpace);
}

_scrollViewer.SetCurrentValue(MaxHeightProperty, maxHeight);
Expand Down
79 changes: 78 additions & 1 deletion tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Moq;
using Xunit;

Expand Down Expand Up @@ -916,6 +917,82 @@ public void Should_Fullfill_MaxLines_Contraint()
}
}

[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void MaxLines_Sets_ScrollViewer_MaxHeight(int maxLines)
{
using (UnitTestApplication.Start(Services))
{
var target = new TextBox
{
Template = CreateTemplate(),
MaxLines = maxLines,

// Define explicit whole number line height for predictable calculations
LineHeight = 20
};

var impl = CreateMockTopLevelImpl();
var topLevel = new TestTopLevel(impl.Object)
{
Template = CreateTopLevelTemplate(),
Content = target
};
topLevel.ApplyTemplate();
topLevel.LayoutManager.ExecuteInitialLayoutPass();

var textPresenter = target.FindDescendantOfType<TextPresenter>();
Assert.Equal("PART_TextPresenter", textPresenter.Name);
Assert.Equal(new Thickness(0), textPresenter.Margin); // Test assumes no margin on TextPresenter

var scrollViewer = target.FindDescendantOfType<ScrollViewer>();
Assert.Equal("PART_ScrollViewer", scrollViewer.Name);
Assert.Equal(maxLines * target.LineHeight, scrollViewer.MaxHeight);
}
}

[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void MaxLines_Sets_ScrollViewer_MaxHeight_With_TextPresenter_Margin(int maxLines)
{
using (UnitTestApplication.Start(Services))
{
var target = new TextBox
{
Template = CreateTemplate(),
MaxLines = maxLines,

// Define explicit whole number line height for predictable calculations
LineHeight = 20
};

var impl = CreateMockTopLevelImpl();
var topLevel = new TestTopLevel(impl.Object)
{
Template = CreateTopLevelTemplate(),
Content = target
};
topLevel.ApplyTemplate();
topLevel.LayoutManager.ExecuteInitialLayoutPass();

var textPresenter = target.FindDescendantOfType<TextPresenter>();
Assert.Equal("PART_TextPresenter", textPresenter.Name);
var textPresenterMargin = new Thickness(horizontal: 0, vertical: 3);
textPresenter.Margin = textPresenterMargin;

target.InvalidateMeasure();
target.Measure(Size.Infinity);

var scrollViewer = target.FindDescendantOfType<ScrollViewer>();
Assert.Equal("PART_ScrollViewer", scrollViewer.Name);
Assert.Equal((maxLines * target.LineHeight) + textPresenterMargin.Top + textPresenterMargin.Bottom, scrollViewer.MaxHeight);
}
}

[Fact]
public void CanUndo_CanRedo_Is_False_When_Initialized()
{
Expand Down Expand Up @@ -1125,7 +1202,7 @@ private IControlTemplate CreateTemplate()
return new FuncControlTemplate<TextBox>((control, scope) =>
new ScrollViewer
{
Name = "Part_ScrollViewer",
Name = "PART_ScrollViewer",
Template = new FuncControlTemplate<ScrollViewer>(ScrollViewerTests.CreateTemplate),
Content = new TextPresenter
{
Expand Down