Browse Source

Handle replacementRange in AvnView (#21608)

* macOS: ensure native selectedRange is up-to-date

* macOS: handle replacementRange in setMarkedText and insertText

* macOS: handle unexpected ranges in attributedSubstringForProposedRange
pull/21624/head
Julien Lebosquain 3 months ago
committed by GitHub
parent
commit
b3014ccf27
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      native/Avalonia.Native/src/OSX/AvnTextInputMethod.h
  2. 8
      native/Avalonia.Native/src/OSX/AvnTextInputMethod.mm
  3. 35
      native/Avalonia.Native/src/OSX/AvnView.mm
  4. 22
      src/Avalonia.Native/AvaloniaNativeTextInputMethod.cs
  5. 5
      src/Avalonia.Native/avn.idl

4
native/Avalonia.Native/src/OSX/AvnTextInputMethod.h

@ -38,7 +38,9 @@ public:
virtual void SetCursorRect (AvnRect rect) override; virtual void SetCursorRect (AvnRect rect) override;
virtual void SetSurroundingText (char* text, int anchorOffset, int cursorOffset) override; virtual void SetSurroundingText (char* text, int start, int end) override;
virtual void SetSelectionInSurroundingText (int start, int end) override;
public: public:
ComPtr<IAvnTextInputMethodClient> Client; ComPtr<IAvnTextInputMethodClient> Client;

8
native/Avalonia.Native/src/OSX/AvnTextInputMethod.mm

@ -31,11 +31,15 @@ HRESULT AvnTextInputMethod::SetClient(IAvnTextInputMethodClient *client) {
void AvnTextInputMethod::Reset() { void AvnTextInputMethod::Reset() {
} }
void AvnTextInputMethod::SetSurroundingText(char* text, int anchorOffset, int cursorOffset) { void AvnTextInputMethod::SetSurroundingText(char* text, int start, int end) {
[_inputMethodDelegate setText:[NSString stringWithUTF8String:text]]; [_inputMethodDelegate setText:[NSString stringWithUTF8String:text]];
[_inputMethodDelegate setSelection: anchorOffset : cursorOffset]; [_inputMethodDelegate setSelection: start:end];
} }
void AvnTextInputMethod::SetCursorRect(AvnRect rect) { void AvnTextInputMethod::SetCursorRect(AvnRect rect) {
[_inputMethodDelegate setCursorRect: rect]; [_inputMethodDelegate setCursorRect: rect];
} }
void AvnTextInputMethod::SetSelectionInSurroundingText(int start, int end) {
[_inputMethodDelegate setSelection: start:end];
}

35
native/Avalonia.Native/src/OSX/AvnView.mm

@ -775,10 +775,21 @@ static void ConvertTilt(NSPoint tilt, float* xTilt, float* yTilt)
markedText = (NSString*) string; markedText = (NSString*) string;
} }
_markedRange = NSMakeRange(_selectedRange.location, [markedText length]);
auto parent = _parent.tryGet(); auto parent = _parent.tryGet();
if(parent->InputMethod->IsActive()){ // Delete any replaced range
if (replacementRange.location != NSNotFound && parent != nullptr && parent->InputMethod->IsActive())
{
parent->InputMethod->Client->SelectInSurroundingText((int)replacementRange.location, (int)(replacementRange.location + replacementRange.length));
uint64_t timestamp = static_cast<uint64_t>([NSDate timeIntervalSinceReferenceDate] * 1000);
parent->TopLevelEvents->RawKeyEvent(KeyDown, timestamp, AvnInputModifiersNone, AvnKeyBack, AvnPhysicalKeyNone, "\b");
parent->TopLevelEvents->RawKeyEvent(KeyUp, timestamp, AvnInputModifiersNone, AvnKeyBack, AvnPhysicalKeyNone, "\b");
}
_markedRange = NSMakeRange(_selectedRange.location, [markedText length]);
if (parent != nullptr && parent->InputMethod->IsActive())
{
parent->InputMethod->Client->SetPreeditText((char*)[markedText UTF8String]); parent->InputMethod->Client->SetPreeditText((char*)[markedText UTF8String]);
} }
} }
@ -807,9 +818,16 @@ static void ConvertTilt(NSPoint tilt, float* xTilt, float* yTilt)
if(actualRange){ if(actualRange){
range = *actualRange; range = *actualRange;
} }
// From the docs: an implementation of this method should be prepared for aRange to be out of bounds.
// In this case, you should return the intersection of the document's range and aRange.
// If the location of aRange is completely outside of the document's range, return nil.
auto finalRange = NSIntersectionRange(range, NSMakeRange(0, _text.length));
NSAttributedString* subString = [_text attributedSubstringFromRange:range]; if (finalRange.length == 0)
return nil;
NSAttributedString* subString = [_text attributedSubstringFromRange:finalRange];
return subString; return subString;
} }
@ -819,9 +837,9 @@ static void ConvertTilt(NSPoint tilt, float* xTilt, float* yTilt)
if(parent == nullptr){ if(parent == nullptr){
return; return;
} }
NSString* text; NSString* text;
if([string isKindOfClass:[NSAttributedString class]]) if([string isKindOfClass:[NSAttributedString class]])
{ {
text = [string string]; text = [string string];
@ -831,6 +849,13 @@ static void ConvertTilt(NSPoint tilt, float* xTilt, float* yTilt)
text = (NSString*) string; text = (NSString*) string;
} }
if (replacementRange.location != NSNotFound &&
![self hasMarkedText] &&
parent->InputMethod->IsActive())
{
parent->InputMethod->Client->SelectInSurroundingText((int)replacementRange.location, (int)(replacementRange.location + replacementRange.length));
}
[self unmarkText]; [self unmarkText];
uint64_t timestamp = static_cast<uint64_t>([NSDate timeIntervalSinceReferenceDate] * 1000); uint64_t timestamp = static_cast<uint64_t>([NSDate timeIntervalSinceReferenceDate] * 1000);

