diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 830d17674a..ed92899cc6 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -148,7 +148,8 @@ namespace Avalonia.Controls { value = CoerceCaretIndex(value); SetAndRaise(CaretIndexProperty, ref _caretIndex, value); - if (_undoRedoHelper.IsLastState && _undoRedoHelper.LastState.Text == Text) + UndoRedoState state; + if (_undoRedoHelper.TryGetLastState(out state) && state.Text == Text) _undoRedoHelper.UpdateLastState(); } } @@ -732,7 +733,7 @@ namespace Avalonia.Controls private void SelectAll() { SelectionStart = 0; - SelectionEnd = Text.Length; + SelectionEnd = Text?.Length ?? 0; } private bool DeleteSelection() diff --git a/src/Avalonia.Controls/Utils/UndoRedoHelper.cs b/src/Avalonia.Controls/Utils/UndoRedoHelper.cs index 2301198a01..71ba3793d1 100644 --- a/src/Avalonia.Controls/Utils/UndoRedoHelper.cs +++ b/src/Avalonia.Controls/Utils/UndoRedoHelper.cs @@ -8,20 +8,19 @@ using Avalonia.Utilities; namespace Avalonia.Controls.Utils { - class UndoRedoHelper : WeakTimer.IWeakTimerSubscriber where TState : IEquatable + class UndoRedoHelper : WeakTimer.IWeakTimerSubscriber where TState : struct, IEquatable { private readonly IUndoRedoHost _host; public interface IUndoRedoHost { - TState UndoRedoState { get; set; } + TState UndoRedoState { get; set; } } - + private readonly LinkedList _states = new LinkedList(); - [NotNull] private LinkedListNode _currentNode; public int Limit { get; set; } = 10; @@ -29,24 +28,31 @@ namespace Avalonia.Controls.Utils public UndoRedoHelper(IUndoRedoHost host) { _host = host; - _states.AddFirst(_host.UndoRedoState); - _currentNode = _states.First; - WeakTimer.StartWeakTimer(this, new TimeSpan(0, 0, 1)); - + WeakTimer.StartWeakTimer(this, TimeSpan.FromSeconds(1)); } public void Undo() { - if (_currentNode?.Previous != null) - { - _currentNode = _currentNode.Previous; - } - - _host.UndoRedoState = _currentNode.Value; + if (_currentNode?.Previous != null) + { + _currentNode = _currentNode.Previous; + _host.UndoRedoState = _currentNode.Value; + } } - public bool IsLastState => _currentNode.Next == null; + public bool IsLastState => _currentNode != null && _currentNode.Next == null; + + public bool TryGetLastState(out TState _state) + { + _state = default(TState); + if (!IsLastState) + return false; + _state = _currentNode.Value; + return true; + } + + public bool HasState => _currentNode != null; public void UpdateLastState(TState state) { _states.Last.Value = state; @@ -57,34 +63,31 @@ namespace Avalonia.Controls.Utils _states.Last.Value = _host.UndoRedoState; } - public TState LastState => _currentNode.Value; - public void DiscardRedo() { - //Linked list sucks, so we are doing this - while (_currentNode.Next != null) + while (_currentNode?.Next != null) _states.Remove(_currentNode.Next); } public void Redo() - { - if (_currentNode?.Next != null) { - _currentNode = _currentNode.Next; - } - - _host.UndoRedoState = _currentNode.Value; + { + if (_currentNode?.Next != null) + { + _currentNode = _currentNode.Next; + _host.UndoRedoState = _currentNode.Value; + } } public void Snapshot() { var current = _host.UndoRedoState; - if (!_currentNode.Value.Equals(current)) + if (_currentNode == null || !_currentNode.Value.Equals(current)) { - if(_currentNode.Next != null) + if (_currentNode?.Next != null) DiscardRedo(); _states.AddLast(current); _currentNode = _states.Last; - if(_states.Count > Limit) + if (_states.Count > Limit) _states.RemoveFirst(); } } diff --git a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs index 5a93538757..e24b6053b0 100644 --- a/src/Skia/Avalonia.Skia/FormattedTextImpl.cs +++ b/src/Skia/Avalonia.Skia/FormattedTextImpl.cs @@ -28,7 +28,9 @@ namespace Avalonia.Skia //Paint.TextEncoding = SKTextEncoding.Utf8; _paint.TextEncoding = SKTextEncoding.Utf16; _paint.IsStroke = false; - _paint.IsAntialias = true; + _paint.IsAntialias = true; + _paint.LcdRenderText = true; + _paint.SubpixelText = true; _paint.Typeface = typeface; _paint.TextSize = (float)fontSize; _paint.TextAlign = textAlignment.ToSKTextAlign(); diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index 168d9786c2..851657ab6c 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -41,6 +41,58 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Press_Ctrl_A_Select_All_Text() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + Template = CreateTemplate(), + Text = "1234" + }; + + RaiseKeyEvent(target, Key.A, InputModifiers.Control); + + Assert.Equal(0, target.SelectionStart); + Assert.Equal(4, target.SelectionEnd); + } + } + + [Fact] + public void Press_Ctrl_A_Select_All_Null_Text() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + Template = CreateTemplate() + }; + + RaiseKeyEvent(target, Key.A, InputModifiers.Control); + + Assert.Equal(0, target.SelectionStart); + Assert.Equal(0, target.SelectionEnd); + } + } + + [Fact] + public void Press_Ctrl_Z_Will_Not_Modify_Text() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + Template = CreateTemplate(), + Text = "1234" + }; + + RaiseKeyEvent(target, Key.Z, InputModifiers.Control); + + Assert.Equal("1234", target.Text); + } + } + [Fact] public void Typing_Beginning_With_0_Should_Not_Modify_Text_When_Bound_To_Int() {