|
|
|
@ -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(); |
|
|
|
} |
|
|
|
} |
|
|
|
|