A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

186 lines
5.1 KiB

using System;
using Avalonia.Controls.Presenters;
using Avalonia.Input.TextInput;
using Avalonia.Media.TextFormatting;
using Avalonia.Threading;
using Avalonia.Utilities;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
internal class TextBoxTextInputMethodClient : ITextInputMethodClient
{
private TextBox? _parent;
private TextPresenter? _presenter;
public IVisual TextViewVisual => _presenter!;
public bool SupportsPreedit => true;
public bool SupportsSurroundingText => true;
public Rect CursorRectangle
{
get
{
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;
}
}
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;
var lineText = GetTextLineText(textLine);
var anchorOffset = Math.Max(0, _parent.SelectionStart - lineStart);
var cursorOffset = Math.Max(0, _presenter.SelectionEnd - lineStart);
return new TextInputMethodSurroundingText
{
Text = lineText ?? "",
AnchorOffset = anchorOffset,
CursorOffset = cursorOffset
};
}
}
private static string GetTextLineText(TextLine textLine)
{
var builder = StringBuilderCache.Acquire(textLine.Length);
foreach (var run in textLine.TextRuns)
{
if(run.Text.Length > 0)
{
#if NET6_0
builder.Append(run.Text.Span);
#else
builder.Append(run.Text.Span.ToArray());
#endif
}
}
var lineText = builder.ToString();
StringBuilderCache.Release(builder);
return lineText;
}
public event EventHandler? TextViewVisualChanged;
public event EventHandler? CursorRectangleChanged;
public event EventHandler? SurroundingTextChanged;
public void SetPreeditText(string? text)
{
if (_presenter == null)
{
return;
}
_presenter.PreeditText = text;
}
public void SelectInSurroundingText(int start, int end)
{
if(_parent is null ||_presenter is null)
{
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;
}
public void SetPresenter(TextPresenter? presenter, TextBox? parent)
{
if(_parent != null)
{
_parent.PropertyChanged -= OnParentPropertyChanged;
}
_parent = parent;
if(_parent != null)
{
_parent.PropertyChanged += OnParentPropertyChanged;
}
if (_presenter != null)
{
_presenter.PreeditText = null;
_presenter.CaretBoundsChanged -= OnCaretBoundsChanged;
}
_presenter = presenter;
if (_presenter != null)
{
_presenter.CaretBoundsChanged += OnCaretBoundsChanged;
}
TextViewVisualChanged?.Invoke(this, EventArgs.Empty);
OnCaretBoundsChanged(this, EventArgs.Empty);
}
private void OnParentPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if(e.Property == TextBox.SelectionStartProperty || e.Property == TextBox.SelectionEndProperty)
{
if (SupportsSurroundingText)
{
SurroundingTextChanged?.Invoke(this, e);
}
}
}
private void OnCaretBoundsChanged(object? sender, EventArgs e)
{
Dispatcher.UIThread.Post(() =>
{
CursorRectangleChanged?.Invoke(this, e);
}, DispatcherPriority.Input);
}
}
}