Browse Source

Remove timer from undo helper, improve u/r for textbox

pull/6024/head
Deadpikle 5 years ago
parent
commit
ca4dee94fd
  1. 57
      src/Avalonia.Controls/TextBox.cs
  2. 11
      src/Avalonia.Controls/Utils/UndoRedoHelper.cs

57
src/Avalonia.Controls/TextBox.cs

@ -31,7 +31,7 @@ namespace Avalonia.Controls
public static KeyGesture PasteGesture { get; } = AvaloniaLocator.Current public static KeyGesture PasteGesture { get; } = AvaloniaLocator.Current
.GetService<PlatformHotkeyConfiguration>()?.Paste.FirstOrDefault(); .GetService<PlatformHotkeyConfiguration>()?.Paste.FirstOrDefault();
public static readonly StyledProperty<bool> AcceptsReturnProperty = public static readonly StyledProperty<bool> AcceptsReturnProperty =
AvaloniaProperty.Register<TextBox, bool>(nameof(AcceptsReturn)); AvaloniaProperty.Register<TextBox, bool>(nameof(AcceptsReturn));
@ -117,7 +117,7 @@ namespace Avalonia.Controls
public static readonly StyledProperty<bool> RevealPasswordProperty = public static readonly StyledProperty<bool> RevealPasswordProperty =
AvaloniaProperty.Register<TextBox, bool>(nameof(RevealPassword)); AvaloniaProperty.Register<TextBox, bool>(nameof(RevealPassword));
public static readonly DirectProperty<TextBox, bool> CanCutProperty = public static readonly DirectProperty<TextBox, bool> CanCutProperty =
AvaloniaProperty.RegisterDirect<TextBox, bool>( AvaloniaProperty.RegisterDirect<TextBox, bool>(
nameof(CanCut), nameof(CanCut),
@ -135,7 +135,7 @@ namespace Avalonia.Controls
public static readonly StyledProperty<bool> IsUndoEnabledProperty = public static readonly StyledProperty<bool> IsUndoEnabledProperty =
AvaloniaProperty.Register<TextBox, bool>( AvaloniaProperty.Register<TextBox, bool>(
nameof(IsUndoEnabled), nameof(IsUndoEnabled),
defaultValue: true); defaultValue: true);
public static readonly DirectProperty<TextBox, int> UndoLimitProperty = public static readonly DirectProperty<TextBox, int> UndoLimitProperty =
@ -174,6 +174,10 @@ namespace Avalonia.Controls
private string _newLine = Environment.NewLine; private string _newLine = Environment.NewLine;
private static readonly string[] invalidCharacters = new String[1] { "\u007f" }; private static readonly string[] invalidCharacters = new String[1] { "\u007f" };
private int _selectedTextChangesMadeSinceLastUndoSnapshot;
private bool _hasDoneSnapshotOnce;
private const int _maxCharsBeforeUndoSnapshot = 7;
static TextBox() static TextBox()
{ {
FocusableProperty.OverrideDefaultValue(typeof(TextBox), true); FocusableProperty.OverrideDefaultValue(typeof(TextBox), true);
@ -202,7 +206,8 @@ namespace Avalonia.Controls
horizontalScrollBarVisibility, horizontalScrollBarVisibility,
BindingPriority.Style); BindingPriority.Style);
_undoRedoHelper = new UndoRedoHelper<UndoRedoState>(this); _undoRedoHelper = new UndoRedoHelper<UndoRedoState>(this);
_selectedTextChangesMadeSinceLastUndoSnapshot = 0;
_hasDoneSnapshotOnce = false;
UpdatePseudoclasses(); UpdatePseudoclasses();
} }
@ -331,6 +336,7 @@ namespace Avalonia.Controls
if (SetAndRaise(TextProperty, ref _text, value) && IsUndoEnabled && !_isUndoingRedoing) if (SetAndRaise(TextProperty, ref _text, value) && IsUndoEnabled && !_isUndoingRedoing)
{ {
_undoRedoHelper.Clear(); _undoRedoHelper.Clear();
SnapshotUndoRedo(); // so we always have an initial state
} }
} }
} }
@ -341,7 +347,6 @@ namespace Avalonia.Controls
get { return GetSelection(); } get { return GetSelection(); }
set set
{ {
SnapshotUndoRedo();
if (string.IsNullOrEmpty(value)) if (string.IsNullOrEmpty(value))
{ {
DeleteSelection(); DeleteSelection();
@ -350,7 +355,6 @@ namespace Avalonia.Controls
{ {
HandleTextInput(value); HandleTextInput(value);
} }
SnapshotUndoRedo();
} }
} }
@ -422,7 +426,7 @@ namespace Avalonia.Controls
get { return _newLine; } get { return _newLine; }
set { SetAndRaise(NewLineProperty, ref _newLine, value); } set { SetAndRaise(NewLineProperty, ref _newLine, value); }
} }
/// <summary> /// <summary>
/// Clears the current selection, maintaining the <see cref="CaretIndex"/> /// Clears the current selection, maintaining the <see cref="CaretIndex"/>
/// </summary> /// </summary>
@ -480,11 +484,13 @@ namespace Avalonia.Controls
var oldValue = _undoRedoHelper.Limit; var oldValue = _undoRedoHelper.Limit;
_undoRedoHelper.Limit = value; _undoRedoHelper.Limit = value;
RaisePropertyChanged(UndoLimitProperty, oldValue, value); RaisePropertyChanged(UndoLimitProperty, oldValue, value);
} }
// from docs at // from docs at
// https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled: // https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled:
// "Setting UndoLimit clears the undo queue." // "Setting UndoLimit clears the undo queue."
_undoRedoHelper.Clear(); _undoRedoHelper.Clear();
_selectedTextChangesMadeSinceLastUndoSnapshot = 0;
_hasDoneSnapshotOnce = false;
} }
} }
@ -515,6 +521,8 @@ namespace Avalonia.Controls
// Therefore, if you disable undo and then re-enable it, undo commands still do not work // Therefore, if you disable undo and then re-enable it, undo commands still do not work
// because the undo stack was emptied when you disabled undo." // because the undo stack was emptied when you disabled undo."
_undoRedoHelper.Clear(); _undoRedoHelper.Clear();
_selectedTextChangesMadeSinceLastUndoSnapshot = 0;
_hasDoneSnapshotOnce = false;
} }
} }
@ -577,23 +585,25 @@ namespace Avalonia.Controls
{ {
return; return;
} }
input = RemoveInvalidCharacters(input); input = RemoveInvalidCharacters(input);
if (string.IsNullOrEmpty(input)) if (string.IsNullOrEmpty(input))
{ {
return; return;
} }
_selectedTextChangesMadeSinceLastUndoSnapshot++;
SnapshotUndoRedo(ignoreChangeCount: false);
string text = Text ?? string.Empty; string text = Text ?? string.Empty;
int caretIndex = CaretIndex; int caretIndex = CaretIndex;
int newLength = input.Length + text.Length - Math.Abs(SelectionStart - SelectionEnd); int newLength = input.Length + text.Length - Math.Abs(SelectionStart - SelectionEnd);
if (MaxLength > 0 && newLength > MaxLength) if (MaxLength > 0 && newLength > MaxLength)
{ {
input = input.Remove(Math.Max(0, input.Length - (newLength - MaxLength))); input = input.Remove(Math.Max(0, input.Length - (newLength - MaxLength)));
} }
if (!string.IsNullOrEmpty(input)) if (!string.IsNullOrEmpty(input))
{ {
DeleteSelection(); DeleteSelection();
@ -696,6 +706,7 @@ namespace Avalonia.Controls
{ {
try try
{ {
SnapshotUndoRedo();
_isUndoingRedoing = true; _isUndoingRedoing = true;
_undoRedoHelper.Undo(); _undoRedoHelper.Undo();
} }
@ -830,7 +841,6 @@ namespace Avalonia.Controls
CaretIndex -= removedCharacters; CaretIndex -= removedCharacters;
ClearSelection(); ClearSelection();
} }
SnapshotUndoRedo();
handled = true; handled = true;
break; break;
@ -858,7 +868,6 @@ namespace Avalonia.Controls
SetTextInternal(text.Substring(0, caretIndex) + SetTextInternal(text.Substring(0, caretIndex) +
text.Substring(caretIndex + removedCharacters)); text.Substring(caretIndex + removedCharacters));
} }
SnapshotUndoRedo();
handled = true; handled = true;
break; break;
@ -868,7 +877,6 @@ namespace Avalonia.Controls
{ {
SnapshotUndoRedo(); SnapshotUndoRedo();
HandleTextInput(NewLine); HandleTextInput(NewLine);
SnapshotUndoRedo();
handled = true; handled = true;
} }
@ -879,7 +887,6 @@ namespace Avalonia.Controls
{ {
SnapshotUndoRedo(); SnapshotUndoRedo();
HandleTextInput("\t"); HandleTextInput("\t");
SnapshotUndoRedo();
handled = true; handled = true;
} }
else else
@ -889,6 +896,10 @@ namespace Avalonia.Controls
break; break;
case Key.Space:
SnapshotUndoRedo(); // always snapshot in between words
break;
default: default:
handled = false; handled = false;
break; break;
@ -1306,11 +1317,19 @@ namespace Avalonia.Controls
} }
} }
private void SnapshotUndoRedo() private void SnapshotUndoRedo(bool ignoreChangeCount = true)
{ {
if (IsUndoEnabled) if (IsUndoEnabled)
{ {
_undoRedoHelper.Snapshot(); if (ignoreChangeCount ||
!_hasDoneSnapshotOnce ||
(!ignoreChangeCount &&
_selectedTextChangesMadeSinceLastUndoSnapshot >= _maxCharsBeforeUndoSnapshot))
{
_undoRedoHelper.Snapshot();
_selectedTextChangesMadeSinceLastUndoSnapshot = 0;
_hasDoneSnapshotOnce = true;
}
} }
} }
} }

