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