From 986d2e4d84168bf161d493618845273f65990ba2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sun, 11 Nov 2018 17:13:43 +0300 Subject: [PATCH] [OSX] Parent-modal dialogs --- native/Avalonia.Native/inc/avalonia-native.h | 2 +- native/Avalonia.Native/src/OSX/common.h | 9 ++ native/Avalonia.Native/src/OSX/window.h | 2 + native/Avalonia.Native/src/OSX/window.mm | 121 +++++++++++------- samples/ControlCatalog/Pages/DialogsPage.xaml | 1 + .../ControlCatalog/Pages/DialogsPage.xaml.cs | 6 + src/Avalonia.Native/WindowImpl.cs | 2 +- 7 files changed, 98 insertions(+), 45 deletions(-) diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h index 0c965b7498..e2b594bc75 100644 --- a/native/Avalonia.Native/inc/avalonia-native.h +++ b/native/Avalonia.Native/inc/avalonia-native.h @@ -207,7 +207,7 @@ 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; 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/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 76243493c4..7d0533e6f3 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; } @@ -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; @@ -1121,13 +1106,13 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent { ComPtr parent = _parent; _parent = NULL; + [self restoreParentWindow]; parent->BaseEvents->Closed(); [parent->View onClosed]; [self setContentView: nil]; } } - -(BOOL)canBecomeKeyWindow { return _canBecomeKeyAndMain; @@ -1138,12 +1123,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/Pages/DialogsPage.xaml b/samples/ControlCatalog/Pages/DialogsPage.xaml index 23b14d009d..353edce51c 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml @@ -4,5 +4,6 @@ + diff --git a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs index f215bf9e64..e380c677c9 100644 --- a/samples/ControlCatalog/Pages/DialogsPage.xaml.cs +++ b/samples/ControlCatalog/Pages/DialogsPage.xaml.cs @@ -34,6 +34,12 @@ namespace ControlCatalog.Pages { new DecoratedWindow().ShowDialog(GetWindow()); }; + this.FindControl