11
src/Avalonia.Controls/Utils/UndoRedoHelper.cs

@ -7,7 +7,7 @@ using Avalonia.Utilities;
namespace Avalonia.Controls.Utils namespace Avalonia.Controls.Utils
{ {
class UndoRedoHelper<TState> : WeakTimer.IWeakTimerSubscriber where TState : struct, IEquatable<TState> class UndoRedoHelper<TState>
{ {
private readonly IUndoRedoHost _host; private readonly IUndoRedoHost _host;
@ -31,7 +31,6 @@ namespace Avalonia.Controls.Utils
public UndoRedoHelper(IUndoRedoHost host) public UndoRedoHelper(IUndoRedoHost host)
{ {
_host = host; _host = host;
WeakTimer.StartWeakTimer(this, TimeSpan.FromSeconds(1));
} }
public void Undo() public void Undo()
@ -61,7 +60,7 @@ namespace Avalonia.Controls.Utils
if (_states.Last != null) if (_states.Last != null)
{ {
_states.Last.Value = state; _states.Last.Value = state;
} }
} }
public void UpdateLastState() public void UpdateLastState()
@ -103,11 +102,5 @@ namespace Avalonia.Controls.Utils
_states.Clear(); _states.Clear();
_currentNode = null; _currentNode = null;
} }
bool WeakTimer.IWeakTimerSubscriber.Tick()
{
Snapshot();
return true;
}
} }
} }

Loading…
Cancel
Save