|
@@ -1,14 +1,13 @@
|
|
|
using System;
|
|
using System;
|
|
|
using System.Runtime.InteropServices;
|
|
using System.Runtime.InteropServices;
|
|
|
-using System.Runtime.Versioning;
|
|
|
|
|
using Avalonia.Controls.Presenters;
|
|
using Avalonia.Controls.Presenters;
|
|
|
-using Foundation;
|
|
|
|
|
-using ObjCRuntime;
|
|
|
|
|
-using Avalonia.Input.TextInput;
|
|
|
|
|
using Avalonia.Input;
|
|
using Avalonia.Input;
|
|
|
using Avalonia.Input.Raw;
|
|
using Avalonia.Input.Raw;
|
|
|
|
|
+using Avalonia.Input.TextInput;
|
|
|
using Avalonia.Logging;
|
|
using Avalonia.Logging;
|
|
|
using CoreGraphics;
|
|
using CoreGraphics;
|
|
|
|
|
+using Foundation;
|
|
|
|
|
+using ObjCRuntime;
|
|
|
using UIKit;
|
|
using UIKit;
|
|
|
// ReSharper disable InconsistentNaming
|
|
// ReSharper disable InconsistentNaming
|
|
|
// ReSharper disable StringLiteralTypo
|
|
// ReSharper disable StringLiteralTypo
|
|
@@ -25,6 +24,9 @@ partial class AvaloniaView
|
|
|
[Adopts("UIKeyInput")]
|
|
[Adopts("UIKeyInput")]
|
|
|
partial class TextInputResponder : UIResponder, IUITextInput
|
|
partial class TextInputResponder : UIResponder, IUITextInput
|
|
|
{
|
|
{
|
|
|
|
|
+ private static AvaloniaEmptyTextPosition? _emptyPosition;
|
|
|
|
|
+ private static AvaloniaEmptyTextPosition EmptyPosition => _emptyPosition ??= new();
|
|
|
|
|
+
|
|
|
private class AvaloniaTextRange : UITextRange, INSCopying
|
|
private class AvaloniaTextRange : UITextRange, INSCopying
|
|
|
{
|
|
{
|
|
|
private UITextPosition? _start;
|
|
private UITextPosition? _start;
|
|
@@ -67,6 +69,15 @@ partial class AvaloniaView
|
|
|
public NSObject Copy(NSZone? zone) => new AvaloniaTextPosition(Index);
|
|
public NSObject Copy(NSZone? zone) => new AvaloniaTextPosition(Index);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private class AvaloniaEmptyTextPosition : UITextPosition, INSCopying
|
|
|
|
|
+ {
|
|
|
|
|
+ public AvaloniaEmptyTextPosition()
|
|
|
|
|
+ {
|
|
|
|
|
+
|
|
|
|
|
+ }
|
|
|
|
|
+ public NSObject Copy(NSZone? zone) => this;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
public TextInputResponder(AvaloniaView view, ITextInputMethodClient client)
|
|
public TextInputResponder(AvaloniaView view, ITextInputMethodClient client)
|
|
|
{
|
|
{
|
|
|
_view = view;
|
|
_view = view;
|
|
@@ -93,7 +104,25 @@ partial class AvaloniaView
|
|
|
|
|
|
|
|
public override NSString TextInputContextIdentifier => new NSString(Guid.NewGuid().ToString());
|
|
public override NSString TextInputContextIdentifier => new NSString(Guid.NewGuid().ToString());
|
|
|
|
|
|
|
|
- public override UITextInputMode TextInputMode => UITextInputMode.CurrentInputMode;
|
|
|
|
|
|
|
+ public override UITextInputMode TextInputMode
|
|
|
|
|
+ {
|
|
|
|
|
+ get
|
|
|
|
|
+ {
|
|
|
|
|
+ var mode = UITextInputMode.CurrentInputMode;
|
|
|
|
|
+ // Can be empty see https://developer.apple.com/documentation/uikit/uitextinputmode/1614522-activeinputmodes
|
|
|
|
|
+ if (mode is null && UITextInputMode.ActiveInputModes.Length > 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ mode = UITextInputMode.ActiveInputModes[0];
|
|
|
|
|
+ }
|
|
|
|
|
+ // See: https://stackoverflow.com/a/33337483/20894223
|
|
|
|
|
+ if (mode is null)
|
|
|
|
|
+ {
|
|
|
|
|
+ using var tv = new UITextView();
|
|
|
|
|
+ mode = tv.TextInputMode;
|
|
|
|
|
+ }
|
|
|
|
|
+ return mode;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
[DllImport("/usr/lib/libobjc.dylib")]
|
|
[DllImport("/usr/lib/libobjc.dylib")]
|
|
|
private static extern void objc_msgSend(IntPtr receiver, IntPtr selector, IntPtr arg);
|
|
private static extern void objc_msgSend(IntPtr receiver, IntPtr selector, IntPtr arg);
|
|
@@ -105,8 +134,8 @@ partial class AvaloniaView
|
|
|
private readonly AvaloniaView _view;
|
|
private readonly AvaloniaView _view;
|
|
|
private string? _markedText;
|
|
private string? _markedText;
|
|
|
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
private void SurroundingTextChanged(object? sender, EventArgs e)
|
|
private void SurroundingTextChanged(object? sender, EventArgs e)
|
|
|
{
|
|
{
|
|
|
Logger.TryGet(LogEventLevel.Debug, ImeLog)?.Log(null, "SurroundingTextChanged");
|
|
Logger.TryGet(LogEventLevel.Debug, ImeLog)?.Log(null, "SurroundingTextChanged");
|
|
@@ -153,9 +182,9 @@ partial class AvaloniaView
|
|
|
switch (ReturnKeyType)
|
|
switch (ReturnKeyType)
|
|
|
{
|
|
{
|
|
|
case UIReturnKeyType.Done:
|
|
case UIReturnKeyType.Done:
|
|
|
- case UIReturnKeyType.Go:
|
|
|
|
|
- case UIReturnKeyType.Send:
|
|
|
|
|
- case UIReturnKeyType.Search:
|
|
|
|
|
|
|
+ case UIReturnKeyType.Go:
|
|
|
|
|
+ case UIReturnKeyType.Send:
|
|
|
|
|
+ case UIReturnKeyType.Search:
|
|
|
ResignFirstResponder();
|
|
ResignFirstResponder();
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
@@ -164,7 +193,7 @@ partial class AvaloniaView
|
|
|
|
|
|
|
|
TextInput(text);
|
|
TextInput(text);
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
void IUIKeyInput.DeleteBackward() => KeyPress(Key.Back);
|
|
void IUIKeyInput.DeleteBackward() => KeyPress(Key.Back);
|
|
|
|
|
|
|
|
bool IUIKeyInput.HasText => true;
|
|
bool IUIKeyInput.HasText => true;
|
|
@@ -176,8 +205,8 @@ partial class AvaloniaView
|
|
|
Logger.TryGet(LogEventLevel.Debug, ImeLog)?.Log(null, "IUIKeyInput.TextInRange {start} {end}", r.StartIndex, r.EndIndex);
|
|
Logger.TryGet(LogEventLevel.Debug, ImeLog)?.Log(null, "IUIKeyInput.TextInRange {start} {end}", r.StartIndex, r.EndIndex);
|
|
|
|
|
|
|
|
string result = "";
|
|
string result = "";
|
|
|
- if(string.IsNullOrEmpty(_markedText))
|
|
|
|
|
- result = s.Text[r.StartIndex .. r.EndIndex];
|
|
|
|
|
|
|
+ if (string.IsNullOrEmpty(_markedText))
|
|
|
|
|
+ result = s.Text[r.StartIndex..r.EndIndex];
|
|
|
else
|
|
else
|
|
|
{
|
|
{
|
|
|
var span = new CombinedSpan3<char>(s.Text.AsSpan().Slice(0, s.CursorOffset),
|
|
var span = new CombinedSpan3<char>(s.Text.AsSpan().Slice(0, s.CursorOffset),
|
|
@@ -214,7 +243,7 @@ partial class AvaloniaView
|
|
|
void IUITextInput.UnmarkText()
|
|
void IUITextInput.UnmarkText()
|
|
|
{
|
|
{
|
|
|
Logger.TryGet(LogEventLevel.Debug, ImeLog)?.Log(null, "IUIKeyInput.UnmarkText");
|
|
Logger.TryGet(LogEventLevel.Debug, ImeLog)?.Log(null, "IUIKeyInput.UnmarkText");
|
|
|
- if(_markedText == null)
|
|
|
|
|
|
|
+ if (_markedText == null)
|
|
|
return;
|
|
return;
|
|
|
var commitString = _markedText;
|
|
var commitString = _markedText;
|
|
|
_markedText = null;
|
|
_markedText = null;
|
|
@@ -239,15 +268,15 @@ partial class AvaloniaView
|
|
|
Logger.TryGet(LogEventLevel.Debug, ImeLog)
|
|
Logger.TryGet(LogEventLevel.Debug, ImeLog)
|
|
|
?.Log(null, "IUIKeyInput.GetPosition {start} {offset}", pos.Index, (int)offset);
|
|
?.Log(null, "IUIKeyInput.GetPosition {start} {offset}", pos.Index, (int)offset);
|
|
|
|
|
|
|
|
- var res = GetPositionCore(pos, offset);
|
|
|
|
|
- Logger.TryGet(LogEventLevel.Debug, ImeLog)
|
|
|
|
|
- ?.Log(null, $"res: " + (res == null ? "null" : (int)res.Index));
|
|
|
|
|
- return res!;
|
|
|
|
|
|
|
+ var res = GetPositionCore(pos, offset);
|
|
|
|
|
+ Logger.TryGet(LogEventLevel.Debug, ImeLog)
|
|
|
|
|
+ ?.Log(null, $"res: " + (res == null ? "null" : (int)res.Index));
|
|
|
|
|
+ return res!;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private AvaloniaTextPosition? GetPositionCore(AvaloniaTextPosition pos, nint offset)
|
|
private AvaloniaTextPosition? GetPositionCore(AvaloniaTextPosition pos, nint offset)
|
|
|
{
|
|
{
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
var end = pos.Index + (int)offset;
|
|
var end = pos.Index + (int)offset;
|
|
|
if (end < 0)
|
|
if (end < 0)
|
|
|
return null!;
|
|
return null!;
|
|
@@ -261,14 +290,14 @@ partial class AvaloniaView
|
|
|
{
|
|
{
|
|
|
var pos = (AvaloniaTextPosition)fromPosition;
|
|
var pos = (AvaloniaTextPosition)fromPosition;
|
|
|
Logger.TryGet(LogEventLevel.Debug, ImeLog)
|
|
Logger.TryGet(LogEventLevel.Debug, ImeLog)
|
|
|
- ?.Log(null, "IUIKeyInput.GetPosition {start} {direction} {offset}", pos.Index, inDirection, (int)offset);
|
|
|
|
|
|
|
+ ?.Log(null, "IUIKeyInput.GetPosition {start} {direction} {offset}", pos.Index, inDirection, (int)offset);
|
|
|
|
|
|
|
|
var res = GetPositionCore(pos, inDirection, offset);
|
|
var res = GetPositionCore(pos, inDirection, offset);
|
|
|
Logger.TryGet(LogEventLevel.Debug, ImeLog)
|
|
Logger.TryGet(LogEventLevel.Debug, ImeLog)
|
|
|
?.Log(null, $"res: " + (res == null ? "null" : (int)res.Index));
|
|
?.Log(null, $"res: " + (res == null ? "null" : (int)res.Index));
|
|
|
return res!;
|
|
return res!;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
private AvaloniaTextPosition? GetPositionCore(AvaloniaTextPosition fromPosition, UITextLayoutDirection inDirection,
|
|
private AvaloniaTextPosition? GetPositionCore(AvaloniaTextPosition fromPosition, UITextLayoutDirection inDirection,
|
|
|
nint offset)
|
|
nint offset)
|
|
|
{
|
|
{
|
|
@@ -348,7 +377,7 @@ partial class AvaloniaView
|
|
|
|
|
|
|
|
CGRect IUITextInput.GetFirstRectForRange(UITextRange range)
|
|
CGRect IUITextInput.GetFirstRectForRange(UITextRange range)
|
|
|
{
|
|
{
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Logger.TryGet(LogEventLevel.Debug, ImeLog)?
|
|
Logger.TryGet(LogEventLevel.Debug, ImeLog)?
|
|
|
.Log(null, "IUITextInput:GetFirstRectForRange");
|
|
.Log(null, "IUITextInput:GetFirstRectForRange");
|
|
|
// TODO: Query from the input client
|
|
// TODO: Query from the input client
|
|
@@ -377,11 +406,11 @@ partial class AvaloniaView
|
|
|
if (presenter is { })
|
|
if (presenter is { })
|
|
|
{
|
|
{
|
|
|
var hitResult = presenter.TextLayout.HitTestPoint(new Point(point.X, point.Y));
|
|
var hitResult = presenter.TextLayout.HitTestPoint(new Point(point.X, point.Y));
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
return new AvaloniaTextPosition(hitResult.TextPosition);
|
|
return new AvaloniaTextPosition(hitResult.TextPosition);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return null;
|
|
|
|
|
|
|
+ return EmptyPosition;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
UITextPosition IUITextInput.GetClosestPositionToPoint(CGPoint point, UITextRange withinRange)
|
|
UITextPosition IUITextInput.GetClosestPositionToPoint(CGPoint point, UITextRange withinRange)
|
|
@@ -440,7 +469,7 @@ partial class AvaloniaView
|
|
|
NSDictionary? IUITextInput.MarkedTextStyle
|
|
NSDictionary? IUITextInput.MarkedTextStyle
|
|
|
{
|
|
{
|
|
|
get => null;
|
|
get => null;
|
|
|
- set {}
|
|
|
|
|
|
|
+ set { }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
UITextPosition IUITextInput.BeginningOfDocument => _beginningOfDocument;
|
|
UITextPosition IUITextInput.BeginningOfDocument => _beginningOfDocument;
|
|
@@ -478,7 +507,7 @@ partial class AvaloniaView
|
|
|
var res = base.ResignFirstResponder();
|
|
var res = base.ResignFirstResponder();
|
|
|
if (res && ReferenceEquals(CurrentAvaloniaResponder, this))
|
|
if (res && ReferenceEquals(CurrentAvaloniaResponder, this))
|
|
|
{
|
|
{
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
Logger.TryGet(LogEventLevel.Debug, "IOSIME")
|
|
Logger.TryGet(LogEventLevel.Debug, "IOSIME")
|
|
|
?.Log(null, "Resigned first responder");
|
|
?.Log(null, "Resigned first responder");
|
|
|
_client.SurroundingTextChanged -= SurroundingTextChanged;
|
|
_client.SurroundingTextChanged -= SurroundingTextChanged;
|