|
|
|
@ -31,7 +31,7 @@ namespace Avalonia.Controls |
|
|
|
|
|
|
|
public static KeyGesture PasteGesture { get; } = AvaloniaLocator.Current |
|
|
|
.GetService<PlatformHotkeyConfiguration>()?.Paste.FirstOrDefault(); |
|
|
|
|
|
|
|
|
|
|
|
public static readonly StyledProperty<bool> AcceptsReturnProperty = |
|
|
|
AvaloniaProperty.Register<TextBox, bool>(nameof(AcceptsReturn)); |
|
|
|
|
|
|
|
@ -117,7 +117,7 @@ namespace Avalonia.Controls |
|
|
|
|
|
|
|
public static readonly StyledProperty<bool> RevealPasswordProperty = |
|
|
|
AvaloniaProperty.Register<TextBox, bool>(nameof(RevealPassword)); |
|
|
|
|
|
|
|
|
|
|
|
public static readonly DirectProperty<TextBox, bool> CanCutProperty = |
|
|
|
AvaloniaProperty.RegisterDirect<TextBox, bool>( |
|
|
|
nameof(CanCut), |
|
|
|
@ -135,7 +135,7 @@ namespace Avalonia.Controls |
|
|
|
|
|
|
|
public static readonly StyledProperty<bool> IsUndoEnabledProperty = |
|
|
|
AvaloniaProperty.Register<TextBox, bool>( |
|
|
|
nameof(IsUndoEnabled), |
|
|
|
nameof(IsUndoEnabled), |
|
|
|
defaultValue: true); |
|
|
|
|
|
|
|
public static readonly DirectProperty<TextBox, int> UndoLimitProperty = |
|
|
|
@ -157,6 +157,10 @@ namespace Avalonia.Controls |
|
|
|
} |
|
|
|
|
|
|
|
public bool Equals(UndoRedoState other) => ReferenceEquals(Text, other.Text) || Equals(Text, other.Text); |
|
|
|
|
|
|
|
public override bool Equals(object obj) => obj is UndoRedoState other && Equals(other); |
|
|
|
|
|
|
|
public override int GetHashCode() => Text.GetHashCode(); |
|
|
|
} |
|
|
|
|
|
|
|
private string _text; |
|
|
|
@ -174,6 +178,10 @@ namespace Avalonia.Controls |
|
|
|
private string _newLine = Environment.NewLine; |
|
|
|
private static readonly string[] invalidCharacters = new String[1] { "\u007f" }; |
|
|
|
|
|
|
|
private int _selectedTextChangesMadeSinceLastUndoSnapshot; |
|
|
|
private bool _hasDoneSnapshotOnce; |
|
|
|
private const int _maxCharsBeforeUndoSnapshot = 7; |
|
|
|
|
|
|
|
static TextBox() |
|
|
|
{ |
|
|
|
FocusableProperty.OverrideDefaultValue(typeof(TextBox), true); |
|
|
|
@ -202,7 +210,8 @@ namespace Avalonia.Controls |
|
|
|
horizontalScrollBarVisibility, |
|
|
|
BindingPriority.Style); |
|
|
|
_undoRedoHelper = new UndoRedoHelper<UndoRedoState>(this); |
|
|
|
|
|
|
|
_selectedTextChangesMadeSinceLastUndoSnapshot = 0; |
|
|
|
_hasDoneSnapshotOnce = false; |
|
|
|
UpdatePseudoclasses(); |
|
|
|
} |
|
|
|
|
|
|
|
@ -331,6 +340,7 @@ namespace Avalonia.Controls |
|
|
|
if (SetAndRaise(TextProperty, ref _text, value) && IsUndoEnabled && !_isUndoingRedoing) |
|
|
|
{ |
|
|
|
_undoRedoHelper.Clear(); |
|
|
|
SnapshotUndoRedo(); // so we always have an initial state
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -341,16 +351,16 @@ namespace Avalonia.Controls |
|
|
|
get { return GetSelection(); } |
|
|
|
set |
|
|
|
{ |
|
|
|
SnapshotUndoRedo(); |
|
|
|
if (string.IsNullOrEmpty(value)) |
|
|
|
{ |
|
|
|
_selectedTextChangesMadeSinceLastUndoSnapshot++; |
|
|
|
SnapshotUndoRedo(ignoreChangeCount: false); |
|
|
|
DeleteSelection(); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
HandleTextInput(value); |
|
|
|
} |
|
|
|
SnapshotUndoRedo(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -422,7 +432,7 @@ namespace Avalonia.Controls |
|
|
|
get { return _newLine; } |
|
|
|
set { SetAndRaise(NewLineProperty, ref _newLine, value); } |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Clears the current selection, maintaining the <see cref="CaretIndex"/>
|
|
|
|
/// </summary>
|
|
|
|
@ -480,11 +490,13 @@ namespace Avalonia.Controls |
|
|
|
var oldValue = _undoRedoHelper.Limit; |
|
|
|
_undoRedoHelper.Limit = value; |
|
|
|
RaisePropertyChanged(UndoLimitProperty, oldValue, value); |
|
|
|
} |
|
|
|
} |
|
|
|
// from docs at
|
|
|
|
// https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.textboxbase.isundoenabled:
|
|
|
|
// "Setting UndoLimit clears the undo queue."
|
|
|
|
_undoRedoHelper.Clear(); |
|
|
|
_selectedTextChangesMadeSinceLastUndoSnapshot = 0; |
|
|
|
_hasDoneSnapshotOnce = false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -515,6 +527,8 @@ namespace Avalonia.Controls |
|
|
|
// 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."
|
|
|
|
_undoRedoHelper.Clear(); |
|
|
|
_selectedTextChangesMadeSinceLastUndoSnapshot = 0; |
|
|
|
_hasDoneSnapshotOnce = false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -577,23 +591,25 @@ namespace Avalonia.Controls |
|
|
|
{ |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
input = RemoveInvalidCharacters(input); |
|
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(input)) |
|
|
|
{ |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
_selectedTextChangesMadeSinceLastUndoSnapshot++; |
|
|
|
SnapshotUndoRedo(ignoreChangeCount: false); |
|
|
|
|
|
|
|
string text = Text ?? string.Empty; |
|
|
|
int caretIndex = CaretIndex; |
|
|
|
int newLength = input.Length + text.Length - Math.Abs(SelectionStart - SelectionEnd); |
|
|
|
|
|
|
|
|
|
|
|
if (MaxLength > 0 && newLength > MaxLength) |
|
|
|
{ |
|
|
|
input = input.Remove(Math.Max(0, input.Length - (newLength - MaxLength))); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrEmpty(input)) |
|
|
|
{ |
|
|
|
DeleteSelection(); |
|
|
|
@ -627,7 +643,6 @@ namespace Avalonia.Controls |
|
|
|
SnapshotUndoRedo(); |
|
|
|
Copy(); |
|
|
|
DeleteSelection(); |
|
|
|
SnapshotUndoRedo(); |
|
|
|
} |
|
|
|
|
|
|
|
public async void Copy() |
|
|
|
@ -647,7 +662,6 @@ namespace Avalonia.Controls |
|
|
|
|
|
|
|
SnapshotUndoRedo(); |
|
|
|
HandleTextInput(text); |
|
|
|
SnapshotUndoRedo(); |
|
|
|
} |
|
|
|
|
|
|
|
protected override void OnKeyDown(KeyEventArgs e) |
|
|
|
@ -696,6 +710,7 @@ namespace Avalonia.Controls |
|
|
|
{ |
|
|
|
try |
|
|
|
{ |
|
|
|
SnapshotUndoRedo(); |
|
|
|
_isUndoingRedoing = true; |
|
|
|
_undoRedoHelper.Undo(); |
|
|
|
} |
|
|
|
@ -830,7 +845,6 @@ namespace Avalonia.Controls |
|
|
|
CaretIndex -= removedCharacters; |
|
|
|
ClearSelection(); |
|
|
|
} |
|
|
|
SnapshotUndoRedo(); |
|
|
|
|
|
|
|
handled = true; |
|
|
|
break; |
|
|
|
@ -858,7 +872,6 @@ namespace Avalonia.Controls |
|
|
|
SetTextInternal(text.Substring(0, caretIndex) + |
|
|
|
text.Substring(caretIndex + removedCharacters)); |
|
|
|
} |
|
|
|
SnapshotUndoRedo(); |
|
|
|
|
|
|
|
handled = true; |
|
|
|
break; |
|
|
|
@ -868,7 +881,6 @@ namespace Avalonia.Controls |
|
|
|
{ |
|
|
|
SnapshotUndoRedo(); |
|
|
|
HandleTextInput(NewLine); |
|
|
|
SnapshotUndoRedo(); |
|
|
|
handled = true; |
|
|
|
} |
|
|
|
|
|
|
|
@ -879,7 +891,6 @@ namespace Avalonia.Controls |
|
|
|
{ |
|
|
|
SnapshotUndoRedo(); |
|
|
|
HandleTextInput("\t"); |
|
|
|
SnapshotUndoRedo(); |
|
|
|
handled = true; |
|
|
|
} |
|
|
|
else |
|
|
|
@ -889,6 +900,10 @@ namespace Avalonia.Controls |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case Key.Space: |
|
|
|
SnapshotUndoRedo(); // always snapshot in between words
|
|
|
|
break; |
|
|
|
|
|
|
|
default: |
|
|
|
handled = false; |
|
|
|
break; |
|
|
|
@ -1319,11 +1334,19 @@ namespace Avalonia.Controls |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void SnapshotUndoRedo() |
|
|
|
private void SnapshotUndoRedo(bool ignoreChangeCount = true) |
|
|
|
{ |
|
|
|
if (IsUndoEnabled) |
|
|
|
{ |
|
|
|
_undoRedoHelper.Snapshot(); |
|
|
|
if (ignoreChangeCount || |
|
|
|
!_hasDoneSnapshotOnce || |
|
|
|
(!ignoreChangeCount && |
|
|
|
_selectedTextChangesMadeSinceLastUndoSnapshot >= _maxCharsBeforeUndoSnapshot)) |
|
|
|
{ |
|
|
|
_undoRedoHelper.Snapshot(); |
|
|
|
_selectedTextChangesMadeSinceLastUndoSnapshot = 0; |
|
|
|
_hasDoneSnapshotOnce = true; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|