Browse Source

Initial macOS IME integration

pull/9526/head
Benedikt Stebner 3 years ago
parent
commit
decf863bc9
  1. 12
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  2. 46
      native/Avalonia.Native/src/OSX/AvnTextInputMethod.h
  3. 41
      native/Avalonia.Native/src/OSX/AvnTextInputMethod.mm
  4. 20
      native/Avalonia.Native/src/OSX/AvnTextInputMethodDelegate.h
  5. 6
      native/Avalonia.Native/src/OSX/AvnView.h
  6. 53
      native/Avalonia.Native/src/OSX/AvnView.mm
  7. 4
      native/Avalonia.Native/src/OSX/WindowBaseImpl.h
  8. 10
      native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
  9. 118
      src/Avalonia.Native/AvaloniaNativeTextInputMethod.cs
  10. 5
      src/Avalonia.Native/WindowImpl.cs
  11. 17
      src/Avalonia.Native/avn.idl

12
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

@ -43,6 +43,9 @@
523484CA26EA688F00EA0C2C /* trayicon.mm in Sources */ = {isa = PBXBuildFile; fileRef = 523484C926EA688F00EA0C2C /* trayicon.mm */; };
5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; };
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; };
8D2F3512292F6AAE007FCF54 /* AvnTextInputMethodDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D2F3511292F6AAE007FCF54 /* AvnTextInputMethodDelegate.h */; };
8D300D65292D0A6800320C49 /* AvnTextInputMethod.h in Headers */ = {isa = PBXBuildFile; fileRef = 8D300D64292D0A6800320C49 /* AvnTextInputMethod.h */; };
8D300D69292E1E5D00320C49 /* AvnTextInputMethod.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8D300D68292E1E5D00320C49 /* AvnTextInputMethod.mm */; };
AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; };
AB1E522C217613570091CD71 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB1E522B217613570091CD71 /* OpenGL.framework */; };
AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB661C1D2148230F00291242 /* AppKit.framework */; };
@ -95,6 +98,9 @@
5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = "<group>"; };
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = "<group>"; };
5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = "<group>"; };
8D2F3511292F6AAE007FCF54 /* AvnTextInputMethodDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnTextInputMethodDelegate.h; sourceTree = "<group>"; };
8D300D64292D0A6800320C49 /* AvnTextInputMethod.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnTextInputMethod.h; sourceTree = "<group>"; };
8D300D68292E1E5D00320C49 /* AvnTextInputMethod.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnTextInputMethod.mm; sourceTree = "<group>"; };
AB00E4F62147CA920032A60A /* main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = "<group>"; };
AB1E522B217613570091CD71 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; };
AB661C1D2148230F00291242 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
@ -140,6 +146,9 @@
AB7A61E62147C814003C5833 = {
isa = PBXGroup;
children = (
8D2F3511292F6AAE007FCF54 /* AvnTextInputMethodDelegate.h */,
8D300D68292E1E5D00320C49 /* AvnTextInputMethod.mm */,
8D300D64292D0A6800320C49 /* AvnTextInputMethod.h */,
BC11A5BC2608D58F0017BAD0 /* automation.h */,
BC11A5BD2608D58F0017BAD0 /* automation.mm */,
1A1852DB23E05814008F0DED /* deadlock.mm */,
@ -210,6 +219,8 @@
1839171DCC651B0638603AC4 /* INSWindowHolder.h in Headers */,
183919D91DB9AAB5D700C2EA /* WindowImpl.h in Headers */,
18391CF07316F819E76B617C /* IWindowStateChanged.h in Headers */,
8D300D65292D0A6800320C49 /* AvnTextInputMethod.h in Headers */,
8D2F3512292F6AAE007FCF54 /* AvnTextInputMethodDelegate.h in Headers */,
18391C28BF1823B5464FDD36 /* ResizeScope.h in Headers */,
18391ED5F611FF62C45F196D /* AvnView.h in Headers */,
18391E1381E2D5BFD60265A9 /* AutoFitContentView.h in Headers */,
@ -289,6 +300,7 @@
BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */,
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
520624B322973F4100C4DCEF /* menu.mm in Sources */,
8D300D69292E1E5D00320C49 /* AvnTextInputMethod.mm in Sources */,
37A517B32159597E00FBA241 /* Screens.mm in Sources */,
1AFD334123E03C4F0042899B /* controlhost.mm in Sources */,
1A465D10246AB61600C5858B /* dnd.mm in Sources */,

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

