|
|
@@ -61,11 +61,9 @@ namespace Avalonia.Controls
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="CaretIndex"/> property
|
|
|
/// </summary>
|
|
|
- public static readonly DirectProperty<TextBox, int> CaretIndexProperty =
|
|
|
- AvaloniaProperty.RegisterDirect<TextBox, int>(
|
|
|
- nameof(CaretIndex),
|
|
|
- o => o.CaretIndex,
|
|
|
- (o, v) => o.CaretIndex = v);
|
|
|
+ public static readonly StyledProperty<int> CaretIndexProperty =
|
|
|
+ AvaloniaProperty.Register<TextBox, int>(nameof(CaretIndex),
|
|
|
+ coerce: CoerceCaretIndex);
|
|
|
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="IsReadOnly"/> property
|
|
|
@@ -100,42 +98,37 @@ namespace Avalonia.Controls
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="SelectionStart"/> property
|
|
|
/// </summary>
|
|
|
- public static readonly DirectProperty<TextBox, int> SelectionStartProperty =
|
|
|
- AvaloniaProperty.RegisterDirect<TextBox, int>(
|
|
|
- nameof(SelectionStart),
|
|
|
- o => o.SelectionStart,
|
|
|
- (o, v) => o.SelectionStart = v);
|
|
|
+ public static readonly StyledProperty<int> SelectionStartProperty =
|
|
|
+ AvaloniaProperty.Register<TextBox, int>(nameof(SelectionStart),
|
|
|
+ coerce: CoerceCaretIndex);
|
|
|
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="SelectionEnd"/> property
|
|
|
/// </summary>
|
|
|
- public static readonly DirectProperty<TextBox, int> SelectionEndProperty =
|
|
|
- AvaloniaProperty.RegisterDirect<TextBox, int>(
|
|
|
- nameof(SelectionEnd),
|
|
|
- o => o.SelectionEnd,
|
|
|
- (o, v) => o.SelectionEnd = v);
|
|
|
+ public static readonly StyledProperty<int> SelectionEndProperty =
|
|
|
+ AvaloniaProperty.Register<TextBox, int>(nameof(SelectionEnd),
|
|
|
+ coerce: CoerceCaretIndex);
|
|
|
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="MaxLength"/> property
|
|
|
/// </summary>
|
|
|
public static readonly StyledProperty<int> MaxLengthProperty =
|
|
|
- AvaloniaProperty.Register<TextBox, int>(nameof(MaxLength), defaultValue: 0);
|
|
|
+ AvaloniaProperty.Register<TextBox, int>(nameof(MaxLength));
|
|
|
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="MaxLines"/> property
|
|
|
/// </summary>
|
|
|
public static readonly StyledProperty<int> MaxLinesProperty =
|
|
|
- AvaloniaProperty.Register<TextBox, int>(nameof(MaxLines), defaultValue: 0);
|
|
|
+ AvaloniaProperty.Register<TextBox, int>(nameof(MaxLines));
|
|
|
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="Text"/> property
|
|
|
/// </summary>
|
|
|
- public static readonly DirectProperty<TextBox, string?> TextProperty =
|
|
|
- TextBlock.TextProperty.AddOwnerWithDataValidation<TextBox>(
|
|
|
- o => o.Text,
|
|
|
- (o, v) => o.Text = v,
|
|
|
+ public static readonly StyledProperty<string?> TextProperty =
|
|
|
+ TextBlock.TextProperty.AddOwner<TextBox>(new(
|
|
|
+ coerce: CoerceText,
|
|
|
defaultBindingMode: BindingMode.TwoWay,
|
|
|
- enableDataValidation: true);
|
|
|
+ enableDataValidation: true));
|
|
|
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="TextAlignment"/> property
|
|
|
@@ -185,9 +178,8 @@ namespace Avalonia.Controls
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="NewLine"/> property
|
|
|
/// </summary>
|
|
|
- public static readonly DirectProperty<TextBox, string> NewLineProperty =
|
|
|
- AvaloniaProperty.RegisterDirect<TextBox, string>(nameof(NewLine),
|
|
|
- textbox => textbox.NewLine, (textbox, newline) => textbox.NewLine = newline);
|
|
|
+ public static readonly StyledProperty<string> NewLineProperty =
|
|
|
+ AvaloniaProperty.Register<TextBox, string>(nameof(NewLine), Environment.NewLine);
|
|
|
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="InnerLeftContent"/> property
|
|
|
@@ -242,12 +234,8 @@ namespace Avalonia.Controls
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="UndoLimit"/> property
|
|
|
/// </summary>
|
|
|
- public static readonly DirectProperty<TextBox, int> UndoLimitProperty =
|
|
|
- AvaloniaProperty.RegisterDirect<TextBox, int>(
|
|
|
- nameof(UndoLimit),
|
|
|
- o => o.UndoLimit,
|
|
|
- (o, v) => o.UndoLimit = v,
|
|
|
- unsetValue: -1);
|
|
|
+ public static readonly StyledProperty<int> UndoLimitProperty =
|
|
|
+ AvaloniaProperty.Register<TextBox, int>(nameof(UndoLimit), UndoRedoHelper<UndoRedoState>.DefaultUndoLimit);
|
|
|
|
|
|
/// <summary>
|
|
|
/// Defines the <see cref="CanUndo"/> property
|
|
|
@@ -318,18 +306,13 @@ namespace Avalonia.Controls
|
|
|
public override int GetHashCode() => Text?.GetHashCode() ?? 0;
|
|
|
}
|
|
|
|
|
|
- private string? _text;
|
|
|
- private int _caretIndex;
|
|
|
- private int _selectionStart;
|
|
|
- private int _selectionEnd;
|
|
|
private TextPresenter? _presenter;
|
|
|
- private TextBoxTextInputMethodClient _imClient = new TextBoxTextInputMethodClient();
|
|
|
- private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
|
|
|
+ private readonly TextBoxTextInputMethodClient _imClient = new();
|
|
|
+ private readonly UndoRedoHelper<UndoRedoState> _undoRedoHelper;
|
|
|
private bool _isUndoingRedoing;
|
|
|
private bool _canCut;
|
|
|
private bool _canCopy;
|
|
|
private bool _canPaste;
|
|
|
- private string _newLine = Environment.NewLine;
|
|
|
private static readonly string[] invalidCharacters = new String[1] { "\u007f" };
|
|
|
private bool _canUndo;
|
|
|
private bool _canRedo;
|
|
|
@@ -399,18 +382,19 @@ namespace Avalonia.Controls
|
|
|
/// </summary>
|
|
|
public int CaretIndex
|
|
|
{
|
|
|
- get => _caretIndex;
|
|
|
- set
|
|
|
- {
|
|
|
- value = CoerceCaretIndex(value);
|
|
|
- SetAndRaise(CaretIndexProperty, ref _caretIndex, value);
|
|
|
+ get => GetValue(CaretIndexProperty);
|
|
|
+ set => SetValue(CaretIndexProperty, value);
|
|
|
+ }
|
|
|
|
|
|
- UndoRedoState state;
|
|
|
- if (IsUndoEnabled && _undoRedoHelper.TryGetLastState(out state) && state.Text == Text)
|
|
|
- _undoRedoHelper.UpdateLastState();
|
|
|
+ private void OnCaretIndexChanged(AvaloniaPropertyChangedEventArgs e)
|
|
|
+ {
|
|
|
+ UndoRedoState state;
|
|
|
+ if (IsUndoEnabled && _undoRedoHelper.TryGetLastState(out state) && state.Text == Text)
|
|
|
+ _undoRedoHelper.UpdateLastState();
|
|
|
|
|
|
- SelectionStart = SelectionEnd = value;
|
|
|
- }
|
|
|
+ var newValue = e.GetNewValue<int>();
|
|
|
+ SetCurrentValue(SelectionStartProperty, newValue);
|
|
|
+ SetCurrentValue(SelectionEndProperty, newValue);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -463,21 +447,18 @@ namespace Avalonia.Controls
|
|
|
/// </summary>
|
|
|
public int SelectionStart
|
|
|
{
|
|
|
- get => _selectionStart;
|
|
|
- set
|
|
|
- {
|
|
|
- value = CoerceCaretIndex(value);
|
|
|
- var changed = SetAndRaise(SelectionStartProperty, ref _selectionStart, value);
|
|
|
+ get => GetValue(SelectionStartProperty);
|
|
|
+ set => SetValue(SelectionStartProperty, value);
|
|
|
+ }
|
|
|
|
|
|
- if (changed)
|
|
|
- {
|
|
|
- UpdateCommandStates();
|
|
|
- }
|
|
|
+ private void OnSelectionStartChanged(AvaloniaPropertyChangedEventArgs e)
|
|
|
+ {
|
|
|
+ UpdateCommandStates();
|
|
|
|
|
|
- if (SelectionEnd == value && CaretIndex != value)
|
|
|
- {
|
|
|
- CaretIndex = value;
|
|
|
- }
|
|
|
+ var value = e.GetNewValue<int>();
|
|
|
+ if (SelectionEnd == value && CaretIndex != value)
|
|
|
+ {
|
|
|
+ SetCurrentValue(CaretIndexProperty, value);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -490,21 +471,18 @@ namespace Avalonia.Controls
|
|
|
/// </remarks>
|
|
|
public int SelectionEnd
|
|
|
{
|
|
|
- get => _selectionEnd;
|
|
|
- set
|
|
|
- {
|
|
|
- value = CoerceCaretIndex(value);
|
|
|
- var changed = SetAndRaise(SelectionEndProperty, ref _selectionEnd, value);
|
|
|
-
|
|
|
- if (changed)
|
|
|
- {
|
|
|
- UpdateCommandStates();
|
|
|
- }
|
|
|
+ get => GetValue(SelectionEndProperty);
|
|
|
+ set => SetValue(SelectionEndProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void OnSelectionEndChanged(AvaloniaPropertyChangedEventArgs e)
|
|
|
+ {
|
|
|
+ UpdateCommandStates();
|
|
|
|
|
|
- if (SelectionStart == value && CaretIndex != value)
|
|
|
- {
|
|
|
- CaretIndex = value;
|
|
|
- }
|
|
|
+ var value = e.GetNewValue<int>();
|
|
|
+ if (SelectionStart == value && CaretIndex != value)
|
|
|
+ {
|
|
|
+ SetCurrentValue(CaretIndexProperty, value);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -550,36 +528,27 @@ namespace Avalonia.Controls
|
|
|
[Content]
|
|
|
public string? Text
|
|
|
{
|
|
|
- get => _text;
|
|
|
- set
|
|
|
- {
|
|
|
- var caretIndex = CaretIndex;
|
|
|
- var selectionStart = SelectionStart;
|
|
|
- var selectionEnd = SelectionEnd;
|
|
|
-
|
|
|
- CaretIndex = CoerceCaretIndex(caretIndex, value);
|
|
|
- SelectionStart = CoerceCaretIndex(selectionStart, value);
|
|
|
- SelectionEnd = CoerceCaretIndex(selectionEnd, value);
|
|
|
-
|
|
|
- // Before #9490, snapshot here was done AFTER text change - this doesn't make sense
|
|
|
- // since intial state would never be no text and you'd always have to make a text
|
|
|
- // change before undo would be available
|
|
|
- // The undo/redo stacks were also cleared at this point, which also doesn't make sense
|
|
|
- // as it is still valid to want to undo a programmatic text set
|
|
|
- // So we snapshot text now BEFORE the change so we can always revert
|
|
|
- // Also don't need to check IsUndoEnabled here, that's done in SnapshotUndoRedo
|
|
|
- if (!_isUndoingRedoing)
|
|
|
- {
|
|
|
- SnapshotUndoRedo();
|
|
|
- }
|
|
|
-
|
|
|
- var textChanged = SetAndRaise(TextProperty, ref _text, value);
|
|
|
+ get => GetValue(TextProperty);
|
|
|
+ set => SetValue(TextProperty, value);
|
|
|
+ }
|
|
|
|
|
|
- if (textChanged)
|
|
|
- {
|
|
|
- RaiseTextChangeEvents();
|
|
|
- }
|
|
|
+ private static string? CoerceText(AvaloniaObject sender, string? value)
|
|
|
+ {
|
|
|
+ var textBox = (TextBox)sender;
|
|
|
+
|
|
|
+ // Before #9490, snapshot here was done AFTER text change - this doesn't make sense
|
|
|
+ // since intial state would never be no text and you'd always have to make a text
|
|
|
+ // change before undo would be available
|
|
|
+ // The undo/redo stacks were also cleared at this point, which also doesn't make sense
|
|
|
+ // as it is still valid to want to undo a programmatic text set
|
|
|
+ // So we snapshot text now BEFORE the change so we can always revert
|
|
|
+ // Also don't need to check IsUndoEnabled here, that's done in SnapshotUndoRedo
|
|
|
+ if (!textBox._isUndoingRedoing)
|
|
|
+ {
|
|
|
+ textBox.SnapshotUndoRedo();
|
|
|
}
|
|
|
+
|
|
|
+ return value;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -691,8 +660,8 @@ namespace Avalonia.Controls
|
|
|
/// </summary>
|
|
|
public string NewLine
|
|
|
{
|
|
|
- get => _newLine;
|
|
|
- set => SetAndRaise(NewLineProperty, ref _newLine, value);
|
|
|
+ get => GetValue(NewLineProperty);
|
|
|
+ set => SetValue(NewLineProperty, value);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -700,7 +669,8 @@ namespace Avalonia.Controls
|
|
|
/// </summary>
|
|
|
public void ClearSelection()
|
|
|
{
|
|
|
- CaretIndex = SelectionStart;
|
|
|
+ SetCurrentValue(CaretIndexProperty, SelectionStart);
|
|
|
+ SetCurrentValue(SelectionEndProperty, SelectionStart);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -744,25 +714,20 @@ namespace Avalonia.Controls
|
|
|
/// </summary>
|
|
|
public int UndoLimit
|
|
|
{
|
|
|
- get => _undoRedoHelper.Limit;
|
|
|
- set
|
|
|
- {
|
|
|
- if (_undoRedoHelper.Limit != value)
|
|
|
- {
|
|
|
- // can't use SetAndRaise due to using _undoRedoHelper.Limit
|
|
|
- // (can't send a ref of a property to SetAndRaise),
|
|
|
- // so use RaisePropertyChanged instead.
|
|
|
- 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;
|
|
|
- }
|
|
|
+ get => GetValue(UndoLimitProperty);
|
|
|
+ set => SetValue(UndoLimitProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void OnUndoLimitChanged(int newValue)
|
|
|
+ {
|
|
|
+ _undoRedoHelper.Limit = newValue;
|
|
|
+
|
|
|
+ // 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;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
@@ -866,9 +831,31 @@ namespace Avalonia.Controls
|
|
|
|
|
|
if (change.Property == TextProperty)
|
|
|
{
|
|
|
+ CoerceValue(CaretIndexProperty);
|
|
|
+ CoerceValue(SelectionStartProperty);
|
|
|
+ CoerceValue(SelectionEndProperty);
|
|
|
+
|
|
|
+ RaiseTextChangeEvents();
|
|
|
+
|
|
|
UpdatePseudoclasses();
|
|
|
UpdateCommandStates();
|
|
|
}
|
|
|
+ else if (change.Property == CaretIndexProperty)
|
|
|
+ {
|
|
|
+ OnCaretIndexChanged(change);
|
|
|
+ }
|
|
|
+ else if (change.Property == SelectionStartProperty)
|
|
|
+ {
|
|
|
+ OnSelectionStartChanged(change);
|
|
|
+ }
|
|
|
+ else if (change.Property == SelectionEndProperty)
|
|
|
+ {
|
|
|
+ OnSelectionEndChanged(change);
|
|
|
+ }
|
|
|
+ else if (change.Property == UndoLimitProperty)
|
|
|
+ {
|
|
|
+ OnUndoLimitChanged(change.GetNewValue<int>());
|
|
|
+ }
|
|
|
else if (change.Property == IsUndoEnabledProperty && change.GetNewValue<bool>() == false)
|
|
|
{
|
|
|
// from docs at
|
|
|
@@ -920,7 +907,7 @@ namespace Avalonia.Controls
|
|
|
(ContextMenu == null || !ContextMenu.IsOpen))
|
|
|
{
|
|
|
ClearSelection();
|
|
|
- RevealPassword = false;
|
|
|
+ SetCurrentValue(RevealPasswordProperty, false);
|
|
|
}
|
|
|
|
|
|
UpdateCommandStates();
|
|
|
@@ -986,35 +973,44 @@ namespace Avalonia.Controls
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- var text = Text ?? string.Empty;
|
|
|
- var newLength = input.Length + text.Length - Math.Abs(SelectionStart - SelectionEnd);
|
|
|
+ var currentText = Text ?? string.Empty;
|
|
|
+ var selectionLength = Math.Abs(SelectionStart - SelectionEnd);
|
|
|
+ var newLength = input.Length + currentText.Length - selectionLength;
|
|
|
|
|
|
if (MaxLength > 0 && newLength > MaxLength)
|
|
|
{
|
|
|
input = input.Remove(Math.Max(0, input.Length - (newLength - MaxLength)));
|
|
|
+ newLength = MaxLength;
|
|
|
}
|
|
|
|
|
|
if (!string.IsNullOrEmpty(input))
|
|
|
{
|
|
|
- var oldText = _text;
|
|
|
-
|
|
|
- DeleteSelection(false);
|
|
|
+ var textBuilder = StringBuilderCache.Acquire(Math.Max(currentText.Length, newLength));
|
|
|
+ textBuilder.Append(currentText);
|
|
|
+
|
|
|
var caretIndex = CaretIndex;
|
|
|
- text = Text ?? string.Empty;
|
|
|
- SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex));
|
|
|
- ClearSelection();
|
|
|
|
|
|
- if (IsUndoEnabled)
|
|
|
+ if (selectionLength != 0)
|
|
|
{
|
|
|
- _undoRedoHelper.DiscardRedo();
|
|
|
+ var (start, _) = GetSelectionRange();
|
|
|
+
|
|
|
+ textBuilder.Remove(start, selectionLength);
|
|
|
+
|
|
|
+ caretIndex = start;
|
|
|
}
|
|
|
|
|
|
- if (_text != oldText)
|
|
|
+ textBuilder.Insert(caretIndex, input);
|
|
|
+
|
|
|
+ SetCurrentValue(TextProperty, StringBuilderCache.GetStringAndRelease(textBuilder));
|
|
|
+
|
|
|
+ ClearSelection();
|
|
|
+
|
|
|
+ if (IsUndoEnabled)
|
|
|
{
|
|
|
- RaisePropertyChanged(TextProperty, oldText, _text);
|
|
|
+ _undoRedoHelper.DiscardRedo();
|
|
|
}
|
|
|
|
|
|
- CaretIndex = caretIndex + input.Length;
|
|
|
+ SetCurrentValue(CaretIndexProperty, caretIndex + input.Length);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -1168,7 +1164,7 @@ namespace Avalonia.Controls
|
|
|
movement = true;
|
|
|
selection = false;
|
|
|
handled = true;
|
|
|
- CaretIndex = _presenter.CaretIndex;
|
|
|
+ SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
|
|
|
}
|
|
|
else if (Match(keymap.MoveCursorToTheEndOfDocument))
|
|
|
{
|
|
|
@@ -1176,7 +1172,7 @@ namespace Avalonia.Controls
|
|
|
movement = true;
|
|
|
selection = false;
|
|
|
handled = true;
|
|
|
- CaretIndex = _presenter.CaretIndex;
|
|
|
+ SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
|
|
|
}
|
|
|
else if (Match(keymap.MoveCursorToTheStartOfLine))
|
|
|
{
|
|
|
@@ -1184,7 +1180,7 @@ namespace Avalonia.Controls
|
|
|
movement = true;
|
|
|
selection = false;
|
|
|
handled = true;
|
|
|
- CaretIndex = _presenter.CaretIndex;
|
|
|
+ SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
|
|
|
}
|
|
|
else if (Match(keymap.MoveCursorToTheEndOfLine))
|
|
|
{
|
|
|
@@ -1192,31 +1188,31 @@ namespace Avalonia.Controls
|
|
|
movement = true;
|
|
|
selection = false;
|
|
|
handled = true;
|
|
|
- CaretIndex = _presenter.CaretIndex;
|
|
|
+ SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
|
|
|
}
|
|
|
else if (Match(keymap.MoveCursorToTheStartOfDocumentWithSelection))
|
|
|
{
|
|
|
- SelectionStart = caretIndex;
|
|
|
+ SetCurrentValue(SelectionStartProperty, caretIndex);
|
|
|
MoveHome(true);
|
|
|
- SelectionEnd = _presenter.CaretIndex;
|
|
|
+ SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
|
|
|
movement = true;
|
|
|
selection = true;
|
|
|
handled = true;
|
|
|
}
|
|
|
else if (Match(keymap.MoveCursorToTheEndOfDocumentWithSelection))
|
|
|
{
|
|
|
- SelectionStart = caretIndex;
|
|
|
+ SetCurrentValue(SelectionStartProperty, caretIndex);
|
|
|
MoveEnd(true);
|
|
|
- SelectionEnd = _presenter.CaretIndex;
|
|
|
+ SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
|
|
|
movement = true;
|
|
|
selection = true;
|
|
|
handled = true;
|
|
|
}
|
|
|
else if (Match(keymap.MoveCursorToTheStartOfLineWithSelection))
|
|
|
{
|
|
|
- SelectionStart = caretIndex;
|
|
|
+ SetCurrentValue(SelectionStartProperty, caretIndex);
|
|
|
MoveHome(false);
|
|
|
- SelectionEnd = _presenter.CaretIndex;
|
|
|
+ SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
|
|
|
movement = true;
|
|
|
selection = true;
|
|
|
handled = true;
|
|
|
@@ -1224,9 +1220,9 @@ namespace Avalonia.Controls
|
|
|
}
|
|
|
else if (Match(keymap.MoveCursorToTheEndOfLineWithSelection))
|
|
|
{
|
|
|
- SelectionStart = caretIndex;
|
|
|
+ SetCurrentValue(SelectionStartProperty, caretIndex);
|
|
|
MoveEnd(false);
|
|
|
- SelectionEnd = _presenter.CaretIndex;
|
|
|
+ SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
|
|
|
movement = true;
|
|
|
selection = true;
|
|
|
handled = true;
|
|
|
@@ -1261,11 +1257,11 @@ namespace Avalonia.Controls
|
|
|
|
|
|
if (selection)
|
|
|
{
|
|
|
- SelectionEnd = _presenter.CaretIndex;
|
|
|
+ SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- CaretIndex = _presenter.CaretIndex;
|
|
|
+ SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
@@ -1283,11 +1279,11 @@ namespace Avalonia.Controls
|
|
|
|
|
|
if (selection)
|
|
|
{
|
|
|
- SelectionEnd = _presenter.CaretIndex;
|
|
|
+ SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- CaretIndex = _presenter.CaretIndex;
|
|
|
+ SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
@@ -1314,11 +1310,13 @@ namespace Avalonia.Controls
|
|
|
|
|
|
var length = end - start;
|
|
|
|
|
|
- var editedText = text.Substring(0, start) + text.Substring(Math.Min(end, text.Length));
|
|
|
+ var sb = StringBuilderCache.Acquire(text.Length);
|
|
|
+ sb.Append(text);
|
|
|
+ sb.Remove(start, end - start);
|
|
|
|
|
|
- SetTextInternal(editedText);
|
|
|
+ SetCurrentValue(TextProperty, StringBuilderCache.GetStringAndRelease(sb));
|
|
|
|
|
|
- CaretIndex = start;
|
|
|
+ SetCurrentValue(CaretIndexProperty, start);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -1346,9 +1344,11 @@ namespace Avalonia.Controls
|
|
|
var start = Math.Min(nextPosition, caretIndex);
|
|
|
var end = Math.Max(nextPosition, caretIndex);
|
|
|
|
|
|
- var editedText = text.Substring(0, start) + text.Substring(Math.Min(end, text.Length));
|
|
|
+ var sb = StringBuilderCache.Acquire(text.Length);
|
|
|
+ sb.Append(text);
|
|
|
+ sb.Remove(start, end - start);
|
|
|
|
|
|
- SetTextInternal(editedText);
|
|
|
+ SetCurrentValue(TextProperty, StringBuilderCache.GetStringAndRelease(sb));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -1425,7 +1425,7 @@ namespace Avalonia.Controls
|
|
|
|
|
|
var clickToSelect = e.KeyModifiers.HasFlag(KeyModifiers.Shift);
|
|
|
|
|
|
- SetAndRaise(CaretIndexProperty, ref _caretIndex, index);
|
|
|
+ SetCurrentValue(CaretIndexProperty, index);
|
|
|
|
|
|
switch (e.ClickCount)
|
|
|
{
|
|
|
@@ -1438,25 +1438,26 @@ namespace Avalonia.Controls
|
|
|
|
|
|
if (index > _wordSelectionStart)
|
|
|
{
|
|
|
- SelectionEnd = StringUtils.NextWord(text, index);
|
|
|
+ SetCurrentValue(SelectionEndProperty, StringUtils.NextWord(text, index));
|
|
|
}
|
|
|
|
|
|
if (index < _wordSelectionStart || previousWord == _wordSelectionStart)
|
|
|
{
|
|
|
- SelectionStart = previousWord;
|
|
|
+ SetCurrentValue(SelectionStartProperty, previousWord);
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- SelectionStart = Math.Min(oldIndex, index);
|
|
|
- SelectionEnd = Math.Max(oldIndex, index);
|
|
|
+ SetCurrentValue(SelectionStartProperty, Math.Min(oldIndex, index));
|
|
|
+ SetCurrentValue(SelectionEndProperty, Math.Max(oldIndex, index));
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
if(_wordSelectionStart == -1 || index < SelectionStart || index > SelectionEnd)
|
|
|
{
|
|
|
- SelectionStart = SelectionEnd = index;
|
|
|
+ SetCurrentValue(SelectionStartProperty, index);
|
|
|
+ SetCurrentValue(SelectionEndProperty, index);
|
|
|
_wordSelectionStart = -1;
|
|
|
}
|
|
|
}
|
|
|
@@ -1466,14 +1467,14 @@ namespace Avalonia.Controls
|
|
|
|
|
|
if (!StringUtils.IsStartOfWord(text, index))
|
|
|
{
|
|
|
- SelectionStart = StringUtils.PreviousWord(text, index);
|
|
|
+ SetCurrentValue(SelectionStartProperty, StringUtils.PreviousWord(text, index));
|
|
|
}
|
|
|
|
|
|
_wordSelectionStart = SelectionStart;
|
|
|
|
|
|
if (!StringUtils.IsEndOfWord(text, index))
|
|
|
{
|
|
|
- SelectionEnd = StringUtils.NextWord(text, index);
|
|
|
+ SetCurrentValue(SelectionEndProperty, StringUtils.NextWord(text, index));
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
@@ -1517,22 +1518,22 @@ namespace Avalonia.Controls
|
|
|
|
|
|
if (distance <= 0)
|
|
|
{
|
|
|
- SelectionStart = StringUtils.PreviousWord(text, caretIndex);
|
|
|
+ SetCurrentValue(SelectionStartProperty, StringUtils.PreviousWord(text, caretIndex));
|
|
|
}
|
|
|
|
|
|
if (distance >= 0)
|
|
|
{
|
|
|
if(SelectionStart != _wordSelectionStart)
|
|
|
{
|
|
|
- SelectionStart = _wordSelectionStart;
|
|
|
+ SetCurrentValue(SelectionStartProperty, _wordSelectionStart);
|
|
|
}
|
|
|
|
|
|
- SelectionEnd = StringUtils.NextWord(text, caretIndex);
|
|
|
+ SetCurrentValue(SelectionEndProperty, StringUtils.NextWord(text, caretIndex));
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- SelectionEnd = caretIndex;
|
|
|
+ SetCurrentValue(SelectionEndProperty, caretIndex);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -1565,7 +1566,9 @@ namespace Avalonia.Controls
|
|
|
caretIndex >= firstSelection && caretIndex <= lastSelection;
|
|
|
if (!didClickInSelection)
|
|
|
{
|
|
|
- CaretIndex = SelectionEnd = SelectionStart = caretIndex;
|
|
|
+ SetCurrentValue(CaretIndexProperty, caretIndex);
|
|
|
+ SetCurrentValue(SelectionEndProperty, caretIndex);
|
|
|
+ SetCurrentValue(SelectionStartProperty, caretIndex);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -1588,10 +1591,10 @@ namespace Avalonia.Controls
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private int CoerceCaretIndex(int value) => CoerceCaretIndex(value, Text);
|
|
|
-
|
|
|
- private static int CoerceCaretIndex(int value, string? text)
|
|
|
+ internal static int CoerceCaretIndex(AvaloniaObject sender, int value)
|
|
|
{
|
|
|
+ var text = sender.GetValue(TextProperty); // method also used by TextPresenter and SelectableTextBlock
|
|
|
+
|
|
|
if (text == null)
|
|
|
{
|
|
|
return 0;
|
|
|
@@ -1619,10 +1622,7 @@ namespace Avalonia.Controls
|
|
|
/// <summary>
|
|
|
/// Clears the text in the TextBox
|
|
|
/// </summary>
|
|
|
- public void Clear()
|
|
|
- {
|
|
|
- Text = string.Empty;
|
|
|
- }
|
|
|
+ public void Clear() => SetCurrentValue(TextProperty, string.Empty);
|
|
|
|
|
|
private void MoveHorizontal(int direction, bool wholeWord, bool isSelecting)
|
|
|
{
|
|
|
@@ -1645,7 +1645,7 @@ namespace Avalonia.Controls
|
|
|
LogicalDirection.Forward :
|
|
|
LogicalDirection.Backward);
|
|
|
|
|
|
- SelectionEnd = _presenter.CaretIndex;
|
|
|
+ SetCurrentValue(SelectionEndProperty, _presenter.CaretIndex);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
@@ -1662,7 +1662,7 @@ namespace Avalonia.Controls
|
|
|
LogicalDirection.Backward);
|
|
|
}
|
|
|
|
|
|
- CaretIndex = _presenter.CaretIndex;
|
|
|
+ SetCurrentValue(CaretIndexProperty, _presenter.CaretIndex);
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
@@ -1678,17 +1678,17 @@ namespace Avalonia.Controls
|
|
|
offset = StringUtils.PreviousWord(text, selectionEnd) - selectionEnd;
|
|
|
}
|
|
|
|
|
|
- SelectionEnd += offset;
|
|
|
+ SetCurrentValue(SelectionEndProperty, SelectionEnd + offset);
|
|
|
|
|
|
_presenter.MoveCaretToTextPosition(SelectionEnd);
|
|
|
|
|
|
if (!isSelecting)
|
|
|
{
|
|
|
- CaretIndex = SelectionEnd;
|
|
|
+ SetCurrentValue(CaretIndexProperty, SelectionEnd);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- SelectionStart = selectionStart;
|
|
|
+ SetCurrentValue(SelectionStartProperty, selectionStart);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -1747,36 +1747,45 @@ namespace Avalonia.Controls
|
|
|
/// </summary>
|
|
|
public void SelectAll()
|
|
|
{
|
|
|
- SelectionStart = 0;
|
|
|
- SelectionEnd = Text?.Length ?? 0;
|
|
|
+ SetCurrentValue(SelectionStartProperty, 0);
|
|
|
+ SetCurrentValue(SelectionEndProperty, Text?.Length ?? 0);
|
|
|
}
|
|
|
|
|
|
- internal bool DeleteSelection(bool raiseTextChanged = true)
|
|
|
+ private (int start, int end) GetSelectionRange()
|
|
|
+ {
|
|
|
+ var selectionStart = SelectionStart;
|
|
|
+ var selectionEnd = SelectionEnd;
|
|
|
+
|
|
|
+ return (Math.Min(selectionStart, selectionEnd), Math.Max(selectionStart, selectionEnd));
|
|
|
+ }
|
|
|
+
|
|
|
+ internal bool DeleteSelection()
|
|
|
{
|
|
|
if (IsReadOnly)
|
|
|
return true;
|
|
|
|
|
|
- var selectionStart = SelectionStart;
|
|
|
- var selectionEnd = SelectionEnd;
|
|
|
+ var (start, end) = GetSelectionRange();
|
|
|
|
|
|
- if (selectionStart != selectionEnd)
|
|
|
+ if (start != end)
|
|
|
{
|
|
|
- var start = Math.Min(selectionStart, selectionEnd);
|
|
|
- var end = Math.Max(selectionStart, selectionEnd);
|
|
|
var text = Text!;
|
|
|
+ var textBuilder = StringBuilderCache.Acquire(text.Length);
|
|
|
|
|
|
- SetTextInternal(text.Substring(0, start) + text.Substring(end), raiseTextChanged);
|
|
|
+ textBuilder.Append(text);
|
|
|
+ textBuilder.Remove(start, end - start);
|
|
|
+
|
|
|
+ SetCurrentValue(TextProperty, textBuilder.ToString());
|
|
|
|
|
|
_presenter?.MoveCaretToTextPosition(start);
|
|
|
|
|
|
- CaretIndex = start;
|
|
|
+ SetCurrentValue(CaretIndexProperty, start);
|
|
|
|
|
|
ClearSelection();
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- CaretIndex = SelectionStart;
|
|
|
+ SetCurrentValue(CaretIndexProperty, SelectionStart);
|
|
|
|
|
|
return false;
|
|
|
}
|
|
|
@@ -1826,46 +1835,30 @@ namespace Avalonia.Controls
|
|
|
}, DispatcherPriority.Normal);
|
|
|
}
|
|
|
|
|
|
- private void SetTextInternal(string value, bool raiseTextChanged = true)
|
|
|
- {
|
|
|
- if (raiseTextChanged)
|
|
|
- {
|
|
|
- bool textChanged = SetAndRaise(TextProperty, ref _text, value);
|
|
|
-
|
|
|
- if (textChanged)
|
|
|
- {
|
|
|
- RaiseTextChangeEvents();
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- _text = value;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
private void SetSelectionForControlBackspace()
|
|
|
{
|
|
|
var selectionStart = CaretIndex;
|
|
|
|
|
|
MoveHorizontal(-1, true, false);
|
|
|
|
|
|
- SelectionStart = selectionStart;
|
|
|
+ SetCurrentValue(SelectionStartProperty, selectionStart);
|
|
|
}
|
|
|
|
|
|
private void SetSelectionForControlDelete()
|
|
|
{
|
|
|
- if (_text == null || _presenter == null)
|
|
|
+ var textLength = Text?.Length ?? 0;
|
|
|
+ if (_presenter == null || textLength == 0)
|
|
|
{
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- SelectionStart = CaretIndex;
|
|
|
+ SetCurrentValue(SelectionStartProperty, CaretIndex);
|
|
|
|
|
|
MoveHorizontal(1, true, true);
|
|
|
|
|
|
- if (SelectionEnd < _text.Length && _text[SelectionEnd] == ' ')
|
|
|
+ if (SelectionEnd < textLength && Text![SelectionEnd] == ' ')
|
|
|
{
|
|
|
- SelectionEnd++;
|
|
|
+ SetCurrentValue(SelectionEndProperty, SelectionEnd + 1);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -1881,8 +1874,8 @@ namespace Avalonia.Controls
|
|
|
get => new UndoRedoState(Text, CaretIndex);
|
|
|
set
|
|
|
{
|
|
|
- Text = value.Text;
|
|
|
- CaretIndex = value.CaretPosition;
|
|
|
+ SetCurrentValue(TextProperty, value.Text);
|
|
|
+ SetCurrentValue(CaretIndexProperty, value.CaretPosition);
|
|
|
ClearSelection();
|
|
|
}
|
|
|
}
|