diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 13bdfcfbbe..01e2331ad0 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -1451,6 +1451,11 @@ namespace Avalonia.Controls { selection = DetectSelection(); + if (!selection && SelectionStart != SelectionEnd) + { + ClearSelectionAndMoveCaretToTextPosition(LogicalDirection.Backward); + } + _presenter.MoveCaretVertical(LogicalDirection.Backward); if (caretIndex != _presenter.CaretIndex) @@ -1473,6 +1478,11 @@ namespace Avalonia.Controls { selection = DetectSelection(); + if (!selection && SelectionStart != SelectionEnd) + { + ClearSelectionAndMoveCaretToTextPosition(LogicalDirection.Forward); + } + _presenter.MoveCaretVertical(); if (caretIndex != _presenter.CaretIndex) @@ -1967,13 +1977,9 @@ namespace Avalonia.Controls { if (selectionStart != selectionEnd) { - // clear the selection and move to the appropriate side of previous selection - var newPosition = direction > 0 ? - Math.Max(selectionStart, selectionEnd) : - Math.Min(selectionStart, selectionEnd); - SetCurrentValue(SelectionStartProperty, newPosition); - SetCurrentValue(SelectionEndProperty, newPosition); - _presenter.MoveCaretToTextPosition(newPosition); + ClearSelectionAndMoveCaretToTextPosition(direction > 0 ? + LogicalDirection.Forward : + LogicalDirection.Backward); } else { @@ -2084,6 +2090,17 @@ namespace Avalonia.Controls _scrollViewer?.PageDown(); } + private void ClearSelectionAndMoveCaretToTextPosition(LogicalDirection direction) + { + var newPosition = direction == LogicalDirection.Forward ? + Math.Max(SelectionStart, SelectionEnd) : + Math.Min(SelectionStart, SelectionEnd); + SetCurrentValue(SelectionStartProperty, newPosition); + SetCurrentValue(SelectionEndProperty, newPosition); + // move caret to appropriate side of previous selection + _presenter?.MoveCaretToTextPosition(newPosition); + } + /// /// Scroll the to the specified line index. /// diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index c1b73955a5..29ba1f3289 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -1430,6 +1430,7 @@ namespace Avalonia.Controls.UnitTests [InlineData(0,4)] [InlineData(2,6)] [InlineData(0,6)] + [InlineData(3,4)] public void When_Selection_From_Left_To_Right_Pressing_Right_Should_Remove_Selection_Moving_Caret_To_End_Of_Previous_Selection(int selectionStart, int selectionEnd) { using (UnitTestApplication.Start(Services)) @@ -1458,6 +1459,7 @@ namespace Avalonia.Controls.UnitTests [InlineData(0,4)] [InlineData(2,6)] [InlineData(0,6)] + [InlineData(3,4)] public void When_Selection_From_Left_To_Right_Pressing_Left_Should_Remove_Selection_Moving_Caret_To_Start_Of_Previous_Selection(int selectionStart, int selectionEnd) { using (UnitTestApplication.Start(Services)) @@ -1486,6 +1488,7 @@ namespace Avalonia.Controls.UnitTests [InlineData(4,0)] [InlineData(6,2)] [InlineData(6,0)] + [InlineData(4,3)] public void When_Selection_From_Right_To_Left_Pressing_Right_Should_Remove_Selection_Moving_Caret_To_Start_Of_Previous_Selection(int selectionStart, int selectionEnd) { using (UnitTestApplication.Start(Services)) @@ -1514,6 +1517,7 @@ namespace Avalonia.Controls.UnitTests [InlineData(4,0)] [InlineData(6,2)] [InlineData(6,0)] + [InlineData(4,3)] public void When_Selection_From_Right_To_Left_Pressing_Left_Should_Remove_Selection_Moving_Caret_To_End_Of_Previous_Selection(int selectionStart, int selectionEnd) { using (UnitTestApplication.Start(Services)) @@ -1591,6 +1595,176 @@ namespace Avalonia.Controls.UnitTests } } + [Theory] + [InlineData(2,4)] + [InlineData(0,4)] + [InlineData(2,6)] + [InlineData(0,6)] + [InlineData(3,4)] + public void When_Selection_From_Left_To_Right_Pressing_Up_Should_Remove_Selection_Moving_Caret_To_Start_Of_Previous_Selection(int selectionStart, int selectionEnd) + { + using (UnitTestApplication.Start(Services)) + { + var tb = new TextBox + { + Template = CreateTemplate(), + Text = "ABCDEF" + }; + + tb.Measure(Size.Infinity); + tb.CaretIndex = selectionStart; + tb.SelectionStart = selectionStart; + tb.SelectionEnd = selectionEnd; + + RaiseKeyEvent(tb, Key.Up, KeyModifiers.None); + + Assert.Equal(selectionStart, tb.SelectionStart); + Assert.Equal(selectionStart, tb.SelectionEnd); + Assert.Equal(selectionStart, tb.CaretIndex); + } + } + + [Theory] + [InlineData(4,2)] + [InlineData(4,0)] + [InlineData(6,2)] + [InlineData(6,0)] + [InlineData(4,3)] + public void When_Selection_From_Right_To_Left_Pressing_Up_Should_Remove_Selection_Moving_Caret_To_End_Of_Previous_Selection(int selectionStart, int selectionEnd) + { + using (UnitTestApplication.Start(Services)) + { + var tb = new TextBox + { + Template = CreateTemplate(), + Text = "ABCDEF" + }; + + tb.Measure(Size.Infinity); + tb.CaretIndex = selectionStart; + tb.SelectionStart = selectionStart; + tb.SelectionEnd = selectionEnd; + + RaiseKeyEvent(tb, Key.Up, KeyModifiers.None); + + Assert.Equal(selectionEnd, tb.SelectionStart); + Assert.Equal(selectionEnd, tb.SelectionEnd); + Assert.Equal(selectionEnd, tb.CaretIndex); + } + } + + [Theory] + [InlineData(0)] + [InlineData(2)] + [InlineData(4)] + [InlineData(6)] + public void When_Select_All_From_Position_Up_Should_Remove_Selection_Moving_Caret_To_Start(int caretIndex) + { + using (UnitTestApplication.Start(Services)) + { + var tb = new TextBox + { + Template = CreateTemplate(), + Text = "ABCDEF" + }; + + tb.Measure(Size.Infinity); + tb.CaretIndex = caretIndex; + + RaiseKeyEvent(tb, Key.A, KeyModifiers.Control); + RaiseKeyEvent(tb, Key.Up, KeyModifiers.None); + + Assert.Equal(0, tb.SelectionStart); + Assert.Equal(0, tb.SelectionEnd); + Assert.Equal(0, tb.CaretIndex); + } + } + + [Theory] + [InlineData(2,4)] + [InlineData(0,4)] + [InlineData(2,6)] + [InlineData(0,6)] + [InlineData(3,4)] + public void When_Selection_From_Left_To_Right_Pressing_Down_Should_Remove_Selection_Moving_Caret_To_End_Of_Previous_Selection(int selectionStart, int selectionEnd) + { + using (UnitTestApplication.Start(Services)) + { + var tb = new TextBox + { + Template = CreateTemplate(), + Text = "ABCDEF" + }; + + tb.Measure(Size.Infinity); + tb.CaretIndex = selectionStart; + tb.SelectionStart = selectionStart; + tb.SelectionEnd = selectionEnd; + + RaiseKeyEvent(tb, Key.Down, KeyModifiers.None); + + Assert.Equal(selectionEnd, tb.SelectionStart); + Assert.Equal(selectionEnd, tb.SelectionEnd); + Assert.Equal(selectionEnd, tb.CaretIndex); + } + } + + [Theory] + [InlineData(4,2)] + [InlineData(4,0)] + [InlineData(6,2)] + [InlineData(6,0)] + [InlineData(4,3)] + public void When_Selection_From_Right_To_Left_Pressing_Down_Should_Remove_Selection_Moving_Caret_To_Start_Of_Previous_Selection(int selectionStart, int selectionEnd) + { + using (UnitTestApplication.Start(Services)) + { + var tb = new TextBox + { + Template = CreateTemplate(), + Text = "ABCDEF" + }; + + tb.Measure(Size.Infinity); + tb.CaretIndex = selectionStart; + tb.SelectionStart = selectionStart; + tb.SelectionEnd = selectionEnd; + + RaiseKeyEvent(tb, Key.Down, KeyModifiers.None); + + Assert.Equal(selectionStart, tb.SelectionStart); + Assert.Equal(selectionStart, tb.SelectionEnd); + Assert.Equal(selectionStart, tb.CaretIndex); + } + } + + [Theory] + [InlineData(0)] + [InlineData(2)] + [InlineData(4)] + [InlineData(6)] + public void When_Select_All_From_Position_Down_Should_Remove_Selection_Moving_Caret_To_End(int caretIndex) + { + using (UnitTestApplication.Start(Services)) + { + var tb = new TextBox + { + Template = CreateTemplate(), + Text = "ABCDEF" + }; + + tb.Measure(Size.Infinity); + tb.CaretIndex = caretIndex; + + RaiseKeyEvent(tb, Key.A, KeyModifiers.Control); + RaiseKeyEvent(tb, Key.Down, KeyModifiers.None); + + Assert.Equal(tb.Text.Length, tb.SelectionStart); + Assert.Equal(tb.Text.Length, tb.SelectionEnd); + Assert.Equal(tb.Text.Length, tb.CaretIndex); + } + } + [Fact] public void TextBox_In_AdornerLayer_Will_Not_Cause_Collection_Modified_In_VisualLayerManager_Measure() {