@ -0,0 +1,46 @@
//
// AvnTextInputMethod.h
// Avalonia.Native.OSX
//
// Created by Benedikt Stebner on 22.11.22.
// Copyright © 2022 Avalonia. All rights reserved.
//
#ifndef AvnTextInputMethod_h
#define AvnTextInputMethod_h
#import <Foundation/Foundation.h>
#include "com.h"
#include "comimpl.h"
#include "avalonia-native.h"
#import "AvnTextInputMethodDelegate.h"
class AvnTextInputMethod: public virtual ComObject, public virtual IAvnTextInputMethod{
private:
id<AvnTextInputMethodDelegate> _inputMethodDelegate;
public:
FORWARD_IUNKNOWN()
BEGIN_INTERFACE_MAP()
INTERFACE_MAP_ENTRY(IAvnTextInputMethod, IID_IAvnTextInputMethod)
END_INTERFACE_MAP()
virtual ~AvnTextInputMethod();
AvnTextInputMethod(id<AvnTextInputMethodDelegate> inputMethodDelegate);
bool IsActive ();
HRESULT SetClient (IAvnTextInputMethodClient* client) override;
virtual void Reset () override;
virtual void SetCursorRect (AvnRect rect) override;
virtual void SetSurroundingText (char* text, int anchorOffset, int cursorOffset) override;
public:
ComPtr<IAvnTextInputMethodClient> Client;
};
#endif /* AvnTextInputMethod_h */

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

@ -0,0 +1,41 @@
//
// AvnTextInputMethod.mm
// Avalonia.Native.OSX
//
// Created by Benedikt Stebner on 23.11.22.
// Copyright © 2022 Avalonia. All rights reserved.
//
#include "AvnTextInputMethod.h"
AvnTextInputMethod::~AvnTextInputMethod() {
Client = nullptr;
}
AvnTextInputMethod::AvnTextInputMethod(id<AvnTextInputMethodDelegate> inputMethodDelegate) {
_inputMethodDelegate = inputMethodDelegate;
}
bool AvnTextInputMethod::IsActive() {
return Client != nullptr;
}
HRESULT AvnTextInputMethod::SetClient(IAvnTextInputMethodClient *client) {
START_COM_CALL;
Client = client;
return S_OK;
}
void AvnTextInputMethod::Reset() {
}
void AvnTextInputMethod::SetSurroundingText(char* text, int anchorOffset, int cursorOffset) {
[_inputMethodDelegate setText:[NSString stringWithUTF8String:text]];
[_inputMethodDelegate setSelection: anchorOffset : cursorOffset];
}
void AvnTextInputMethod::SetCursorRect(AvnRect rect) {
[_inputMethodDelegate setCursorRect: rect];
}

20
native/Avalonia.Native/src/OSX/AvnTextInputMethodDelegate.h

@ -0,0 +1,20 @@
//
// AvnTextInputMethodHost.h
// Avalonia.Native.OSX
//
// Created by Benedikt Stebner on 24.11.22.
// Copyright © 2022 Avalonia. All rights reserved.
//
#ifndef AvnTextInputMethodHost_h
#define AvnTextInputMethodHost_h
@protocol AvnTextInputMethodDelegate
@required
-(void) setText:(NSString* _Nonnull) text;
-(void) setCursorRect:(AvnRect) cursorRect;
-(void) setSelection: (int) start : (int) end;
@end
#endif /* AvnTextInputMethodHost_h */

6
native/Avalonia.Native/src/OSX/AvnView.h

