diff --git a/Avalonia.sln b/Avalonia.sln
index ea30514c3e..c8e513f94c 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -99,7 +99,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\HarfBuzzSharp.props = build\HarfBuzzSharp.props
build\JetBrains.Annotations.props = build\JetBrains.Annotations.props
build\JetBrains.dotMemoryUnit.props = build\JetBrains.dotMemoryUnit.props
- build\Magick.NET-Q16-AnyCPU.props = build\Magick.NET-Q16-AnyCPU.props
build\Microsoft.CSharp.props = build\Microsoft.CSharp.props
build\Microsoft.Reactive.Testing.props = build\Microsoft.Reactive.Testing.props
build\Moq.props = build\Moq.props
@@ -118,6 +117,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Props", "Props", "{F3AC8BC1
build\System.Memory.props = build\System.Memory.props
build\UnitTests.NetFX.props = build\UnitTests.NetFX.props
build\XUnit.props = build\XUnit.props
+ build\ImageSharp.props = build\ImageSharp.props
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Targets", "Targets", "{4D6FAF79-58B4-482F-9122-0668C346364C}"
diff --git a/build/CoreLibraries.props b/build/CoreLibraries.props
index 314d38190a..9448a31d73 100644
--- a/build/CoreLibraries.props
+++ b/build/CoreLibraries.props
@@ -3,8 +3,6 @@
-
-
diff --git a/build/Magick.NET-Q16-AnyCPU.props b/build/ImageSharp.props
similarity index 63%
rename from build/Magick.NET-Q16-AnyCPU.props
rename to build/ImageSharp.props
index 21d9cdcb1f..178c274ac9 100644
--- a/build/Magick.NET-Q16-AnyCPU.props
+++ b/build/ImageSharp.props
@@ -1,5 +1,5 @@
-
+
diff --git a/native/Avalonia.Native/inc/rendertarget.h b/native/Avalonia.Native/inc/rendertarget.h
index 2b0338d099..a59915777f 100644
--- a/native/Avalonia.Native/inc/rendertarget.h
+++ b/native/Avalonia.Native/inc/rendertarget.h
@@ -1,3 +1,8 @@
+#pragma once
+
+#include "com.h"
+#include "comimpl.h"
+#include "avalonia-native.h"
@protocol IRenderTarget
-(void) setNewLayer: (CALayer*) layer;
diff --git a/native/Avalonia.Native/src/OSX/AutoFitContentView.h b/native/Avalonia.Native/src/OSX/AutoFitContentView.h
new file mode 100644
index 0000000000..7f1f764141
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/AutoFitContentView.h
@@ -0,0 +1,17 @@
+//
+// Created by Dan Walmsley on 05/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#pragma once
+
+#import
+#include "avalonia-native.h"
+
+@interface AutoFitContentView : NSView
+-(AutoFitContentView* _Nonnull) initWithContent: (NSView* _Nonnull) content;
+-(void) ShowTitleBar: (bool) show;
+-(void) SetTitleBarHeightHint: (double) height;
+
+-(void) ShowBlur: (bool) show;
+@end
\ No newline at end of file
diff --git a/native/Avalonia.Native/src/OSX/AutoFitContentView.mm b/native/Avalonia.Native/src/OSX/AutoFitContentView.mm
new file mode 100644
index 0000000000..314c579b76
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/AutoFitContentView.mm
@@ -0,0 +1,106 @@
+//
+// Created by Dan Walmsley on 05/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#include "AvnView.h"
+#include "AutoFitContentView.h"
+#include "WindowInterfaces.h"
+#include "WindowProtocol.h"
+
+@implementation AutoFitContentView
+{
+ NSVisualEffectView* _titleBarMaterial;
+ NSBox* _titleBarUnderline;
+ NSView* _content;
+ NSVisualEffectView* _blurBehind;
+ double _titleBarHeightHint;
+ bool _settingSize;
+}
+
+-(AutoFitContentView* _Nonnull) initWithContent:(NSView *)content
+{
+ _titleBarHeightHint = -1;
+ _content = content;
+ _settingSize = false;
+
+ [self setAutoresizesSubviews:true];
+ [self setWantsLayer:true];
+
+ _titleBarMaterial = [NSVisualEffectView new];
+ [_titleBarMaterial setBlendingMode:NSVisualEffectBlendingModeWithinWindow];
+ [_titleBarMaterial setMaterial:NSVisualEffectMaterialTitlebar];
+ [_titleBarMaterial setWantsLayer:true];
+ _titleBarMaterial.hidden = true;
+
+ _titleBarUnderline = [NSBox new];
+ _titleBarUnderline.boxType = NSBoxSeparator;
+ _titleBarUnderline.fillColor = [NSColor underPageBackgroundColor];
+ _titleBarUnderline.hidden = true;
+
+ [self addSubview:_titleBarMaterial];
+ [self addSubview:_titleBarUnderline];
+
+ _blurBehind = [NSVisualEffectView new];
+ [_blurBehind setBlendingMode:NSVisualEffectBlendingModeBehindWindow];
+ [_blurBehind setMaterial:NSVisualEffectMaterialLight];
+ [_blurBehind setWantsLayer:true];
+ _blurBehind.hidden = true;
+
+ [_blurBehind setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+ [_content setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
+
+ [self addSubview:_blurBehind];
+ [self addSubview:_content];
+
+ [self setWantsLayer:true];
+ return self;
+}
+
+-(void) ShowBlur:(bool)show
+{
+ _blurBehind.hidden = !show;
+}
+
+-(void) ShowTitleBar: (bool) show
+{
+ _titleBarMaterial.hidden = !show;
+ _titleBarUnderline.hidden = !show;
+}
+
+-(void) SetTitleBarHeightHint: (double) height
+{
+ _titleBarHeightHint = height;
+
+ [self setFrameSize:self.frame.size];
+}
+
+-(void)setFrameSize:(NSSize)newSize
+{
+ if(_settingSize)
+ {
+ return;
+ }
+
+ _settingSize = true;
+ [super setFrameSize:newSize];
+
+ auto window = static_cast>([self window]);
+
+ // TODO get actual titlebar size
+
+ double height = _titleBarHeightHint == -1 ? [window getExtendedTitleBarHeight] : _titleBarHeightHint;
+
+ NSRect tbar;
+ tbar.origin.x = 0;
+ tbar.origin.y = newSize.height - height;
+ tbar.size.width = newSize.width;
+ tbar.size.height = height;
+
+ [_titleBarMaterial setFrame:tbar];
+ tbar.size.height = height < 1 ? 0 : 1;
+ [_titleBarUnderline setFrame:tbar];
+
+ _settingSize = false;
+}
+@end
\ No newline at end of file
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 7571d51c9f..6fc3977d4e 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
@@ -7,6 +7,24 @@
objects = {
/* Begin PBXBuildFile section */
+ 18391068E48EF96E3DB5FDAB /* ResizeScope.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18391E45702740FE9DD69695 /* ResizeScope.mm */; };
+ 1839125F057B0A4EB1760058 /* WindowImpl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 183919BF108EB72A029F7671 /* WindowImpl.mm */; };
+ 183914E50CF6D2EFC1667F7C /* WindowInterfaces.h in Headers */ = {isa = PBXBuildFile; fileRef = 18391DB45C7D892E61BF388C /* WindowInterfaces.h */; };
+ 1839151F32D1BB1AB51A7BB6 /* AvnPanelWindow.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18391884C7476DA4E53A492D /* AvnPanelWindow.mm */; };
+ 183916173528EC2737DBE5E1 /* WindowBaseImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 183915BFF0E234CD3604A7CD /* WindowBaseImpl.h */; };
+ 1839171DCC651B0638603AC4 /* INSWindowHolder.h in Headers */ = {isa = PBXBuildFile; fileRef = 18391BBB7782C296D424071F /* INSWindowHolder.h */; };
+ 1839179A55FC1421BEE83330 /* WindowBaseImpl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18391676ECF0E983F4964357 /* WindowBaseImpl.mm */; };
+ 183919D91DB9AAB5D700C2EA /* WindowImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 18391CD090AA776E7E841AC9 /* WindowImpl.h */; };
+ 18391AA7E0BBA74D184C5734 /* AutoFitContentView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1839166350F32661F3ABD70F /* AutoFitContentView.mm */; };
+ 18391AC16726CBC45856233B /* AvnWindow.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1839155B28B20FFB672D29C6 /* AvnWindow.mm */; };
+ 18391AC65ADD7DDD33FBE737 /* PopupImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = 183910513F396141938832B5 /* PopupImpl.h */; };
+ 18391C28BF1823B5464FDD36 /* ResizeScope.h in Headers */ = {isa = PBXBuildFile; fileRef = 1839171D898F9BFC1373631A /* ResizeScope.h */; };
+ 18391CF07316F819E76B617C /* IWindowStateChanged.h in Headers */ = {isa = PBXBuildFile; fileRef = 183913C6BFD6856BD42D19FD /* IWindowStateChanged.h */; };
+ 18391D4EB311BC7EF8B8C0A6 /* AvnView.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1839132D0E2454D911F1D1F9 /* AvnView.mm */; };
+ 18391D8CD1756DC858DC1A09 /* PopupImpl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18391BB698579F40F1783F31 /* PopupImpl.mm */; };
+ 18391E1381E2D5BFD60265A9 /* AutoFitContentView.h in Headers */ = {isa = PBXBuildFile; fileRef = 18391654EF0E7AB3D3AB4071 /* AutoFitContentView.h */; };
+ 18391ED5F611FF62C45F196D /* AvnView.h in Headers */ = {isa = PBXBuildFile; fileRef = 18391D1669284AD2EC9E866A /* AvnView.h */; };
+ 18391F1E2411C79405A9943A /* WindowProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 1839122E037567BDD1D09DEB /* WindowProtocol.h */; };
1A002B9E232135EE00021753 /* app.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A002B9D232135EE00021753 /* app.mm */; };
1A1852DC23E05814008F0DED /* deadlock.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A1852DB23E05814008F0DED /* deadlock.mm */; };
1A3E5EA823E9E83B00EDE661 /* rendertarget.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */; };
@@ -28,13 +46,30 @@
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 */; };
- AB661C202148286E00291242 /* window.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB661C1F2148286E00291242 /* window.mm */; };
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */; };
BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */ = {isa = PBXBuildFile; fileRef = BC11A5BC2608D58F0017BAD0 /* automation.h */; };
BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */ = {isa = PBXBuildFile; fileRef = BC11A5BD2608D58F0017BAD0 /* automation.mm */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
+ 183910513F396141938832B5 /* PopupImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PopupImpl.h; sourceTree = ""; };
+ 1839122E037567BDD1D09DEB /* WindowProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowProtocol.h; sourceTree = ""; };
+ 1839132D0E2454D911F1D1F9 /* AvnView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnView.mm; sourceTree = ""; };
+ 183913C6BFD6856BD42D19FD /* IWindowStateChanged.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = IWindowStateChanged.h; sourceTree = ""; };
+ 1839155B28B20FFB672D29C6 /* AvnWindow.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnWindow.mm; sourceTree = ""; };
+ 183915BFF0E234CD3604A7CD /* WindowBaseImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowBaseImpl.h; sourceTree = ""; };
+ 18391654EF0E7AB3D3AB4071 /* AutoFitContentView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AutoFitContentView.h; sourceTree = ""; };
+ 1839166350F32661F3ABD70F /* AutoFitContentView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AutoFitContentView.mm; sourceTree = ""; };
+ 18391676ECF0E983F4964357 /* WindowBaseImpl.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WindowBaseImpl.mm; sourceTree = ""; };
+ 1839171D898F9BFC1373631A /* ResizeScope.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ResizeScope.h; sourceTree = ""; };
+ 18391884C7476DA4E53A492D /* AvnPanelWindow.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = AvnPanelWindow.mm; sourceTree = ""; };
+ 183919BF108EB72A029F7671 /* WindowImpl.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WindowImpl.mm; sourceTree = ""; };
+ 18391BB698579F40F1783F31 /* PopupImpl.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = PopupImpl.mm; sourceTree = ""; };
+ 18391BBB7782C296D424071F /* INSWindowHolder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = INSWindowHolder.h; sourceTree = ""; };
+ 18391CD090AA776E7E841AC9 /* WindowImpl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowImpl.h; sourceTree = ""; };
+ 18391D1669284AD2EC9E866A /* AvnView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AvnView.h; sourceTree = ""; };
+ 18391DB45C7D892E61BF388C /* WindowInterfaces.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WindowInterfaces.h; sourceTree = ""; };
+ 18391E45702740FE9DD69695 /* ResizeScope.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ResizeScope.mm; sourceTree = ""; };
1A002B9D232135EE00021753 /* app.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = app.mm; sourceTree = ""; };
1A1852DB23E05814008F0DED /* deadlock.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = deadlock.mm; sourceTree = ""; };
1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = rendertarget.mm; sourceTree = ""; };
@@ -48,7 +83,6 @@
37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../../inc; sourceTree = ""; };
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 = ""; };
@@ -62,7 +96,6 @@
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; };
- AB661C1F2148286E00291242 /* window.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = window.mm; sourceTree = ""; };
AB661C212148288600291242 /* common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = common.h; sourceTree = ""; };
AB7A61EF2147C815003C5833 /* libAvalonia.Native.OSX.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libAvalonia.Native.OSX.dylib; sourceTree = BUILT_PRODUCTS_DIR; };
AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = platformthreading.mm; sourceTree = ""; };
@@ -118,8 +151,6 @@
AB661C212148288600291242 /* common.h */,
379860FE214DA0C000CD0246 /* KeyTransform.h */,
37E2330E21583241000CB7E2 /* KeyTransform.mm */,
- AB661C1F2148286E00291242 /* window.mm */,
- 37C09D8A21581EF2006A6758 /* window.h */,
AB00E4F62147CA920032A60A /* main.mm */,
37155CE3233C00EB0034DCE9 /* menu.h */,
520624B222973F4100C4DCEF /* menu.mm */,
@@ -130,6 +161,24 @@
37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
AB7A61F02147C815003C5833 /* Products */,
AB661C1C2148230E00291242 /* Frameworks */,
+ 18391676ECF0E983F4964357 /* WindowBaseImpl.mm */,
+ 183915BFF0E234CD3604A7CD /* WindowBaseImpl.h */,
+ 18391BBB7782C296D424071F /* INSWindowHolder.h */,
+ 183919BF108EB72A029F7671 /* WindowImpl.mm */,
+ 18391CD090AA776E7E841AC9 /* WindowImpl.h */,
+ 183913C6BFD6856BD42D19FD /* IWindowStateChanged.h */,
+ 18391E45702740FE9DD69695 /* ResizeScope.mm */,
+ 1839171D898F9BFC1373631A /* ResizeScope.h */,
+ 1839132D0E2454D911F1D1F9 /* AvnView.mm */,
+ 18391D1669284AD2EC9E866A /* AvnView.h */,
+ 1839166350F32661F3ABD70F /* AutoFitContentView.mm */,
+ 18391654EF0E7AB3D3AB4071 /* AutoFitContentView.h */,
+ 18391884C7476DA4E53A492D /* AvnPanelWindow.mm */,
+ 1839122E037567BDD1D09DEB /* WindowProtocol.h */,
+ 1839155B28B20FFB672D29C6 /* AvnWindow.mm */,
+ 18391DB45C7D892E61BF388C /* WindowInterfaces.h */,
+ 18391BB698579F40F1783F31 /* PopupImpl.mm */,
+ 183910513F396141938832B5 /* PopupImpl.h */,
);
sourceTree = "";
};
@@ -150,6 +199,16 @@
files = (
37155CE4233C00EB0034DCE9 /* menu.h in Headers */,
BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */,
+ 183916173528EC2737DBE5E1 /* WindowBaseImpl.h in Headers */,
+ 1839171DCC651B0638603AC4 /* INSWindowHolder.h in Headers */,
+ 183919D91DB9AAB5D700C2EA /* WindowImpl.h in Headers */,
+ 18391CF07316F819E76B617C /* IWindowStateChanged.h in Headers */,
+ 18391C28BF1823B5464FDD36 /* ResizeScope.h in Headers */,
+ 18391ED5F611FF62C45F196D /* AvnView.h in Headers */,
+ 18391E1381E2D5BFD60265A9 /* AutoFitContentView.h in Headers */,
+ 18391F1E2411C79405A9943A /* WindowProtocol.h in Headers */,
+ 183914E50CF6D2EFC1667F7C /* WindowInterfaces.h in Headers */,
+ 18391AC65ADD7DDD33FBE737 /* PopupImpl.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -228,7 +287,14 @@
1A465D10246AB61600C5858B /* dnd.mm in Sources */,
AB00E4F72147CA920032A60A /* main.mm in Sources */,
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */,
- AB661C202148286E00291242 /* window.mm in Sources */,
+ 1839179A55FC1421BEE83330 /* WindowBaseImpl.mm in Sources */,
+ 1839125F057B0A4EB1760058 /* WindowImpl.mm in Sources */,
+ 18391068E48EF96E3DB5FDAB /* ResizeScope.mm in Sources */,
+ 18391D4EB311BC7EF8B8C0A6 /* AvnView.mm in Sources */,
+ 18391AA7E0BBA74D184C5734 /* AutoFitContentView.mm in Sources */,
+ 1839151F32D1BB1AB51A7BB6 /* AvnPanelWindow.mm in Sources */,
+ 18391AC16726CBC45856233B /* AvnWindow.mm in Sources */,
+ 18391D8CD1756DC858DC1A09 /* PopupImpl.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
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 5d20a135b9..87a8312c38 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
@@ -56,10 +56,14 @@
+
+
+
+
+#import
+#import
+#include "common.h"
+#include "WindowImpl.h"
+#include "KeyTransform.h"
+
+@class AvnAccessibilityElement;
+
+@interface AvnView : NSView
+-(AvnView* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
+-(NSEvent* _Nonnull) lastMouseDownEvent;
+-(AvnPoint) translateLocalPoint:(AvnPoint)pt;
+-(void) setSwRenderedFrame: (AvnFramebuffer* _Nonnull) fb dispose: (IUnknown* _Nonnull) dispose;
+-(void) onClosed;
+
+-(AvnPlatformResizeReason) getResizeReason;
+-(void) setResizeReason:(AvnPlatformResizeReason)reason;
++ (AvnPoint)toAvnPoint:(CGPoint)p;
+@end
\ No newline at end of file
diff --git a/native/Avalonia.Native/src/OSX/AvnView.mm b/native/Avalonia.Native/src/OSX/AvnView.mm
new file mode 100644
index 0000000000..02526afbcb
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/AvnView.mm
@@ -0,0 +1,712 @@
+//
+// Created by Dan Walmsley on 05/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#import
+#include "AvnView.h"
+#include "automation.h"
+#import "WindowInterfaces.h"
+
+@implementation AvnView
+{
+ ComPtr _parent;
+ NSTrackingArea* _area;
+ bool _isLeftPressed, _isMiddlePressed, _isRightPressed, _isXButton1Pressed, _isXButton2Pressed;
+ AvnInputModifiers _modifierState;
+ NSEvent* _lastMouseDownEvent;
+ bool _lastKeyHandled;
+ AvnPixelSize _lastPixelSize;
+ NSObject* _renderTarget;
+ AvnPlatformResizeReason _resizeReason;
+ AvnAccessibilityElement* _accessibilityChild;
+}
+
+- (void)onClosed
+{
+ @synchronized (self)
+ {
+ _parent = nullptr;
+ }
+}
+
+- (NSEvent*) lastMouseDownEvent
+{
+ return _lastMouseDownEvent;
+}
+
+- (void) updateRenderTarget
+{
+ [_renderTarget resize:_lastPixelSize withScale:static_cast([[self window] backingScaleFactor])];
+ [self setNeedsDisplayInRect:[self frame]];
+}
+
+-(AvnView*) initWithParent: (WindowBaseImpl*) parent
+{
+ self = [super init];
+ _renderTarget = parent->renderTarget;
+ [self setWantsLayer:YES];
+ [self setLayerContentsRedrawPolicy: NSViewLayerContentsRedrawDuringViewResize];
+
+ _parent = parent;
+ _area = nullptr;
+ _lastPixelSize.Height = 100;
+ _lastPixelSize.Width = 100;
+ [self registerForDraggedTypes: @[@"public.data", GetAvnCustomDataType()]];
+
+ _modifierState = AvnInputModifiersNone;
+ return self;
+}
+
+- (BOOL)isFlipped
+{
+ return YES;
+}
+
+- (BOOL)wantsUpdateLayer
+{
+ return YES;
+}
+
+- (void)setLayer:(CALayer *)layer
+{
+ [_renderTarget setNewLayer: layer];
+ [super setLayer: layer];
+}
+
+- (BOOL)isOpaque
+{
+ return YES;
+}
+
+- (BOOL)acceptsFirstResponder
+{
+ return true;
+}
+
+- (BOOL)acceptsFirstMouse:(NSEvent *)event
+{
+ return true;
+}
+
+- (BOOL)canBecomeKeyView
+{
+ return true;
+}
+
+-(void)setFrameSize:(NSSize)newSize
+{
+ [super setFrameSize:newSize];
+
+ if(_area != nullptr)
+ {
+ [self removeTrackingArea:_area];
+ _area = nullptr;
+ }
+
+ if (_parent == nullptr)
+ {
+ return;
+ }
+
+ NSRect rect = NSZeroRect;
+ rect.size = newSize;
+
+ NSTrackingAreaOptions options = NSTrackingActiveAlways | NSTrackingMouseMoved | NSTrackingMouseEnteredAndExited | NSTrackingEnabledDuringMouseDrag;
+ _area = [[NSTrackingArea alloc] initWithRect:rect options:options owner:self userInfo:nullptr];
+ [self addTrackingArea:_area];
+
+ _parent->UpdateCursor();
+
+ auto fsize = [self convertSizeToBacking: [self frame].size];
+
+ if(_lastPixelSize.Width != (int)fsize.width || _lastPixelSize.Height != (int)fsize.height)
+ {
+ _lastPixelSize.Width = (int)fsize.width;
+ _lastPixelSize.Height = (int)fsize.height;
+ [self updateRenderTarget];
+
+ auto reason = [self inLiveResize] ? ResizeUser : _resizeReason;
+ _parent->BaseEvents->Resized(AvnSize{newSize.width, newSize.height}, reason);
+ }
+}
+
+- (void)updateLayer
+{
+ AvnInsidePotentialDeadlock deadlock;
+ if (_parent == nullptr)
+ {
+ return;
+ }
+
+ _parent->BaseEvents->RunRenderPriorityJobs();
+
+ if (_parent == nullptr)
+ {
+ return;
+ }
+
+ _parent->BaseEvents->Paint();
+}
+
+- (void)drawRect:(NSRect)dirtyRect
+{
+ return;
+}
+
+-(void) setSwRenderedFrame: (AvnFramebuffer*) fb dispose: (IUnknown*) dispose
+{
+ @autoreleasepool {
+ [_renderTarget setSwFrame:fb];
+ dispose->Release();
+ }
+}
+
+- (AvnPoint) translateLocalPoint:(AvnPoint)pt
+{
+ pt.Y = [self bounds].size.height - pt.Y;
+ return pt;
+}
+
++ (AvnPoint)toAvnPoint:(CGPoint)p
+{
+ AvnPoint result;
+
+ result.X = p.x;
+ result.Y = p.y;
+
+ return result;
+}
+
+- (void) viewDidChangeBackingProperties
+{
+ auto fsize = [self convertSizeToBacking: [self frame].size];
+ _lastPixelSize.Width = (int)fsize.width;
+ _lastPixelSize.Height = (int)fsize.height;
+ [self updateRenderTarget];
+
+ if(_parent != nullptr)
+ {
+ _parent->BaseEvents->ScalingChanged([_parent->Window backingScaleFactor]);
+ }
+
+ [super viewDidChangeBackingProperties];
+}
+
+- (bool) ignoreUserInput:(bool)trigerInputWhenDisabled
+{
+ if(_parent == nullptr)
+ {
+ return TRUE;
+ }
+
+ auto parentWindow = _parent->GetWindowProtocol();
+
+ if(parentWindow == nil || ![parentWindow shouldTryToHandleEvents])
+ {
+ if(trigerInputWhenDisabled)
+ {
+ auto window = dynamic_cast(_parent.getRaw());
+
+ if(window != nullptr)
+ {
+ window->WindowEvents->GotInputWhenDisabled();
+ }
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+- (void)mouseEvent:(NSEvent *)event withType:(AvnRawMouseEventType) type
+{
+ bool triggerInputWhenDisabled = type != Move;
+
+ if([self ignoreUserInput: triggerInputWhenDisabled])
+ {
+ return;
+ }
+
+ auto localPoint = [self convertPoint:[event locationInWindow] toView:self];
+ auto avnPoint = [AvnView toAvnPoint:localPoint];
+ auto point = [self translateLocalPoint:avnPoint];
+ AvnVector delta = { 0, 0};
+
+ if(type == Wheel)
+ {
+ auto speed = 5;
+
+ if([event hasPreciseScrollingDeltas])
+ {
+ speed = 50;
+ }
+
+ delta.X = [event scrollingDeltaX] / speed;
+ delta.Y = [event scrollingDeltaY] / speed;
+
+ if(delta.X == 0 && delta.Y == 0)
+ {
+ return;
+ }
+ }
+ else if (type == Magnify)
+ {
+ delta.X = delta.Y = [event magnification];
+ }
+ else if (type == Rotate)
+ {
+ delta.X = delta.Y = [event rotation];
+ }
+ else if (type == Swipe)
+ {
+ delta.X = [event deltaX];
+ delta.Y = [event deltaY];
+ }
+
+ uint32 timestamp = static_cast([event timestamp] * 1000);
+ auto modifiers = [self getModifiers:[event modifierFlags]];
+
+ if(type != Move ||
+ (
+ [self window] != nil &&
+ (
+ [[self window] firstResponder] == nil
+ || ![[[self window] firstResponder] isKindOfClass: [NSView class]]
+ )
+ )
+ )
+ [self becomeFirstResponder];
+
+ if(_parent != nullptr)
+ {
+ _parent->BaseEvents->RawMouseEvent(type, timestamp, modifiers, point, delta);
+ }
+
+ [super mouseMoved:event];
+}
+
+- (BOOL) resignFirstResponder
+{
+ _parent->BaseEvents->LostFocus();
+ return YES;
+}
+
+- (void)mouseMoved:(NSEvent *)event
+{
+ [self mouseEvent:event withType:Move];
+}
+
+- (void)mouseDown:(NSEvent *)event
+{
+ _isLeftPressed = true;
+ _lastMouseDownEvent = event;
+ [self mouseEvent:event withType:LeftButtonDown];
+}
+
+- (void)otherMouseDown:(NSEvent *)event
+{
+ _lastMouseDownEvent = event;
+
+ switch(event.buttonNumber)
+ {
+ case 2:
+ case 3:
+ _isMiddlePressed = true;
+ [self mouseEvent:event withType:MiddleButtonDown];
+ break;
+ case 4:
+ _isXButton1Pressed = true;
+ [self mouseEvent:event withType:XButton1Down];
+ break;
+ case 5:
+ _isXButton2Pressed = true;
+ [self mouseEvent:event withType:XButton2Down];
+ break;
+
+ default:
+ break;
+ }
+}
+
+- (void)rightMouseDown:(NSEvent *)event
+{
+ _isRightPressed = true;
+ _lastMouseDownEvent = event;
+ [self mouseEvent:event withType:RightButtonDown];
+}
+
+- (void)mouseUp:(NSEvent *)event
+{
+ _isLeftPressed = false;
+ [self mouseEvent:event withType:LeftButtonUp];
+}
+
+- (void)otherMouseUp:(NSEvent *)event
+{
+ switch(event.buttonNumber)
+ {
+ case 2:
+ case 3:
+ _isMiddlePressed = false;
+ [self mouseEvent:event withType:MiddleButtonUp];
+ break;
+ case 4:
+ _isXButton1Pressed = false;
+ [self mouseEvent:event withType:XButton1Up];
+ break;
+ case 5:
+ _isXButton2Pressed = false;
+ [self mouseEvent:event withType:XButton2Up];
+ break;
+
+ default:
+ break;
+ }
+}
+
+- (void)rightMouseUp:(NSEvent *)event
+{
+ _isRightPressed = false;
+ [self mouseEvent:event withType:RightButtonUp];
+}
+
+- (void)mouseDragged:(NSEvent *)event
+{
+ [self mouseEvent:event withType:Move];
+ [super mouseDragged:event];
+}
+
+- (void)otherMouseDragged:(NSEvent *)event
+{
+ [self mouseEvent:event withType:Move];
+ [super otherMouseDragged:event];
+}
+
+- (void)rightMouseDragged:(NSEvent *)event
+{
+ [self mouseEvent:event withType:Move];
+ [super rightMouseDragged:event];
+}
+
+- (void)scrollWheel:(NSEvent *)event
+{
+ [self mouseEvent:event withType:Wheel];
+ [super scrollWheel:event];
+}
+
+- (void)magnifyWithEvent:(NSEvent *)event
+{
+ [self mouseEvent:event withType:Magnify];
+ [super magnifyWithEvent:event];
+}
+
+- (void)rotateWithEvent:(NSEvent *)event
+{
+ [self mouseEvent:event withType:Rotate];
+ [super rotateWithEvent:event];
+}
+
+- (void)swipeWithEvent:(NSEvent *)event
+{
+ [self mouseEvent:event withType:Swipe];
+ [super swipeWithEvent:event];
+}
+
+- (void)mouseEntered:(NSEvent *)event
+{
+ [super mouseEntered:event];
+}
+
+- (void)mouseExited:(NSEvent *)event
+{
+ [self mouseEvent:event withType:LeaveWindow];
+ [super mouseExited:event];
+}
+
+- (void) keyboardEvent: (NSEvent *) event withType: (AvnRawKeyEventType)type
+{
+ if([self ignoreUserInput: false])
+ {
+ return;
+ }
+
+ auto key = s_KeyMap[[event keyCode]];
+
+ uint32_t timestamp = static_cast([event timestamp] * 1000);
+ auto modifiers = [self getModifiers:[event modifierFlags]];
+
+ if(_parent != nullptr)
+ {
+ _lastKeyHandled = _parent->BaseEvents->RawKeyEvent(type, timestamp, modifiers, key);
+ }
+}
+
+- (BOOL)performKeyEquivalent:(NSEvent *)event
+{
+ bool result = _lastKeyHandled;
+
+ _lastKeyHandled = false;
+
+ return result;
+}
+
+- (void)flagsChanged:(NSEvent *)event
+{
+ auto newModifierState = [self getModifiers:[event modifierFlags]];
+
+ bool isAltCurrentlyPressed = (_modifierState & Alt) == Alt;
+ bool isControlCurrentlyPressed = (_modifierState & Control) == Control;
+ bool isShiftCurrentlyPressed = (_modifierState & Shift) == Shift;
+ bool isCommandCurrentlyPressed = (_modifierState & Windows) == Windows;
+
+ bool isAltPressed = (newModifierState & Alt) == Alt;
+ bool isControlPressed = (newModifierState & Control) == Control;
+ bool isShiftPressed = (newModifierState & Shift) == Shift;
+ bool isCommandPressed = (newModifierState & Windows) == Windows;
+
+
+ if (isAltPressed && !isAltCurrentlyPressed)
+ {
+ [self keyboardEvent:event withType:KeyDown];
+ }
+ else if (isAltCurrentlyPressed && !isAltPressed)
+ {
+ [self keyboardEvent:event withType:KeyUp];
+ }
+
+ if (isControlPressed && !isControlCurrentlyPressed)
+ {
+ [self keyboardEvent:event withType:KeyDown];
+ }
+ else if (isControlCurrentlyPressed && !isControlPressed)
+ {
+ [self keyboardEvent:event withType:KeyUp];
+ }
+
+ if (isShiftPressed && !isShiftCurrentlyPressed)
+ {
+ [self keyboardEvent:event withType:KeyDown];
+ }
+ else if(isShiftCurrentlyPressed && !isShiftPressed)
+ {
+ [self keyboardEvent:event withType:KeyUp];
+ }
+
+ if(isCommandPressed && !isCommandCurrentlyPressed)
+ {
+ [self keyboardEvent:event withType:KeyDown];
+ }
+ else if(isCommandCurrentlyPressed && ! isCommandPressed)
+ {
+ [self keyboardEvent:event withType:KeyUp];
+ }
+
+ _modifierState = newModifierState;
+
+ [[self inputContext] handleEvent:event];
+ [super flagsChanged:event];
+}
+
+- (void)keyDown:(NSEvent *)event
+{
+ [self keyboardEvent:event withType:KeyDown];
+ [[self inputContext] handleEvent:event];
+ [super keyDown:event];
+}
+
+- (void)keyUp:(NSEvent *)event
+{
+ [self keyboardEvent:event withType:KeyUp];
+ [super keyUp:event];
+}
+
+- (AvnInputModifiers)getModifiers:(NSEventModifierFlags)mod
+{
+ unsigned int rv = 0;
+
+ if (mod & NSEventModifierFlagControl)
+ rv |= Control;
+ if (mod & NSEventModifierFlagShift)
+ rv |= Shift;
+ if (mod & NSEventModifierFlagOption)
+ rv |= Alt;
+ if (mod & NSEventModifierFlagCommand)
+ rv |= Windows;
+
+ if (_isLeftPressed)
+ rv |= LeftMouseButton;
+ if (_isMiddlePressed)
+ rv |= MiddleMouseButton;
+ if (_isRightPressed)
+ rv |= RightMouseButton;
+ if (_isXButton1Pressed)
+ rv |= XButton1MouseButton;
+ if (_isXButton2Pressed)
+ rv |= XButton2MouseButton;
+
+ return (AvnInputModifiers)rv;
+}
+
+- (BOOL)hasMarkedText
+{
+ return _lastKeyHandled;
+}
+
+- (NSRange)markedRange
+{
+ return NSMakeRange(NSNotFound, 0);
+}
+
+- (NSRange)selectedRange
+{
+ return NSMakeRange(NSNotFound, 0);
+}
+
+- (void)setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
+{
+
+}
+
+- (void)unmarkText
+{
+
+}
+
+- (NSArray *)validAttributesForMarkedText
+{
+ return [NSArray new];
+}
+
+- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)range actualRange:(NSRangePointer)actualRange
+{
+ return [NSAttributedString new];
+}
+
+- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
+{
+ if(!_lastKeyHandled)
+ {
+ if(_parent != nullptr)
+ {
+ _lastKeyHandled = _parent->BaseEvents->RawTextInputEvent(0, [string UTF8String]);
+ }
+ }
+}
+
+- (NSUInteger)characterIndexForPoint:(NSPoint)point
+{
+ return 0;
+}
+
+- (NSRect)firstRectForCharacterRange:(NSRange)range actualRange:(NSRangePointer)actualRange
+{
+ CGRect result = { 0 };
+
+ return result;
+}
+
+- (NSDragOperation)triggerAvnDragEvent: (AvnDragEventType) type info: (id )info
+{
+ auto localPoint = [self convertPoint:[info draggingLocation] toView:self];
+ auto avnPoint = [AvnView toAvnPoint:localPoint];
+ auto point = [self translateLocalPoint:avnPoint];
+ auto modifiers = [self getModifiers:[[NSApp currentEvent] modifierFlags]];
+ NSDragOperation nsop = [info draggingSourceOperationMask];
+
+ auto effects = ConvertDragDropEffects(nsop);
+ int reffects = (int)_parent->BaseEvents
+ ->DragEvent(type, point, modifiers, effects,
+ CreateClipboard([info draggingPasteboard], nil),
+ GetAvnDataObjectHandleFromDraggingInfo(info));
+
+ NSDragOperation ret = static_cast(0);
+
+ // Ensure that the managed part didn't add any new effects
+ reffects = (int)effects & reffects;
+
+ // OSX requires exactly one operation
+ if((reffects & (int)AvnDragDropEffects::Copy) != 0)
+ ret = NSDragOperationCopy;
+ else if((reffects & (int)AvnDragDropEffects::Move) != 0)
+ ret = NSDragOperationMove;
+ else if((reffects & (int)AvnDragDropEffects::Link) != 0)
+ ret = NSDragOperationLink;
+ if(ret == 0)
+ ret = NSDragOperationNone;
+ return ret;
+}
+
+- (NSDragOperation)draggingEntered:(id )sender
+{
+ return [self triggerAvnDragEvent: AvnDragEventType::Enter info:sender];
+}
+
+- (NSDragOperation)draggingUpdated:(id )sender
+{
+ return [self triggerAvnDragEvent: AvnDragEventType::Over info:sender];
+}
+
+- (void)draggingExited:(id )sender
+{
+ [self triggerAvnDragEvent: AvnDragEventType::Leave info:sender];
+}
+
+- (BOOL)prepareForDragOperation:(id )sender
+{
+ return [self triggerAvnDragEvent: AvnDragEventType::Over info:sender] != NSDragOperationNone;
+}
+
+- (BOOL)performDragOperation:(id )sender
+{
+ return [self triggerAvnDragEvent: AvnDragEventType::Drop info:sender] != NSDragOperationNone;
+}
+
+- (void)concludeDragOperation:(nullable id )sender
+{
+
+}
+
+- (AvnPlatformResizeReason)getResizeReason
+{
+ return _resizeReason;
+}
+
+- (void)setResizeReason:(AvnPlatformResizeReason)reason
+{
+ _resizeReason = reason;
+}
+
+- (AvnAccessibilityElement *) accessibilityChild
+{
+ if (_accessibilityChild == nil)
+ {
+ auto peer = _parent->BaseEvents->GetAutomationPeer();
+
+ if (peer == nil)
+ return nil;
+
+ _accessibilityChild = [AvnAccessibilityElement acquire:peer];
+ }
+
+ return _accessibilityChild;
+}
+
+- (NSArray *)accessibilityChildren
+{
+ auto child = [self accessibilityChild];
+ return NSAccessibilityUnignoredChildrenForOnlyChild(child);
+}
+
+- (id)accessibilityHitTest:(NSPoint)point
+{
+ return [[self accessibilityChild] accessibilityHitTest:point];
+}
+
+- (id)accessibilityFocusedUIElement
+{
+ return [[self accessibilityChild] accessibilityFocusedUIElement];
+}
+
+@end
\ No newline at end of file
diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm
new file mode 100644
index 0000000000..6ff19ead68
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm
@@ -0,0 +1,441 @@
+//
+// Created by Dan Walmsley on 06/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+
+#import
+#import "WindowProtocol.h"
+#import "WindowBaseImpl.h"
+
+#ifdef IS_NSPANEL
+#define BASE_CLASS NSPanel
+#define CLASS_NAME AvnPanel
+#else
+#define BASE_CLASS NSWindow
+#define CLASS_NAME AvnWindow
+#endif
+
+#import
+#include "common.h"
+#include "menu.h"
+#include "automation.h"
+#include "WindowBaseImpl.h"
+#include "WindowImpl.h"
+#include "AvnView.h"
+#include "WindowInterfaces.h"
+#include "PopupImpl.h"
+
+@implementation CLASS_NAME
+{
+ ComPtr _parent;
+ bool _closed;
+ bool _isEnabled;
+ bool _isExtended;
+ AvnMenu* _menu;
+}
+
+-(void) setIsExtended:(bool)value;
+{
+ _isExtended = value;
+}
+
+-(bool) isDialog
+{
+ return _parent->IsDialog();
+}
+
+-(double) getExtendedTitleBarHeight
+{
+ if(_isExtended)
+ {
+ for (id subview in self.contentView.superview.subviews)
+ {
+ if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")])
+ {
+ NSView *titlebarView = [subview subviews][0];
+
+ return (double)titlebarView.frame.size.height;
+ }
+ }
+
+ return -1;
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+- (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];
+
+ if(response == NSModalResponseContinue)
+ {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [self pollModalSession:session];
+ });
+ }
+ else if (!_closed)
+ {
+ [self orderOut:self];
+ [NSApp endModalSession:session];
+ }
+}
+
+-(void) showWindowMenuWithAppMenu
+{
+ if(_menu != nullptr)
+ {
+ auto appMenuItem = ::GetAppMenuItem();
+
+ if(appMenuItem != nullptr)
+ {
+ auto appMenu = [appMenuItem menu];
+
+ [appMenu removeItem:appMenuItem];
+
+ [_menu insertItem:appMenuItem atIndex:0];
+
+ [_menu setHasGlobalMenuItem:true];
+ }
+
+ [NSApp setMenu:_menu];
+ }
+ else
+ {
+ [self showAppMenuOnly];
+ }
+}
+
+-(void) showAppMenuOnly
+{
+ auto appMenuItem = ::GetAppMenuItem();
+
+ if(appMenuItem != nullptr)
+ {
+ auto appMenu = ::GetAppMenu();
+
+ auto nativeAppMenu = dynamic_cast(appMenu);
+
+ [[appMenuItem menu] removeItem:appMenuItem];
+
+ if(_menu != nullptr)
+ {
+ [_menu setHasGlobalMenuItem:false];
+ }
+
+ [nativeAppMenu->GetNative() addItem:appMenuItem];
+
+ [NSApp setMenu:nativeAppMenu->GetNative()];
+ }
+}
+
+-(void) applyMenu:(AvnMenu *)menu
+{
+ if(menu == nullptr)
+ {
+ menu = [AvnMenu new];
+ }
+
+ _menu = menu;
+}
+
+-(CLASS_NAME*) initWithParent: (WindowBaseImpl*) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask;
+{
+ // 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
+ // until several ms after the window is physically on the screen.
+ self = [super initWithContentRect:contentRect styleMask: styleMask backing:NSBackingStoreBuffered defer:false];
+
+ [self setReleasedWhenClosed:false];
+ _parent = parent;
+ [self setDelegate:self];
+ _closed = false;
+ _isEnabled = true;
+
+ [self backingScaleFactor];
+ [self setOpaque:NO];
+ [self setBackgroundColor: [NSColor clearColor]];
+
+ _isExtended = false;
+ return self;
+}
+
+- (BOOL)windowShouldClose:(NSWindow *)sender
+{
+ auto window = dynamic_cast(_parent.getRaw());
+
+ if(window != nullptr)
+ {
+ return !window->WindowEvents->Closing();
+ }
+
+ return true;
+}
+
+- (void)windowDidChangeBackingProperties:(NSNotification *)notification
+{
+ [self backingScaleFactor];
+}
+
+- (void)windowWillClose:(NSNotification *)notification
+{
+ _closed = true;
+ if(_parent)
+ {
+ ComPtr parent = _parent;
+ _parent = NULL;
+ [self restoreParentWindow];
+ parent->BaseEvents->Closed();
+ [parent->View onClosed];
+ }
+}
+
+-(BOOL)canBecomeKeyWindow
+{
+ // 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 ch = static_cast>(uch);
+ if(ch == nil)
+ continue;
+ if (ch.isDialog)
+ return false;
+ }
+
+ return true;
+}
+
+-(BOOL)canBecomeMainWindow
+{
+#ifdef IS_NSPANEL
+ return false;
+#else
+ return true;
+#endif
+}
+
+-(bool)shouldTryToHandleEvents
+{
+ return _isEnabled;
+}
+
+-(void) setEnabled:(bool)enable
+{
+ _isEnabled = enable;
+}
+
+-(void)becomeKeyWindow
+{
+ [self showWindowMenuWithAppMenu];
+
+ if(_parent != nullptr)
+ {
+ _parent->BaseEvents->Activated();
+ }
+
+ [super becomeKeyWindow];
+}
+
+-(void) restoreParentWindow;
+{
+ auto parent = [self parentWindow];
+
+ if(parent != nil)
+ {
+ [parent removeChildWindow:self];
+ }
+}
+
+- (void)windowDidMiniaturize:(NSNotification *)notification
+{
+ auto parent = dynamic_cast(_parent.operator->());
+
+ if(parent != nullptr)
+ {
+ parent->WindowStateChanged();
+ }
+}
+
+- (void)windowDidDeminiaturize:(NSNotification *)notification
+{
+ auto parent = dynamic_cast(_parent.operator->());
+
+ if(parent != nullptr)
+ {
+ parent->WindowStateChanged();
+ }
+}
+
+- (void)windowDidResize:(NSNotification *)notification
+{
+ auto parent = dynamic_cast(_parent.operator->());
+
+ if(parent != nullptr)
+ {
+ parent->WindowStateChanged();
+ }
+}
+
+- (void)windowWillExitFullScreen:(NSNotification *)notification
+{
+ auto parent = dynamic_cast(_parent.operator->());
+
+ if(parent != nullptr)
+ {
+ parent->StartStateTransition();
+ }
+}
+
+- (void)windowDidExitFullScreen:(NSNotification *)notification
+{
+ auto parent = dynamic_cast(_parent.operator->());
+
+ if(parent != nullptr)
+ {
+ parent->EndStateTransition();
+
+ if(parent->Decorations() != SystemDecorationsFull && parent->WindowState() == Maximized)
+ {
+ NSRect screenRect = [[self screen] visibleFrame];
+ [self setFrame:screenRect display:YES];
+ }
+
+ if(parent->WindowState() == Minimized)
+ {
+ [self miniaturize:nullptr];
+ }
+
+ parent->WindowStateChanged();
+ }
+}
+
+- (void)windowWillEnterFullScreen:(NSNotification *)notification
+{
+ auto parent = dynamic_cast(_parent.operator->());
+
+ if(parent != 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();
+
+ [self showAppMenuOnly];
+
+ [super resignKeyWindow];
+}
+
+- (void)windowDidMove:(NSNotification *)notification
+{
+ AvnPoint position;
+
+ if(_parent != nullptr)
+ {
+ auto cparent = dynamic_cast(_parent.getRaw());
+
+ if(cparent != nullptr)
+ {
+ if(cparent->WindowState() == Maximized)
+ {
+ cparent->SetWindowState(Normal);
+ }
+ }
+
+ _parent->GetPosition(&position);
+ _parent->BaseEvents->PositionChanged(position);
+ }
+}
+
+- (AvnPoint) translateLocalPoint:(AvnPoint)pt
+{
+ pt.Y = [self frame].size.height - pt.Y;
+ return pt;
+}
+
+- (void)sendEvent:(NSEvent *)event
+{
+ [super sendEvent:event];
+
+ /// This is to detect non-client clicks. This can only be done on Windows... not popups, hence the dynamic_cast.
+ if(_parent != nullptr && dynamic_cast(_parent.getRaw()) != nullptr)
+ {
+ switch(event.type)
+ {
+ case NSEventTypeLeftMouseDown:
+ {
+ AvnView* view = _parent->View;
+ NSPoint windowPoint = [event locationInWindow];
+ NSPoint viewPoint = [view convertPoint:windowPoint fromView:nil];
+
+ if (!NSPointInRect(viewPoint, view.bounds))
+ {
+ auto avnPoint = [AvnView toAvnPoint:windowPoint];
+ auto point = [self translateLocalPoint:avnPoint];
+ AvnVector delta = { 0, 0 };
+
+ _parent->BaseEvents->RawMouseEvent(NonClientLeftButtonDown, static_cast([event timestamp] * 1000), AvnInputModifiersNone, point, delta);
+ }
+ }
+ break;
+
+ case NSEventTypeMouseEntered:
+ {
+ _parent->UpdateCursor();
+ }
+ break;
+
+ case NSEventTypeMouseExited:
+ {
+ [[NSCursor arrowCursor] set];
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+- (void)disconnectParent {
+ _parent = nullptr;
+}
+
+@end
+
diff --git a/native/Avalonia.Native/src/OSX/INSWindowHolder.h b/native/Avalonia.Native/src/OSX/INSWindowHolder.h
new file mode 100644
index 0000000000..ae64a53e7d
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/INSWindowHolder.h
@@ -0,0 +1,17 @@
+//
+// Created by Dan Walmsley on 04/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#ifndef AVALONIA_NATIVE_OSX_INSWINDOWHOLDER_H
+#define AVALONIA_NATIVE_OSX_INSWINDOWHOLDER_H
+
+@class AvnView;
+
+struct INSWindowHolder
+{
+ virtual NSWindow* _Nonnull GetNSWindow () = 0;
+ virtual NSView* _Nonnull GetNSView () = 0;
+};
+
+#endif //AVALONIA_NATIVE_OSX_INSWINDOWHOLDER_H
diff --git a/native/Avalonia.Native/src/OSX/IWindowStateChanged.h b/native/Avalonia.Native/src/OSX/IWindowStateChanged.h
new file mode 100644
index 0000000000..f0905da3ac
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/IWindowStateChanged.h
@@ -0,0 +1,18 @@
+//
+// Created by Dan Walmsley on 04/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#ifndef AVALONIA_NATIVE_OSX_IWINDOWSTATECHANGED_H
+#define AVALONIA_NATIVE_OSX_IWINDOWSTATECHANGED_H
+
+struct IWindowStateChanged
+{
+ virtual void WindowStateChanged () = 0;
+ virtual void StartStateTransition () = 0;
+ virtual void EndStateTransition () = 0;
+ virtual SystemDecorations Decorations () = 0;
+ virtual AvnWindowState WindowState () = 0;
+};
+
+#endif //AVALONIA_NATIVE_OSX_IWINDOWSTATECHANGED_H
diff --git a/native/Avalonia.Native/src/OSX/PopupImpl.h b/native/Avalonia.Native/src/OSX/PopupImpl.h
new file mode 100644
index 0000000000..451019a6a4
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/PopupImpl.h
@@ -0,0 +1,9 @@
+//
+// Created by Dan Walmsley on 06/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#ifndef AVALONIA_NATIVE_OSX_POPUPIMPL_H
+#define AVALONIA_NATIVE_OSX_POPUPIMPL_H
+
+#endif //AVALONIA_NATIVE_OSX_POPUPIMPL_H
diff --git a/native/Avalonia.Native/src/OSX/PopupImpl.mm b/native/Avalonia.Native/src/OSX/PopupImpl.mm
new file mode 100644
index 0000000000..64a8780158
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/PopupImpl.mm
@@ -0,0 +1,68 @@
+//
+// Created by Dan Walmsley on 06/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#include "WindowInterfaces.h"
+#include "AvnView.h"
+#include "WindowImpl.h"
+#include "automation.h"
+#include "menu.h"
+#include "common.h"
+#import "WindowBaseImpl.h"
+#import "WindowProtocol.h"
+#import
+#include "PopupImpl.h"
+
+class PopupImpl : public virtual WindowBaseImpl, public IAvnPopup
+{
+private:
+ BEGIN_INTERFACE_MAP()
+ INHERIT_INTERFACE_MAP(WindowBaseImpl)
+ INTERFACE_MAP_ENTRY(IAvnPopup, IID_IAvnPopup)
+ END_INTERFACE_MAP()
+ virtual ~PopupImpl(){}
+ ComPtr WindowEvents;
+ PopupImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
+ {
+ WindowEvents = events;
+ [Window setLevel:NSPopUpMenuWindowLevel];
+ }
+protected:
+ virtual NSWindowStyleMask GetStyle() override
+ {
+ return NSWindowStyleMaskBorderless;
+ }
+
+ virtual HRESULT Resize(double x, double y, AvnPlatformResizeReason reason) override
+ {
+ START_COM_CALL;
+
+ @autoreleasepool
+ {
+ if (Window != nullptr)
+ {
+ [Window setContentSize:NSSize{x, y}];
+
+ [Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(lastPositionSet))];
+ }
+
+ return S_OK;
+ }
+ }
+public:
+ virtual bool ShouldTakeFocusOnShow() override
+ {
+ return false;
+ }
+};
+
+
+extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl)
+{
+ @autoreleasepool
+ {
+ IAvnPopup* ptr = dynamic_cast(new PopupImpl(events, gl));
+ return ptr;
+ }
+}
\ No newline at end of file
diff --git a/native/Avalonia.Native/src/OSX/ResizeScope.h b/native/Avalonia.Native/src/OSX/ResizeScope.h
new file mode 100644
index 0000000000..9a43c158fe
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/ResizeScope.h
@@ -0,0 +1,24 @@
+//
+// Created by Dan Walmsley on 04/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#ifndef AVALONIA_NATIVE_OSX_RESIZESCOPE_H
+#define AVALONIA_NATIVE_OSX_RESIZESCOPE_H
+
+#include "avalonia-native.h"
+
+@class AvnView;
+
+class ResizeScope
+{
+public:
+ ResizeScope(AvnView* _Nonnull view, AvnPlatformResizeReason reason);
+
+ ~ResizeScope();
+private:
+ AvnView* _Nonnull _view;
+ AvnPlatformResizeReason _restore;
+};
+
+#endif //AVALONIA_NATIVE_OSX_RESIZESCOPE_H
diff --git a/native/Avalonia.Native/src/OSX/ResizeScope.mm b/native/Avalonia.Native/src/OSX/ResizeScope.mm
new file mode 100644
index 0000000000..9f1177af8b
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/ResizeScope.mm
@@ -0,0 +1,18 @@
+//
+// Created by Dan Walmsley on 04/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#import
+#include "ResizeScope.h"
+#include "AvnView.h"
+
+ResizeScope::ResizeScope(AvnView *view, AvnPlatformResizeReason reason) {
+ _view = view;
+ _restore = [view getResizeReason];
+ [view setResizeReason:reason];
+}
+
+ResizeScope::~ResizeScope() {
+ [_view setResizeReason:_restore];
+}
diff --git a/native/Avalonia.Native/src/OSX/SystemDialogs.mm b/native/Avalonia.Native/src/OSX/SystemDialogs.mm
index a47221056b..535b6c3b66 100644
--- a/native/Avalonia.Native/src/OSX/SystemDialogs.mm
+++ b/native/Avalonia.Native/src/OSX/SystemDialogs.mm
@@ -1,5 +1,5 @@
#include "common.h"
-#include "window.h"
+#include "INSWindowHolder.h"
class SystemDialogs : public ComSingleObject
{
diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h
new file mode 100644
index 0000000000..eff13bcb23
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h
@@ -0,0 +1,130 @@
+//
+// Created by Dan Walmsley on 04/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#ifndef AVALONIA_NATIVE_OSX_WINDOWBASEIMPL_H
+#define AVALONIA_NATIVE_OSX_WINDOWBASEIMPL_H
+
+#include "rendertarget.h"
+#include "INSWindowHolder.h"
+
+@class AutoFitContentView;
+@class AvnMenu;
+@protocol AvnWindowProtocol;
+
+class WindowBaseImpl : public virtual ComObject,
+ public virtual IAvnWindowBase,
+ public INSWindowHolder {
+private:
+ NSCursor *cursor;
+
+public:
+ FORWARD_IUNKNOWN()
+
+BEGIN_INTERFACE_MAP()
+ INTERFACE_MAP_ENTRY(IAvnWindowBase, IID_IAvnWindowBase)
+ END_INTERFACE_MAP()
+
+ virtual ~WindowBaseImpl();
+
+ AutoFitContentView *StandardContainer;
+ AvnView *View;
+ NSWindow * Window;
+ ComPtr BaseEvents;
+ ComPtr _glContext;
+ NSObject *renderTarget;
+ AvnPoint lastPositionSet;
+ NSSize lastSize;
+ NSSize lastMinSize;
+ NSSize lastMaxSize;
+ AvnMenu* lastMenu;
+ NSString *_lastTitle;
+
+ bool _shown;
+ bool _inResize;
+
+ WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl);
+
+ virtual HRESULT ObtainNSWindowHandle(void **ret) override;
+
+ virtual HRESULT ObtainNSWindowHandleRetained(void **ret) override;
+
+ virtual HRESULT ObtainNSViewHandle(void **ret) override;
+
+ virtual HRESULT ObtainNSViewHandleRetained(void **ret) override;
+
+ virtual NSWindow *GetNSWindow() override;
+
+ virtual NSView *GetNSView() override;
+
+ virtual HRESULT Show(bool activate, bool isDialog) override;
+
+ virtual bool ShouldTakeFocusOnShow();
+
+ virtual HRESULT Hide() override;
+
+ virtual HRESULT Activate() override;
+
+ virtual HRESULT SetTopMost(bool value) override;
+
+ virtual HRESULT Close() override;
+
+ virtual HRESULT GetClientSize(AvnSize *ret) override;
+
+ virtual HRESULT GetFrameSize(AvnSize *ret) override;
+
+ virtual HRESULT GetScaling(double *ret) override;
+
+ virtual HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize) override;
+
+ virtual HRESULT Resize(double x, double y, AvnPlatformResizeReason reason) override;
+
+ virtual HRESULT Invalidate(__attribute__((unused)) AvnRect rect) override;
+
+ virtual HRESULT SetMainMenu(IAvnMenu *menu) override;
+
+ virtual HRESULT BeginMoveDrag() override;
+
+ virtual HRESULT BeginResizeDrag(__attribute__((unused)) AvnWindowEdge edge) override;
+
+ virtual HRESULT GetPosition(AvnPoint *ret) override;
+
+ virtual HRESULT SetPosition(AvnPoint point) override;
+
+ virtual HRESULT PointToClient(AvnPoint point, AvnPoint *ret) override;
+
+ virtual HRESULT PointToScreen(AvnPoint point, AvnPoint *ret) override;
+
+ virtual HRESULT ThreadSafeSetSwRenderedFrame(AvnFramebuffer *fb, IUnknown *dispose) override;
+
+ virtual HRESULT SetCursor(IAvnCursor *cursor) override;
+
+ virtual void UpdateCursor();
+
+ virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget **ppv) override;
+
+ virtual HRESULT CreateNativeControlHost(IAvnNativeControlHost **retOut) override;
+
+ virtual HRESULT SetBlurEnabled(bool enable) override;
+
+ virtual HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point,
+ IAvnClipboard *clipboard, IAvnDndResultCallback *cb,
+ void *sourceHandle) override;
+
+ virtual bool IsDialog();
+
+ id GetWindowProtocol ();
+
+protected:
+ virtual NSWindowStyleMask GetStyle();
+
+ void UpdateStyle();
+
+private:
+ void CreateNSWindow (bool isDialog);
+ void CleanNSWindow ();
+ void InitialiseNSWindow ();
+};
+
+#endif //AVALONIA_NATIVE_OSX_WINDOWBASEIMPL_H
diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
new file mode 100644
index 0000000000..db5eb54e3f
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
@@ -0,0 +1,589 @@
+//
+// Created by Dan Walmsley on 04/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#import
+#include "common.h"
+#include "AvnView.h"
+#include "menu.h"
+#include "automation.h"
+#include "cursor.h"
+#include "ResizeScope.h"
+#include "AutoFitContentView.h"
+#import "WindowProtocol.h"
+#import "WindowInterfaces.h"
+#include "WindowBaseImpl.h"
+
+
+WindowBaseImpl::~WindowBaseImpl() {
+ View = nullptr;
+ Window = nullptr;
+}
+
+WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl) {
+ _shown = false;
+ _inResize = false;
+ BaseEvents = events;
+ _glContext = gl;
+ renderTarget = [[IOSurfaceRenderTarget alloc] initWithOpenGlContext:gl];
+ View = [[AvnView alloc] initWithParent:this];
+ StandardContainer = [[AutoFitContentView new] initWithContent:View];
+
+ lastPositionSet.X = 100;
+ lastPositionSet.Y = 100;
+ lastSize = NSSize { 100, 100 };
+ lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX};
+ lastMinSize = NSSize { 0, 0 };
+ _lastTitle = @"";
+
+ Window = nullptr;
+ lastMenu = nullptr;
+}
+
+HRESULT WindowBaseImpl::ObtainNSViewHandle(void **ret) {
+ START_COM_CALL;
+
+ if (ret == nullptr) {
+ return E_POINTER;
+ }
+
+ *ret = (__bridge void *) View;
+
+ return S_OK;
+}
+
+HRESULT WindowBaseImpl::ObtainNSViewHandleRetained(void **ret) {
+ START_COM_CALL;
+
+ if (ret == nullptr) {
+ return E_POINTER;
+ }
+
+ *ret = (__bridge_retained void *) View;
+
+ return S_OK;
+}
+
+NSWindow *WindowBaseImpl::GetNSWindow() {
+ return Window;
+}
+
+NSView *WindowBaseImpl::GetNSView() {
+ return View;
+}
+
+HRESULT WindowBaseImpl::ObtainNSWindowHandleRetained(void **ret) {
+ START_COM_CALL;
+
+ if (ret == nullptr) {
+ return E_POINTER;
+ }
+
+ *ret = (__bridge_retained void *) Window;
+
+ return S_OK;
+}
+
+HRESULT WindowBaseImpl::Show(bool activate, bool isDialog) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ CreateNSWindow(isDialog);
+ InitialiseNSWindow();
+
+ SetPosition(lastPositionSet);
+ UpdateStyle();
+
+ [Window setTitle:_lastTitle];
+
+ if (ShouldTakeFocusOnShow() && activate) {
+ [Window orderFront:Window];
+ [Window makeKeyAndOrderFront:Window];
+ [Window makeFirstResponder:View];
+ [NSApp activateIgnoringOtherApps:YES];
+ } else {
+ [Window orderFront:Window];
+ }
+
+ _shown = true;
+
+ return S_OK;
+ }
+}
+
+bool WindowBaseImpl::ShouldTakeFocusOnShow() {
+ return true;
+}
+
+HRESULT WindowBaseImpl::ObtainNSWindowHandle(void **ret) {
+ START_COM_CALL;
+
+ if (ret == nullptr) {
+ return E_POINTER;
+ }
+
+ *ret = (__bridge void *) Window;
+
+ return S_OK;
+}
+
+HRESULT WindowBaseImpl::Hide() {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ if (Window != nullptr) {
+ [Window orderOut:Window];
+
+ [GetWindowProtocol() restoreParentWindow];
+ }
+
+ return S_OK;
+ }
+}
+
+HRESULT WindowBaseImpl::Activate() {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ if (Window != nullptr) {
+ [Window makeKeyAndOrderFront:nil];
+ [NSApp activateIgnoringOtherApps:YES];
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT WindowBaseImpl::SetTopMost(bool value) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ [Window setLevel:value ? NSFloatingWindowLevel : NSNormalWindowLevel];
+
+ return S_OK;
+ }
+}
+
+HRESULT WindowBaseImpl::Close() {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ if (Window != nullptr) {
+ auto window = Window;
+ Window = nullptr;
+
+ try {
+ // Seems to throw sometimes on application exit.
+ [window close];
+ }
+ catch (NSException *) {}
+ }
+
+ return S_OK;
+ }
+}
+
+HRESULT WindowBaseImpl::GetClientSize(AvnSize *ret) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ if (ret == nullptr)
+ return E_POINTER;
+
+ auto frame = [View frame];
+ ret->Width = frame.size.width;
+ ret->Height = frame.size.height;
+
+ return S_OK;
+ }
+}
+
+HRESULT WindowBaseImpl::GetFrameSize(AvnSize *ret) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ if (ret == nullptr)
+ return E_POINTER;
+
+ auto frame = [Window frame];
+ ret->Width = frame.size.width;
+ ret->Height = frame.size.height;
+
+ return S_OK;
+ }
+}
+
+HRESULT WindowBaseImpl::GetScaling(double *ret) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ if (ret == nullptr)
+ return E_POINTER;
+
+ if (Window == nullptr) {
+ *ret = 1;
+ return S_OK;
+ }
+
+ *ret = [Window backingScaleFactor];
+ return S_OK;
+ }
+}
+
+HRESULT WindowBaseImpl::SetMinMaxSize(AvnSize minSize, AvnSize maxSize) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ lastMinSize = ToNSSize(minSize);
+ lastMaxSize = ToNSSize(maxSize);
+
+ if(Window != nullptr) {
+ [Window setContentMinSize:lastMinSize];
+ [Window setContentMaxSize:lastMaxSize];
+ }
+
+ return S_OK;
+ }
+}
+
+HRESULT WindowBaseImpl::Resize(double x, double y, AvnPlatformResizeReason reason) {
+ if (_inResize) {
+ return S_OK;
+ }
+
+ _inResize = true;
+
+ START_COM_CALL;
+ auto resizeBlock = ResizeScope(View, reason);
+
+ @autoreleasepool {
+ auto maxSize = lastMaxSize;
+ auto minSize = lastMinSize;
+
+ if (x < minSize.width) {
+ x = minSize.width;
+ }
+
+ if (y < minSize.height) {
+ y = minSize.height;
+ }
+
+ if (x > maxSize.width) {
+ x = maxSize.width;
+ }
+
+ if (y > maxSize.height) {
+ y = maxSize.height;
+ }
+
+ @try {
+ if (!_shown) {
+ BaseEvents->Resized(AvnSize{x, y}, reason);
+ }
+
+ lastSize = NSSize {x, y};
+
+ if(Window != nullptr) {
+ [Window setContentSize:lastSize];
+ }
+ }
+ @finally {
+ _inResize = false;
+ }
+
+ return S_OK;
+ }
+}
+
+HRESULT WindowBaseImpl::Invalidate(__attribute__((unused)) AvnRect rect) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ [View setNeedsDisplayInRect:[View frame]];
+
+ return S_OK;
+ }
+}
+
+HRESULT WindowBaseImpl::SetMainMenu(IAvnMenu *menu) {
+ START_COM_CALL;
+
+ auto nativeMenu = dynamic_cast(menu);
+
+ lastMenu = nativeMenu->GetNative();
+
+ if(Window != nullptr) {
+ [GetWindowProtocol() applyMenu:lastMenu];
+
+ if ([Window isKeyWindow]) {
+ [GetWindowProtocol() showWindowMenuWithAppMenu];
+ }
+ }
+
+ return S_OK;
+}
+
+HRESULT WindowBaseImpl::BeginMoveDrag() {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ auto lastEvent = [View lastMouseDownEvent];
+
+ if (lastEvent == nullptr) {
+ return S_OK;
+ }
+
+ [Window performWindowDragWithEvent:lastEvent];
+
+ return S_OK;
+ }
+}
+
+HRESULT WindowBaseImpl::BeginResizeDrag(__attribute__((unused)) AvnWindowEdge edge) {
+ START_COM_CALL;
+
+ return S_OK;
+}
+
+HRESULT WindowBaseImpl::GetPosition(AvnPoint *ret) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ if (ret == nullptr) {
+ return E_POINTER;
+ }
+
+ auto frame = [Window frame];
+
+ ret->X = frame.origin.x;
+ ret->Y = frame.origin.y + frame.size.height;
+
+ *ret = ConvertPointY(*ret);
+
+ return S_OK;
+ }
+}
+
+HRESULT WindowBaseImpl::SetPosition(AvnPoint point) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ lastPositionSet = point;
+ [Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(point))];
+
+ return S_OK;
+ }
+}
+
+HRESULT WindowBaseImpl::PointToClient(AvnPoint point, AvnPoint *ret) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ if (ret == nullptr) {
+ return E_POINTER;
+ }
+
+ point = ConvertPointY(point);
+ NSRect convertRect = [Window convertRectFromScreen:NSMakeRect(point.X, point.Y, 0.0, 0.0)];
+ auto viewPoint = NSMakePoint(convertRect.origin.x, convertRect.origin.y);
+
+ *ret = [View translateLocalPoint:ToAvnPoint(viewPoint)];
+
+ return S_OK;
+ }
+}
+
+HRESULT WindowBaseImpl::PointToScreen(AvnPoint point, AvnPoint *ret) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ if (ret == nullptr) {
+ return E_POINTER;
+ }
+
+ auto cocoaViewPoint = ToNSPoint([View translateLocalPoint:point]);
+ NSRect convertRect = [Window convertRectToScreen:NSMakeRect(cocoaViewPoint.x, cocoaViewPoint.y, 0.0, 0.0)];
+ auto cocoaScreenPoint = NSPointFromCGPoint(NSMakePoint(convertRect.origin.x, convertRect.origin.y));
+ *ret = ConvertPointY(ToAvnPoint(cocoaScreenPoint));
+
+ return S_OK;
+ }
+}
+
+HRESULT WindowBaseImpl::ThreadSafeSetSwRenderedFrame(AvnFramebuffer *fb, IUnknown *dispose) {
+ START_COM_CALL;
+
+ [View setSwRenderedFrame:fb dispose:dispose];
+ return S_OK;
+}
+
+HRESULT WindowBaseImpl::SetCursor(IAvnCursor *cursor) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ Cursor *avnCursor = dynamic_cast(cursor);
+ this->cursor = avnCursor->GetNative();
+ UpdateCursor();
+
+ if (avnCursor->IsHidden()) {
+ [NSCursor hide];
+ } else {
+ [NSCursor unhide];
+ }
+
+ return S_OK;
+ }
+}
+
+void WindowBaseImpl::UpdateCursor() {
+ if (cursor != nil) {
+ [cursor set];
+ }
+}
+
+HRESULT WindowBaseImpl::CreateGlRenderTarget(IAvnGlSurfaceRenderTarget **ppv) {
+ START_COM_CALL;
+
+ if (View == NULL)
+ return E_FAIL;
+ *ppv = [renderTarget createSurfaceRenderTarget];
+ return static_cast(*ppv == nil ? E_FAIL : S_OK);
+}
+
+HRESULT WindowBaseImpl::CreateNativeControlHost(IAvnNativeControlHost **retOut) {
+ START_COM_CALL;
+
+ if (View == NULL)
+ return E_FAIL;
+ *retOut = ::CreateNativeControlHost(View);
+ return S_OK;
+}
+
+HRESULT WindowBaseImpl::SetBlurEnabled(bool enable) {
+ START_COM_CALL;
+
+ [StandardContainer ShowBlur:enable];
+
+ return S_OK;
+}
+
+HRESULT WindowBaseImpl::BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point, IAvnClipboard *clipboard, IAvnDndResultCallback *cb, void *sourceHandle) {
+ START_COM_CALL;
+
+ auto item = TryGetPasteboardItem(clipboard);
+ [item setString:@"" forType:GetAvnCustomDataType()];
+ if (item == nil)
+ return E_INVALIDARG;
+ if (View == NULL)
+ return E_FAIL;
+
+ auto nsevent = [NSApp currentEvent];
+ auto nseventType = [nsevent type];
+
+ // If current event isn't a mouse one (probably due to malfunctioning user app)
+ // attempt to forge a new one
+ if (!((nseventType >= NSEventTypeLeftMouseDown && nseventType <= NSEventTypeMouseExited)
+ || (nseventType >= NSEventTypeOtherMouseDown && nseventType <= NSEventTypeOtherMouseDragged))) {
+ NSRect convertRect = [Window convertRectToScreen:NSMakeRect(point.X, point.Y, 0.0, 0.0)];
+ auto nspoint = NSMakePoint(convertRect.origin.x, convertRect.origin.y);
+ CGPoint cgpoint = NSPointToCGPoint(nspoint);
+ auto cgevent = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, cgpoint, kCGMouseButtonLeft);
+ nsevent = [NSEvent eventWithCGEvent:cgevent];
+ CFRelease(cgevent);
+ }
+
+ auto dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:item];
+
+ auto dragItemImage = [NSImage imageNamed:NSImageNameMultipleDocuments];
+ NSRect dragItemRect = {(float) point.X, (float) point.Y, [dragItemImage size].width, [dragItemImage size].height};
+ [dragItem setDraggingFrame:dragItemRect contents:dragItemImage];
+
+ int op = 0;
+ int ieffects = (int) effects;
+ if ((ieffects & (int) AvnDragDropEffects::Copy) != 0)
+ op |= NSDragOperationCopy;
+ if ((ieffects & (int) AvnDragDropEffects::Link) != 0)
+ op |= NSDragOperationLink;
+ if ((ieffects & (int) AvnDragDropEffects::Move) != 0)
+ op |= NSDragOperationMove;
+ [View beginDraggingSessionWithItems:@[dragItem] event:nsevent
+ source:CreateDraggingSource((NSDragOperation) op, cb, sourceHandle)];
+ return S_OK;
+}
+
+bool WindowBaseImpl::IsDialog() {
+ return false;
+}
+
+NSWindowStyleMask WindowBaseImpl::GetStyle() {
+ return NSWindowStyleMaskBorderless;
+}
+
+void WindowBaseImpl::UpdateStyle() {
+ [Window setStyleMask:GetStyle()];
+}
+
+void WindowBaseImpl::CleanNSWindow() {
+ if(Window != nullptr) {
+ [GetWindowProtocol() disconnectParent];
+ [Window close];
+ Window = nullptr;
+ }
+}
+
+void WindowBaseImpl::CreateNSWindow(bool isDialog) {
+ if (isDialog) {
+ if (![Window isKindOfClass:[AvnPanel class]]) {
+ CleanNSWindow();
+
+ Window = [[AvnPanel alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()];
+ }
+ } else {
+ if (![Window isKindOfClass:[AvnWindow class]]) {
+ CleanNSWindow();
+
+ Window = [[AvnWindow alloc] initWithParent:this contentRect:NSRect{0, 0, lastSize} styleMask:GetStyle()];
+ }
+ }
+}
+
+void WindowBaseImpl::InitialiseNSWindow() {
+ if(Window != nullptr) {
+ [Window setContentView:StandardContainer];
+ [Window setStyleMask:NSWindowStyleMaskBorderless];
+ [Window setBackingType:NSBackingStoreBuffered];
+
+ [Window setContentSize:lastSize];
+ [Window setContentMinSize:lastMinSize];
+ [Window setContentMaxSize:lastMaxSize];
+
+ [Window setOpaque:false];
+
+ if (lastMenu != nullptr) {
+ [GetWindowProtocol() applyMenu:lastMenu];
+
+ if ([Window isKeyWindow]) {
+ [GetWindowProtocol() showWindowMenuWithAppMenu];
+ }
+ }
+ }
+}
+
+id WindowBaseImpl::GetWindowProtocol() {
+ if(Window == nullptr)
+ {
+ return nullptr;
+ }
+
+ return static_cast>(Window);
+}
+
+extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl)
+{
+ @autoreleasepool
+ {
+ IAvnWindow* ptr = (IAvnWindow*)new WindowImpl(events, gl);
+ return ptr;
+ }
+}
diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.h b/native/Avalonia.Native/src/OSX/WindowImpl.h
new file mode 100644
index 0000000000..a4ee4f447c
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/WindowImpl.h
@@ -0,0 +1,96 @@
+//
+// Created by Dan Walmsley on 04/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#ifndef AVALONIA_NATIVE_OSX_WINDOWIMPL_H
+#define AVALONIA_NATIVE_OSX_WINDOWIMPL_H
+
+#import "WindowBaseImpl.h"
+#include "IWindowStateChanged.h"
+
+class WindowImpl : public virtual WindowBaseImpl, public virtual IAvnWindow, public IWindowStateChanged
+{
+private:
+ bool _canResize;
+ bool _fullScreenActive;
+ SystemDecorations _decorations;
+ AvnWindowState _lastWindowState;
+ AvnWindowState _actualWindowState;
+ bool _inSetWindowState;
+ NSRect _preZoomSize;
+ bool _transitioningWindowState;
+ bool _isClientAreaExtended;
+ bool _isDialog;
+ AvnExtendClientAreaChromeHints _extendClientHints;
+
+ FORWARD_IUNKNOWN()
+BEGIN_INTERFACE_MAP()
+ INHERIT_INTERFACE_MAP(WindowBaseImpl)
+ INTERFACE_MAP_ENTRY(IAvnWindow, IID_IAvnWindow)
+ END_INTERFACE_MAP()
+ virtual ~WindowImpl()
+ {
+ }
+
+ ComPtr WindowEvents;
+
+ WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl);
+
+ void HideOrShowTrafficLights ();
+
+ virtual HRESULT Show (bool activate, bool isDialog) override;
+
+ virtual HRESULT SetEnabled (bool enable) override;
+
+ virtual HRESULT SetParent (IAvnWindow* parent) override;
+
+ void StartStateTransition () override ;
+
+ void EndStateTransition () override ;
+
+ SystemDecorations Decorations () override ;
+
+ AvnWindowState WindowState () override ;
+
+ void WindowStateChanged () override ;
+
+ bool UndecoratedIsMaximized ();
+
+ bool IsZoomed ();
+
+ void DoZoom();
+
+ virtual HRESULT SetCanResize(bool value) override;
+
+ virtual HRESULT SetDecorations(SystemDecorations value) override;
+
+ virtual HRESULT SetTitle (char* utf8title) override;
+
+ virtual HRESULT SetTitleBarColor(AvnColor color) override;
+
+ virtual HRESULT GetWindowState (AvnWindowState*ret) override;
+
+ virtual HRESULT TakeFocusFromChildren () override;
+
+ virtual HRESULT SetExtendClientArea (bool enable) override;
+
+ virtual HRESULT SetExtendClientAreaHints (AvnExtendClientAreaChromeHints hints) override;
+
+ virtual HRESULT GetExtendTitleBarHeight (double*ret) override;
+
+ virtual HRESULT SetExtendTitleBarHeight (double value) override;
+
+ void EnterFullScreenMode ();
+
+ void ExitFullScreenMode ();
+
+ virtual HRESULT SetWindowState (AvnWindowState state) override;
+
+ virtual bool IsDialog() override;
+
+protected:
+ virtual NSWindowStyleMask GetStyle() override;
+};
+
+#endif //AVALONIA_NATIVE_OSX_WINDOWIMPL_H
diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm
new file mode 100644
index 0000000000..7ab2b2b5fc
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm
@@ -0,0 +1,552 @@
+//
+// Created by Dan Walmsley on 04/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#import
+#include "AutoFitContentView.h"
+#include "AvnView.h"
+#include "automation.h"
+#include "WindowProtocol.h"
+
+WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBaseImpl(events, gl) {
+ _isClientAreaExtended = false;
+ _extendClientHints = AvnDefaultChrome;
+ _fullScreenActive = false;
+ _canResize = true;
+ _decorations = SystemDecorationsFull;
+ _transitioningWindowState = false;
+ _inSetWindowState = false;
+ _lastWindowState = Normal;
+ _actualWindowState = Normal;
+ WindowEvents = events;
+ [Window disableCursorRects];
+ [Window setTabbingMode:NSWindowTabbingModeDisallowed];
+ [Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
+}
+
+void WindowImpl::HideOrShowTrafficLights() {
+ if (Window == nil) {
+ return;
+ }
+
+ 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]]) {
+ if (_isClientAreaExtended) {
+ auto wantsChrome = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
+
+ [button setHidden:!wantsChrome];
+ } else {
+ [button setHidden:(_decorations != SystemDecorationsFull)];
+ }
+
+ [button setWantsLayer:true];
+ }
+ }
+ }
+ }
+}
+
+HRESULT WindowImpl::Show(bool activate, bool isDialog) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ _isDialog = isDialog;
+
+ bool created = Window == nullptr;
+
+ WindowBaseImpl::Show(activate, isDialog);
+
+ if(created)
+ {
+ if(_isClientAreaExtended)
+ {
+ [GetWindowProtocol() setIsExtended:true];
+ SetExtendClientArea(true);
+ }
+ }
+
+ HideOrShowTrafficLights();
+
+ return SetWindowState(_lastWindowState);
+ }
+}
+
+HRESULT WindowImpl::SetEnabled(bool enable) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ [GetWindowProtocol() setEnabled:enable];
+ return S_OK;
+ }
+}
+
+HRESULT WindowImpl::SetParent(IAvnWindow *parent) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ if (parent == nullptr)
+ return E_POINTER;
+
+ auto cparent = dynamic_cast(parent);
+ if (cparent == nullptr)
+ return E_INVALIDARG;
+
+ // If one tries to show a child window with a minimized parent window, then the parent window will be
+ // restored but macOS isn't kind enough to *tell* us that, so the window will be left in a non-interactive
+ // state. Detect this and explicitly restore the parent window ourselves to avoid this situation.
+ if (cparent->WindowState() == Minimized)
+ cparent->SetWindowState(Normal);
+
+ [Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary];
+ [cparent->Window addChildWindow:Window ordered:NSWindowAbove];
+
+ UpdateStyle();
+
+ return S_OK;
+ }
+}
+
+void WindowImpl::StartStateTransition() {
+ _transitioningWindowState = true;
+}
+
+void WindowImpl::EndStateTransition() {
+ _transitioningWindowState = false;
+}
+
+SystemDecorations WindowImpl::Decorations() {
+ return _decorations;
+}
+
+AvnWindowState WindowImpl::WindowState() {
+ return _lastWindowState;
+}
+
+void WindowImpl::WindowStateChanged() {
+ if (_shown && !_inSetWindowState && !_transitioningWindowState) {
+ AvnWindowState state;
+ GetWindowState(&state);
+
+ if (_lastWindowState != state) {
+ if (_isClientAreaExtended) {
+ if (_lastWindowState == FullScreen) {
+ // we exited fs.
+ if (_extendClientHints & AvnOSXThickTitleBar) {
+ Window.toolbar = [NSToolbar new];
+ Window.toolbar.showsBaselineSeparator = false;
+ }
+
+ [Window setTitlebarAppearsTransparent:true];
+
+ [StandardContainer setFrameSize:StandardContainer.frame.size];
+ } else if (state == FullScreen) {
+ // we entered fs.
+ if (_extendClientHints & AvnOSXThickTitleBar) {
+ Window.toolbar = nullptr;
+ }
+
+ [Window setTitlebarAppearsTransparent:false];
+
+ [StandardContainer setFrameSize:StandardContainer.frame.size];
+ }
+ }
+
+ _lastWindowState = state;
+ _actualWindowState = state;
+ WindowEvents->WindowStateChanged(state);
+ }
+ }
+}
+
+bool WindowImpl::UndecoratedIsMaximized() {
+ auto windowSize = [Window frame];
+ auto available = [Window screen].visibleFrame;
+ return CGRectEqualToRect(windowSize, available);
+}
+
+bool WindowImpl::IsZoomed() {
+ return _decorations == SystemDecorationsFull ? [Window isZoomed] : UndecoratedIsMaximized();
+}
+
+void WindowImpl::DoZoom() {
+ switch (_decorations) {
+ case SystemDecorationsNone:
+ case SystemDecorationsBorderOnly:
+ [Window setFrame:[Window screen].visibleFrame display:true];
+ break;
+
+
+ case SystemDecorationsFull:
+ [Window performZoom:Window];
+ break;
+ }
+}
+
+HRESULT WindowImpl::SetCanResize(bool value) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ _canResize = value;
+ UpdateStyle();
+ return S_OK;
+ }
+}
+
+HRESULT WindowImpl::SetDecorations(SystemDecorations value) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ auto currentWindowState = _lastWindowState;
+ _decorations = value;
+
+ if (_fullScreenActive) {
+ return S_OK;
+ }
+
+ UpdateStyle();
+
+ HideOrShowTrafficLights();
+
+ 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:
+ [Window setHasShadow:YES];
+ [Window setTitleVisibility:NSWindowTitleVisible];
+ [Window setTitlebarAppearsTransparent:NO];
+ [Window setTitle:_lastTitle];
+
+ if (currentWindowState == Maximized) {
+ auto newFrame = [Window contentRectForFrameRect:[Window frame]].size;
+
+ [View setFrameSize:newFrame];
+ }
+ break;
+ }
+
+ return S_OK;
+ }
+}
+
+HRESULT WindowImpl::SetTitle(char *utf8title) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ _lastTitle = [NSString stringWithUTF8String:(const char *) utf8title];
+ [Window setTitle:_lastTitle];
+
+ return S_OK;
+ }
+}
+
+HRESULT WindowImpl::SetTitleBarColor(AvnColor color) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ float a = (float) color.Alpha / 255.0f;
+ float r = (float) color.Red / 255.0f;
+ float g = (float) color.Green / 255.0f;
+ float b = (float) color.Blue / 255.0f;
+
+ auto nscolor = [NSColor colorWithSRGBRed:r green:g blue:b alpha:a];
+
+ // Based on the titlebar color we have to choose either light or dark
+ // OSX doesnt let you set a foreground color for titlebar.
+ if ((r * 0.299 + g * 0.587 + b * 0.114) > 186.0f / 255.0f) {
+ [Window setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantLight]];
+ } else {
+ [Window setAppearance:[NSAppearance appearanceNamed:NSAppearanceNameVibrantDark]];
+ }
+
+ [Window setTitlebarAppearsTransparent:true];
+ [Window setBackgroundColor:nscolor];
+ }
+
+ return S_OK;
+}
+
+HRESULT WindowImpl::GetWindowState(AvnWindowState *ret) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ if (ret == nullptr) {
+ return E_POINTER;
+ }
+
+ if (([Window styleMask] & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen) {
+ *ret = FullScreen;
+ return S_OK;
+ }
+
+ if ([Window isMiniaturized]) {
+ *ret = Minimized;
+ return S_OK;
+ }
+
+ if (IsZoomed()) {
+ *ret = Maximized;
+ return S_OK;
+ }
+
+ *ret = Normal;
+
+ return S_OK;
+ }
+}
+
+HRESULT WindowImpl::TakeFocusFromChildren() {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ if (Window == nil)
+ return S_OK;
+ if ([Window isKeyWindow])
+ [Window makeFirstResponder:View];
+
+ return S_OK;
+ }
+}
+
+HRESULT WindowImpl::SetExtendClientArea(bool enable) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ _isClientAreaExtended = enable;
+
+ if(Window != nullptr) {
+ if (enable) {
+ Window.titleVisibility = NSWindowTitleHidden;
+
+ [Window setTitlebarAppearsTransparent:true];
+
+ auto wantsTitleBar = (_extendClientHints & AvnSystemChrome) || (_extendClientHints & AvnPreferSystemChrome);
+
+ if (wantsTitleBar) {
+ [StandardContainer ShowTitleBar:true];
+ } else {
+ [StandardContainer ShowTitleBar:false];
+ }
+
+ if (_extendClientHints & AvnOSXThickTitleBar) {
+ Window.toolbar = [NSToolbar new];
+ Window.toolbar.showsBaselineSeparator = false;
+ } else {
+ Window.toolbar = nullptr;
+ }
+ } else {
+ Window.titleVisibility = NSWindowTitleVisible;
+ Window.toolbar = nullptr;
+ [Window setTitlebarAppearsTransparent:false];
+ View.layer.zPosition = 0;
+ }
+
+ [GetWindowProtocol() setIsExtended:enable];
+
+ HideOrShowTrafficLights();
+
+ UpdateStyle();
+ }
+
+ return S_OK;
+ }
+}
+
+HRESULT WindowImpl::SetExtendClientAreaHints(AvnExtendClientAreaChromeHints hints) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ _extendClientHints = hints;
+
+ SetExtendClientArea(_isClientAreaExtended);
+ return S_OK;
+ }
+}
+
+HRESULT WindowImpl::GetExtendTitleBarHeight(double *ret) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ if (ret == nullptr) {
+ return E_POINTER;
+ }
+
+ *ret = [GetWindowProtocol() getExtendedTitleBarHeight];
+
+ return S_OK;
+ }
+}
+
+HRESULT WindowImpl::SetExtendTitleBarHeight(double value) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ [StandardContainer SetTitleBarHeightHint:value];
+ return S_OK;
+ }
+}
+
+void WindowImpl::EnterFullScreenMode() {
+ _fullScreenActive = true;
+
+ [Window setTitle:_lastTitle];
+ [Window toggleFullScreen:nullptr];
+}
+
+void WindowImpl::ExitFullScreenMode() {
+ [Window toggleFullScreen:nullptr];
+
+ _fullScreenActive = false;
+
+ SetDecorations(_decorations);
+}
+
+HRESULT WindowImpl::SetWindowState(AvnWindowState state) {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ if (Window == nullptr) {
+ return S_OK;
+ }
+
+ if (_actualWindowState == state) {
+ return S_OK;
+ }
+
+ _inSetWindowState = true;
+
+ auto currentState = _actualWindowState;
+ _lastWindowState = state;
+
+ if (currentState == Normal) {
+ _preZoomSize = [Window frame];
+ }
+
+ if (_shown) {
+ switch (state) {
+ case Maximized:
+ if (currentState == FullScreen) {
+ ExitFullScreenMode();
+ }
+
+ lastPositionSet.X = 0;
+ lastPositionSet.Y = 0;
+
+ if ([Window isMiniaturized]) {
+ [Window deminiaturize:Window];
+ }
+
+ if (!IsZoomed()) {
+ DoZoom();
+ }
+ break;
+
+ case Minimized:
+ if (currentState == FullScreen) {
+ ExitFullScreenMode();
+ } else {
+ [Window miniaturize:Window];
+ }
+ break;
+
+ case FullScreen:
+ if ([Window isMiniaturized]) {
+ [Window deminiaturize:Window];
+ }
+
+ EnterFullScreenMode();
+ break;
+
+ case Normal:
+ if ([Window isMiniaturized]) {
+ [Window deminiaturize:Window];
+ }
+
+ if (currentState == FullScreen) {
+ ExitFullScreenMode();
+ }
+
+ if (IsZoomed()) {
+ if (_decorations == SystemDecorationsFull) {
+ DoZoom();
+ } else {
+ [Window setFrame:_preZoomSize display:true];
+ auto newFrame = [Window contentRectForFrameRect:[Window frame]].size;
+
+ [View setFrameSize:newFrame];
+ }
+
+ }
+ break;
+ }
+
+ _actualWindowState = _lastWindowState;
+ WindowEvents->WindowStateChanged(_actualWindowState);
+ }
+
+
+ _inSetWindowState = false;
+
+ return S_OK;
+ }
+}
+
+bool WindowImpl::IsDialog() {
+ return _isDialog;
+}
+
+NSWindowStyleMask WindowImpl::GetStyle() {
+ unsigned long s = this->_isDialog ? NSWindowStyleMaskUtilityWindow : NSWindowStyleMaskBorderless;
+
+ switch (_decorations) {
+ case SystemDecorationsNone:
+ s = s | NSWindowStyleMaskFullSizeContentView;
+ break;
+
+ case SystemDecorationsBorderOnly:
+ s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskFullSizeContentView;
+ break;
+
+ case SystemDecorationsFull:
+ s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskBorderless;
+
+ if (_canResize) {
+ s = s | NSWindowStyleMaskResizable;
+ }
+ break;
+ }
+
+ if ([Window parentWindow] == nullptr) {
+ s |= NSWindowStyleMaskMiniaturizable;
+ }
+
+ if (_isClientAreaExtended) {
+ s |= NSWindowStyleMaskFullSizeContentView | NSWindowStyleMaskTexturedBackground;
+ }
+ return s;
+}
diff --git a/native/Avalonia.Native/src/OSX/WindowInterfaces.h b/native/Avalonia.Native/src/OSX/WindowInterfaces.h
new file mode 100644
index 0000000000..6e6d62e85e
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/WindowInterfaces.h
@@ -0,0 +1,17 @@
+//
+// Created by Dan Walmsley on 06/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#import
+#import
+#include "WindowProtocol.h"
+#include "WindowBaseImpl.h"
+
+@interface AvnWindow : NSWindow
+-(AvnWindow* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask;
+@end
+
+@interface AvnPanel : NSPanel
+-(AvnPanel* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent contentRect: (NSRect)contentRect styleMask: (NSWindowStyleMask)styleMask;
+@end
\ No newline at end of file
diff --git a/native/Avalonia.Native/src/OSX/WindowProtocol.h b/native/Avalonia.Native/src/OSX/WindowProtocol.h
new file mode 100644
index 0000000000..92194706de
--- /dev/null
+++ b/native/Avalonia.Native/src/OSX/WindowProtocol.h
@@ -0,0 +1,25 @@
+//
+// Created by Dan Walmsley on 06/05/2022.
+// Copyright (c) 2022 Avalonia. All rights reserved.
+//
+
+#pragma once
+
+#import
+
+@class AvnMenu;
+
+@protocol AvnWindowProtocol
+-(void) pollModalSession: (NSModalSession _Nonnull) session;
+-(void) restoreParentWindow;
+-(bool) shouldTryToHandleEvents;
+-(void) setEnabled: (bool) enable;
+-(void) showAppMenuOnly;
+-(void) showWindowMenuWithAppMenu;
+-(void) applyMenu:(AvnMenu* _Nullable)menu;
+
+-(double) getExtendedTitleBarHeight;
+-(void) setIsExtended:(bool)value;
+-(void) disconnectParent;
+-(bool) isDialog;
+@end
\ No newline at end of file
diff --git a/native/Avalonia.Native/src/OSX/app.mm b/native/Avalonia.Native/src/OSX/app.mm
index 05b129baca..14f1f6888c 100644
--- a/native/Avalonia.Native/src/OSX/app.mm
+++ b/native/Avalonia.Native/src/OSX/app.mm
@@ -82,18 +82,6 @@ ComPtr _events;
_isHandlingSendEvent = oldHandling;
}
}
-
-// This is needed for certain embedded controls
-- (BOOL) isHandlingSendEvent
-{
- return _isHandlingSendEvent;
-}
-
-- (void)setHandlingSendEvent:(BOOL)handlingSendEvent
-{
- _isHandlingSendEvent = handlingSendEvent;
-}
-
@end
extern void InitializeAvnApp(IAvnApplicationEvents* events)
diff --git a/native/Avalonia.Native/src/OSX/automation.h b/native/Avalonia.Native/src/OSX/automation.h
index 4a12a965fd..367df3619d 100644
--- a/native/Avalonia.Native/src/OSX/automation.h
+++ b/native/Avalonia.Native/src/OSX/automation.h
@@ -1,6 +1,6 @@
-#import
-#include "window.h"
+#pragma once
+#import
NS_ASSUME_NONNULL_BEGIN
class IAvnAutomationPeer;
diff --git a/native/Avalonia.Native/src/OSX/automation.mm b/native/Avalonia.Native/src/OSX/automation.mm
index 7d697140c2..d0c8d7a9db 100644
--- a/native/Avalonia.Native/src/OSX/automation.mm
+++ b/native/Avalonia.Native/src/OSX/automation.mm
@@ -1,7 +1,8 @@
#include "common.h"
#include "automation.h"
#include "AvnString.h"
-#include "window.h"
+#include "INSWindowHolder.h"
+#include "AvnView.h"
@interface AvnAccessibilityElement (Events)
- (void) raiseChildrenChanged;
diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h
index 9186d9e15a..a90a235b9d 100644
--- a/native/Avalonia.Native/src/OSX/common.h
+++ b/native/Avalonia.Native/src/OSX/common.h
@@ -27,7 +27,7 @@ extern IAvnMenuItem* CreateAppMenuItem();
extern IAvnMenuItem* CreateAppMenuItemSeparator();
extern IAvnApplicationCommands* CreateApplicationCommands();
extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent);
-extern void SetAppMenu (NSString* appName, IAvnMenu* appMenu);
+extern void SetAppMenu(IAvnMenu *menu);
extern void SetServicesMenu (IAvnMenu* menu);
extern IAvnMenu* GetAppMenu ();
extern NSMenuItem* GetAppMenuItem ();
@@ -38,7 +38,6 @@ extern NSPoint ToNSPoint (AvnPoint p);
extern NSRect ToNSRect (AvnRect r);
extern AvnPoint ToAvnPoint (NSPoint p);
extern AvnPoint ConvertPointY (AvnPoint p);
-extern CGFloat PrimaryDisplayHeight();
extern NSSize ToNSSize (AvnSize s);
#ifdef DEBUG
#define NSDebugLog(...) NSLog(__VA_ARGS__)
diff --git a/native/Avalonia.Native/src/OSX/cursor.mm b/native/Avalonia.Native/src/OSX/cursor.mm
index dc38294a18..8638a03531 100644
--- a/native/Avalonia.Native/src/OSX/cursor.mm
+++ b/native/Avalonia.Native/src/OSX/cursor.mm
@@ -1,6 +1,5 @@
#include "common.h"
#include "cursor.h"
-#include