From 33d95c6af5c03343748d22c2972cfc5475c3dc83 Mon Sep 17 00:00:00 2001 From: markl65536 <33181200+markl65536@users.noreply.github.com> Date: Fri, 9 Feb 2024 01:48:19 +0100 Subject: [PATCH] Discard additional lines upon inserting when AcceptsReturn=false (#14173) * Discard additional lines upon inserting when AcceptReturn=false * Use LineBreakEnumerator for detecting line breaks * Remove unused member Co-authored-by: Max Katz * Use Grapheme enumerator --------- Co-authored-by: Markus Co-authored-by: Max Katz Co-authored-by: Benedikt Stebner --- src/Avalonia.Controls/TextBox.cs | 34 +++++++++++++---- .../TextBoxTests.cs | 38 +++++++++++++++++++ 2 files changed, 65 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 5466c61b68..30e2518dac 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -16,6 +16,7 @@ using Avalonia.Utilities; using Avalonia.Controls.Metadata; using Avalonia.Media.TextFormatting; using Avalonia.Automation.Peers; +using Avalonia.Media.TextFormatting.Unicode; using Avalonia.Threading; namespace Avalonia.Controls @@ -837,7 +838,7 @@ namespace Avalonia.Controls _scrollViewer = e.NameScope.Find("PART_ScrollViewer"); - if(_scrollViewer != null) + if (_scrollViewer != null) { _scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged; } @@ -886,9 +887,9 @@ namespace Avalonia.Controls private void PresenterPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) { - if(e.Property == TextPresenter.PreeditTextProperty) + if (e.Property == TextPresenter.PreeditTextProperty) { - if(string.IsNullOrEmpty(e.OldValue as string) && !string.IsNullOrEmpty(e.NewValue as string)) + if (string.IsNullOrEmpty(e.OldValue as string) && !string.IsNullOrEmpty(e.NewValue as string)) { PseudoClasses.Set(":empty", false); @@ -1013,7 +1014,7 @@ namespace Avalonia.Controls return; } - input = RemoveInvalidCharacters(input); + input = SanitizeInputText(input); if (string.IsNullOrEmpty(input)) { @@ -1066,11 +1067,30 @@ namespace Avalonia.Controls } } - private string? RemoveInvalidCharacters(string? text) + private string? SanitizeInputText(string? text) { if (text is null) return null; + if (!AcceptsReturn) + { + var lineBreakStart = 0; + var graphemeEnumerator = new GraphemeEnumerator(text.AsSpan()); + + while (graphemeEnumerator.MoveNext(out var grapheme)) + { + if (grapheme.FirstCodepoint.IsBreakChar) + { + break; + } + + lineBreakStart += grapheme.Length; + } + + // All lines except the first one are discarded when TextBox does not accept Return key + text = text.Substring(0, lineBreakStart); + } + for (var i = 0; i < invalidCharacters.Length; i++) { text = text.Replace(invalidCharacters[i], string.Empty); @@ -1757,7 +1777,7 @@ namespace Avalonia.Controls SetCurrentValue(SelectionEndProperty, caretIndex); } - if(SelectionStart != SelectionEnd) + if (SelectionStart != SelectionEnd) { _presenter.TextSelectionHandleCanvas?.ShowContextMenu(); } @@ -2228,7 +2248,7 @@ namespace Avalonia.Controls protected override Size MeasureOverride(Size availableSize) { - if(_scrollViewer != null) + if (_scrollViewer != null) { var maxHeight = double.PositiveInfinity; diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index 9ed8bd9966..00894b069c 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -949,6 +949,44 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Insert_Multiline_Text_Should_Accept_Extra_Lines_When_AcceptsReturn_Is_True() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + AcceptsReturn = true + }; + + RaiseTextEvent(target, $"123 {Environment.NewLine}456"); + + Assert.Equal($"123 {Environment.NewLine}456", target.Text); + } + } + + [Fact] + public void Insert_Multiline_Text_Should_Discard_Extra_Lines_When_AcceptsReturn_Is_False() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + AcceptsReturn = false + }; + + RaiseTextEvent(target, $"123 {"\r"}456"); + + Assert.Equal("123 ", target.Text); + + target.Text = ""; + + RaiseTextEvent(target, $"123 {"\r\n"}456"); + + Assert.Equal("123 ", target.Text); + } + } + [Fact] public void Should_Fullfill_MaxLines_Contraint() {