@ -5,8 +5,6 @@
#pragma once
#import <Foundation/Foundation.h>
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#include "common.h"
#include "WindowImpl.h"
@ -14,7 +12,7 @@
@class AvnAccessibilityElement;
@interface AvnView : NSView<NSTextInputClient, NSDraggingDestination>
@interface AvnView : NSView<NSTextInputClient, NSDraggingDestination, AvnTextInputMethodDelegate>
-(AvnView* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
-(NSEvent* _Nonnull) lastMouseDownEvent;
-(AvnPoint) translateLocalPoint:(AvnPoint)pt;
@ -24,4 +22,4 @@
-(AvnPlatformResizeReason) getResizeReason;
-(void) setResizeReason:(AvnPlatformResizeReason)reason;
+ (AvnPoint)toAvnPoint:(CGPoint)p;
@end
@end

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

@ -12,6 +12,7 @@
{
ComPtr<WindowBaseImpl> _parent;
NSTrackingArea* _area;
NSMutableAttributedString* _markedText;
bool _isLeftPressed, _isMiddlePressed, _isRightPressed, _isXButton1Pressed, _isXButton2Pressed;
AvnInputModifiers _modifierState;
NSEvent* _lastMouseDownEvent;
@ -20,6 +21,9 @@
NSObject<IRenderTarget>* _renderTarget;
AvnPlatformResizeReason _resizeReason;
AvnAccessibilityElement* _accessibilityChild;
AvnRect _cursorRect;
NSString* _text;
NSRange _selection;
}
- (void)onClosed
@ -560,11 +564,13 @@
- (BOOL)hasMarkedText
{
return _lastKeyHandled;
return [_markedText length] > 0;
}
- (NSRange)markedRange
{
if([_markedText length] > 0)
return NSMakeRange(0, [_markedText length] - 1);
return NSMakeRange(NSNotFound, 0);
}
@ -575,12 +581,31 @@
- (void)setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
{
if([string isKindOfClass:[NSAttributedString class]])
{
_markedText = [[NSMutableAttributedString alloc] initWithAttributedString:string];
}
else
{
_markedText = [[NSMutableAttributedString alloc] initWithString:string];
}
if(!_parent->InputMethod->IsActive()){
return;
}
_parent->InputMethod->Client->SetPreeditText((char*)[_markedText.string UTF8String]);
}
- (void)unmarkText
{
[[_markedText mutableString] setString:@""];
if(!_parent->InputMethod->IsActive()){
return;
}
_parent->InputMethod->Client->SetPreeditText(nullptr);
}
- (NSArray<NSString *> *)validAttributesForMarkedText
@ -606,14 +631,16 @@
- (NSUInteger)characterIndexForPoint:(NSPoint)point
{
return 0;
return NSNotFound;
}
- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange
{
CGRect result = { 0 };
return result;
if(!_parent->InputMethod->IsActive()){
return NSZeroRect;
}
return ToNSRect(_cursorRect);
}
- (NSDragOperation)triggerAvnDragEvent: (AvnDragEventType) type info: (id <NSDraggingInfo>)info
@ -718,4 +745,16 @@
return [[self accessibilityChild] accessibilityFocusedUIElement];
}
- (void) setText:(NSString *)text{
_text = text;
}
- (void) setSelection:(int)start :(int)end{
_selection = NSMakeRange(start, end - start);
}
- (void) setCursorRect:(AvnRect)rect{
_cursorRect = rect;
}
@end

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

@ -8,6 +8,7 @@
#include "rendertarget.h"
#include "INSWindowHolder.h"
#include "AvnTextInputMethod.h"
@class AutoFitContentView;
@class AvnMenu;
@ -103,6 +104,8 @@ BEGIN_INTERFACE_MAP()
id<AvnWindowProtocol> GetWindowProtocol ();
virtual void BringToFront ();
virtual HRESULT GetInputMethod(IAvnTextInputMethod **retOut) override;
protected:
virtual NSWindowStyleMask GetStyle();
@ -131,6 +134,7 @@ public:
NSObject <IRenderTarget> *renderTarget;
NSWindow * Window;
ComPtr<IAvnWindowBaseEvents> BaseEvents;
ComPtr<AvnTextInputMethod> InputMethod;
AvnView *View;
};

10
native/Avalonia.Native/src/OSX/WindowBaseImpl.mm

