diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index 0c965b7498..4b814a9cfb 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -173,6 +173,12 @@ public: virtual HRESULT ObtainGlFeature(IAvnGlFeature** ppv) = 0; }; +AVNCOM(IAvnString, 17) : IUnknown +{ + virtual HRESULT Pointer(void**retOut) = 0; + virtual HRESULT Length(int*ret) = 0; +}; + AVNCOM(IAvnWindowBase, 02) : IUnknown { virtual HRESULT Show() = 0; @@ -207,10 +213,10 @@ AVNCOM(IAvnPopup, 03) : virtual IAvnWindowBase AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase { - virtual HRESULT ShowDialog (IUnknown**ppv) = 0; + virtual HRESULT ShowDialog (IAvnWindow* parent) = 0; virtual HRESULT SetCanResize(bool value) = 0; virtual HRESULT SetHasDecorations(bool value) = 0; - virtual HRESULT SetTitle (const char* title) = 0; + virtual HRESULT SetTitle (void* utf8Title) = 0; virtual HRESULT SetTitleBarColor (AvnColor color) = 0; virtual HRESULT SetWindowState(AvnWindowState state) = 0; virtual HRESULT GetWindowState(AvnWindowState*ret) = 0; @@ -315,8 +321,8 @@ AVNCOM(IAvnScreens, 0e) : IUnknown AVNCOM(IAvnClipboard, 0f) : IUnknown { - virtual HRESULT GetText (void** retOut) = 0; - virtual HRESULT SetText (char* text) = 0; + virtual HRESULT GetText (IAvnString**ppv) = 0; + virtual HRESULT SetText (void* utf8Text) = 0; virtual HRESULT Clear() = 0; }; 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 bd8ac481a8..cc74d5669f 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 @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; }; 37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; }; + 37DDA9B0219330F8002E132B /* AvnString.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37DDA9AF219330F8002E132B /* AvnString.mm */; }; 37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37E2330E21583241000CB7E2 /* KeyTransform.mm */; }; 5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; }; 5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; }; @@ -26,6 +27,8 @@ 37A517B22159597E00FBA241 /* Screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Screens.mm; sourceTree = ""; }; 37C09D8721580FE4006A6758 /* SystemDialogs.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SystemDialogs.mm; sourceTree = ""; }; 37C09D8A21581EF2006A6758 /* window.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = window.h; sourceTree = ""; }; + 37DDA9AF219330F8002E132B /* AvnString.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnString.mm; sourceTree = ""; }; + 37DDA9B121933371002E132B /* AvnString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AvnString.h; sourceTree = ""; }; 37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = ""; }; 5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = ""; }; 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = ""; }; @@ -65,6 +68,8 @@ AB7A61E62147C814003C5833 = { isa = PBXGroup; children = ( + 37DDA9B121933371002E132B /* AvnString.h */, + 37DDA9AF219330F8002E132B /* AvnString.mm */, 37A4E71A2178846A00EACBCD /* headers */, AB573DC3217605E400D389A2 /* gl.mm */, 5BF943652167AD1D009CAE35 /* cursor.h */, @@ -161,6 +166,7 @@ files = ( 5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */, 5B21A982216530F500CEE36E /* cursor.mm in Sources */, + 37DDA9B0219330F8002E132B /* AvnString.mm in Sources */, AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */, 37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */, 37A517B32159597E00FBA241 /* Screens.mm in Sources */, diff --git a/native/Avalonia.Native/src/OSX/AvnString.h b/native/Avalonia.Native/src/OSX/AvnString.h new file mode 100644 index 0000000000..9a8f5a1318 --- /dev/null +++ b/native/Avalonia.Native/src/OSX/AvnString.h @@ -0,0 +1,14 @@ +// +// AvnString.h +// Avalonia.Native.OSX +// +// Created by Dan Walmsley on 07/11/2018. +// Copyright © 2018 Avalonia. All rights reserved. +// + +#ifndef AvnString_h +#define AvnString_h + +extern IAvnString* CreateAvnString(NSString* string); + +#endif /* AvnString_h */ diff --git a/native/Avalonia.Native/src/OSX/AvnString.mm b/native/Avalonia.Native/src/OSX/AvnString.mm new file mode 100644 index 0000000000..b491cf2a92 --- /dev/null +++ b/native/Avalonia.Native/src/OSX/AvnString.mm @@ -0,0 +1,55 @@ +// +// AvnString.m +// Avalonia.Native.OSX +// +// Created by Dan Walmsley on 07/11/2018. +// Copyright © 2018 Avalonia. All rights reserved. +// + +#include "common.h" + +class AvnStringImpl : public virtual ComSingleObject +{ +private: + NSString* _string; + +public: + FORWARD_IUNKNOWN() + + AvnStringImpl(NSString* string) + { + _string = string; + } + + virtual HRESULT Pointer(void**retOut) override + { + @autoreleasepool + { + if(retOut == nullptr) + { + return E_POINTER; + } + + *retOut = (void*)_string.UTF8String; + + return S_OK; + } + } + + virtual HRESULT Length(int*retOut) override + { + if(retOut == nullptr) + { + return E_POINTER; + } + + *retOut = (int)_string.length; + + return S_OK; + } +}; + +IAvnString* CreateAvnString(NSString* string) +{ + return new AvnStringImpl(string); +} diff --git a/native/Avalonia.Native/src/OSX/clipboard.mm b/native/Avalonia.Native/src/OSX/clipboard.mm index 19e5c25801..53c1fe3c2c 100644 --- a/native/Avalonia.Native/src/OSX/clipboard.mm +++ b/native/Avalonia.Native/src/OSX/clipboard.mm @@ -2,29 +2,34 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. #include "common.h" +#include "AvnString.h" class Clipboard : public ComSingleObject { public: FORWARD_IUNKNOWN() - virtual HRESULT GetText (void** retOut) override + virtual HRESULT GetText (IAvnString**ppv) override { @autoreleasepool { - NSString *str = [[NSPasteboard generalPasteboard] stringForType:NSPasteboardTypeString]; - *retOut = (void *)str.UTF8String; + if(ppv == nullptr) + { + return E_POINTER; + } + + *ppv = CreateAvnString([[NSPasteboard generalPasteboard] stringForType:NSPasteboardTypeString]); + + return S_OK; } - - return S_OK; } - virtual HRESULT SetText (char* text) override + virtual HRESULT SetText (void* utf8String) override { @autoreleasepool { NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard]; [pasteBoard clearContents]; - [pasteBoard setString:@(text) forType:NSPasteboardTypeString]; + [pasteBoard setString:[NSString stringWithUTF8String:(const char*)utf8String] forType:NSPasteboardTypeString]; } return S_OK; diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h index 1647569269..f748dd59ad 100644 --- a/native/Avalonia.Native/src/OSX/common.h +++ b/native/Avalonia.Native/src/OSX/common.h @@ -31,4 +31,13 @@ extern NSSize ToNSSize (AvnSize s); #define NSDebugLog(...) (void)0 #endif +template inline T* objc_cast(id from) { + if(from == nil) + return nil; + if ([from isKindOfClass:[T class]]) { + return static_cast(from); + } + return nil; +} + #endif diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index 4d6dcfed85..5043246c53 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -32,27 +32,23 @@ public: - (void) do; @end @implementation ThreadingInitializer - -pthread_mutex_t mutex; -pthread_cond_t cond; - +{ + int _fds[2]; +} - (void) runOnce { - pthread_mutex_lock(&mutex); - pthread_cond_signal(&cond); - pthread_mutex_unlock(&mutex); + char buf[]={0}; + write(_fds[1], buf, 1); } - (void) do { - pthread_mutex_init(&mutex, NULL); - pthread_cond_init(&cond, NULL); + pipe(_fds); [[[NSThread alloc] initWithTarget:self selector:@selector(runOnce) object:nil] start]; - pthread_mutex_lock(&mutex); - pthread_cond_wait(&cond, &mutex); - pthread_mutex_unlock(&mutex); - pthread_cond_destroy(&cond); - pthread_mutex_destroy(&mutex); + char buf[1]; + read(_fds[0], buf, 1); + close(_fds[0]); + close(_fds[1]); } diff --git a/native/Avalonia.Native/src/OSX/window.h b/native/Avalonia.Native/src/OSX/window.h index 34592993c3..e2221217f3 100644 --- a/native/Avalonia.Native/src/OSX/window.h +++ b/native/Avalonia.Native/src/OSX/window.h @@ -18,6 +18,8 @@ class WindowBaseImpl; -(AvnWindow* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent; -(void) setCanBecomeKeyAndMain; -(void) pollModalSession: (NSModalSession _Nonnull) session; +-(void) restoreParentWindow; +-(bool) shouldTryToHandleEvents; @end struct INSWindowHolder diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm index 362b765b3d..ae00bde780 100644 --- a/native/Avalonia.Native/src/OSX/window.mm +++ b/native/Avalonia.Native/src/OSX/window.mm @@ -109,6 +109,7 @@ public: if(Window != nullptr) { [Window orderOut:Window]; + [Window restoreParentWindow]; } return S_OK; @@ -392,30 +393,6 @@ protected: } }; -class ModalDisposable : public ComUnknownObject -{ - NSModalSession _session; - AvnWindow* _window; - - void Dispose () - { - [_window orderOut:_window]; - [NSApp endModalSession:_session]; - } - -public: - ModalDisposable(AvnWindow* window, NSModalSession session) - { - _session = session; - _window = window; - } - - virtual ~ModalDisposable() - { - Dispose(); - } -}; - class WindowImpl : public virtual WindowBaseImpl, public virtual IAvnWindow, public IWindowStateChanged { private: @@ -444,32 +421,27 @@ private: { @autoreleasepool { + if([Window parentWindow] != nil) + [[Window parentWindow] removeChildWindow:Window]; WindowBaseImpl::Show(); return SetWindowState(_lastWindowState); } } - virtual HRESULT ShowDialog (IUnknown**ppv) override + virtual HRESULT ShowDialog (IAvnWindow* parent) override { @autoreleasepool { - if(ppv == nullptr) - { + if(parent == nullptr) return E_POINTER; - } - - auto session = [NSApp beginModalSessionForWindow:Window]; - auto disposable = new ModalDisposable(Window, session); - *ppv = disposable; - - SetPosition(lastPositionSet); - UpdateStyle(); - - [Window setTitle:_lastTitle]; - [Window setTitleVisibility:NSWindowTitleVisible]; + + auto cparent = dynamic_cast(parent); + if(cparent == nullptr) + return E_INVALIDARG; - [Window pollModalSession:session]; + [cparent->Window addChildWindow:Window ordered:NSWindowAbove]; + WindowBaseImpl::Show(); return S_OK; } @@ -530,11 +502,11 @@ private: } } - virtual HRESULT SetTitle (const char* title) override + virtual HRESULT SetTitle (void* utf8title) override { @autoreleasepool { - _lastTitle = [NSString stringWithUTF8String:title]; + _lastTitle = [NSString stringWithUTF8String:(const char*)utf8title]; [Window setTitle:_lastTitle]; [Window setTitleVisibility:NSWindowTitleVisible]; @@ -843,8 +815,19 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent [super viewDidChangeBackingProperties]; } +- (bool) ignoreUserInput +{ + auto parentWindow = objc_cast([self window]); + if(parentWindow == nil || ![parentWindow shouldTryToHandleEvents]) + return TRUE; + return FALSE; +} + - (void)mouseEvent:(NSEvent *)event withType:(AvnRawMouseEventType) type { + if([self ignoreUserInput]) + return; + [self becomeFirstResponder]; auto localPoint = [self convertPoint:[event locationInWindow] toView:self]; auto avnPoint = [self toAvnPoint:localPoint]; @@ -952,7 +935,9 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent } - (void) keyboardEvent: (NSEvent *) event withType: (AvnRawKeyEventType)type -{ +{ + if([self ignoreUserInput]) + return; auto key = s_KeyMap[[event keyCode]]; auto timestamp = [event timestamp] * 1000; @@ -1125,13 +1110,15 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent { ComPtr parent = _parent; _parent = NULL; + [self restoreParentWindow]; parent->BaseEvents->Closed(); [parent->View onClosed]; - [self setContentView: nil]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self setContentView: nil]; + }); } } - -(BOOL)canBecomeKeyWindow { return _canBecomeKeyAndMain; @@ -1142,12 +1129,62 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent return _canBecomeKeyAndMain; } +-(bool) activateAppropriateChild: (bool)activating +{ + for(NSWindow* uch in [self childWindows]) + { + auto ch = objc_cast(uch); + if(ch == nil) + continue; + [ch activateAppropriateChild:false]; + return FALSE; + } + + if(!activating) + [self makeKeyAndOrderFront:self]; + return TRUE; +} + +-(bool)shouldTryToHandleEvents +{ + for(NSWindow* uch in [self childWindows]) + { + auto ch = objc_cast(uch); + if(ch == nil) + continue; + return FALSE; + } + return TRUE; +} + +-(void)makeKeyWindow +{ + if([self activateAppropriateChild: true]) + { + [super makeKeyWindow]; + } +} + -(void)becomeKeyWindow { - _parent->BaseEvents->Activated(); - [super becomeKeyWindow]; + if([self activateAppropriateChild: true]) + { + _parent->BaseEvents->Activated(); + [super becomeKeyWindow]; + } +} + +-(void) restoreParentWindow; +{ + auto parent = objc_cast([self parentWindow]); + if(parent != nil) + { + [parent removeChildWindow:self]; + [parent activateAppropriateChild: false]; + } } + - (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame { return true; diff --git a/samples/ControlCatalog.Desktop/Program.cs b/samples/ControlCatalog.Desktop/Program.cs index a2048005a4..329b2ab5a3 100644 --- a/samples/ControlCatalog.Desktop/Program.cs +++ b/samples/ControlCatalog.Desktop/Program.cs @@ -22,7 +22,7 @@ namespace ControlCatalog /// This method is needed for IDE previewer infrastructure /// public static AppBuilder BuildAvaloniaApp() - => AppBuilder.Configure().LogToDebug().UsePlatformDetect(); + => AppBuilder.Configure().LogToDebug().UsePlatformDetect().UseReactiveUI(); private static void ConfigureAssetAssembly(AppBuilder builder) { diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml b/samples/ControlCatalog/Pages/DialogsPage.xaml index 710d791f3a..353edce51c 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml @@ -3,10 +3,7 @@ - - - Modal to window - + diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index 8b3e810f0a..e380c677c9 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -31,12 +31,18 @@ namespace ControlCatalog.Pages }.ShowAsync(GetWindow()); }; this.FindControl