Browse Source

Added MinLines property and related tests to the TextBox control. (#14313)

Co-authored-by: Andre118 <stu209450@gmail.com>
pull/14157
Andre118 2 years ago
committed by GitHub
parent
commit
82809b5220
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 55
      src/Avalonia.Controls/TextBox.cs
  2. 112
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

55
src/Avalonia.Controls/TextBox.cs

@ -124,6 +124,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<int> MaxLinesProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLines));
/// <summary>
/// Defines the <see cref="MinLines"/> property
/// </summary>
public static readonly StyledProperty<int> MinLinesProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(MinLines));
/// <summary>
/// Defines the <see cref="Text"/> property
/// </summary>
@ -519,6 +525,15 @@ namespace Avalonia.Controls
set => SetValue(MaxLinesProperty, value);
}
/// <summary>
/// Gets or sets the minimum number of visible lines to size to.
/// </summary>
public int MinLines
{
get => GetValue(MinLinesProperty);
set => SetValue(MinLinesProperty, value);
}
/// <summary>
/// Gets or sets the spacing between characters
/// </summary>
@ -913,6 +928,10 @@ namespace Avalonia.Controls
{
InvalidateMeasure();
}
else if (change.Property == MinLinesProperty)
{
InvalidateMeasure();
}
else if (change.Property == UndoLimitProperty)
{
OnUndoLimitChanged(change.GetNewValue<int>());
@ -1836,7 +1855,7 @@ namespace Avalonia.Controls
}
SetCurrentValue(SelectionEndProperty, SelectionEnd + offset);
if (moveCaretPosition)
{
_presenter.MoveCaretToTextPosition(SelectionEnd);
@ -2034,7 +2053,7 @@ namespace Avalonia.Controls
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;
@ -2073,8 +2092,8 @@ namespace Avalonia.Controls
var selectionStart = CaretIndex;
MoveHorizontal(-1, true, false, false);
if (SelectionEnd > 0 &&
if (SelectionEnd > 0 &&
selectionStart < text.Length && text[selectionStart] == ' ')
{
SetCurrentValue(SelectionEndProperty, SelectionEnd - 1);
@ -2203,30 +2222,46 @@ namespace Avalonia.Controls
var fontSize = FontSize;
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 textLayout = new TextLayout(new LineTextSource(MaxLines), paragraphProperties);
var verticalSpace = GetVerticalSpaceBetweenScrollViewerAndPresenter();
maxHeight = Math.Ceiling(textLayout.Height + verticalSpace);
}
_scrollViewer.SetCurrentValue(MaxHeightProperty, maxHeight);
var minHeight = 0.0;
if (MinLines > 0 && double.IsNaN(Height))
{
var fontSize = FontSize;
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 LineTextSource(MinLines), paragraphProperties);
var verticalSpace = GetVerticalSpaceBetweenScrollViewerAndPresenter();
minHeight = Math.Ceiling(textLayout.Height + verticalSpace);
}
_scrollViewer.SetCurrentValue(MinHeightProperty, minHeight);
}
return base.MeasureOverride(availableSize);
}
private class MaxLinesTextSource : ITextSource
private class LineTextSource : ITextSource
{
private readonly int _maxLines;
private readonly int _lines;
public MaxLinesTextSource(int maxLines)
public LineTextSource(int lines)
{
_maxLines = maxLines;
_lines = lines;
}
public TextRun? GetTextRun(int textSourceIndex)
{
if (textSourceIndex >= _maxLines)
if (textSourceIndex >= _lines)
{
return null;
}

112
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@ -1066,6 +1066,118 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void Should_Fullfill_MinLines_Contraint()
{
using (UnitTestApplication.Start(Services))
{
var target = new TextBox
{
Template = CreateTemplate(),
Text = "ABC \n DEF \n GHI",
MinLines = 3,
AcceptsReturn = true
};
var impl = CreateMockTopLevelImpl();
var topLevel = new TestTopLevel(impl.Object)
{
Template = CreateTopLevelTemplate()
};
topLevel.Content = target;
topLevel.ApplyTemplate();
topLevel.LayoutManager.ExecuteInitialLayoutPass();
target.ApplyTemplate();
target.Measure(Size.Infinity);
var initialHeight = target.DesiredSize.Height;
target.Text = "";
target.InvalidateMeasure();
target.Measure(Size.Infinity);
Assert.Equal(initialHeight, target.DesiredSize.Height);
}
}
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void MinLines_Sets_ScrollViewer_MinHeight(int minLines)
{
using (UnitTestApplication.Start(Services))
{
var target = new TextBox
{
Template = CreateTemplate(),
MinLines = minLines,
// 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(minLines * target.LineHeight, scrollViewer.MinHeight);
}
}
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void MinLines_Sets_ScrollViewer_MinHeight_With_TextPresenter_Margin(int minLines)
{
using (UnitTestApplication.Start(Services))
{
var target = new TextBox
{
Template = CreateTemplate(),
MinLines = minLines,
// 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((minLines * target.LineHeight) + textPresenterMargin.Top + textPresenterMargin.Bottom, scrollViewer.MinHeight);
}
}
[Fact]
public void CanUndo_CanRedo_Is_False_When_Initialized()
{

Loading…
Cancel
Save