22
src/Avalonia.Native/AvaloniaNativeTextInputMethod.cs

@ -32,22 +32,25 @@ namespace Avalonia.Native
{ {
_client.SurroundingTextChanged -= OnSurroundingTextChanged; _client.SurroundingTextChanged -= OnSurroundingTextChanged;
_client.CursorRectangleChanged -= OnCursorRectangleChanged; _client.CursorRectangleChanged -= OnCursorRectangleChanged;
_client.SelectionChanged -= OnSelectionChanged;
_nativeClient?.Dispose(); _nativeClient?.Dispose();
} }
_nativeClient = null; _nativeClient = null;
_client = client; _client = client;
if (_client != null) if (_client != null)
{ {
_nativeClient = new AvnTextInputMethodClient(_client); _nativeClient = new AvnTextInputMethodClient(_client);
OnSurroundingTextChanged(this, EventArgs.Empty); OnSurroundingTextChanged(this, EventArgs.Empty);
OnCursorRectangleChanged(this, EventArgs.Empty); OnCursorRectangleChanged(this, EventArgs.Empty);
// Note: OnSelectionChanged isn't called, it's already up-to-date thanks to OnSurroundingTextChanged
_client.SurroundingTextChanged += OnSurroundingTextChanged; _client.SurroundingTextChanged += OnSurroundingTextChanged;
_client.CursorRectangleChanged += OnCursorRectangleChanged; _client.CursorRectangleChanged += OnCursorRectangleChanged;
_client.SelectionChanged += OnSelectionChanged;
} }
_inputMethod.SetClient(_nativeClient); _inputMethod.SetClient(_nativeClient);
@ -92,7 +95,7 @@ namespace Avalonia.Native
{ {
return; return;
} }
var surroundingText = _client.SurroundingText; var surroundingText = _client.SurroundingText;
var selection = _client.Selection; var selection = _client.Selection;
@ -103,6 +106,17 @@ namespace Avalonia.Native
); );
} }
private void OnSelectionChanged(object? sender, EventArgs e)
{
if (_client is null)
{
return;
}
var selection = _client.Selection;
_inputMethod.SetSelectionInSurroundingText(selection.Start, selection.End);
}
public void SetCursorRect(Rect rect) public void SetCursorRect(Rect rect)
{ {
_inputMethod.SetCursorRect(rect.ToAvnRect()); _inputMethod.SetCursorRect(rect.ToAvnRect());

5
src/Avalonia.Native/avn.idl

@ -872,7 +872,7 @@ interface IAvnWindowEvents : IAvnWindowBaseEvents
interface IAvnTextInputMethodClient : IUnknown interface IAvnTextInputMethodClient : IUnknown
{ {
void SetPreeditText(char* preeditText); void SetPreeditText(char* preeditText);
void SelectInSurroundingText(int start, int length); void SelectInSurroundingText(int start, int end);
} }
[uuid(1382a29f-e260-4c7a-b83f-c99fc72e27c2)] [uuid(1382a29f-e260-4c7a-b83f-c99fc72e27c2)]
@ -881,7 +881,8 @@ interface IAvnTextInputMethod : IUnknown
HRESULT SetClient(IAvnTextInputMethodClient* client); HRESULT SetClient(IAvnTextInputMethodClient* client);
void Reset(); void Reset();
void SetCursorRect(AvnRect rect); void SetCursorRect(AvnRect rect);
void SetSurroundingText(char* text, int anchorOffset, int cursorOffset); void SetSurroundingText(char* text, int start, int end);
void SetSelectionInSurroundingText(int start, int end);
} }
[uuid(e34ae0f8-18b4-48a3-b09d-2e6b19a3cf5e)] [uuid(e34ae0f8-18b4-48a3-b09d-2e6b19a3cf5e)]

Loading…
Cancel
Save