diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props
index 08a9aa3ceb..4f69f39e02 100644
--- a/build/SkiaSharp.props
+++ b/build/SkiaSharp.props
@@ -1,6 +1,6 @@
-
-
+
+
diff --git a/native/Avalonia.Native/inc/avalonia-native.h b/native/Avalonia.Native/inc/avalonia-native.h
index 3475eff654..38d99db5c9 100644
--- a/native/Avalonia.Native/inc/avalonia-native.h
+++ b/native/Avalonia.Native/inc/avalonia-native.h
@@ -1,5 +1,6 @@
#include "com.h"
#include "key.h"
+#include "stddef.h"
#define AVNCOM(name, id) COMINTERFACE(name, 2e2cda0a, 9ae5, 4f1b, 8e, 20, 08, 1a, 04, 27, 9f, id)
@@ -19,8 +20,9 @@ struct IAvnGlContext;
struct IAvnGlDisplay;
struct IAvnGlSurfaceRenderTarget;
struct IAvnGlSurfaceRenderingSession;
-struct IAvnAppMenu;
-struct IAvnAppMenuItem;
+struct IAvnMenu;
+struct IAvnMenuItem;
+struct IAvnMenuEvents;
enum SystemDecorations {
SystemDecorationsNone = 0,
@@ -133,6 +135,7 @@ enum AvnWindowState
Normal,
Minimized,
Maximized,
+ FullScreen,
};
enum AvnStandardCursorType
@@ -175,6 +178,13 @@ enum AvnWindowEdge
WindowEdgeSouthEast
};
+enum AvnMenuItemToggleType
+{
+ None,
+ CheckMark,
+ Radio
+};
+
AVNCOM(IAvaloniaNativeFactory, 01) : IUnknown
{
public:
@@ -188,11 +198,10 @@ public:
virtual HRESULT CreateClipboard(IAvnClipboard** ppv) = 0;
virtual HRESULT CreateCursorFactory(IAvnCursorFactory** ppv) = 0;
virtual HRESULT ObtainGlDisplay(IAvnGlDisplay** ppv) = 0;
- virtual HRESULT ObtainAppMenu(IAvnAppMenu** retOut) = 0;
- virtual HRESULT SetAppMenu(IAvnAppMenu* menu) = 0;
- virtual HRESULT CreateMenu (IAvnAppMenu** ppv) = 0;
- virtual HRESULT CreateMenuItem (IAvnAppMenuItem** ppv) = 0;
- virtual HRESULT CreateMenuItemSeperator (IAvnAppMenuItem** ppv) = 0;
+ virtual HRESULT SetAppMenu(IAvnMenu* menu) = 0;
+ virtual HRESULT CreateMenu (IAvnMenuEvents* cb, IAvnMenu** ppv) = 0;
+ virtual HRESULT CreateMenuItem (IAvnMenuItem** ppv) = 0;
+ virtual HRESULT CreateMenuItemSeperator (IAvnMenuItem** ppv) = 0;
};
AVNCOM(IAvnString, 17) : IUnknown
@@ -222,8 +231,7 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown
virtual HRESULT SetTopMost (bool value) = 0;
virtual HRESULT SetCursor(IAvnCursor* cursor) = 0;
virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget** ret) = 0;
- virtual HRESULT SetMainMenu(IAvnAppMenu* menu) = 0;
- virtual HRESULT ObtainMainMenu(IAvnAppMenu** retOut) = 0;
+ virtual HRESULT SetMainMenu(IAvnMenu* menu) = 0;
virtual HRESULT ObtainNSWindowHandle(void** retOut) = 0;
virtual HRESULT ObtainNSWindowHandleRetained(void** retOut) = 0;
virtual HRESULT ObtainNSViewHandle(void** retOut) = 0;
@@ -239,7 +247,7 @@ AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase
{
virtual HRESULT ShowDialog (IAvnWindow* parent) = 0;
virtual HRESULT SetCanResize(bool value) = 0;
- virtual HRESULT SetHasDecorations(SystemDecorations value) = 0;
+ virtual HRESULT SetDecorations(SystemDecorations value) = 0;
virtual HRESULT SetTitle (void* utf8Title) = 0;
virtual HRESULT SetTitleBarColor (AvnColor color) = 0;
virtual HRESULT SetWindowState(AvnWindowState state) = 0;
@@ -388,10 +396,10 @@ AVNCOM(IAvnGlSurfaceRenderingSession, 16) : IUnknown
virtual HRESULT GetScaling(double* ret) = 0;
};
-AVNCOM(IAvnAppMenu, 17) : IUnknown
+AVNCOM(IAvnMenu, 17) : IUnknown
{
- virtual HRESULT AddItem (IAvnAppMenuItem* item) = 0;
- virtual HRESULT RemoveItem (IAvnAppMenuItem* item) = 0;
+ virtual HRESULT InsertItem (int index, IAvnMenuItem* item) = 0;
+ virtual HRESULT RemoveItem (IAvnMenuItem* item) = 0;
virtual HRESULT SetTitle (void* utf8String) = 0;
virtual HRESULT Clear () = 0;
};
@@ -401,12 +409,23 @@ AVNCOM(IAvnPredicateCallback, 18) : IUnknown
virtual bool Evaluate() = 0;
};
-AVNCOM(IAvnAppMenuItem, 19) : IUnknown
+AVNCOM(IAvnMenuItem, 19) : IUnknown
{
- virtual HRESULT SetSubMenu (IAvnAppMenu* menu) = 0;
+ virtual HRESULT SetSubMenu (IAvnMenu* menu) = 0;
virtual HRESULT SetTitle (void* utf8String) = 0;
virtual HRESULT SetGesture (void* utf8String, AvnInputModifiers modifiers) = 0;
virtual HRESULT SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) = 0;
+ virtual HRESULT SetIsChecked (bool isChecked) = 0;
+ virtual HRESULT SetToggleType (AvnMenuItemToggleType toggleType) = 0;
+ virtual HRESULT SetIcon (void* data, size_t length) = 0;
+};
+
+AVNCOM(IAvnMenuEvents, 1A) : IUnknown
+{
+ /**
+ * NeedsUpdate
+ */
+ virtual void NeedsUpdate () = 0;
};
extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative();
diff --git a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/xcshareddata/xcschemes/Avalonia.Native.OSX.xcscheme b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/xcshareddata/xcschemes/Avalonia.Native.OSX.xcscheme
index 1a665d3ea5..5d20a135b9 100644
--- a/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/xcshareddata/xcschemes/Avalonia.Native.OSX.xcscheme
+++ b/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/xcshareddata/xcschemes/Avalonia.Native.OSX.xcscheme
@@ -29,8 +29,6 @@
shouldUseLaunchSchemeArgsEnv = "YES">
-
-
-
-
@end
-extern NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivationPolicyRegular;
+NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivationPolicyRegular;
+
@implementation AvnAppDelegate
- (void)applicationWillFinishLaunching:(NSNotification *)notification
{
@@ -14,6 +15,10 @@ extern NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationA
}
[[NSApplication sharedApplication] setActivationPolicy: AvnDesiredActivationPolicy];
+
+ [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"NSFullScreenMenuItemEverywhere"];
+
+ [[NSApplication sharedApplication] setHelpMenu: [[NSMenu new] initWithTitle:@""]];
}
}
diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h
index 85403abfe7..7a433bfd9f 100644
--- a/native/Avalonia.Native/src/OSX/common.h
+++ b/native/Avalonia.Native/src/OSX/common.h
@@ -15,11 +15,11 @@ extern IAvnScreens* CreateScreens();
extern IAvnClipboard* CreateClipboard();
extern IAvnCursorFactory* CreateCursorFactory();
extern IAvnGlDisplay* GetGlDisplay();
-extern IAvnAppMenu* CreateAppMenu();
-extern IAvnAppMenuItem* CreateAppMenuItem();
-extern IAvnAppMenuItem* CreateAppMenuItemSeperator();
-extern void SetAppMenu (NSString* appName, IAvnAppMenu* appMenu);
-extern IAvnAppMenu* GetAppMenu ();
+extern IAvnMenu* CreateAppMenu(IAvnMenuEvents* events);
+extern IAvnMenuItem* CreateAppMenuItem();
+extern IAvnMenuItem* CreateAppMenuItemSeperator();
+extern void SetAppMenu (NSString* appName, IAvnMenu* appMenu);
+extern IAvnMenu* GetAppMenu ();
extern NSMenuItem* GetAppMenuItem ();
extern void InitializeAvnApp();
diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm
index a2134de6c1..a63353bc0a 100644
--- a/native/Avalonia.Native/src/OSX/main.mm
+++ b/native/Avalonia.Native/src/OSX/main.mm
@@ -92,12 +92,11 @@ void SetProcessName(NSString* appTitle) {
PrivateLSASN asn = ls_get_current_application_asn_func();
// Constant used by WebKit; what exactly it means is unknown.
const int magic_session_constant = -2;
- OSErr err =
+
ls_set_application_information_item_func(magic_session_constant, asn,
ls_display_name_key,
process_name,
NULL /* optional out param */);
- //LOG_IF(ERROR, err) << "Call to set process name failed, err " << err;
}
class MacOptions : public ComSingleObject
@@ -228,41 +227,29 @@ public:
return S_OK;
}
- virtual HRESULT CreateMenu (IAvnAppMenu** ppv) override
+ virtual HRESULT CreateMenu (IAvnMenuEvents* cb, IAvnMenu** ppv) override
{
- *ppv = ::CreateAppMenu();
+ *ppv = ::CreateAppMenu(cb);
return S_OK;
}
- virtual HRESULT CreateMenuItem (IAvnAppMenuItem** ppv) override
+ virtual HRESULT CreateMenuItem (IAvnMenuItem** ppv) override
{
*ppv = ::CreateAppMenuItem();
return S_OK;
}
- virtual HRESULT CreateMenuItemSeperator (IAvnAppMenuItem** ppv) override
+ virtual HRESULT CreateMenuItemSeperator (IAvnMenuItem** ppv) override
{
*ppv = ::CreateAppMenuItemSeperator();
return S_OK;
}
- virtual HRESULT SetAppMenu (IAvnAppMenu* appMenu) override
+ virtual HRESULT SetAppMenu (IAvnMenu* appMenu) override
{
::SetAppMenu(s_appTitle, appMenu);
return S_OK;
}
-
- virtual HRESULT ObtainAppMenu(IAvnAppMenu** retOut) override
- {
- if(retOut == nullptr)
- {
- return E_POINTER;
- }
-
- *retOut = ::GetAppMenu();
-
- return S_OK;
- }
};
extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative()
diff --git a/native/Avalonia.Native/src/OSX/menu.h b/native/Avalonia.Native/src/OSX/menu.h
index befbe6a7e0..bfbc6801f8 100644
--- a/native/Avalonia.Native/src/OSX/menu.h
+++ b/native/Avalonia.Native/src/OSX/menu.h
@@ -14,8 +14,10 @@
class AvnAppMenuItem;
class AvnAppMenu;
-@interface AvnMenu : NSMenu // for some reason it doesnt detect nsmenu here but compiler doesnt complain
-- (void)setMenu:(NSMenu*) menu;
+@interface AvnMenu : NSMenu
+- (id) initWithDelegate: (NSObject*) del;
+- (void) setHasGlobalMenuItem: (bool) value;
+- (bool) hasGlobalMenuItem;
@end
@interface AvnMenuItem : NSMenuItem
@@ -23,13 +25,14 @@ class AvnAppMenu;
- (void)didSelectItem:(id)sender;
@end
-class AvnAppMenuItem : public ComSingleObject
+class AvnAppMenuItem : public ComSingleObject
{
private:
NSMenuItem* _native; // here we hold a pointer to an AvnMenuItem
IAvnActionCallback* _callback;
IAvnPredicateCallback* _predicate;
bool _isSeperator;
+ bool _isCheckable;
public:
FORWARD_IUNKNOWN()
@@ -38,7 +41,7 @@ public:
NSMenuItem* GetNative();
- virtual HRESULT SetSubMenu (IAvnAppMenu* menu) override;
+ virtual HRESULT SetSubMenu (IAvnMenu* menu) override;
virtual HRESULT SetTitle (void* utf8String) override;
@@ -46,29 +49,36 @@ public:
virtual HRESULT SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) override;
+ virtual HRESULT SetIsChecked (bool isChecked) override;
+
+ virtual HRESULT SetToggleType (AvnMenuItemToggleType toggleType) override;
+
+ virtual HRESULT SetIcon (void* data, size_t length) override;
+
bool EvaluateItemEnabled();
void RaiseOnClicked();
};
-class AvnAppMenu : public ComSingleObject
+class AvnAppMenu : public ComSingleObject
{
private:
AvnMenu* _native;
+ ComPtr _baseEvents;
public:
FORWARD_IUNKNOWN()
- AvnAppMenu();
-
- AvnAppMenu(AvnMenu* native);
-
+ AvnAppMenu(IAvnMenuEvents* events);
+
AvnMenu* GetNative();
- virtual HRESULT AddItem (IAvnAppMenuItem* item) override;
+ void RaiseNeedsUpdate ();
+
+ virtual HRESULT InsertItem (int index, IAvnMenuItem* item) override;
- virtual HRESULT RemoveItem (IAvnAppMenuItem* item) override;
+ virtual HRESULT RemoveItem (IAvnMenuItem* item) override;
virtual HRESULT SetTitle (void* utf8String) override;
@@ -76,5 +86,9 @@ public:
};
+@interface AvnMenuDelegate : NSObject
+- (id) initWithParent: (AvnAppMenu*) parent;
+@end
+
#endif
diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm
index 1d2f075ccb..dc1245cd23 100644
--- a/native/Avalonia.Native/src/OSX/menu.mm
+++ b/native/Avalonia.Native/src/OSX/menu.mm
@@ -4,6 +4,30 @@
#include "window.h"
@implementation AvnMenu
+{
+ bool _isReparented;
+ NSObject* _wtf;
+}
+
+- (id) initWithDelegate: (NSObject*)del
+{
+ self = [super init];
+ self.delegate = del;
+ _wtf = del;
+ _isReparented = false;
+ return self;
+}
+
+- (bool)hasGlobalMenuItem
+{
+ return _isReparented;
+}
+
+- (void)setHasGlobalMenuItem:(bool)value
+{
+ _isReparented = value;
+}
+
@end
@implementation AvnMenuItem
@@ -46,6 +70,7 @@
AvnAppMenuItem::AvnAppMenuItem(bool isSeperator)
{
+ _isCheckable = false;
_isSeperator = isSeperator;
if(isSeperator)
@@ -65,49 +90,134 @@ NSMenuItem* AvnAppMenuItem::GetNative()
return _native;
}
-HRESULT AvnAppMenuItem::SetSubMenu (IAvnAppMenu* menu)
+HRESULT AvnAppMenuItem::SetSubMenu (IAvnMenu* menu)
{
- auto nsMenu = dynamic_cast(menu)->GetNative();
-
- [_native setSubmenu: nsMenu];
-
- return S_OK;
+ @autoreleasepool
+ {
+ if(menu != nullptr)
+ {
+ auto nsMenu = dynamic_cast(menu)->GetNative();
+
+ [_native setSubmenu: nsMenu];
+ }
+ else
+ {
+ [_native setSubmenu: nullptr];
+ }
+
+ return S_OK;
+ }
}
HRESULT AvnAppMenuItem::SetTitle (void* utf8String)
{
- if (utf8String != nullptr)
+ @autoreleasepool
{
- [_native setTitle:[NSString stringWithUTF8String:(const char*)utf8String]];
+ if (utf8String != nullptr)
+ {
+ [_native setTitle:[NSString stringWithUTF8String:(const char*)utf8String]];
+ }
+
+ return S_OK;
}
-
- return S_OK;
}
HRESULT AvnAppMenuItem::SetGesture (void* key, AvnInputModifiers modifiers)
{
- NSEventModifierFlags flags = 0;
-
- if (modifiers & Control)
- flags |= NSEventModifierFlagControl;
- if (modifiers & Shift)
- flags |= NSEventModifierFlagShift;
- if (modifiers & Alt)
- flags |= NSEventModifierFlagOption;
- if (modifiers & Windows)
- flags |= NSEventModifierFlagCommand;
-
- [_native setKeyEquivalent:[NSString stringWithUTF8String:(const char*)key]];
- [_native setKeyEquivalentModifierMask:flags];
-
- return S_OK;
+ @autoreleasepool
+ {
+ NSEventModifierFlags flags = 0;
+
+ if (modifiers & Control)
+ flags |= NSEventModifierFlagControl;
+ if (modifiers & Shift)
+ flags |= NSEventModifierFlagShift;
+ if (modifiers & Alt)
+ flags |= NSEventModifierFlagOption;
+ if (modifiers & Windows)
+ flags |= NSEventModifierFlagCommand;
+
+ [_native setKeyEquivalent:[NSString stringWithUTF8String:(const char*)key]];
+ [_native setKeyEquivalentModifierMask:flags];
+
+ return S_OK;
+ }
}
HRESULT AvnAppMenuItem::SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback)
{
- _predicate = predicate;
- _callback = callback;
- return S_OK;
+ @autoreleasepool
+ {
+ _predicate = predicate;
+ _callback = callback;
+ return S_OK;
+ }
+}
+
+HRESULT AvnAppMenuItem::SetIsChecked (bool isChecked)
+{
+ @autoreleasepool
+ {
+ [_native setState:(isChecked && _isCheckable ? NSOnState : NSOffState)];
+ return S_OK;
+ }
+}
+
+HRESULT AvnAppMenuItem::SetToggleType(AvnMenuItemToggleType toggleType)
+{
+ @autoreleasepool
+ {
+ switch(toggleType)
+ {
+ case AvnMenuItemToggleType::None:
+ [_native setOnStateImage: [NSImage imageNamed:@"NSMenuCheckmark"]];
+
+ _isCheckable = false;
+ break;
+
+ case AvnMenuItemToggleType::CheckMark:
+ [_native setOnStateImage: [NSImage imageNamed:@"NSMenuCheckmark"]];
+
+ _isCheckable = true;
+ break;
+
+ case AvnMenuItemToggleType::Radio:
+ [_native setOnStateImage: [NSImage imageNamed:@"NSMenuItemBullet"]];
+
+ _isCheckable = true;
+ break;
+ }
+
+ return S_OK;
+ }
+}
+
+HRESULT AvnAppMenuItem::SetIcon(void *data, size_t length)
+{
+ @autoreleasepool
+ {
+ if(data != nullptr)
+ {
+ NSData *imageData = [NSData dataWithBytes:data length:length];
+ NSImage *image = [[NSImage alloc] initWithData:imageData];
+
+ NSSize originalSize = [image size];
+
+ NSSize size;
+ size.height = [[NSFont menuFontOfSize:0] pointSize] * 1.333333;
+
+ auto scaleFactor = size.height / originalSize.height;
+ size.width = originalSize.width * scaleFactor;
+
+ [image setSize: size];
+ [_native setImage:image];
+ }
+ else
+ {
+ [_native setImage:nullptr];
+ }
+ return S_OK;
+ }
}
bool AvnAppMenuItem::EvaluateItemEnabled()
@@ -130,71 +240,123 @@ void AvnAppMenuItem::RaiseOnClicked()
}
}
-AvnAppMenu::AvnAppMenu()
+AvnAppMenu::AvnAppMenu(IAvnMenuEvents* events)
{
- _native = [AvnMenu new];
+ _baseEvents = events;
+ id del = [[AvnMenuDelegate alloc] initWithParent: this];
+ _native = [[AvnMenu alloc] initWithDelegate: del];
}
-AvnAppMenu::AvnAppMenu(AvnMenu* native)
-{
- _native = native;
-}
AvnMenu* AvnAppMenu::GetNative()
{
return _native;
}
-HRESULT AvnAppMenu::AddItem (IAvnAppMenuItem* item)
+void AvnAppMenu::RaiseNeedsUpdate()
{
- auto avnMenuItem = dynamic_cast(item);
-
- if(avnMenuItem != nullptr)
+ if(_baseEvents != nullptr)
{
- [_native addItem: avnMenuItem->GetNative()];
+ _baseEvents->NeedsUpdate();
}
-
- return S_OK;
}
-HRESULT AvnAppMenu::RemoveItem (IAvnAppMenuItem* item)
+HRESULT AvnAppMenu::InsertItem(int index, IAvnMenuItem *item)
{
- auto avnMenuItem = dynamic_cast(item);
-
- if(avnMenuItem != nullptr)
+ @autoreleasepool
{
- [_native removeItem:avnMenuItem->GetNative()];
+ if([_native hasGlobalMenuItem])
+ {
+ index++;
+ }
+
+ auto avnMenuItem = dynamic_cast(item);
+
+ if(avnMenuItem != nullptr)
+ {
+ [_native insertItem: avnMenuItem->GetNative() atIndex:index];
+ }
+
+ return S_OK;
+ }
+}
+
+HRESULT AvnAppMenu::RemoveItem (IAvnMenuItem* item)
+{
+ @autoreleasepool
+ {
+ auto avnMenuItem = dynamic_cast(item);
+
+ if(avnMenuItem != nullptr)
+ {
+ [_native removeItem:avnMenuItem->GetNative()];
+ }
+
+ return S_OK;
}
-
- return S_OK;
}
HRESULT AvnAppMenu::SetTitle (void* utf8String)
{
- if (utf8String != nullptr)
+ @autoreleasepool
{
- [_native setTitle:[NSString stringWithUTF8String:(const char*)utf8String]];
+ if (utf8String != nullptr)
+ {
+ [_native setTitle:[NSString stringWithUTF8String:(const char*)utf8String]];
+ }
+
+ return S_OK;
}
-
- return S_OK;
}
HRESULT AvnAppMenu::Clear()
{
- [_native removeAllItems];
- return S_OK;
+ @autoreleasepool
+ {
+ [_native removeAllItems];
+ return S_OK;
+ }
+}
+
+@implementation AvnMenuDelegate
+{
+ ComPtr _parent;
}
+- (id) initWithParent:(AvnAppMenu *)parent
+{
+ self = [super init];
+ _parent = parent;
+ return self;
+}
+- (BOOL)menu:(NSMenu *)menu updateItem:(NSMenuItem *)item atIndex:(NSInteger)index shouldCancel:(BOOL)shouldCancel
+{
+ if(shouldCancel)
+ return NO;
+ return YES;
+}
+
+- (NSInteger)numberOfItemsInMenu:(NSMenu *)menu
+{
+ return [menu numberOfItems];
+}
+
+- (void)menuNeedsUpdate:(NSMenu *)menu
+{
+ _parent->RaiseNeedsUpdate();
+}
+
+
+@end
-extern IAvnAppMenu* CreateAppMenu()
+extern IAvnMenu* CreateAppMenu(IAvnMenuEvents* cb)
{
@autoreleasepool
{
- id menuBar = [NSMenu new];
- return new AvnAppMenu(menuBar);
+ return new AvnAppMenu(cb);
}
}
-extern IAvnAppMenuItem* CreateAppMenuItem()
+extern IAvnMenuItem* CreateAppMenuItem()
{
@autoreleasepool
{
@@ -202,7 +364,7 @@ extern IAvnAppMenuItem* CreateAppMenuItem()
}
}
-extern IAvnAppMenuItem* CreateAppMenuItemSeperator()
+extern IAvnMenuItem* CreateAppMenuItemSeperator()
{
@autoreleasepool
{
@@ -210,10 +372,10 @@ extern IAvnAppMenuItem* CreateAppMenuItemSeperator()
}
}
-static IAvnAppMenu* s_appMenu = nullptr;
+static IAvnMenu* s_appMenu = nullptr;
static NSMenuItem* s_appMenuItem = nullptr;
-extern void SetAppMenu (NSString* appName, IAvnAppMenu* menu)
+extern void SetAppMenu (NSString* appName, IAvnMenu* menu)
{
s_appMenu = menu;
@@ -294,7 +456,7 @@ extern void SetAppMenu (NSString* appName, IAvnAppMenu* menu)
}
}
-extern IAvnAppMenu* GetAppMenu ()
+extern IAvnMenu* GetAppMenu ()
{
return s_appMenu;
}
diff --git a/native/Avalonia.Native/src/OSX/platformthreading.mm b/native/Avalonia.Native/src/OSX/platformthreading.mm
index 2d72226faf..f93436d157 100644
--- a/native/Avalonia.Native/src/OSX/platformthreading.mm
+++ b/native/Avalonia.Native/src/OSX/platformthreading.mm
@@ -54,9 +54,11 @@ private:
{
public:
FORWARD_IUNKNOWN()
+
bool Running = false;
bool Cancelled = false;
- virtual void Cancel()
+
+ virtual void Cancel() override
{
Cancelled = true;
if(Running)
diff --git a/native/Avalonia.Native/src/OSX/window.h b/native/Avalonia.Native/src/OSX/window.h
index 5c85a2f423..ec8fe9e6ee 100644
--- a/native/Avalonia.Native/src/OSX/window.h
+++ b/native/Avalonia.Native/src/OSX/window.h
@@ -19,7 +19,11 @@ class WindowBaseImpl;
-(void) pollModalSession: (NSModalSession _Nonnull) session;
-(void) restoreParentWindow;
-(bool) shouldTryToHandleEvents;
--(void) applyMenu:(NSMenu *)menu;
+-(bool) isModal;
+-(void) setModal: (bool) isModal;
+-(void) showAppMenuOnly;
+-(void) showWindowMenuWithAppMenu;
+-(void) applyMenu:(NSMenu* _Nullable)menu;
-(double) getScaling;
@end
@@ -31,6 +35,10 @@ struct INSWindowHolder
struct IWindowStateChanged
{
virtual void WindowStateChanged () = 0;
+ virtual void StartStateTransition () = 0;
+ virtual void EndStateTransition () = 0;
+ virtual SystemDecorations Decorations () = 0;
+ virtual AvnWindowState WindowState () = 0;
};
#endif /* window_h */
diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm
index 6298118c10..091219fc72 100644
--- a/native/Avalonia.Native/src/OSX/window.mm
+++ b/native/Avalonia.Native/src/OSX/window.mm
@@ -27,7 +27,7 @@ public:
NSObject* renderTarget;
AvnPoint lastPositionSet;
NSString* _lastTitle;
- IAvnAppMenu* _mainMenu;
+ IAvnMenu* _mainMenu;
bool _shown;
WindowBaseImpl(IAvnWindowBaseEvents* events, IAvnGlContext* gl)
@@ -234,7 +234,7 @@ public:
}
}
- virtual HRESULT SetMainMenu(IAvnAppMenu* menu) override
+ virtual HRESULT SetMainMenu(IAvnMenu* menu) override
{
_mainMenu = menu;
@@ -244,18 +244,11 @@ public:
[Window applyMenu:nsmenu];
- return S_OK;
- }
-
- virtual HRESULT ObtainMainMenu(IAvnAppMenu** ret) override
- {
- if(ret == nullptr)
+ if ([Window isKeyWindow])
{
- return E_POINTER;
+ [Window showWindowMenuWithAppMenu];
}
- *ret = _mainMenu;
-
return S_OK;
}
@@ -398,7 +391,7 @@ protected:
void UpdateStyle()
{
- [Window setStyleMask:GetStyle()];
+ [Window setStyleMask: GetStyle()];
}
public:
@@ -411,10 +404,13 @@ public:
class WindowImpl : public virtual WindowBaseImpl, public virtual IAvnWindow, public IWindowStateChanged
{
private:
- bool _canResize = true;
- SystemDecorations _hasDecorations = SystemDecorationsFull;
- CGRect _lastUndecoratedFrame;
+ bool _canResize;
+ bool _fullScreenActive;
+ SystemDecorations _decorations;
AvnWindowState _lastWindowState;
+ bool _inSetWindowState;
+ NSRect _preZoomSize;
+ bool _transitioningWindowState;
FORWARD_IUNKNOWN()
BEGIN_INTERFACE_MAP()
@@ -428,10 +424,30 @@ private:
ComPtr WindowEvents;
WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
{
+ _fullScreenActive = false;
+ _canResize = true;
+ _decorations = SystemDecorationsFull;
+ _transitioningWindowState = false;
+ _inSetWindowState = false;
_lastWindowState = Normal;
WindowEvents = events;
[Window setCanBecomeKeyAndMain];
[Window disableCursorRects];
+ [Window setTabbingMode:NSWindowTabbingModeDisallowed];
+ }
+
+ void HideOrShowTrafficLights ()
+ {
+ for (id subview in Window.contentView.superview.subviews) {
+ if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")]) {
+ NSView *titlebarView = [subview subviews][0];
+ for (id button in titlebarView.subviews) {
+ if ([button isKindOfClass:[NSButton class]]) {
+ [button setHidden: (_decorations != SystemDecorationsFull)];
+ }
+ }
+ }
+ }
}
virtual HRESULT Show () override
@@ -440,8 +456,13 @@ private:
{
if([Window parentWindow] != nil)
[[Window parentWindow] removeChildWindow:Window];
+
+ [Window setModal:FALSE];
+
WindowBaseImpl::Show();
+ HideOrShowTrafficLights();
+
return SetWindowState(_lastWindowState);
}
}
@@ -457,44 +478,74 @@ private:
if(cparent == nullptr)
return E_INVALIDARG;
+ [Window setModal:TRUE];
+
[cparent->Window addChildWindow:Window ordered:NSWindowAbove];
WindowBaseImpl::Show();
+ HideOrShowTrafficLights();
+
return S_OK;
}
}
+ void StartStateTransition () override
+ {
+ _transitioningWindowState = true;
+ }
+
+ void EndStateTransition () override
+ {
+ _transitioningWindowState = false;
+ }
+
+ SystemDecorations Decorations () override
+ {
+ return _decorations;
+ }
+
+ AvnWindowState WindowState () override
+ {
+ return _lastWindowState;
+ }
+
void WindowStateChanged () override
{
- AvnWindowState state;
- GetWindowState(&state);
- WindowEvents->WindowStateChanged(state);
+ if(!_inSetWindowState && !_transitioningWindowState)
+ {
+ AvnWindowState state;
+ GetWindowState(&state);
+
+ if(_lastWindowState != state)
+ {
+ _lastWindowState = state;
+ WindowEvents->WindowStateChanged(state);
+ }
+ }
}
bool UndecoratedIsMaximized ()
{
- return CGRectEqualToRect([Window frame], [Window screen].visibleFrame);
+ auto windowSize = [Window frame];
+ auto available = [Window screen].visibleFrame;
+ return CGRectEqualToRect(windowSize, available);
}
bool IsZoomed ()
{
- return _hasDecorations != SystemDecorationsNone ? [Window isZoomed] : UndecoratedIsMaximized();
+ return _decorations == SystemDecorationsFull ? [Window isZoomed] : UndecoratedIsMaximized();
}
void DoZoom()
{
- switch (_hasDecorations)
+ switch (_decorations)
{
case SystemDecorationsNone:
- if (!UndecoratedIsMaximized())
- {
- _lastUndecoratedFrame = [Window frame];
- }
-
- [Window zoom:Window];
+ case SystemDecorationsBorderOnly:
+ [Window setFrame:[Window screen].visibleFrame display:true];
break;
- case SystemDecorationsBorderOnly:
+
case SystemDecorationsFull:
[Window performZoom:Window];
break;
@@ -511,25 +562,52 @@ private:
}
}
- virtual HRESULT SetHasDecorations(SystemDecorations value) override
+ virtual HRESULT SetDecorations(SystemDecorations value) override
{
@autoreleasepool
{
- _hasDecorations = value;
+ auto currentWindowState = _lastWindowState;
+ _decorations = value;
+
+ if(_fullScreenActive)
+ {
+ return S_OK;
+ }
+
+ auto currentFrame = [Window frame];
+
UpdateStyle();
+
+ HideOrShowTrafficLights();
- switch (_hasDecorations)
+ switch (_decorations)
{
case SystemDecorationsNone:
[Window setHasShadow:NO];
[Window setTitleVisibility:NSWindowTitleHidden];
[Window setTitlebarAppearsTransparent:YES];
+
+ if(currentWindowState == Maximized)
+ {
+ if(!UndecoratedIsMaximized())
+ {
+ DoZoom();
+ }
+ }
break;
case SystemDecorationsBorderOnly:
[Window setHasShadow:YES];
[Window setTitleVisibility:NSWindowTitleHidden];
[Window setTitlebarAppearsTransparent:YES];
+
+ if(currentWindowState == Maximized)
+ {
+ if(!UndecoratedIsMaximized())
+ {
+ DoZoom();
+ }
+ }
break;
case SystemDecorationsFull:
@@ -537,6 +615,13 @@ private:
[Window setTitleVisibility:NSWindowTitleVisible];
[Window setTitlebarAppearsTransparent:NO];
[Window setTitle:_lastTitle];
+
+ if(currentWindowState == Maximized)
+ {
+ auto newFrame = [Window contentRectForFrameRect:[Window frame]].size;
+
+ [View setFrameSize:newFrame];
+ }
break;
}
@@ -593,13 +678,19 @@ private:
return E_POINTER;
}
+ if(([Window styleMask] & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen)
+ {
+ *ret = FullScreen;
+ return S_OK;
+ }
+
if([Window isMiniaturized])
{
*ret = Minimized;
return S_OK;
}
- if([Window isZoomed])
+ if(IsZoomed())
{
*ret = Maximized;
return S_OK;
@@ -611,16 +702,57 @@ private:
}
}
+ void EnterFullScreenMode ()
+ {
+ _fullScreenActive = true;
+
+ [Window setHasShadow:YES];
+ [Window setTitleVisibility:NSWindowTitleVisible];
+ [Window setTitlebarAppearsTransparent:NO];
+ [Window setTitle:_lastTitle];
+
+ [Window setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskResizable];
+
+ [Window toggleFullScreen:nullptr];
+ }
+
+ void ExitFullScreenMode ()
+ {
+ [Window toggleFullScreen:nullptr];
+
+ _fullScreenActive = false;
+
+ SetDecorations(_decorations);
+ }
+
virtual HRESULT SetWindowState (AvnWindowState state) override
{
@autoreleasepool
{
+ if(_lastWindowState == state)
+ {
+ return S_OK;
+ }
+
+ _inSetWindowState = true;
+
+ auto currentState = _lastWindowState;
_lastWindowState = state;
+ if(currentState == Normal)
+ {
+ _preZoomSize = [Window frame];
+ }
+
if(_shown)
{
switch (state) {
case Maximized:
+ if(currentState == FullScreen)
+ {
+ ExitFullScreenMode();
+ }
+
lastPositionSet.X = 0;
lastPositionSet.Y = 0;
@@ -636,40 +768,66 @@ private:
break;
case Minimized:
- [Window miniaturize:Window];
+ if(currentState == FullScreen)
+ {
+ ExitFullScreenMode();
+ }
+ else
+ {
+ [Window miniaturize:Window];
+ }
break;
- default:
+ case FullScreen:
if([Window isMiniaturized])
{
[Window deminiaturize:Window];
}
+ EnterFullScreenMode();
+ break;
+
+ case Normal:
+ if([Window isMiniaturized])
+ {
+ [Window deminiaturize:Window];
+ }
+
+ if(currentState == FullScreen)
+ {
+ ExitFullScreenMode();
+ }
+
if(IsZoomed())
{
- DoZoom();
+ if(_decorations == SystemDecorationsFull)
+ {
+ DoZoom();
+ }
+ else
+ {
+ [Window setFrame:_preZoomSize display:true];
+ auto newFrame = [Window contentRectForFrameRect:[Window frame]].size;
+
+ [View setFrameSize:newFrame];
+ }
+
}
break;
}
}
+ _inSetWindowState = false;
+
return S_OK;
}
}
virtual void OnResized () override
{
- if(_shown)
+ if(_shown && !_inSetWindowState && !_transitioningWindowState)
{
- auto windowState = [Window isMiniaturized] ? Minimized
- : (IsZoomed() ? Maximized : Normal);
-
- if (windowState != _lastWindowState)
- {
- _lastWindowState = windowState;
-
- WindowEvents->WindowStateChanged(windowState);
- }
+ WindowStateChanged();
}
}
@@ -678,22 +836,23 @@ protected:
{
unsigned long s = NSWindowStyleMaskBorderless;
- switch (_hasDecorations)
+ switch (_decorations)
{
case SystemDecorationsNone:
+ s = s | NSWindowStyleMaskFullSizeContentView | NSWindowStyleMaskMiniaturizable;
break;
case SystemDecorationsBorderOnly:
- s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView;
+ s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView | NSWindowStyleMaskMiniaturizable;
break;
case SystemDecorationsFull:
s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskBorderless;
+
if(_canResize)
{
s = s | NSWindowStyleMaskResizable;
}
-
break;
}
@@ -1151,8 +1310,8 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
ComPtr _parent;
bool _canBecomeKeyAndMain;
bool _closed;
- NSMenu* _menu;
- bool _isAppMenuApplied;
+ bool _isModal;
+ AvnMenu* _menu;
double _lastScaling;
}
@@ -1172,6 +1331,20 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
}
}
+- (void)performClose:(id)sender
+{
+ if([[self delegate] respondsToSelector:@selector(windowShouldClose:)])
+ {
+ if(![[self delegate] windowShouldClose:self]) return;
+ }
+ else if([self respondsToSelector:@selector(windowShouldClose:)])
+ {
+ if(![self windowShouldClose:self]) return;
+ }
+
+ [self close];
+}
+
- (void)pollModalSession:(nonnull NSModalSession)session
{
auto response = [NSApp runModalSession:session];
@@ -1189,32 +1362,64 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
}
}
--(void) applyMenu:(NSMenu *)menu
+-(void) showWindowMenuWithAppMenu
{
- if(menu == nullptr)
+ if(_menu != nullptr)
{
- menu = [NSMenu new];
+ auto appMenuItem = ::GetAppMenuItem();
+
+ if(appMenuItem != nullptr)
+ {
+ auto appMenu = [appMenuItem menu];
+
+ [appMenu removeItem:appMenuItem];
+
+ [_menu insertItem:appMenuItem atIndex:0];
+
+ [_menu setHasGlobalMenuItem:true];
+ }
+
+ [NSApp setMenu:_menu];
}
+}
+
+-(void) showAppMenuOnly
+{
+ auto appMenuItem = ::GetAppMenuItem();
- _menu = menu;
-
- if ([self isKeyWindow])
+ if(appMenuItem != nullptr)
{
- auto appMenu = ::GetAppMenuItem();
+ auto appMenu = ::GetAppMenu();
+
+ auto nativeAppMenu = dynamic_cast(appMenu);
- if(appMenu != nullptr)
+ [[appMenuItem menu] removeItem:appMenuItem];
+
+ if(_menu != nullptr)
{
- [[appMenu menu] removeItem:appMenu];
-
- [_menu insertItem:appMenu atIndex:0];
-
- _isAppMenuApplied = true;
+ [_menu setHasGlobalMenuItem:false];
}
- [NSApp setMenu:menu];
+ [nativeAppMenu->GetNative() addItem:appMenuItem];
+
+ [NSApp setMenu:nativeAppMenu->GetNative()];
+ }
+ else
+ {
+ [NSApp setMenu:nullptr];
}
}
+-(void) applyMenu:(AvnMenu *)menu
+{
+ if(menu == nullptr)
+ {
+ menu = [AvnMenu new];
+ }
+
+ _menu = menu;
+}
+
-(void) setCanBecomeKeyAndMain
{
_canBecomeKeyAndMain = true;
@@ -1298,11 +1503,25 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
auto ch = objc_cast(uch);
if(ch == nil)
continue;
+
+ if(![ch isModal])
+ continue;
+
return FALSE;
}
return TRUE;
}
+-(bool) isModal
+{
+ return _isModal;
+}
+
+-(void) setModal: (bool) isModal
+{
+ _isModal = isModal;
+}
+
-(void)makeKeyWindow
{
if([self activateAppropriateChild: true])
@@ -1315,23 +1534,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
{
if([self activateAppropriateChild: true])
{
- if(_menu == nullptr)
- {
- _menu = [NSMenu new];
- }
-
- auto appMenu = ::GetAppMenuItem();
-
- if(appMenu != nullptr)
- {
- [[appMenu menu] removeItem:appMenu];
-
- [_menu insertItem:appMenu atIndex:0];
-
- _isAppMenuApplied = true;
- }
-
- [NSApp setMenu:_menu];
+ [self showWindowMenuWithAppMenu];
_parent->BaseEvents->Activated();
[super becomeKeyWindow];
@@ -1370,39 +1573,79 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)windowDidResize:(NSNotification *)notification
{
- _parent->OnResized();
+ auto parent = dynamic_cast(_parent.operator->());
+
+ if(parent != nullptr)
+ {
+ parent->WindowStateChanged();
+ }
}
-- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame
+- (void)windowWillExitFullScreen:(NSNotification *)notification
{
- return true;
+ auto parent = dynamic_cast(_parent.operator->());
+
+ if(parent != nullptr)
+ {
+ parent->StartStateTransition();
+ }
}
--(void)resignKeyWindow
+- (void)windowDidExitFullScreen:(NSNotification *)notification
{
- if(_parent)
- _parent->BaseEvents->Deactivated();
-
- auto appMenuItem = ::GetAppMenuItem();
+ auto parent = dynamic_cast(_parent.operator->());
- if(appMenuItem != nullptr)
+ if(parent != nullptr)
{
- auto appMenu = ::GetAppMenu();
+ parent->EndStateTransition();
- auto nativeAppMenu = dynamic_cast(appMenu);
-
- [[appMenuItem menu] removeItem:appMenuItem];
+ if(parent->Decorations() != SystemDecorationsFull && parent->WindowState() == Maximized)
+ {
+ NSRect screenRect = [[self screen] visibleFrame];
+ [self setFrame:screenRect display:YES];
+ }
- [nativeAppMenu->GetNative() addItem:appMenuItem];
+ if(parent->WindowState() == Minimized)
+ {
+ [self miniaturize:nullptr];
+ }
- [NSApp setMenu:nativeAppMenu->GetNative()];
+ parent->WindowStateChanged();
}
- else
+}
+
+- (void)windowWillEnterFullScreen:(NSNotification *)notification
+{
+ auto parent = dynamic_cast(_parent.operator->());
+
+ if(parent != nullptr)
{
- [NSApp setMenu:nullptr];
+ parent->StartStateTransition();
+ }
+}
+
+- (void)windowDidEnterFullScreen:(NSNotification *)notification
+{
+ auto parent = dynamic_cast(_parent.operator->());
+
+ if(parent != nullptr)
+ {
+ parent->EndStateTransition();
+ parent->WindowStateChanged();
}
+}
+
+- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame
+{
+ return true;
+}
+
+-(void)resignKeyWindow
+{
+ if(_parent)
+ _parent->BaseEvents->Deactivated();
- // remove window menu items from appmenu?
+ [self showAppMenuOnly];
[super resignKeyWindow];
}
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index f3f70719e3..e02308b5c6 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -59,8 +59,8 @@
-
-
+
+
No Decorations
Border Only
Full Decorations
@@ -69,6 +69,7 @@
Light
Dark
+
diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml
index d25de9c1f5..935db20757 100644
--- a/samples/ControlCatalog/MainWindow.xaml
+++ b/samples/ControlCatalog/MainWindow.xaml
@@ -7,16 +7,16 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ControlCatalog.ViewModels"
xmlns:v="clr-namespace:ControlCatalog.Views"
- x:Class="ControlCatalog.MainWindow">
+ x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}">
-
+
-
+
@@ -36,6 +36,24 @@
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs
index b40fdb4a17..d97325ef8d 100644
--- a/samples/ControlCatalog/MainWindow.xaml.cs
+++ b/samples/ControlCatalog/MainWindow.xaml.cs
@@ -29,6 +29,7 @@ namespace ControlCatalog
DataContext = new MainWindowViewModel(_notificationArea);
_recentMenu = ((NativeMenu.GetMenu(this).Items[0] as NativeMenuItem).Menu.Items[2] as NativeMenuItem).Menu;
+
var mainMenu = this.FindControl