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 = public static readonly StyledProperty<int> MaxLinesProperty =
AvaloniaProperty.Register<TextBox, int>(nameof(MaxLines)); 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> /// <summary>
/// Defines the <see cref="Text"/> property /// Defines the <see cref="Text"/> property
/// </summary> /// </summary>
@ -519,6 +525,15 @@ namespace Avalonia.Controls
set => SetValue(MaxLinesProperty, value); 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> /// <summary>
/// Gets or sets the spacing between characters /// Gets or sets the spacing between characters
/// </summary> /// </summary>
@ -913,6 +928,10 @@ namespace Avalonia.Controls
{ {
InvalidateMeasure(); InvalidateMeasure();
} }
else if (change.Property == MinLinesProperty)
{
InvalidateMeasure();
}
else if (change.Property == UndoLimitProperty) else if (change.Property == UndoLimitProperty)
{ {
OnUndoLimitChanged(change.GetNewValue<int>()); OnUndoLimitChanged(change.GetNewValue<int>());
@ -1836,7 +1855,7 @@ namespace Avalonia.Controls
} }
SetCurrentValue(SelectionEndProperty, SelectionEnd + offset); SetCurrentValue(SelectionEndProperty, SelectionEnd + offset);
if (moveCaretPosition) if (moveCaretPosition)
{ {
_presenter.MoveCaretToTextPosition(SelectionEnd); _presenter.MoveCaretToTextPosition(SelectionEnd);
@ -2034,7 +2053,7 @@ namespace Avalonia.Controls
var margin = visual.GetValue<Thickness>(Layoutable.MarginProperty); var margin = visual.GetValue<Thickness>(Layoutable.MarginProperty);
var padding = visual.GetValue<Thickness>(Decorator.PaddingProperty); var padding = visual.GetValue<Thickness>(Decorator.PaddingProperty);
verticalSpace += margin.Top + padding.Top + padding.Bottom + margin.Bottom; verticalSpace += margin.Top + padding.Top + padding.Bottom + margin.Bottom;
visual = visual.VisualParent; visual = visual.VisualParent;
@ -2073,8 +2092,8 @@ namespace Avalonia.Controls
var selectionStart = CaretIndex; var selectionStart = CaretIndex;
MoveHorizontal(-1, true, false, false); MoveHorizontal(-1, true, false, false);
if (SelectionEnd > 0 && if (SelectionEnd > 0 &&
selectionStart < text.Length && text[selectionStart] == ' ') selectionStart < text.Length && text[selectionStart] == ' ')
{ {
SetCurrentValue(SelectionEndProperty, SelectionEnd - 1); SetCurrentValue(SelectionEndProperty, SelectionEnd - 1);
@ -2203,30 +2222,46 @@ namespace Avalonia.Controls
var fontSize = FontSize; var fontSize = FontSize;
var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch); var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
var paragraphProperties = TextLayout.CreateTextParagraphProperties(typeface, fontSize, null, default, default, null, default, LineHeight, default); 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(); var verticalSpace = GetVerticalSpaceBetweenScrollViewerAndPresenter();
maxHeight = Math.Ceiling(textLayout.Height + verticalSpace); maxHeight = Math.Ceiling(textLayout.Height + verticalSpace);
} }
_scrollViewer.SetCurrentValue(MaxHeightProperty, maxHeight); _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); 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) public TextRun? GetTextRun(int textSourceIndex)
{ {
if (textSourceIndex >= _maxLines) if (textSourceIndex >= _lines)
{ {
return null; 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] [Fact]
public void CanUndo_CanRedo_Is_False_When_Initialized() public void CanUndo_CanRedo_Is_False_When_Initialized()
{ {

Loading…
Cancel
Save