|
@@ -1,132 +1,80 @@
|
|
|
using System;
|
|
|
-using System.Diagnostics;
|
|
|
using Avalonia.Controls.Presenters;
|
|
|
using Avalonia.Input.TextInput;
|
|
|
-using Avalonia.Media;
|
|
|
using Avalonia.Media.TextFormatting;
|
|
|
-using Avalonia.Threading;
|
|
|
using Avalonia.Utilities;
|
|
|
|
|
|
namespace Avalonia.Controls
|
|
|
{
|
|
|
- internal class TextBoxTextInputMethodClient : ITextInputMethodClient
|
|
|
+ internal class TextBoxTextInputMethodClient : TextInputMethodClient
|
|
|
{
|
|
|
private TextBox? _parent;
|
|
|
private TextPresenter? _presenter;
|
|
|
- private ITextEditable? _textEditable;
|
|
|
+ private bool _isPropertyChange;
|
|
|
|
|
|
- public Visual TextViewVisual => _presenter!;
|
|
|
+ public override Visual TextViewVisual => _presenter!;
|
|
|
|
|
|
- public bool SupportsPreedit => true;
|
|
|
+ public override bool SupportsPreedit => true;
|
|
|
|
|
|
- public bool SupportsSurroundingText => true;
|
|
|
+ public override bool SupportsSurroundingText => true;
|
|
|
|
|
|
- public Rect CursorRectangle
|
|
|
+ public void SetPresenter(TextPresenter? presenter, TextBox? parent)
|
|
|
{
|
|
|
- get
|
|
|
+ if (_parent != null)
|
|
|
{
|
|
|
- if (_parent == null || _presenter == null)
|
|
|
- {
|
|
|
- return default;
|
|
|
- }
|
|
|
-
|
|
|
- var transform = _presenter.TransformToVisual(_parent);
|
|
|
-
|
|
|
- if (transform == null)
|
|
|
- {
|
|
|
- return default;
|
|
|
- }
|
|
|
-
|
|
|
- var rect = _presenter.GetCursorRectangle().TransformToAABB(transform.Value);
|
|
|
-
|
|
|
- return rect;
|
|
|
+ _parent.PropertyChanged -= OnParentPropertyChanged;
|
|
|
}
|
|
|
- }
|
|
|
-
|
|
|
- public TextInputMethodSurroundingText SurroundingText
|
|
|
- {
|
|
|
- get
|
|
|
- {
|
|
|
- if (_presenter is null || _parent is null)
|
|
|
- {
|
|
|
- return default;
|
|
|
- }
|
|
|
-
|
|
|
- var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(_presenter.CaretIndex, false);
|
|
|
|
|
|
- var textLine = _presenter.TextLayout.TextLines[lineIndex];
|
|
|
-
|
|
|
- var lineStart = textLine.FirstTextSourceIndex;
|
|
|
+ _parent = parent;
|
|
|
|
|
|
- var lineText = GetTextLineText(textLine);
|
|
|
+ if (_parent != null)
|
|
|
+ {
|
|
|
+ _parent.PropertyChanged += OnParentPropertyChanged;
|
|
|
+ }
|
|
|
|
|
|
- var anchorOffset = Math.Max(0, _parent.SelectionStart - lineStart);
|
|
|
+ var oldPresenter = _presenter;
|
|
|
|
|
|
- var cursorOffset = Math.Max(0, _presenter.SelectionEnd - lineStart);
|
|
|
+ if (oldPresenter != null)
|
|
|
+ {
|
|
|
+ oldPresenter.ClearValue(TextPresenter.PreeditTextProperty);
|
|
|
|
|
|
- return new TextInputMethodSurroundingText
|
|
|
- {
|
|
|
- Text = lineText ?? "",
|
|
|
- AnchorOffset = anchorOffset,
|
|
|
- CursorOffset = cursorOffset
|
|
|
- };
|
|
|
+ oldPresenter.CaretBoundsChanged -= OnPresenterCursorRectangleChanged;
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- public ITextEditable? TextEditable
|
|
|
- {
|
|
|
- get => _textEditable; set
|
|
|
+ _presenter = presenter;
|
|
|
+
|
|
|
+ if (_presenter != null)
|
|
|
{
|
|
|
- if (_textEditable != null)
|
|
|
- {
|
|
|
- _textEditable.TextChanged -= TextEditable_TextChanged;
|
|
|
- _textEditable.SelectionChanged -= TextEditable_SelectionChanged;
|
|
|
- _textEditable.CompositionChanged -= TextEditable_CompositionChanged;
|
|
|
- }
|
|
|
+ _presenter.CaretBoundsChanged += OnPresenterCursorRectangleChanged;
|
|
|
+ }
|
|
|
|
|
|
- _textEditable = value;
|
|
|
+ OnTextViewVisualChanged(oldPresenter, presenter);
|
|
|
|
|
|
- if (_textEditable != null)
|
|
|
- {
|
|
|
- _textEditable.TextChanged += TextEditable_TextChanged;
|
|
|
- _textEditable.SelectionChanged += TextEditable_SelectionChanged;
|
|
|
- _textEditable.CompositionChanged += TextEditable_CompositionChanged;
|
|
|
-
|
|
|
- if (_presenter != null)
|
|
|
- {
|
|
|
- _textEditable.Text = _presenter.Text;
|
|
|
- _textEditable.SelectionStart = _presenter.SelectionStart;
|
|
|
- _textEditable.SelectionEnd = _presenter.SelectionEnd;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ OnPresenterCursorRectangleChanged(this, EventArgs.Empty);
|
|
|
}
|
|
|
|
|
|
- private void TextEditable_CompositionChanged(object? sender, EventArgs e)
|
|
|
+ public override void SetPreeditText(string? preeditText)
|
|
|
{
|
|
|
- if (_presenter != null && _textEditable != null)
|
|
|
+ if (_presenter == null || _parent == null)
|
|
|
{
|
|
|
- _presenter.SetCurrentValue(TextPresenter.CompositionRegionProperty, new TextRange(_textEditable.CompositionStart, _textEditable.CompositionEnd));
|
|
|
+ return;
|
|
|
}
|
|
|
+
|
|
|
+ _presenter.SetCurrentValue(TextPresenter.PreeditTextProperty, preeditText);
|
|
|
}
|
|
|
|
|
|
- private void TextEditable_SelectionChanged(object? sender, EventArgs e)
|
|
|
+ protected override void OnSelectionChanged(TextSelection oldValue, TextSelection newValue)
|
|
|
{
|
|
|
- if (_parent != null && _textEditable != null)
|
|
|
+ base.OnSelectionChanged(oldValue, newValue);
|
|
|
+
|
|
|
+ if (_isPropertyChange)
|
|
|
{
|
|
|
- _parent.SelectionStart = _textEditable.SelectionStart;
|
|
|
- _parent.SelectionEnd = _textEditable.SelectionEnd;
|
|
|
+ return;
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- private void TextEditable_TextChanged(object? sender, EventArgs e)
|
|
|
- {
|
|
|
- if (_parent != null)
|
|
|
+ if (oldValue != newValue)
|
|
|
{
|
|
|
- if (_parent.Text != _textEditable?.Text)
|
|
|
- {
|
|
|
- _parent.Text = _textEditable?.Text;
|
|
|
- }
|
|
|
+ SetParentSelection(newValue);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -153,166 +101,112 @@ namespace Avalonia.Controls
|
|
|
return lineText;
|
|
|
}
|
|
|
|
|
|
- public event EventHandler? TextViewVisualChanged;
|
|
|
-
|
|
|
- public event EventHandler? CursorRectangleChanged;
|
|
|
-
|
|
|
- public event EventHandler? SurroundingTextChanged;
|
|
|
-
|
|
|
- private string? _presenterText;
|
|
|
- private int _compositionStart;
|
|
|
-
|
|
|
- public void SetPreeditText(string? preeditText)
|
|
|
+ private void OnParentTextChanged()
|
|
|
{
|
|
|
- if (_presenter == null || _parent == null)
|
|
|
+ if (_presenter is null || _parent is null)
|
|
|
{
|
|
|
- return;
|
|
|
- }
|
|
|
+ SurroundingText = "";
|
|
|
|
|
|
- if (_presenterText is null)
|
|
|
- {
|
|
|
- _presenterText = _parent.Text ?? "";
|
|
|
- _compositionStart = _parent.CaretIndex;
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- var text = GetText(preeditText);
|
|
|
-
|
|
|
- _presenter.SetCurrentValue(TextPresenter.TextProperty, text);
|
|
|
-
|
|
|
- _presenter.SetCurrentValue(TextPresenter.PreeditTextProperty, preeditText);
|
|
|
-
|
|
|
- _presenter.UpdateCaret(new CharacterHit(_compositionStart + (preeditText != null ? preeditText.Length : 0)), false);
|
|
|
-
|
|
|
- if (string.IsNullOrEmpty(preeditText))
|
|
|
+#if DEBUG
|
|
|
+ if (_parent.CaretIndex != _presenter.CaretIndex)
|
|
|
{
|
|
|
- _presenterText = null;
|
|
|
+ throw new InvalidOperationException("TextBox and TextPresenter are out of sync");
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- private string? GetText(string? preeditText)
|
|
|
- {
|
|
|
- if (string.IsNullOrEmpty(preeditText))
|
|
|
+ if (_parent.Text != _presenter.Text)
|
|
|
{
|
|
|
- return _presenterText;
|
|
|
+ throw new InvalidOperationException("TextBox and TextPresenter are out of sync");
|
|
|
}
|
|
|
+#endif
|
|
|
|
|
|
- if (string.IsNullOrEmpty(_presenterText))
|
|
|
- {
|
|
|
- return preeditText;
|
|
|
- }
|
|
|
+ var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(_presenter.CaretIndex, false);
|
|
|
|
|
|
- var sb = StringBuilderCache.Acquire(_presenterText.Length + preeditText.Length);
|
|
|
+ var textLine = _presenter.TextLayout.TextLines[lineIndex];
|
|
|
|
|
|
- sb.Append(_presenterText);
|
|
|
- sb.Insert(_compositionStart, preeditText);
|
|
|
+ var lineText = GetTextLineText(textLine);
|
|
|
|
|
|
- return StringBuilderCache.GetStringAndRelease(sb);
|
|
|
+ SurroundingText = lineText;
|
|
|
}
|
|
|
|
|
|
- public void SetComposingRegion(TextRange? region)
|
|
|
+ private void OnPresenterCursorRectangleChanged(object? sender, EventArgs e)
|
|
|
{
|
|
|
- if (_presenter == null)
|
|
|
+ if (_parent == null || _presenter == null)
|
|
|
{
|
|
|
+ CursorRectangle = default;
|
|
|
+
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- _presenter.SetCurrentValue(TextPresenter.CompositionRegionProperty, region);
|
|
|
- }
|
|
|
+ var transform = _presenter.TransformToVisual(_parent);
|
|
|
|
|
|
- public void SelectInSurroundingText(int start, int end)
|
|
|
- {
|
|
|
- if (_parent is null || _presenter is null)
|
|
|
+ if (transform == null)
|
|
|
{
|
|
|
+ CursorRectangle = default;
|
|
|
+
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(_presenter.CaretIndex, false);
|
|
|
-
|
|
|
- var textLine = _presenter.TextLayout.TextLines[lineIndex];
|
|
|
-
|
|
|
- var lineStart = textLine.FirstTextSourceIndex;
|
|
|
-
|
|
|
- var selectionStart = lineStart + start;
|
|
|
- var selectionEnd = lineStart + end;
|
|
|
-
|
|
|
- _parent.SelectionStart = selectionStart;
|
|
|
- _parent.SelectionEnd = selectionEnd;
|
|
|
+ CursorRectangle = _presenter.GetCursorRectangle().TransformToAABB(transform.Value);
|
|
|
}
|
|
|
|
|
|
- public void SetPresenter(TextPresenter? presenter, TextBox? parent)
|
|
|
+ private void OnParentPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
|
|
{
|
|
|
- if (_parent != null)
|
|
|
+ _isPropertyChange = true;
|
|
|
+
|
|
|
+ if (e.Property == TextBox.TextProperty)
|
|
|
{
|
|
|
- _parent.PropertyChanged -= OnParentPropertyChanged;
|
|
|
+ OnParentTextChanged();
|
|
|
}
|
|
|
|
|
|
- _parent = parent;
|
|
|
-
|
|
|
- if (_parent != null)
|
|
|
+ if (e.Property == TextBox.SelectionStartProperty || e.Property == TextBox.SelectionEndProperty)
|
|
|
{
|
|
|
- _parent.PropertyChanged += OnParentPropertyChanged;
|
|
|
+ Selection = GetParentSelection();
|
|
|
}
|
|
|
|
|
|
- if (_presenter != null)
|
|
|
+ _isPropertyChange = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private TextSelection GetParentSelection()
|
|
|
+ {
|
|
|
+ if (_presenter is null || _parent is null)
|
|
|
{
|
|
|
- _presenter.ClearValue(TextPresenter.PreeditTextProperty);
|
|
|
+ return default;
|
|
|
+ }
|
|
|
|
|
|
- _presenter.ClearValue(TextPresenter.CompositionRegionProperty);
|
|
|
+ var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(_parent.CaretIndex, false);
|
|
|
|
|
|
- _presenter.CaretBoundsChanged -= OnCaretBoundsChanged;
|
|
|
- }
|
|
|
+ var textLine = _presenter.TextLayout.TextLines[lineIndex];
|
|
|
|
|
|
- _presenter = presenter;
|
|
|
+ var lineStart = textLine.FirstTextSourceIndex;
|
|
|
|
|
|
- if (_presenter != null)
|
|
|
- {
|
|
|
- _presenter.CaretBoundsChanged += OnCaretBoundsChanged;
|
|
|
- }
|
|
|
+ var selectionStart = Math.Max(0, _parent.SelectionStart - lineStart);
|
|
|
|
|
|
- TextViewVisualChanged?.Invoke(this, EventArgs.Empty);
|
|
|
+ var selectionEnd = Math.Max(0, _parent.SelectionEnd - lineStart);
|
|
|
|
|
|
- OnCaretBoundsChanged(this, EventArgs.Empty);
|
|
|
+ return new TextSelection(selectionStart, selectionEnd);
|
|
|
}
|
|
|
|
|
|
- private void OnParentPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
|
|
|
+ private void SetParentSelection(TextSelection selection)
|
|
|
{
|
|
|
- if (e.Property == TextBox.SelectionStartProperty || e.Property == TextBox.SelectionEndProperty)
|
|
|
+ if (_parent is null || _presenter is null)
|
|
|
{
|
|
|
- if (SupportsSurroundingText)
|
|
|
- {
|
|
|
- SurroundingTextChanged?.Invoke(this, e);
|
|
|
- }
|
|
|
- if (_textEditable != null)
|
|
|
- {
|
|
|
- var value = (int)(e.NewValue ?? 0);
|
|
|
- if (e.Property == TextBox.SelectionStartProperty)
|
|
|
- {
|
|
|
- _textEditable.SelectionStart = value;
|
|
|
- }
|
|
|
-
|
|
|
- if (e.Property == TextBox.SelectionEndProperty)
|
|
|
- {
|
|
|
- _textEditable.SelectionEnd = value;
|
|
|
- }
|
|
|
- }
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- if (e.Property == TextBox.TextProperty)
|
|
|
- {
|
|
|
- if (_textEditable != null)
|
|
|
- {
|
|
|
- _textEditable.Text = (string?)e.NewValue;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ var lineIndex = _presenter.TextLayout.GetLineIndexFromCharacterIndex(_parent.CaretIndex, false);
|
|
|
|
|
|
- private void OnCaretBoundsChanged(object? sender, EventArgs e)
|
|
|
- {
|
|
|
- Dispatcher.UIThread.Post(() =>
|
|
|
- {
|
|
|
- CursorRectangleChanged?.Invoke(this, e);
|
|
|
+ var textLine = _presenter.TextLayout.TextLines[lineIndex];
|
|
|
+
|
|
|
+ var lineStart = textLine.FirstTextSourceIndex;
|
|
|
+
|
|
|
+ var selectionStart = lineStart + selection.Start;
|
|
|
+ var selectionEnd = lineStart + selection.End;
|
|
|
|
|
|
- }, DispatcherPriority.Input);
|
|
|
+ _parent.SelectionStart = selectionStart;
|
|
|
+ _parent.SelectionEnd = selectionEnd;
|
|
|
}
|
|
|
}
|
|
|
}
|