diff --git a/azure-pipelines-integrationtests.yml b/azure-pipelines-integrationtests.yml index dec94a44d5..939d09c959 100644 --- a/azure-pipelines-integrationtests.yml +++ b/azure-pipelines-integrationtests.yml @@ -24,6 +24,7 @@ jobs: fi sudo xcode-select -s /Applications/Xcode.app/Contents/Developer pkill node + pkill testmanagerd appium > appium.out & pkill IntegrationTestApp ./build.sh CompileNative diff --git a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj index c49290314d..eb0b528fd3 100644 --- a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj +++ b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj @@ -44,6 +44,9 @@ 5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; }; 5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; }; 855EDC9F28C6546F00807998 /* PlatformBehaviorInhibition.mm in Sources */ = {isa = PBXBuildFile; fileRef = 855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.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 */; }; @@ -97,6 +100,9 @@ 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = ""; }; 5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = ""; }; 855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformBehaviorInhibition.mm; sourceTree = ""; }; + 8D2F3511292F6AAE007FCF54 /* AvnTextInputMethodDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnTextInputMethodDelegate.h; sourceTree = ""; }; + 8D300D64292D0A6800320C49 /* AvnTextInputMethod.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnTextInputMethod.h; sourceTree = ""; }; + 8D300D68292E1E5D00320C49 /* AvnTextInputMethod.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnTextInputMethod.mm; sourceTree = ""; }; AB00E4F62147CA920032A60A /* main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = ""; }; 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; }; @@ -143,6 +149,9 @@ isa = PBXGroup; children = ( 855EDC9E28C6546F00807998 /* PlatformBehaviorInhibition.mm */, + 8D2F3511292F6AAE007FCF54 /* AvnTextInputMethodDelegate.h */, + 8D300D68292E1E5D00320C49 /* AvnTextInputMethod.mm */, + 8D300D64292D0A6800320C49 /* AvnTextInputMethod.h */, BC11A5BC2608D58F0017BAD0 /* automation.h */, BC11A5BD2608D58F0017BAD0 /* automation.mm */, 1A1852DB23E05814008F0DED /* deadlock.mm */, @@ -213,6 +222,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 */, @@ -293,6 +304,7 @@ 37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */, 855EDC9F28C6546F00807998 /* PlatformBehaviorInhibition.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 */, diff --git a/native/Avalonia.Native/src/OSX/AvnTextInputMethod.h b/native/Avalonia.Native/src/OSX/AvnTextInputMethod.h new file mode 100644 index 0000000000..4e5116ee71 --- /dev/null +++ b/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 + +#include "com.h" +#include "comimpl.h" +#include "avalonia-native.h" +#import "AvnTextInputMethodDelegate.h" + +class AvnTextInputMethod: public virtual ComObject, public virtual IAvnTextInputMethod{ +private: + id _inputMethodDelegate; +public: + FORWARD_IUNKNOWN() + + BEGIN_INTERFACE_MAP() + INTERFACE_MAP_ENTRY(IAvnTextInputMethod, IID_IAvnTextInputMethod) + END_INTERFACE_MAP() + + virtual ~AvnTextInputMethod(); + + AvnTextInputMethod(id 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 Client; +}; +#endif /* AvnTextInputMethod_h */ diff --git a/native/Avalonia.Native/src/OSX/AvnTextInputMethod.mm b/native/Avalonia.Native/src/OSX/AvnTextInputMethod.mm new file mode 100644 index 0000000000..8c3ae080fa --- /dev/null +++ b/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 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]; +} diff --git a/native/Avalonia.Native/src/OSX/AvnTextInputMethodDelegate.h b/native/Avalonia.Native/src/OSX/AvnTextInputMethodDelegate.h new file mode 100644 index 0000000000..9f321ca595 --- /dev/null +++ b/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 */ diff --git a/native/Avalonia.Native/src/OSX/AvnView.h b/native/Avalonia.Native/src/OSX/AvnView.h index 86a68d34c5..256caa70e9 100644 --- a/native/Avalonia.Native/src/OSX/AvnView.h +++ b/native/Avalonia.Native/src/OSX/AvnView.h @@ -5,8 +5,6 @@ #pragma once #import - -#import #import #include "common.h" #include "WindowImpl.h" @@ -14,7 +12,7 @@ @class AvnAccessibilityElement; -@interface AvnView : NSView +@interface AvnView : NSView -(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 \ No newline at end of file +@end diff --git a/native/Avalonia.Native/src/OSX/AvnView.mm b/native/Avalonia.Native/src/OSX/AvnView.mm index bcfdc23053..5440b76bfe 100644 --- a/native/Avalonia.Native/src/OSX/AvnView.mm +++ b/native/Avalonia.Native/src/OSX/AvnView.mm @@ -12,6 +12,7 @@ { ComPtr _parent; NSTrackingArea* _area; + NSMutableAttributedString* _markedText; bool _isLeftPressed, _isMiddlePressed, _isRightPressed, _isXButton1Pressed, _isXButton2Pressed; AvnInputModifiers _modifierState; NSEvent* _lastMouseDownEvent; @@ -20,6 +21,9 @@ NSObject* _renderTarget; AvnPlatformResizeReason _resizeReason; AvnAccessibilityElement* _accessibilityChild; + NSRect _cursorRect; + NSMutableString* _text; + NSRange _selection; } - (void)onClosed @@ -518,7 +522,7 @@ - (void)keyDown:(NSEvent *)event { [self keyboardEvent:event withType:KeyDown]; - [[self inputContext] handleEvent:event]; + _lastKeyHandled = [[self inputContext] handleEvent:event]; [super keyDown:event]; } @@ -557,27 +561,50 @@ - (BOOL)hasMarkedText { - return _lastKeyHandled; + return [_markedText length] > 0; } - (NSRange)markedRange { + if([_markedText length] > 0) + return NSMakeRange(0, [_markedText length] - 1); return NSMakeRange(NSNotFound, 0); } - (NSRange)selectedRange { - return NSMakeRange(NSNotFound, 0); + return _selection; } - (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); + + [[self inputContext] discardMarkedText]; } - (NSArray *)validAttributesForMarkedText @@ -587,30 +614,38 @@ - (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range actualRange:(NSRangePointer)actualRange { - return [NSAttributedString new]; + return nullptr; } - (void)insertText:(id)string replacementRange:(NSRange)replacementRange { - if(!_lastKeyHandled) - { + //[_text replaceCharactersInRange:replacementRange withString:string]; + + [self unmarkText]; + + //if(!_lastKeyHandled) + //{ if(_parent != nullptr) { _lastKeyHandled = _parent->BaseEvents->RawTextInputEvent(0, [string UTF8String]); } - } + //} + + [[self inputContext] invalidateCharacterCoordinates]; } - (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 _cursorRect; } - (NSDragOperation)triggerAvnDragEvent: (AvnDragEventType) type info: (id )info @@ -715,4 +750,28 @@ return [[self accessibilityChild] accessibilityFocusedUIElement]; } +- (void) setText:(NSString *)text{ + [_text setString:text]; + + [[self inputContext] discardMarkedText]; +} + +- (void) setSelection:(int)start :(int)end{ + _selection = NSMakeRange(start, end - start); + + [[self inputContext] invalidateCharacterCoordinates]; +} + +- (void) setCursorRect:(AvnRect)rect{ + NSRect cursorRect = ToNSRect(rect); + NSRect windowRectOnScreen = [[self window] convertRectToScreen:self.frame]; + + windowRectOnScreen.size = cursorRect.size; + windowRectOnScreen.origin = NSMakePoint(windowRectOnScreen.origin.x + cursorRect.origin.x, windowRectOnScreen.origin.y + self.frame.size.height - cursorRect.origin.y - cursorRect.size.height); + + _cursorRect = windowRectOnScreen; + + [[self inputContext] invalidateCharacterCoordinates]; +} + @end diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h index 93decef136..be7d933b6a 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h +++ b/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 GetWindowProtocol (); virtual void BringToFront (); + + virtual HRESULT GetInputMethod(IAvnTextInputMethod **retOut) override; protected: virtual NSWindowStyleMask CalculateStyleMask() = 0; @@ -130,6 +133,7 @@ public: NSObject *renderTarget; NSWindow * Window; ComPtr BaseEvents; + ComPtr InputMethod; AvnView *View; }; diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index b579920c6b..edf6bcf508 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -15,6 +15,7 @@ #import "WindowProtocol.h" #import "WindowInterfaces.h" #include "WindowBaseImpl.h" +#include "AvnTextInputMethod.h" WindowBaseImpl::~WindowBaseImpl() { @@ -29,6 +30,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 }; @@ -605,6 +607,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 diff --git a/samples/ControlCatalog/Pages/FlyoutsPage.axaml b/samples/ControlCatalog/Pages/FlyoutsPage.axaml index 54aa9d1b67..36e2e8f4f8 100644 --- a/samples/ControlCatalog/Pages/FlyoutsPage.axaml +++ b/samples/ControlCatalog/Pages/FlyoutsPage.axaml @@ -190,7 +190,7 @@ -