@ -14,6 +14,7 @@
#import "WindowProtocol.h"
#import "WindowInterfaces.h"
#include "WindowBaseImpl.h"
#include "AvnTextInputMethod.h"
WindowBaseImpl::~WindowBaseImpl() {
@ -28,6 +29,7 @@ WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl,
_glContext = gl;
renderTarget = [[IOSurfaceRenderTarget alloc] initWithOpenGlContext:gl];
View = [[AvnView alloc] initWithParent:this];
InputMethod = new AvnTextInputMethod(View);
StandardContainer = [[AutoFitContentView new] initWithContent:View];
lastPositionSet = { 0, 0 };
@ -612,6 +614,14 @@ void WindowBaseImpl::BringToFront()
// do nothing.
}
HRESULT WindowBaseImpl::GetInputMethod(IAvnTextInputMethod **retOut) {
START_COM_CALL;
*retOut = InputMethod;
return S_OK;
}
extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl)
{
@autoreleasepool

118
src/Avalonia.Native/AvaloniaNativeTextInputMethod.cs

@ -0,0 +1,118 @@
using System;
using Avalonia.Input.TextInput;
using Avalonia.Native.Interop;
namespace Avalonia.Native
{
internal class AvaloniaNativeTextInputMethod : ITextInputMethodImpl, IDisposable
{
private ITextInputMethodClient _client;
private IAvnTextInputMethodClient _nativeClient;
private readonly IAvnTextInputMethod _inputMethod;
public AvaloniaNativeTextInputMethod(IAvnWindowBase nativeWindow)
{
_inputMethod = nativeWindow.InputMethod;
}
public void Dispose()
{
_inputMethod.Dispose();
_nativeClient?.Dispose();
}
public void Reset()
{
_inputMethod.Reset();
}
public void SetClient(ITextInputMethodClient client)
{
if (_client is { SupportsSurroundingText: true })
{
_client.SurroundingTextChanged -= OnSurroundingTextChanged;
_client.CursorRectangleChanged -= OnCursorRectangleChanged;
_nativeClient?.Dispose();
}
_nativeClient = null;
_client = client;
if (client != null)
{
_nativeClient = new AvnTextInputMethodClient(client);
OnSurroundingTextChanged(this, EventArgs.Empty);
OnCursorRectangleChanged(this, EventArgs.Empty);
_client.SurroundingTextChanged += OnSurroundingTextChanged;
_client.CursorRectangleChanged += OnCursorRectangleChanged;
}
_inputMethod.SetClient(_nativeClient);
}
private void OnCursorRectangleChanged(object sender, EventArgs e)
{
if (_client == null)
{
return;
}
_inputMethod.SetCursorRect(_client.CursorRectangle.ToAvnRect());
}
private void OnSurroundingTextChanged(object sender, EventArgs e)
{
if (_client == null)
{
return;
}
var surroundingText = _client.SurroundingText;
_inputMethod.SetSurroundingText(
surroundingText.Text,
surroundingText.AnchorOffset,
surroundingText.CursorOffset
);
}
public void SetCursorRect(Rect rect)
{
_inputMethod.SetCursorRect(rect.ToAvnRect());
}
public void SetOptions(TextInputOptions options)
{
}
private class AvnTextInputMethodClient : NativeCallbackBase, IAvnTextInputMethodClient
{
private readonly ITextInputMethodClient _client;
public AvnTextInputMethodClient(ITextInputMethodClient client)
{
_client = client;
}
public void SetPreeditText(string preeditText)
{
if (_client.SupportsPreedit)
{
_client.SetPreeditText(preeditText);
}
}
public void SelectInSurroundingText(int start, int end)
{
if (_client.SupportsSurroundingText)
{
_client.SelectInSurroundingText(start, end);
}
}
}
}
}

5
src/Avalonia.Native/WindowImpl.cs

@ -19,6 +19,7 @@ namespace Avalonia.Native
private double _extendTitleBarHeight = -1;
private DoubleClickHelper _doubleClickHelper;
private readonly ITopLevelNativeMenuExporter _nativeMenuExporter;
private readonly AvaloniaNativeTextInputMethod _inputMethod;
internal WindowImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts,
AvaloniaNativeGlPlatformGraphics glFeature) : base(factory, opts, glFeature)
@ -33,6 +34,8 @@ namespace Avalonia.Native
}
_nativeMenuExporter = new AvaloniaNativeMenuExporter(_native, factory);
_inputMethod = new AvaloniaNativeTextInputMethod(_native);
}
class WindowEvents : WindowBaseEvents, IAvnWindowEvents
@ -89,6 +92,8 @@ namespace Avalonia.Native
_native.SetTitle(title ?? "");
}
public ITextInputMethodImpl TextInputMethod => _inputMethod;
public WindowState WindowState
{
get => (WindowState)_native.WindowState;

17
src/Avalonia.Native/avn.idl

@ -545,6 +545,7 @@ interface IAvnWindowBase : IUnknown
IAvnClipboard* clipboard, IAvnDndResultCallback* cb, [intptr]void* sourceHandle);
HRESULT SetTransparencyMode(AvnWindowTransparencyMode mode);
HRESULT SetFrameThemeVariant(AvnPlatformThemeVariant mode);
HRESULT GetInputMethod(IAvnTextInputMethod **ppv);
}
[uuid(83e588f3-6981-4e48-9ea0-e1e569f79a91), cpp-virtual-inherits]
@ -611,6 +612,22 @@ interface IAvnWindowEvents : IAvnWindowBaseEvents
void GotInputWhenDisabled();
}
[uuid(f2079145-a2d9-42b8-a85e-2732e3c2b055)]
interface IAvnTextInputMethodClient : IUnknown
{
void SetPreeditText(char* preeditText);
void SelectInSurroundingText(int start, int length);
}
[uuid(1382a29f-e260-4c7a-b83f-c99fc72e27c2)]
interface IAvnTextInputMethod : IUnknown
{
HRESULT SetClient(IAvnTextInputMethodClient* client);
void Reset();
void SetCursorRect(AvnRect rect);
void SetSurroundingText(char* text, int anchorOffset, int cursorOffset);
}
[uuid(e34ae0f8-18b4-48a3-b09d-2e6b19a3cf5e)]
interface IAvnMacOptions : IUnknown
{

Loading…
Cancel
Save