Browse Source

macOS implementation

Max Katz 2 years ago
parent
commit
8f11c5a6ed

+ 4 - 0
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 = "<group>"; };
 		BC11A5BD2608D58F0017BAD0 /* automation.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = automation.mm; sourceTree = "<group>"; };
 		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 = "<group>"; };
 /* 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;
 		};

+ 111 - 0
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<IAvnPlatformSettings, &IID_IAvnPlatformSettings>
+{
+    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<unsigned int>(CGColorGetAlpha(color.CGColor) * 0xFF);
+        unsigned int red = static_cast<unsigned int>(components[0] * 0xFF);
+        unsigned int green = static_cast<unsigned int>(components[1] * 0xFF);
+        unsigned int blue = static_cast<unsigned int>(components[2] * 0xFF);
+        return (alpha << 24) + (red << 16) + (green << 8) + blue;
+    }
+};
+
+@implementation CocoaThemeObserver
+{
+    ComPtr<IAvnActionCallback> _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();
+}

+ 2 - 0
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;

+ 18 - 0
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;
 

+ 1 - 0
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 ();

+ 10 - 0
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()

+ 1 - 1
src/Avalonia.Native/AvaloniaNativePlatform.cs

@@ -104,7 +104,7 @@ namespace Avalonia.Native
                 .Bind<ICursorFactory>().ToConstant(new CursorFactory(_factory.CreateCursorFactory()))
                 .Bind<IPlatformIconLoader>().ToSingleton<IconLoader>()
                 .Bind<IKeyboardDevice>().ToConstant(KeyboardDevice)
-                .Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()
+                .Bind<IPlatformSettings>().ToConstant(new NativePlatformSettings(_factory.CreatePlatformSettings()))
                 .Bind<IWindowingPlatform>().ToConstant(this)
                 .Bind<IClipboard>().ToConstant(new ClipboardImpl(_factory.CreateClipboard()))
                 .Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))

+ 61 - 0
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();
+        }
+    }
+}

+ 5 - 0
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; }

+ 16 - 0
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);
+}