Browse Source

Merge pull request #8211 from AvaloniaUI/fixes/osx-dialog-keep-on-top-when-deactivated

[OSX] dialogs stay ontop of main window when another app is displayed.
pull/8246/head
Dan Walmsley 4 years ago
committed by GitHub
parent
commit
72d97c8750
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      native/Avalonia.Native/src/OSX/AvnPanelWindow.mm
  2. 4
      native/Avalonia.Native/src/OSX/AvnView.mm
  3. 70
      native/Avalonia.Native/src/OSX/AvnWindow.mm
  4. 2
      native/Avalonia.Native/src/OSX/INSWindowHolder.h
  5. 6
      native/Avalonia.Native/src/OSX/WindowBaseImpl.h
  6. 19
      native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
  7. 7
      native/Avalonia.Native/src/OSX/WindowImpl.h
  8. 81
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  9. 1
      native/Avalonia.Native/src/OSX/WindowProtocol.h

2
native/Avalonia.Native/src/OSX/AvnPanelWindow.mm

@ -3,8 +3,6 @@
// Copyright (c) 2022 Avalonia. All rights reserved. // Copyright (c) 2022 Avalonia. All rights reserved.
// //
#pragma once
#define IS_NSPANEL #define IS_NSPANEL
#include "AvnWindow.mm" #include "AvnWindow.mm"

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

