Browse Source

fix TextBox multiline selection with up/down keys till start/end of text (#18746)

* fix: TextBox multiline selection with up/down keys till start/end of text

* refactor to preserve public API of TextPresenter

* refactor and cleanup
release/11.3.1
Rastislav Svoboda 9 months ago
committed by Julien Lebosquain
parent
commit
f07db02a28
  1. 104
      src/Avalonia.Controls/TextBox.cs
  2. 62
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

104
src/Avalonia.Controls/TextBox.cs

@ -1464,59 +1464,23 @@ namespace Avalonia.Controls
break;
case Key.Up:
selection = DetectSelection();
MoveVertical(LogicalDirection.Backward, selection);
if (caretIndex != _presenter.CaretIndex)
{
selection = DetectSelection();
if (!selection && SelectionStart != SelectionEnd)
{
ClearSelectionAndMoveCaretToTextPosition(LogicalDirection.Backward);
}
_presenter.MoveCaretVertical(LogicalDirection.Backward);
if (caretIndex != _presenter.CaretIndex)
{
movement = true;
}
if (selection)
{
SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
}
else
{
SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
}
break;
movement = true;
}
break;
case Key.Down:
selection = DetectSelection();
MoveVertical(LogicalDirection.Forward, selection);
if (caretIndex != _presenter.CaretIndex)
{
selection = DetectSelection();
if (!selection && SelectionStart != SelectionEnd)
{
ClearSelectionAndMoveCaretToTextPosition(LogicalDirection.Forward);
}
_presenter.MoveCaretVertical();
if (caretIndex != _presenter.CaretIndex)
{
movement = true;
}
if (selection)
{
SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
}
else
{
SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
}
break;
movement = true;
}
break;
case Key.Back:
{
SnapshotUndoRedo();
@ -2038,6 +2002,50 @@ namespace Avalonia.Controls
}
}
private void MoveVertical(LogicalDirection direction, bool isSelecting)
{
if (_presenter is null)
{
return;
}
if (isSelecting)
{
var oldCaretIndex = _presenter.CaretIndex;
_presenter.MoveCaretVertical(direction);
var newCaretIndex = _presenter.CaretIndex;
if (oldCaretIndex == newCaretIndex)
{
var text = Text ?? string.Empty;
// caret did not move while we are selecting so we could not move to previous/next line,
// but check if we are already at the 'boundary' of the text
if (direction == LogicalDirection.Forward && newCaretIndex < text.Length)
{
_presenter.MoveCaretToTextPosition(text.Length);
}
else if (direction == LogicalDirection.Backward && newCaretIndex > 0)
{
_presenter.MoveCaretToTextPosition(0);
}
}
SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
}
else
{
if (SelectionStart != SelectionEnd)
{
ClearSelectionAndMoveCaretToTextPosition(direction);
}
_presenter.MoveCaretVertical(direction);
SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
}
}
private void MoveHome(bool document)
{
if (_presenter is null)

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

@ -1875,6 +1875,68 @@ namespace Avalonia.Controls.UnitTests
}
}
[Theory]
[InlineData(0)]
[InlineData(4)]
[InlineData(8)]
public void When_Selecting_Multiline_Selection_Should_Be_Extended_With_Up_Arrow_Key_Till_Start_Of_Text(int caretOffsetFromEnd)
{
using (UnitTestApplication.Start(Services))
{
var tb = new TextBox
{
Template = CreateTemplate(),
Text = """
AAAAAA
BBBB
CCCCCCCC
""",
AcceptsReturn = true
};
tb.ApplyTemplate();
tb.Measure(Size.Infinity);
tb.CaretIndex = tb.Text.Length - caretOffsetFromEnd;
RaiseKeyEvent(tb, Key.Up, KeyModifiers.Shift);
RaiseKeyEvent(tb, Key.Up, KeyModifiers.Shift);
RaiseKeyEvent(tb, Key.Up, KeyModifiers.Shift);
RaiseKeyEvent(tb, Key.Up, KeyModifiers.Shift);
Assert.Equal(0, tb.SelectionEnd);
}
}
[Theory]
[InlineData(0)]
[InlineData(3)]
[InlineData(6)]
public void When_Selecting_Multiline_Selection_Should_Be_Extended_With_Down_Arrow_Key_Till_End_Of_Text(int caretOffsetFromStart)
{
using (UnitTestApplication.Start(Services))
{
var tb = new TextBox
{
Template = CreateTemplate(),
Text = """
AAAAAA
BBBB
CCCCCCCC
""",
AcceptsReturn = true
};
tb.ApplyTemplate();
tb.Measure(Size.Infinity);
tb.CaretIndex = caretOffsetFromStart;
RaiseKeyEvent(tb, Key.Down, KeyModifiers.Shift);
RaiseKeyEvent(tb, Key.Down, KeyModifiers.Shift);
RaiseKeyEvent(tb, Key.Down, KeyModifiers.Shift);
RaiseKeyEvent(tb, Key.Down, KeyModifiers.Shift);
Assert.Equal(tb.Text.Length, tb.SelectionEnd);
}
}
[Fact]
public void TextBox_In_AdornerLayer_Will_Not_Cause_Collection_Modified_In_VisualLayerManager_Measure()
{

Loading…
Cancel
Save