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 ace4a71a56..41d1534f8d 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 @@ -50,6 +50,7 @@ BC11A5BE2608D58F0017BAD0 /* automation.h in Headers */ = {isa = PBXBuildFile; fileRef = BC11A5BC2608D58F0017BAD0 /* automation.h */; }; BC11A5BF2608D58F0017BAD0 /* automation.mm in Sources */ = {isa = PBXBuildFile; fileRef = BC11A5BD2608D58F0017BAD0 /* automation.mm */; }; ED3791C42862E1F40080BD62 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */; }; + EDF8CDCD2964CB01001EE34F /* PlatformSettings.mm in Sources */ = {isa = PBXBuildFile; fileRef = EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -103,6 +104,7 @@ BC11A5BC2608D58F0017BAD0 /* automation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = automation.h; sourceTree = ""; }; BC11A5BD2608D58F0017BAD0 /* automation.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = automation.mm; sourceTree = ""; }; ED3791C32862E1F40080BD62 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; }; + EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PlatformSettings.mm; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -163,6 +165,7 @@ 1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */, 37A517B22159597E00FBA241 /* Screens.mm */, 37C09D8721580FE4006A6758 /* SystemDialogs.mm */, + EDF8CDCC2964CB01001EE34F /* PlatformSettings.mm */, AB7A61F02147C815003C5833 /* Products */, AB661C1C2148230E00291242 /* Frameworks */, 18391676ECF0E983F4964357 /* WindowBaseImpl.mm */, @@ -299,6 +302,7 @@ 1839151F32D1BB1AB51A7BB6 /* AvnPanelWindow.mm in Sources */, 18391AC16726CBC45856233B /* AvnWindow.mm in Sources */, 18391D8CD1756DC858DC1A09 /* PopupImpl.mm in Sources */, + EDF8CDCD2964CB01001EE34F /* PlatformSettings.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/native/Avalonia.Native/src/OSX/PlatformSettings.mm b/native/Avalonia.Native/src/OSX/PlatformSettings.mm new file mode 100644 index 0000000000..e709cd5135 --- /dev/null +++ b/native/Avalonia.Native/src/OSX/PlatformSettings.mm @@ -0,0 +1,111 @@ +#include "common.h" + +@interface CocoaThemeObserver : NSObject +-(id)initWithCallback:(IAvnActionCallback *)callback; +@end + +class PlatformSettings : public ComSingleObject +{ + CocoaThemeObserver* observer; + +public: + FORWARD_IUNKNOWN() + virtual AvnPlatformThemeVariant GetPlatformTheme() override + { + @autoreleasepool + { + if (@available(macOS 10.14, *)) + { + if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAqua + || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameVibrantLight + || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastAqua + || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastVibrantLight) { + return AvnPlatformThemeVariant::Light; + } else if (NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameDarkAqua + || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameVibrantDark + || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastDarkAqua + || NSApplication.sharedApplication.effectiveAppearance.name == NSAppearanceNameAccessibilityHighContrastVibrantDark) { + return AvnPlatformThemeVariant::Dark; + } + } + return AvnPlatformThemeVariant::Light; + } + } + + virtual unsigned int GetAccentColor() override + { + @autoreleasepool + { + if (@available(macOS 10.14, *)) + { + auto color = [NSColor controlAccentColor]; + return to_argb(color); + } + else + { + return 0; + } + } + } + + virtual void RegisterColorsChange(IAvnActionCallback *callback) override + { + if (@available(macOS 10.14, *)) + { + observer = [[CocoaThemeObserver alloc] initWithCallback: callback]; + [[NSApplication sharedApplication] addObserver:observer forKeyPath:@"effectiveAppearance" options:NSKeyValueObservingOptionNew context:nil]; + } + } + +private: + unsigned int to_argb(NSColor* color) + { + const CGFloat* components = CGColorGetComponents(color.CGColor); + unsigned int alpha = static_cast(CGColorGetAlpha(color.CGColor) * 0xFF); + unsigned int red = static_cast(components[0] * 0xFF); + unsigned int green = static_cast(components[1] * 0xFF); + unsigned int blue = static_cast(components[2] * 0xFF); + return (alpha << 24) + (red << 16) + (green << 8) + blue; + } +}; + +@implementation CocoaThemeObserver +{ + ComPtr _callback; +} +- (id) initWithCallback:(IAvnActionCallback *)callback{ + self = [super init]; + if (self) { + _callback = callback; + } + return self; +} + +/*- (void)didChangeValueForKey:(NSString *)key { + if([key isEqualToString:@"effectiveAppearance"]) { + _callback->Run(); + } + else { + [super didChangeValueForKey:key]; + } +}*/ + +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context { + if([keyPath isEqualToString:@"effectiveAppearance"]) { + _callback->Run(); + } else { + [super observeValueForKeyPath:keyPath + ofObject:object + change:change + context:context]; + } +} +@end + +extern IAvnPlatformSettings* CreatePlatformSettings() +{ + return new PlatformSettings(); +} diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h index 7fb002e54f..1404ec361c 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h @@ -92,6 +92,8 @@ BEGIN_INTERFACE_MAP() virtual HRESULT SetTransparencyMode(AvnWindowTransparencyMode mode) override; + virtual HRESULT SetFrameThemeVariant(AvnPlatformThemeVariant variant) override; + virtual HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point, IAvnClipboard *clipboard, IAvnDndResultCallback *cb, void *sourceHandle) override; diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index 9946ad9b10..39dde5a582 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -498,6 +498,24 @@ HRESULT WindowBaseImpl::SetTransparencyMode(AvnWindowTransparencyMode mode) { return S_OK; } +HRESULT WindowBaseImpl::SetFrameThemeVariant(AvnPlatformThemeVariant variant) { + START_COM_CALL; + + NSAppearanceName appearanceName; + if (@available(macOS 10.14, *)) + { + appearanceName = variant == AvnPlatformThemeVariant::Dark ? NSAppearanceNameDarkAqua : NSAppearanceNameAqua; + } + else + { + appearanceName = variant == AvnPlatformThemeVariant::Dark ? NSAppearanceNameVibrantDark : NSAppearanceNameAqua; + } + + [Window setAppearance: [NSAppearance appearanceNamed: appearanceName]]; + + return S_OK; +} + HRESULT WindowBaseImpl::BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point, IAvnClipboard *clipboard, IAvnDndResultCallback *cb, void *sourceHandle) { START_COM_CALL; diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h index 7ee7205776..972927b99d 100644 --- a/native/Avalonia.Native/src/OSX/common.h +++ b/native/Avalonia.Native/src/OSX/common.h @@ -27,6 +27,7 @@ extern IAvnMenuItem* CreateAppMenuItem(); extern IAvnMenuItem* CreateAppMenuItemSeparator(); extern IAvnApplicationCommands* CreateApplicationCommands(); extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent); +extern IAvnPlatformSettings* CreatePlatformSettings(); extern void SetAppMenu(IAvnMenu *menu); extern void SetServicesMenu (IAvnMenu* menu); extern IAvnMenu* GetAppMenu (); diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index c29d4108d0..99063e600e 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -398,6 +398,16 @@ public: } } + virtual HRESULT CreatePlatformSettings (IAvnPlatformSettings** ppv) override + { + START_COM_CALL; + + @autoreleasepool + { + *ppv = ::CreatePlatformSettings(); + return S_OK; + } + } }; extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative() diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index 6d5925c0ae..fc23fbb226 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -104,7 +104,7 @@ namespace Avalonia.Native .Bind().ToConstant(new CursorFactory(_factory.CreateCursorFactory())) .Bind().ToSingleton() .Bind().ToConstant(KeyboardDevice) - .Bind().ToSingleton() + .Bind().ToConstant(new NativePlatformSettings(_factory.CreatePlatformSettings())) .Bind().ToConstant(this) .Bind().ToConstant(new ClipboardImpl(_factory.CreateClipboard())) .Bind().ToConstant(new DefaultRenderTimer(60)) diff --git a/src/Avalonia.Native/NativePlatformSettings.cs b/src/Avalonia.Native/NativePlatformSettings.cs new file mode 100644 index 0000000000..cea748bbae --- /dev/null +++ b/src/Avalonia.Native/NativePlatformSettings.cs @@ -0,0 +1,61 @@ +using System; +using Avalonia.Media; +using Avalonia.Native.Interop; +using Avalonia.Platform; + +namespace Avalonia.Native; + +internal class NativePlatformSettings : DefaultPlatformSettings +{ + private readonly IAvnPlatformSettings _platformSettings; + private PlatformColorValues _lastColorValues; + + public NativePlatformSettings(IAvnPlatformSettings platformSettings) + { + _platformSettings = platformSettings; + platformSettings.RegisterColorsChange(new ColorsChangeCallback(this)); + } + + public override PlatformColorValues GetColorValues() + { + var theme = (PlatformThemeVariant)_platformSettings.PlatformTheme; + var color = _platformSettings.AccentColor; + + if (color > 0) + { + _lastColorValues = new PlatformColorValues(theme, Color.FromUInt32(color)); + } + else + { + _lastColorValues = new PlatformColorValues(theme); + } + + return _lastColorValues; + } + + public void OnColorValuesChanged() + { + var oldColorValues = _lastColorValues; + var colorValues = GetColorValues(); + + if (oldColorValues != colorValues) + { + OnColorValuesChanged(colorValues); + } + } + + private class ColorsChangeCallback : NativeCallbackBase, IAvnActionCallback + { + private readonly NativePlatformSettings _settings; + + public ColorsChangeCallback(NativePlatformSettings settings) + { + _settings = settings; + } + + public void Run() + { + _settings.OnColorValuesChanged(); + } + } +} diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index ca57e30b1c..7686b42275 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -519,6 +519,11 @@ namespace Avalonia.Native public WindowTransparencyLevel TransparencyLevel { get; private set; } = WindowTransparencyLevel.Transparent; + public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) + { + _native.SetFrameThemeVariant((AvnPlatformThemeVariant)themeVariant); + } + public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new AcrylicPlatformCompensationLevels(1, 0, 0); public IPlatformHandle Handle { get; private set; } diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index 8057b162f5..29f839c826 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -473,6 +473,12 @@ enum AvnWindowTransparencyMode Blur } +enum AvnPlatformThemeVariant +{ + Light, + Dark +} + [uuid(809c652e-7396-11d2-9771-00a0c9b4d50c)] interface IAvaloniaNativeFactory : IUnknown { @@ -494,6 +500,7 @@ interface IAvaloniaNativeFactory : IUnknown HRESULT CreateMenuItemSeparator(IAvnMenuItem** ppv); HRESULT CreateTrayIcon(IAvnTrayIcon** ppv); HRESULT CreateApplicationCommands(IAvnApplicationCommands** ppv); + HRESULT CreatePlatformSettings(IAvnPlatformSettings** ppv); } [uuid(233e094f-9b9f-44a3-9a6e-6948bbdd9fb1)] @@ -535,6 +542,7 @@ interface IAvnWindowBase : IUnknown HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point, IAvnClipboard* clipboard, IAvnDndResultCallback* cb, [intptr]void* sourceHandle); HRESULT SetTransparencyMode(AvnWindowTransparencyMode mode); + HRESULT SetFrameThemeVariant(AvnPlatformThemeVariant mode); } [uuid(83e588f3-6981-4e48-9ea0-e1e569f79a91), cpp-virtual-inherits] @@ -906,3 +914,11 @@ interface IAvnAutomationNode : IUnknown void PropertyChanged(AvnAutomationProperty property); void FocusChanged(); } + +[uuid(d1f009cc-9d2d-493b-845d-90d2c104baae)] +interface IAvnPlatformSettings : IUnknown +{ + AvnPlatformThemeVariant GetPlatformTheme(); + uint GetAccentColor(); + void RegisterColorsChange(IAvnActionCallback* callback); +}