@ -222,7 +222,7 @@
- (void)mouseEvent:(NSEvent *)event withType:(AvnRawMouseEventType) type - (void)mouseEvent:(NSEvent *)event withType:(AvnRawMouseEventType) type
{ {
bool triggerInputWhenDisabled = type != Move; bool triggerInputWhenDisabled = type != Move && type != LeaveWindow;
if([self ignoreUserInput: triggerInputWhenDisabled]) if([self ignoreUserInput: triggerInputWhenDisabled])
{ {
@ -709,4 +709,4 @@
return [[self accessibilityChild] accessibilityFocusedUIElement]; return [[self accessibilityChild] accessibilityFocusedUIElement];
} }
@end @end

70
native/Avalonia.Native/src/OSX/AvnWindow.mm

@ -68,7 +68,7 @@
} }
} }
- (void)performClose:(id)sender - (void)performClose:(id _Nullable )sender
{ {
if([[self delegate] respondsToSelector:@selector(windowShouldClose:)]) if([[self delegate] respondsToSelector:@selector(windowShouldClose:)])
{ {
@ -147,7 +147,7 @@
} }
} }
-(void) applyMenu:(AvnMenu *)menu -(void) applyMenu:(AvnMenu *_Nullable)menu
{ {
if(menu == nullptr) if(menu == nullptr)
{ {
@ -157,7 +157,7 @@
_menu = menu; _menu = menu;
} }
-(CLASS_NAME*) initWithParent: (WindowBaseImpl*) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask; -(CLASS_NAME*_Nonnull) initWithParent: (WindowBaseImpl*_Nonnull) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask;
{ {
// https://jameshfisher.com/2020/07/10/why-is-the-contentrect-of-my-nswindow-ignored/ // https://jameshfisher.com/2020/07/10/why-is-the-contentrect-of-my-nswindow-ignored/
// create nswindow with specific contentRect, otherwise we wont be able to resize the window // create nswindow with specific contentRect, otherwise we wont be able to resize the window
@ -183,7 +183,7 @@
return self; return self;
} }
- (BOOL)windowShouldClose:(NSWindow *)sender - (BOOL)windowShouldClose:(NSWindow *_Nonnull)sender
{ {
auto window = dynamic_cast<WindowImpl*>(_parent.getRaw()); auto window = dynamic_cast<WindowImpl*>(_parent.getRaw());
@ -195,21 +195,28 @@
return true; return true;
} }
- (void)windowDidChangeBackingProperties:(NSNotification *)notification - (void)windowDidChangeBackingProperties:(NSNotification *_Nonnull)notification
{ {
[self backingScaleFactor]; [self backingScaleFactor];
} }
- (void)windowWillClose:(NSNotification *)notification - (void)windowWillClose:(NSNotification *_Nonnull)notification
{ {
_closed = true; _closed = true;
if(_parent) if(_parent)
{ {
ComPtr<WindowBaseImpl> parent = _parent; ComPtr<WindowBaseImpl> parent = _parent;
_parent = NULL; _parent = NULL;
[self restoreParentWindow];
auto window = dynamic_cast<WindowImpl*>(parent.getRaw());
if(window != nullptr)
{
window->SetParent(nullptr);
}
parent->BaseEvents->Closed(); parent->BaseEvents->Closed();
[parent->View onClosed]; [parent->View onClosed];
} }
@ -220,17 +227,11 @@
if(_canBecomeKeyWindow) if(_canBecomeKeyWindow)
{ {
// If the window has a child window being shown as a dialog then don't allow it to become the key window. // If the window has a child window being shown as a dialog then don't allow it to become the key window.
for(NSWindow* uch in [self childWindows]) auto parent = dynamic_cast<WindowImpl*>(_parent.getRaw());
if(parent != nullptr)
{ {
if (![uch conformsToProtocol:@protocol(AvnWindowProtocol)]) return parent->CanBecomeKeyWindow();
{
continue;
}
id <AvnWindowProtocol> ch = (id <AvnWindowProtocol>) uch;
if(ch.isDialog)
return false;
} }
return true; return true;
@ -273,17 +274,12 @@
[super becomeKeyWindow]; [super becomeKeyWindow];
} }
-(void) restoreParentWindow; - (void)windowDidBecomeKey:(NSNotification *_Nonnull)notification
{ {
auto parent = [self parentWindow]; _parent->BringToFront();
if(parent != nil)
{
[parent removeChildWindow:self];
}
} }
- (void)windowDidMiniaturize:(NSNotification *)notification - (void)windowDidMiniaturize:(NSNotification *_Nonnull)notification
{ {
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->()); auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
@ -293,7 +289,7 @@
} }
} }
- (void)windowDidDeminiaturize:(NSNotification *)notification - (void)windowDidDeminiaturize:(NSNotification *_Nonnull)notification
{ {
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->()); auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
@ -303,7 +299,7 @@
} }
} }
- (void)windowDidResize:(NSNotification *)notification - (void)windowDidResize:(NSNotification *_Nonnull)notification
{ {
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->()); auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
@ -313,7 +309,7 @@
} }
} }
- (void)windowWillExitFullScreen:(NSNotification *)notification - (void)windowWillExitFullScreen:(NSNotification *_Nonnull)notification
{ {
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->()); auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
@ -323,7 +319,7 @@
} }
} }
- (void)windowDidExitFullScreen:(NSNotification *)notification - (void)windowDidExitFullScreen:(NSNotification *_Nonnull)notification
{ {
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->()); auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
@ -346,7 +342,7 @@
} }
} }
- (void)windowWillEnterFullScreen:(NSNotification *)notification - (void)windowWillEnterFullScreen:(NSNotification *_Nonnull)notification
{ {
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->()); auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
@ -356,7 +352,7 @@
} }
} }
- (void)windowDidEnterFullScreen:(NSNotification *)notification - (void)windowDidEnterFullScreen:(NSNotification *_Nonnull)notification
{ {
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->()); auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
@ -367,7 +363,7 @@
} }
} }
- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame - (BOOL)windowShouldZoom:(NSWindow *_Nonnull)window toFrame:(NSRect)newFrame
{ {
return true; return true;
} }
@ -378,11 +374,13 @@
_parent->BaseEvents->Deactivated(); _parent->BaseEvents->Deactivated();
[self showAppMenuOnly]; [self showAppMenuOnly];
[self invalidateShadow];
[super resignKeyWindow]; [super resignKeyWindow];
} }
- (void)windowDidMove:(NSNotification *)notification - (void)windowDidMove:(NSNotification *_Nonnull)notification
{ {
AvnPoint position; AvnPoint position;
@ -414,7 +412,7 @@
return pt; return pt;
} }
- (void)sendEvent:(NSEvent *)event - (void)sendEvent:(NSEvent *_Nonnull)event
{ {
[super sendEvent:event]; [super sendEvent:event];
@ -437,8 +435,10 @@
_parent->BaseEvents->RawMouseEvent(NonClientLeftButtonDown, static_cast<uint32>([event timestamp] * 1000), AvnInputModifiersNone, point, delta); _parent->BaseEvents->RawMouseEvent(NonClientLeftButtonDown, static_cast<uint32>([event timestamp] * 1000), AvnInputModifiersNone, point, delta);
} }
_parent->BringToFront();
} }
break; break;
case NSEventTypeMouseEntered: case NSEventTypeMouseEntered:
{ {

2
native/Avalonia.Native/src/OSX/INSWindowHolder.h

@ -11,7 +11,7 @@
struct INSWindowHolder struct INSWindowHolder
{ {
virtual NSWindow* _Nonnull GetNSWindow () = 0; virtual NSWindow* _Nonnull GetNSWindow () = 0;
virtual NSView* _Nonnull GetNSView () = 0; virtual AvnView* _Nonnull GetNSView () = 0;
}; };
#endif //AVALONIA_NATIVE_OSX_INSWINDOWHOLDER_H #endif //AVALONIA_NATIVE_OSX_INSWINDOWHOLDER_H

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

@ -26,7 +26,7 @@ BEGIN_INTERFACE_MAP()
virtual ~WindowBaseImpl(); virtual ~WindowBaseImpl();
WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl); WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl, bool usePanel = false);
virtual HRESULT ObtainNSWindowHandle(void **ret) override; virtual HRESULT ObtainNSWindowHandle(void **ret) override;
@ -38,7 +38,7 @@ BEGIN_INTERFACE_MAP()
virtual NSWindow *GetNSWindow() override; virtual NSWindow *GetNSWindow() override;
virtual NSView *GetNSView() override; virtual AvnView *GetNSView() override;
virtual HRESULT Show(bool activate, bool isDialog) override; virtual HRESULT Show(bool activate, bool isDialog) override;
@ -99,6 +99,8 @@ BEGIN_INTERFACE_MAP()
virtual bool IsDialog(); virtual bool IsDialog();
id<AvnWindowProtocol> GetWindowProtocol (); id<AvnWindowProtocol> GetWindowProtocol ();
virtual void BringToFront ();
protected: protected:
virtual NSWindowStyleMask GetStyle(); virtual NSWindowStyleMask GetStyle();

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

