diff --git a/Avalonia.sln b/Avalonia.sln index 1e2a3c6027..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}" @@ -169,6 +169,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlatformSanityChecks", "sam EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.UnitTests", "tests\Avalonia.ReactiveUI.UnitTests\Avalonia.ReactiveUI.UnitTests.csproj", "{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.ColorPicker", "src\Avalonia.Controls.ColorPicker\Avalonia.Controls.ColorPicker.csproj", "{1ECC012A-8837-4AE2-9BDA-3E2857898727}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls.DataGrid", "src\Avalonia.Controls.DataGrid\Avalonia.Controls.DataGrid.csproj", "{3278F3A9-9509-4A3F-A15B-BDC8B5BFF632}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Dialogs", "src\Avalonia.Dialogs\Avalonia.Dialogs.csproj", "{4D55985A-1EE2-4F25-AD39-6EA8BC04F8FB}" @@ -1963,6 +1965,30 @@ Global {2B390431-288C-435C-BB6B-A374033BD8D1}.Release|iPhone.Build.0 = Release|Any CPU {2B390431-288C-435C-BB6B-A374033BD8D1}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {2B390431-288C-435C-BB6B-A374033BD8D1}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhone.Build.0 = Debug|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhone.Build.0 = Debug|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|Any CPU.Build.0 = Release|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhone.ActiveCfg = Release|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhone.Build.0 = Release|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {1ECC012A-8837-4AE2-9BDA-3E2857898727}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE 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 class CursorFactory : public ComSingleObject { diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index ea79c494d7..6ee86b21ae 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -1,7 +1,6 @@ //This file will contain actual IID structures #define COM_GUIDS_MATERIALIZE #include "common.h" -#include "window.h" static NSString* s_appTitle = @"Avalonia"; @@ -343,7 +342,7 @@ public: @autoreleasepool { - ::SetAppMenu(s_appTitle, appMenu); + ::SetAppMenu(appMenu); return S_OK; } } @@ -428,7 +427,3 @@ AvnPoint ConvertPointY (AvnPoint p) return p; } -CGFloat PrimaryDisplayHeight() -{ - return NSMaxY([[[NSScreen screens] firstObject] frame]); -} diff --git a/native/Avalonia.Native/src/OSX/menu.h b/native/Avalonia.Native/src/OSX/menu.h index 186fcf255b..ce46ac11e0 100644 --- a/native/Avalonia.Native/src/OSX/menu.h +++ b/native/Avalonia.Native/src/OSX/menu.h @@ -31,7 +31,6 @@ private: NSMenuItem* _native; // here we hold a pointer to an AvnMenuItem IAvnActionCallback* _callback; IAvnPredicateCallback* _predicate; - bool _isSeparator; bool _isCheckable; public: diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm index 2dbe76bc6d..b05588a441 100644 --- a/native/Avalonia.Native/src/OSX/menu.mm +++ b/native/Avalonia.Native/src/OSX/menu.mm @@ -1,7 +1,6 @@ #include "common.h" #include "menu.h" -#include "window.h" #include "KeyTransform.h" #include #include /* For kVK_ constants, and TIS functions. */ @@ -74,8 +73,7 @@ AvnAppMenuItem::AvnAppMenuItem(bool isSeparator) { _isCheckable = false; - _isSeparator = isSeparator; - + if(isSeparator) { _native = [NSMenuItem separatorItem]; @@ -460,7 +458,7 @@ extern IAvnMenuItem* CreateAppMenuItemSeparator() static IAvnMenu* s_appMenu = nullptr; static NSMenuItem* s_appMenuItem = nullptr; -extern void SetAppMenu (NSString* appName, IAvnMenu* menu) +extern void SetAppMenu(IAvnMenu *menu) { s_appMenu = menu; diff --git a/native/Avalonia.Native/src/OSX/rendertarget.mm b/native/Avalonia.Native/src/OSX/rendertarget.mm index dc5c24e41e..266d0345d1 100644 --- a/native/Avalonia.Native/src/OSX/rendertarget.mm +++ b/native/Avalonia.Native/src/OSX/rendertarget.mm @@ -1,14 +1,10 @@ #include "common.h" #include "rendertarget.h" -#import #import #import -#include -#include #include #include -#include @interface IOSurfaceHolder : NSObject @end diff --git a/native/Avalonia.Native/src/OSX/window.h b/native/Avalonia.Native/src/OSX/window.h deleted file mode 100644 index 1369ceaea0..0000000000 --- a/native/Avalonia.Native/src/OSX/window.h +++ /dev/null @@ -1,77 +0,0 @@ -#ifndef window_h -#define window_h - -class WindowBaseImpl; - -@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; --(AvnPixelSize) getPixelSize; --(AvnPlatformResizeReason) getResizeReason; --(void) setResizeReason:(AvnPlatformResizeReason)reason; -+ (AvnPoint)toAvnPoint:(CGPoint)p; -@end - -@interface AutoFitContentView : NSView --(AutoFitContentView* _Nonnull) initWithContent: (NSView* _Nonnull) content; --(void) ShowTitleBar: (bool) show; --(void) SetTitleBarHeightHint: (double) height; --(void) SetContent: (NSView* _Nonnull) content; --(void) ShowBlur: (bool) show; -@end - -@interface AvnWindow : NSWindow -+(void) closeAll; --(AvnWindow* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent; --(void) setCanBecomeKeyAndMain; --(void) pollModalSession: (NSModalSession _Nonnull) session; --(void) restoreParentWindow; --(bool) shouldTryToHandleEvents; --(void) setEnabled: (bool) enable; --(void) showAppMenuOnly; --(void) showWindowMenuWithAppMenu; --(void) applyMenu:(NSMenu* _Nullable)menu; --(double) getScaling; --(double) getExtendedTitleBarHeight; --(void) setIsExtended:(bool)value; --(bool) isDialog; -@end - -struct INSWindowHolder -{ - virtual AvnWindow* _Nonnull GetNSWindow () = 0; - virtual AvnView* _Nonnull GetNSView () = 0; -}; - -struct IWindowStateChanged -{ - virtual void WindowStateChanged () = 0; - virtual void StartStateTransition () = 0; - virtual void EndStateTransition () = 0; - virtual SystemDecorations Decorations () = 0; - virtual AvnWindowState WindowState () = 0; -}; - -class ResizeScope -{ -public: - ResizeScope(AvnView* _Nonnull view, AvnPlatformResizeReason reason) - { - _view = view; - _restore = [view getResizeReason]; - [view setResizeReason:reason]; - } - - ~ResizeScope() - { - [_view setResizeReason:_restore]; - } -private: - AvnView* _Nonnull _view; - AvnPlatformResizeReason _restore; -}; - -#endif /* window_h */ diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm deleted file mode 100644 index d16c466fe6..0000000000 --- a/native/Avalonia.Native/src/OSX/window.mm +++ /dev/null @@ -1,2590 +0,0 @@ -#include "common.h" -#include "window.h" -#include "KeyTransform.h" -#include "cursor.h" -#include "menu.h" -#include -#include "rendertarget.h" -#include "AvnString.h" -#include "automation.h" - -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() - { - View = NULL; - Window = NULL; - } - AutoFitContentView* StandardContainer; - AvnView* View; - AvnWindow* Window; - ComPtr BaseEvents; - ComPtr _glContext; - NSObject* renderTarget; - AvnPoint lastPositionSet; - NSString* _lastTitle; - IAvnMenu* _mainMenu; - - bool _shown; - bool _inResize; - - WindowBaseImpl(IAvnWindowBaseEvents* events, IAvnGlContext* gl) - { - _shown = false; - _inResize = false; - _mainMenu = nullptr; - BaseEvents = events; - _glContext = gl; - renderTarget = [[IOSurfaceRenderTarget alloc] initWithOpenGlContext: gl]; - View = [[AvnView alloc] initWithParent:this]; - StandardContainer = [[AutoFitContentView new] initWithContent:View]; - - Window = [[AvnWindow alloc] initWithParent:this]; - [Window setContentView: StandardContainer]; - - lastPositionSet.X = 100; - lastPositionSet.Y = 100; - _lastTitle = @""; - - [Window setStyleMask:NSWindowStyleMaskBorderless]; - [Window setBackingType:NSBackingStoreBuffered]; - - [Window setOpaque:false]; - } - - virtual HRESULT ObtainNSWindowHandle(void** ret) override - { - START_COM_CALL; - - if (ret == nullptr) - { - return E_POINTER; - } - - *ret = (__bridge void*)Window; - - return S_OK; - } - - virtual HRESULT ObtainNSWindowHandleRetained(void** ret) override - { - START_COM_CALL; - - if (ret == nullptr) - { - return E_POINTER; - } - - *ret = (__bridge_retained void*)Window; - - return S_OK; - } - - virtual HRESULT ObtainNSViewHandle(void** ret) override - { - START_COM_CALL; - - if (ret == nullptr) - { - return E_POINTER; - } - - *ret = (__bridge void*)View; - - return S_OK; - } - - virtual HRESULT ObtainNSViewHandleRetained(void** ret) override - { - START_COM_CALL; - - if (ret == nullptr) - { - return E_POINTER; - } - - *ret = (__bridge_retained void*)View; - - return S_OK; - } - - virtual AvnWindow* GetNSWindow() override - { - return Window; - } - - virtual AvnView* GetNSView() override - { - return View; - } - - virtual HRESULT Show(bool activate, bool isDialog) override - { - START_COM_CALL; - - @autoreleasepool - { - 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; - } - } - - virtual bool ShouldTakeFocusOnShow() - { - return true; - } - - virtual HRESULT Hide () override - { - START_COM_CALL; - - @autoreleasepool - { - if(Window != nullptr) - { - [Window orderOut:Window]; - [Window restoreParentWindow]; - } - - return S_OK; - } - } - - virtual HRESULT Activate () override - { - START_COM_CALL; - - @autoreleasepool - { - if(Window != nullptr) - { - [Window makeKeyAndOrderFront:nil]; - [NSApp activateIgnoringOtherApps:YES]; - } - } - - return S_OK; - } - - virtual HRESULT SetTopMost (bool value) override - { - START_COM_CALL; - - @autoreleasepool - { - [Window setLevel: value ? NSFloatingWindowLevel : NSNormalWindowLevel]; - - return S_OK; - } - } - - virtual HRESULT Close() override - { - 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; - } - } - - virtual HRESULT GetClientSize(AvnSize* ret) override - { - 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; - } - } - - virtual HRESULT GetFrameSize(AvnSize* ret) override - { - 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; - } - } - - virtual HRESULT GetScaling (double* ret) override - { - START_COM_CALL; - - @autoreleasepool - { - if(ret == nullptr) - return E_POINTER; - - if(Window == nullptr) - { - *ret = 1; - return S_OK; - } - - *ret = [Window backingScaleFactor]; - return S_OK; - } - } - - virtual HRESULT SetMinMaxSize (AvnSize minSize, AvnSize maxSize) override - { - START_COM_CALL; - - @autoreleasepool - { - [Window setMinSize: ToNSSize(minSize)]; - [Window setMaxSize: ToNSSize(maxSize)]; - - return S_OK; - } - } - - virtual HRESULT Resize(double x, double y, AvnPlatformResizeReason reason) override - { - if(_inResize) - { - return S_OK; - } - - _inResize = true; - - START_COM_CALL; - auto resizeBlock = ResizeScope(View, reason); - - @autoreleasepool - { - auto maxSize = [Window maxSize]; - auto minSize = [Window minSize]; - - 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); - } - - [Window setContentSize:NSSize{x,y}]; - [Window invalidateShadow]; - } - @finally - { - _inResize = false; - } - - return S_OK; - } - } - - virtual HRESULT Invalidate (AvnRect rect) override - { - START_COM_CALL; - - @autoreleasepool - { - [View setNeedsDisplayInRect:[View frame]]; - - return S_OK; - } - } - - virtual HRESULT SetMainMenu(IAvnMenu* menu) override - { - START_COM_CALL; - - _mainMenu = menu; - - auto nativeMenu = dynamic_cast(menu); - - auto nsmenu = nativeMenu->GetNative(); - - [Window applyMenu:nsmenu]; - - if ([Window isKeyWindow]) - { - [Window showWindowMenuWithAppMenu]; - } - - return S_OK; - } - - virtual HRESULT BeginMoveDrag () override - { - START_COM_CALL; - - @autoreleasepool - { - auto lastEvent = [View lastMouseDownEvent]; - - if(lastEvent == nullptr) - { - return S_OK; - } - - [Window performWindowDragWithEvent:lastEvent]; - - return S_OK; - } - } - - virtual HRESULT BeginResizeDrag (AvnWindowEdge edge) override - { - START_COM_CALL; - - return S_OK; - } - - virtual HRESULT GetPosition (AvnPoint* ret) override - { - 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; - } - } - - virtual HRESULT SetPosition (AvnPoint point) override - { - START_COM_CALL; - - @autoreleasepool - { - lastPositionSet = point; - [Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(point))]; - - return S_OK; - } - } - - virtual HRESULT PointToClient (AvnPoint point, AvnPoint* ret) override - { - START_COM_CALL; - - @autoreleasepool - { - if(ret == nullptr) - { - return E_POINTER; - } - - point = ConvertPointY(point); - NSRect convertRect = [Window convertRectToScreen: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; - } - } - - virtual HRESULT PointToScreen (AvnPoint point, AvnPoint* ret) override - { - 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; - } - } - - virtual HRESULT ThreadSafeSetSwRenderedFrame(AvnFramebuffer* fb, IUnknown* dispose) override - { - START_COM_CALL; - - [View setSwRenderedFrame: fb dispose: dispose]; - return S_OK; - } - - virtual HRESULT SetCursor(IAvnCursor* cursor) override - { - 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; - } - } - - virtual void UpdateCursor() - { - if (cursor != nil) - { - [cursor set]; - } - } - - virtual HRESULT CreateGlRenderTarget(IAvnGlSurfaceRenderTarget** ppv) override - { - START_COM_CALL; - - if(View == NULL) - return E_FAIL; - *ppv = [renderTarget createSurfaceRenderTarget]; - return *ppv == nil ? E_FAIL : S_OK; - } - - virtual HRESULT CreateNativeControlHost(IAvnNativeControlHost** retOut) override - { - START_COM_CALL; - - if(View == NULL) - return E_FAIL; - *retOut = ::CreateNativeControlHost(View); - return S_OK; - } - - virtual HRESULT SetBlurEnabled (bool enable) override - { - START_COM_CALL; - - [StandardContainer ShowBlur:enable]; - - return S_OK; - } - - virtual HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point, - IAvnClipboard* clipboard, IAvnDndResultCallback* cb, - void* sourceHandle) override - { - 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; - } - - virtual bool IsDialog() - { - return false; - } - -protected: - virtual NSWindowStyleMask GetStyle() - { - return NSWindowStyleMaskBorderless; - } - - void UpdateStyle() - { - [Window setStyleMask: GetStyle()]; - } - -public: - virtual void OnResized () - { - - } -}; - -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) : WindowBaseImpl(events, gl) - { - _isClientAreaExtended = false; - _extendClientHints = AvnDefaultChrome; - _fullScreenActive = false; - _canResize = true; - _decorations = SystemDecorationsFull; - _transitioningWindowState = false; - _inSetWindowState = false; - _lastWindowState = Normal; - _actualWindowState = Normal; - WindowEvents = events; - [Window setCanBecomeKeyAndMain]; - [Window disableCursorRects]; - [Window setTabbingMode:NSWindowTabbingModeDisallowed]; - [Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary]; - } - - void 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]; - } - } - } - } - } - - virtual HRESULT Show (bool activate, bool isDialog) override - { - START_COM_CALL; - - @autoreleasepool - { - _isDialog = isDialog; - WindowBaseImpl::Show(activate, isDialog); - - HideOrShowTrafficLights(); - - return SetWindowState(_lastWindowState); - } - } - - virtual HRESULT SetEnabled (bool enable) override - { - START_COM_CALL; - - @autoreleasepool - { - [Window setEnabled:enable]; - return S_OK; - } - } - - virtual HRESULT SetParent (IAvnWindow* parent) override - { - 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 StartStateTransition () override - { - _transitioningWindowState = true; - } - - void EndStateTransition () override - { - _transitioningWindowState = false; - } - - SystemDecorations Decorations () override - { - return _decorations; - } - - AvnWindowState WindowState () override - { - return _lastWindowState; - } - - void WindowStateChanged () override - { - 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 UndecoratedIsMaximized () - { - auto windowSize = [Window frame]; - auto available = [Window screen].visibleFrame; - return CGRectEqualToRect(windowSize, available); - } - - bool IsZoomed () - { - return _decorations == SystemDecorationsFull ? [Window isZoomed] : UndecoratedIsMaximized(); - } - - void DoZoom() - { - switch (_decorations) - { - case SystemDecorationsNone: - case SystemDecorationsBorderOnly: - [Window setFrame:[Window screen].visibleFrame display:true]; - break; - - - case SystemDecorationsFull: - [Window performZoom:Window]; - break; - } - } - - virtual HRESULT SetCanResize(bool value) override - { - START_COM_CALL; - - @autoreleasepool - { - _canResize = value; - UpdateStyle(); - return S_OK; - } - } - - virtual HRESULT SetDecorations(SystemDecorations value) override - { - 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; - } - } - - virtual HRESULT SetTitle (char* utf8title) override - { - START_COM_CALL; - - @autoreleasepool - { - _lastTitle = [NSString stringWithUTF8String:(const char*)utf8title]; - [Window setTitle:_lastTitle]; - - return S_OK; - } - } - - virtual HRESULT SetTitleBarColor(AvnColor color) override - { - 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; - } - - virtual HRESULT GetWindowState (AvnWindowState*ret) override - { - 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; - } - } - - virtual HRESULT TakeFocusFromChildren () override - { - START_COM_CALL; - - @autoreleasepool - { - if(Window == nil) - return S_OK; - if([Window isKeyWindow]) - [Window makeFirstResponder: View]; - - return S_OK; - } - } - - virtual HRESULT SetExtendClientArea (bool enable) override - { - START_COM_CALL; - - @autoreleasepool - { - _isClientAreaExtended = enable; - - 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; - } - - [Window setIsExtended:enable]; - - HideOrShowTrafficLights(); - - UpdateStyle(); - - return S_OK; - } - } - - virtual HRESULT SetExtendClientAreaHints (AvnExtendClientAreaChromeHints hints) override - { - START_COM_CALL; - - @autoreleasepool - { - _extendClientHints = hints; - - SetExtendClientArea(_isClientAreaExtended); - return S_OK; - } - } - - virtual HRESULT GetExtendTitleBarHeight (double*ret) override - { - START_COM_CALL; - - @autoreleasepool - { - if(ret == nullptr) - { - return E_POINTER; - } - - *ret = [Window getExtendedTitleBarHeight]; - - return S_OK; - } - } - - virtual HRESULT SetExtendTitleBarHeight (double value) override - { - START_COM_CALL; - - @autoreleasepool - { - [StandardContainer SetTitleBarHeightHint:value]; - return S_OK; - } - } - - void EnterFullScreenMode () - { - _fullScreenActive = true; - - [Window setTitle:_lastTitle]; - [Window toggleFullScreen:nullptr]; - } - - void ExitFullScreenMode () - { - [Window toggleFullScreen:nullptr]; - - _fullScreenActive = false; - - SetDecorations(_decorations); - } - - virtual HRESULT SetWindowState (AvnWindowState state) override - { - 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; - } - } - - virtual void OnResized () override - { - if(_shown && !_inSetWindowState && !_transitioningWindowState) - { - WindowStateChanged(); - } - } - - virtual bool IsDialog() override - { - return _isDialog; - } - -protected: - virtual NSWindowStyleMask GetStyle() override - { - unsigned long s = 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; - } -}; - -NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEventTrackingRunLoopMode, NSModalPanelRunLoopMode, NSRunLoopCommonModes, NSConnectionReplyMode, nil]; - -@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 = objc_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; -} - --(void) SetContent: (NSView* _Nonnull) content -{ - if(content != nullptr) - { - [content removeFromSuperview]; - [self addSubview:content]; - _content = content; - } -} -@end - -@implementation AvnView -{ - ComPtr _parent; - ComPtr _swRenderedFrame; - AvnFramebuffer _swRenderedFrameBuffer; - bool _queuedDisplayFromThread; - NSTrackingArea* _area; - bool _isLeftPressed, _isMiddlePressed, _isRightPressed, _isXButton1Pressed, _isXButton2Pressed, _isMouseOver; - AvnInputModifiers _modifierState; - NSEvent* _lastMouseDownEvent; - bool _lastKeyHandled; - AvnPixelSize _lastPixelSize; - NSObject* _renderTarget; - AvnPlatformResizeReason _resizeReason; - AvnAccessibilityElement* _accessibilityChild; -} - -- (void)onClosed -{ - @synchronized (self) - { - _parent = nullptr; - } -} - --(AvnPixelSize) getPixelSize -{ - return _lastPixelSize; -} - -- (NSEvent*) lastMouseDownEvent -{ - return _lastMouseDownEvent; -} - -- (void) updateRenderTarget -{ - [_renderTarget resize:_lastPixelSize withScale: [[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 -{ - auto parentWindow = objc_cast([self window]); - - 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; - - 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]; - } - - auto timestamp = [event timestamp] * 1000; - auto modifiers = [self getModifiers:[event modifierFlags]]; - - if(type != AvnRawMouseEventType::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; - } -} - -- (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; - } -} - -- (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 -{ - _isMouseOver = true; - [super mouseEntered:event]; -} - -- (void)mouseExited:(NSEvent *)event -{ - _isMouseOver = false; - [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]]; - - auto timestamp = [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; - - 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 = 0; - - // Ensure that the managed part didn't add any new effects - reffects = (int)effects & (int)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 - - -@implementation AvnWindow -{ - ComPtr _parent; - bool _canBecomeKeyAndMain; - bool _closed; - bool _isEnabled; - bool _isExtended; - AvnMenu* _menu; - double _lastScaling; - IAvnAutomationPeer* _automationPeer; - NSMutableArray* _automationChildren; -} - --(void) setIsExtended:(bool)value; -{ - _isExtended = value; -} - --(bool) isDialog -{ - return _parent->IsDialog(); -} - --(double) getScaling -{ - return _lastScaling; -} - --(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)closeAll -{ - [[NSApplication sharedApplication] terminate:self]; -} - -- (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()]; - } - else - { - [NSApp setMenu:nullptr]; - } -} - --(void) applyMenu:(AvnMenu *)menu -{ - if(menu == nullptr) - { - menu = [AvnMenu new]; - } - - _menu = menu; -} - --(void) setCanBecomeKeyAndMain -{ - _canBecomeKeyAndMain = true; -} - --(AvnWindow*) initWithParent: (WindowBaseImpl*) parent -{ - self = [super init]; - [self setReleasedWhenClosed:false]; - _parent = parent; - [self setDelegate:self]; - _closed = false; - _isEnabled = true; - - _lastScaling = [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 -{ - _lastScaling = [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 (_canBecomeKeyAndMain) - { - // 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 = objc_cast(uch); - if(ch == nil) - continue; - if (ch.isDialog) - return false; - } - - return true; - } - - return false; -} - --(BOOL)canBecomeMainWindow -{ - return _canBecomeKeyAndMain; -} - --(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 = objc_cast([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; - - _parent->BaseEvents->RawMouseEvent(NonClientLeftButtonDown, [event timestamp] * 1000, AvnInputModifiersNone, point, delta); - } - } - break; - - case NSEventTypeMouseEntered: - { - _parent->UpdateCursor(); - } - break; - - case NSEventTypeMouseExited: - { - [[NSCursor arrowCursor] set]; - } - break; - - default: - break; - } - } -} - -@end - -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; - } -} - -extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl) -{ - @autoreleasepool - { - IAvnWindow* ptr = (IAvnWindow*)new WindowImpl(events, gl); - return ptr; - } -} diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index 08ac17d0c4..f98956faac 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -117,7 +117,6 @@ namespace ControlCatalog.NetCore EnableWmPointerEvents = true }) .UseSkia() - .UseManagedSystemDialogs() .AfterSetup(builder => { builder.Instance!.AttachDevTools(new Avalonia.Diagnostics.DevToolsOptions() diff --git a/samples/ControlCatalog/App.xaml.cs b/samples/ControlCatalog/App.xaml.cs index 866fb8632a..7ebb87094a 100644 --- a/samples/ControlCatalog/App.xaml.cs +++ b/samples/ControlCatalog/App.xaml.cs @@ -18,6 +18,16 @@ namespace ControlCatalog DataContext = new ApplicationViewModel(); } + public static readonly StyleInclude ColorPickerFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) + { + Source = new Uri("avares://Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml") + }; + + public static readonly StyleInclude ColorPickerDefault = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) + { + Source = new Uri("avares://Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml") + }; + public static readonly StyleInclude DataGridFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles")) { Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml") @@ -69,7 +79,8 @@ namespace ControlCatalog public override void Initialize() { Styles.Insert(0, Fluent); - Styles.Insert(1, DataGridFluent); + Styles.Insert(1, ColorPickerFluent); + Styles.Insert(2, DataGridFluent); AvaloniaXamlLoader.Load(this); } diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj index c0e24357ca..7cbd8a3f9c 100644 --- a/samples/ControlCatalog/ControlCatalog.csproj +++ b/samples/ControlCatalog/ControlCatalog.csproj @@ -23,6 +23,7 @@ + diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml index c61ba231e0..9c618318d6 100644 --- a/samples/ControlCatalog/MainView.xaml +++ b/samples/ControlCatalog/MainView.xaml @@ -43,6 +43,9 @@ + + + diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs index e8ea39abbb..0326946c08 100644 --- a/samples/ControlCatalog/MainView.xaml.cs +++ b/samples/ControlCatalog/MainView.xaml.cs @@ -49,7 +49,8 @@ namespace ControlCatalog App.Fluent.Mode = FluentThemeMode.Light; } Application.Current.Styles[0] = App.Fluent; - Application.Current.Styles[1] = App.DataGridFluent; + Application.Current.Styles[1] = App.ColorPickerFluent; + Application.Current.Styles[2] = App.DataGridFluent; } else if (theme == CatalogTheme.FluentDark) { @@ -59,19 +60,22 @@ namespace ControlCatalog App.Fluent.Mode = FluentThemeMode.Dark; } Application.Current.Styles[0] = App.Fluent; - Application.Current.Styles[1] = App.DataGridFluent; + Application.Current.Styles[1] = App.ColorPickerFluent; + Application.Current.Styles[2] = App.DataGridFluent; } else if (theme == CatalogTheme.DefaultLight) { App.Default.Mode = Avalonia.Themes.Default.SimpleThemeMode.Light; Application.Current.Styles[0] = App.DefaultLight; - Application.Current.Styles[1] = App.DataGridDefault; + Application.Current.Styles[1] = App.ColorPickerDefault; + Application.Current.Styles[2] = App.DataGridDefault; } else if (theme == CatalogTheme.DefaultDark) { App.Default.Mode = Avalonia.Themes.Default.SimpleThemeMode.Dark; Application.Current.Styles[0] = App.DefaultDark; - Application.Current.Styles[1] = App.DataGridDefault; + Application.Current.Styles[1] = App.ColorPickerDefault; + Application.Current.Styles[2] = App.DataGridDefault; } } }; diff --git a/samples/ControlCatalog/Pages/ColorPickerPage.xaml b/samples/ControlCatalog/Pages/ColorPickerPage.xaml new file mode 100644 index 0000000000..c0c83d6a35 --- /dev/null +++ b/samples/ControlCatalog/Pages/ColorPickerPage.xaml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs b/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs new file mode 100644 index 0000000000..6e017e381f --- /dev/null +++ b/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs @@ -0,0 +1,19 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace ControlCatalog.Pages +{ + public partial class ColorPickerPage : UserControl + { + public ColorPickerPage() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + } +} diff --git a/samples/ControlCatalog/Pages/ComboBoxPage.xaml b/samples/ControlCatalog/Pages/ComboBoxPage.xaml index 64e80a8e11..9f2fbb88f3 100644 --- a/samples/ControlCatalog/Pages/ComboBoxPage.xaml +++ b/samples/ControlCatalog/Pages/ComboBoxPage.xaml @@ -86,6 +86,16 @@ + + + + + + Inline Items + Inline Item 2 + Inline Item 3 + Inline Item 4 + WrapSelection diff --git a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml index 8305d72d1f..1d42b92096 100644 --- a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml +++ b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml @@ -53,10 +53,12 @@ UniformGrid - Horizontal + + ("scroller"); _scrollToLast = this.FindControl