@ -21,7 +21,7 @@ WindowBaseImpl::~WindowBaseImpl() {
Window = nullptr; Window = nullptr;
} }
WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl) { WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl, bool usePanel) {
_shown = false; _shown = false;
_inResize = false; _inResize = false;
BaseEvents = events; BaseEvents = events;
@ -36,8 +36,10 @@ WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl)
lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX}; lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX};
lastMinSize = NSSize { 0, 0 }; lastMinSize = NSSize { 0, 0 };
Window = nullptr;
lastMenu = nullptr; lastMenu = nullptr;
CreateNSWindow(usePanel);
InitialiseNSWindow();
} }
HRESULT WindowBaseImpl::ObtainNSViewHandle(void **ret) { HRESULT WindowBaseImpl::ObtainNSViewHandle(void **ret) {
@ -68,7 +70,7 @@ NSWindow *WindowBaseImpl::GetNSWindow() {
return Window; return Window;
} }
NSView *WindowBaseImpl::GetNSView() { AvnView *WindowBaseImpl::GetNSView() {
return View; return View;
} }
@ -88,7 +90,6 @@ HRESULT WindowBaseImpl::Show(bool activate, bool isDialog) {
START_COM_CALL; START_COM_CALL;
@autoreleasepool { @autoreleasepool {
CreateNSWindow(isDialog);
InitialiseNSWindow(); InitialiseNSWindow();
if(hasPosition) if(hasPosition)
@ -143,8 +144,6 @@ HRESULT WindowBaseImpl::Hide() {
@autoreleasepool { @autoreleasepool {
if (Window != nullptr) { if (Window != nullptr) {
[Window orderOut:Window]; [Window orderOut:Window];
[GetWindowProtocol() restoreParentWindow];
} }
return S_OK; return S_OK;
@ -558,6 +557,8 @@ void WindowBaseImpl::CreateNSWindow(bool isDialog) {
CleanNSWindow(); CleanNSWindow();
Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()]; Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()];
[Window setHidesOnDeactivate:false];
} }
} else { } else {
if (![Window isKindOfClass:[AvnWindow class]]) { if (![Window isKindOfClass:[AvnWindow class]]) {
@ -585,6 +586,7 @@ void WindowBaseImpl::InitialiseNSWindow() {
[Window setOpaque:false]; [Window setOpaque:false];
[Window setHasShadow:true];
[Window invalidateShadow]; [Window invalidateShadow];
if (lastMenu != nullptr) { if (lastMenu != nullptr) {
@ -608,6 +610,11 @@ id <AvnWindowProtocol> WindowBaseImpl::GetWindowProtocol() {
return (id <AvnWindowProtocol>) Window; return (id <AvnWindowProtocol>) Window;
} }
void WindowBaseImpl::BringToFront()
{
// do nothing.
}
extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl) extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl)
{ {
@autoreleasepool @autoreleasepool

7
native/Avalonia.Native/src/OSX/WindowImpl.h

@ -8,6 +8,7 @@
#import "WindowBaseImpl.h" #import "WindowBaseImpl.h"
#include "IWindowStateChanged.h" #include "IWindowStateChanged.h"
#include <list>
class WindowImpl : public virtual WindowBaseImpl, public virtual IAvnWindow, public IWindowStateChanged class WindowImpl : public virtual WindowBaseImpl, public virtual IAvnWindow, public IWindowStateChanged
{ {
@ -22,6 +23,8 @@ private:
bool _transitioningWindowState; bool _transitioningWindowState;
bool _isClientAreaExtended; bool _isClientAreaExtended;
bool _isDialog; bool _isDialog;
WindowImpl* _parent;
std::list<WindowImpl*> _children;
AvnExtendClientAreaChromeHints _extendClientHints; AvnExtendClientAreaChromeHints _extendClientHints;
FORWARD_IUNKNOWN() FORWARD_IUNKNOWN()
@ -90,6 +93,10 @@ BEGIN_INTERFACE_MAP()
virtual bool IsDialog() override; virtual bool IsDialog() override;
virtual void OnInitialiseNSWindow() override; virtual void OnInitialiseNSWindow() override;
virtual void BringToFront () override;
bool CanBecomeKeyWindow ();
protected: protected:
virtual NSWindowStyleMask GetStyle() override; virtual NSWindowStyleMask GetStyle() override;

81
native/Avalonia.Native/src/OSX/WindowImpl.mm

@ -10,6 +10,7 @@
#include "WindowProtocol.h" #include "WindowProtocol.h"
WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBaseImpl(events, gl) { WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBaseImpl(events, gl) {
_children = std::list<WindowImpl*>();
_isClientAreaExtended = false; _isClientAreaExtended = false;
_extendClientHints = AvnDefaultChrome; _extendClientHints = AvnDefaultChrome;
_fullScreenActive = false; _fullScreenActive = false;
@ -20,6 +21,7 @@ WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBase
_lastWindowState = Normal; _lastWindowState = Normal;
_actualWindowState = Normal; _actualWindowState = Normal;
_lastTitle = @""; _lastTitle = @"";
_parent = nullptr;
WindowEvents = events; WindowEvents = events;
} }
@ -61,6 +63,11 @@ void WindowImpl::OnInitialiseNSWindow(){
[GetWindowProtocol() setIsExtended:true]; [GetWindowProtocol() setIsExtended:true];
SetExtendClientArea(true); SetExtendClientArea(true);
} }
if(_parent != nullptr)
{
SetParent(_parent);
}
} }
HRESULT WindowImpl::Show(bool activate, bool isDialog) { HRESULT WindowImpl::Show(bool activate, bool isDialog) {
@ -90,26 +97,66 @@ HRESULT WindowImpl::SetParent(IAvnWindow *parent) {
START_COM_CALL; START_COM_CALL;
@autoreleasepool { @autoreleasepool {
if (parent == nullptr) if(_parent != nullptr)
return E_POINTER; {
_parent->_children.remove(this);
auto parent = _parent;
dispatch_async(dispatch_get_main_queue(), ^{
parent->BringToFront();
});
}
auto cparent = dynamic_cast<WindowImpl *>(parent); auto cparent = dynamic_cast<WindowImpl *>(parent);
if (cparent == nullptr)
return E_INVALIDARG; _parent = cparent;
// If one tries to show a child window with a minimized parent window, then the parent window will be if(_parent != nullptr && Window != nullptr){
// restored but macOS isn't kind enough to *tell* us that, so the window will be left in a non-interactive // If one tries to show a child window with a minimized parent window, then the parent window will be
// state. Detect this and explicitly restore the parent window ourselves to avoid this situation. // restored but macOS isn't kind enough to *tell* us that, so the window will be left in a non-interactive
if (cparent->WindowState() == Minimized) // state. Detect this and explicitly restore the parent window ourselves to avoid this situation.
cparent->SetWindowState(Normal); if (cparent->WindowState() == Minimized)
cparent->SetWindowState(Normal);
[Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary];
cparent->_children.push_back(this);
UpdateStyle();
}
[Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary]; return S_OK;
[cparent->Window addChildWindow:Window ordered:NSWindowAbove]; }
}
UpdateStyle(); void WindowImpl::BringToFront()
{
if(IsDialog())
{
Activate();
}
else
{
[Window orderFront:nullptr];
}
for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
{
(*iterator)->BringToFront();
}
}
return S_OK; bool WindowImpl::CanBecomeKeyWindow()
{
for(auto iterator = _children.begin(); iterator != _children.end(); iterator++)
{
if((*iterator)->IsDialog())
{
return false;
}
} }
return true;
} }
void WindowImpl::StartStateTransition() { void WindowImpl::StartStateTransition() {
@ -523,7 +570,7 @@ bool WindowImpl::IsDialog() {
} }
NSWindowStyleMask WindowImpl::GetStyle() { NSWindowStyleMask WindowImpl::GetStyle() {
unsigned long s = this->_isDialog ? NSWindowStyleMaskDocModalWindow : NSWindowStyleMaskBorderless; unsigned long s = NSWindowStyleMaskBorderless;
switch (_decorations) { switch (_decorations) {
case SystemDecorationsNone: case SystemDecorationsNone:
@ -535,7 +582,7 @@ NSWindowStyleMask WindowImpl::GetStyle() {
break; break;
case SystemDecorationsFull: case SystemDecorationsFull:
s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskBorderless; s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable;
if (_canResize) { if (_canResize) {
s = s | NSWindowStyleMaskResizable; s = s | NSWindowStyleMaskResizable;
@ -543,7 +590,7 @@ NSWindowStyleMask WindowImpl::GetStyle() {
break; break;
} }
if ([Window parentWindow] == nullptr) { if (!IsDialog()) {
s |= NSWindowStyleMaskMiniaturizable; s |= NSWindowStyleMaskMiniaturizable;
} }

1
native/Avalonia.Native/src/OSX/WindowProtocol.h

@ -11,7 +11,6 @@
@protocol AvnWindowProtocol @protocol AvnWindowProtocol
-(void) pollModalSession: (NSModalSession _Nonnull) session; -(void) pollModalSession: (NSModalSession _Nonnull) session;
-(void) restoreParentWindow;
-(bool) shouldTryToHandleEvents; -(bool) shouldTryToHandleEvents;
-(void) setEnabled: (bool) enable; -(void) setEnabled: (bool) enable;
-(void) showAppMenuOnly; -(void) showAppMenuOnly;

Loading…
Cancel
Save