It is currently Fri Feb 26, 2021 8:03 pm

All times are UTC + 1 hour




Post new topic Reply to topic  [ 4 posts ] 
Author Message
 Post subject: [Mac] How to handle Dark Mode toggle?
PostPosted: Thu Feb 18, 2021 5:10 am 
Offline
Addict
Addict
User avatar

Joined: Tue Dec 23, 2003 3:54 am
Posts: 1873
Hello, I'm using PB 5.73 x64 on MacOS Catalina and it *mostly* handles Light Mode vs Dark Mode correctly.

Based on some forum code and PB IDE code, I have workarounds to detect Dark Mode, and get MacOS system colors.
(I should check the authors and thank them here!)
System colors are used for drawing custom gadgets, eg. CanvasGadgets.

If I start in Light Mode --> everything looks OK
If I start in Dark Mode --> everything looks OK

But if I toggle Dark/Light Mode while my PB programs are running...
(1) Window background colors update but StatusBar background colors don't
I posted about that here: viewtopic.php?f=24&t=76728&p=565922#p565922

(2) My procedure to get the system colors still returns the initial colors... it doesn't get the updated colors

(3) (Least important) I am polling to detect when Dark Mode toggles, which is OK. But an event-driven detection would be appreciated!

Any advice? Thanks to all you Cocoa experts!



Demo:

Code:
CompilerIf #PB_Compiler_OS <> #PB_OS_MacOS
  CompilerError "Please test on MacOS!"
CompilerEndIf

OpenWindow(0, 0, 0, 640, 480, "Dark Mode Test", #PB_Window_ScreenCentered | #PB_Window_SystemMenu)
CreateStatusBar(0, WindowID(0))
AddStatusBarField(#PB_Ignore)
StatusBarText(0, 0, "Status Bar Text")

Macro SetCanvasColor(Canvas, Color)
  If StartDrawing(CanvasOutput(Canvas))
    Box(0, 0, OutputWidth(), OutputHeight(), Color)
    StopDrawing()
  EndIf
EndMacro

y = 40
TextGadget(#PB_Any, 20, y, 200, 30, "windowBackgroundColor")
  CanvasGadget(0, 220, y, 30, 30, #PB_Canvas_Border)
  y + 30
TextGadget(#PB_Any, 20, y, 200, 30, "systemGrayColor")
  CanvasGadget(1, 220, y, 30, 30, #PB_Canvas_Border)
  y + 30
TextGadget(#PB_Any, 20, y, 200, 30, "controlBackgroundColor")
  CanvasGadget(2, 220, y, 30, 30, #PB_Canvas_Border)
  y + 30
TextGadget(#PB_Any, 20, y, 200, 30, "textColor")
  CanvasGadget(3, 220, y, 30, 30, #PB_Canvas_Border)
  y + 30

TextGadget(#PB_Any, 20, 420, 400, 30, "Toggle Dark Mode, and see if the Status Bar below changes color")

AddWindowTimer(0, 0, 1000)

DarkModeFlag.i = -1



Procedure.i Cocoa_IsDarkMode()
  Protected *appearance = CocoaMessage(0, CocoaMessage(0, 0, "NSUserDefaults standardUserDefaults"), "stringForKey:$", @"AppleInterfaceStyle")
  If (*appearance)
    *appearance = CocoaMessage(0, *appearance, "UTF8String")
    If (FindString(PeekS(*appearance, -1, #PB_UTF8), "Dark"))
      ProcedureReturn (#True)
    EndIf
  EndIf
  ProcedureReturn (#False)
EndProcedure


Procedure.i Cocoa_GetSysColor(NSColorName.s)
  ; "windowBackgroundColor"
  ; "systemGrayColor"
  ; "controlBackgroundColor"
  ; "textColor"
 
  Protected.CGFloat r, g, b
  Protected NSColor.i, NSColorSpace.i
 
  ; There is no controlAccentColor on macOS < 10.14
  If ((NSColorName = "controlAccentColor") And (OSVersion() < #PB_OS_MacOSX_10_14))
    ProcedureReturn ($D5ABAD)
  EndIf
 
  ; There are no system colors on macOS < 10.10
  If ((Left(NSColorName, 6) = "system") And (OSVersion() < #PB_OS_MacOSX_10_10))
    NSColorName = LCase(Mid(NSColorName, 7, 1)) + Mid(NSColorName, 8)
  EndIf
 
  NSColorSpace = CocoaMessage(0, 0, "NSColorSpace deviceRGBColorSpace")
  NSColor = CocoaMessage(0, CocoaMessage(0, 0, "NSColor " + NSColorName), "colorUsingColorSpace:", NSColorSpace)
  If (NSColor)
    CocoaMessage(@r, NSColor, "redComponent")
    CocoaMessage(@g, NSColor, "greenComponent")
    CocoaMessage(@b, NSColor, "blueComponent")
    ProcedureReturn (RGB(r * 255.0, g * 255.0, b * 255.0))
  EndIf
EndProcedure



Repeat
  Event = WaitWindowEvent()
  If (Event = #PB_Event_Timer)
    NewDarkMode = Cocoa_IsDarkMode()
    If NewDarkMode <> DarkModeFlag
      DarkModeFlag = NewDarkMode
      SetCanvasColor(0, Cocoa_GetSysColor("windowBackgroundColor"))
      SetCanvasColor(1, Cocoa_GetSysColor("systemGrayColor"))
      SetCanvasColor(2, Cocoa_GetSysColor("controlBackgroundColor"))
      SetCanvasColor(3, Cocoa_GetSysColor("textColor"))
      If (#True) ; Even if you re-create StatusBar, it remains in original color
        FreeStatusBar(0)
        CreateStatusBar(0, WindowID(0))
        AddStatusBarField(#PB_Ignore)
        StatusBarText(0, 0, "Status Bar Text")
      EndIf
      If DarkModeFlag
        MessageRequester("", "Dark Mode!")
      Else
        MessageRequester("", "Light / Regular Mode")
      EndIf
    EndIf
  EndIf
Until (Event = #PB_Event_CloseWindow) Or (Event = #PB_Event_Menu)

_________________
On GitHub: PB Includes - IDE Tools - Color Themes - IDE Branches - TabBarGadget Mods


Top
 Profile  
Reply with quote  
 Post subject: Re: [Mac] How to handle Dark Mode toggle?
PostPosted: Thu Feb 18, 2021 1:29 pm 
Offline
Enthusiast
Enthusiast
User avatar

Joined: Wed Jan 12, 2011 3:48 pm
Posts: 236
Location: Serbia
You need to use NSAppearance setCurrentAppearance for #1 and subscribe to theme change event for #3.
Check out those 2 examples:
viewtopic.php?p=546930#p546930
viewtopic.php?p=494505#p494505

Not sure about #2, but try setting the appearance first and maybe it will be fixed.

_________________
PB examples collection
pb-osx-globalhotkeys
pb-osx-notifications


Top
 Profile  
Reply with quote  
 Post subject: Re: [Mac] How to handle Dark Mode toggle?
PostPosted: Thu Feb 18, 2021 6:57 pm 
Offline
Addict
Addict
User avatar

Joined: Fri May 12, 2006 6:51 pm
Posts: 2876
Location: Germany
To respond to the colour change, deseven has already found.

I reported the problem with the status bar as a bug.
In order to surround the problem with the status bar for the time being, I have simply replaced the library "statusbar" in PB version v5.73 with PB version v5.70. So we are waiting for an update.

With the library "statusbar" from version v5.70, the color change also works.
Code:
;-TOP

Procedure NSColorByNameToRGB(NSColorName.s)
  Protected.cgfloat red, green, blue
  Protected nscolorspace, rgb
  nscolorspace = CocoaMessage(0, CocoaMessage(0, 0, "NSColor " + NSColorName), "colorUsingColorSpaceName:$", @"NSCalibratedRGBColorSpace")
  If nscolorspace
    CocoaMessage(@red, nscolorspace, "redComponent")
    CocoaMessage(@green, nscolorspace, "greenComponent")
    CocoaMessage(@blue, nscolorspace, "blueComponent")
    rgb = RGB(red * 255.0, green * 255.0, blue * 255.0)
    ProcedureReturn rgb
  EndIf
EndProcedure

; ----

; >> Key-Value observer code <<<

EnumerationBinary
  #NSKeyValueObservingOptionNew
  #NSKeyValueObservingOptionOld
EndEnumeration

Global *NSKeyValueChangeNewKey.Integer = dlsym_(#RTLD_DEFAULT, "NSKeyValueChangeNewKey")
Global *NSKeyValueChangeOldKey.Integer = dlsym_(#RTLD_DEFAULT, "NSKeyValueChangeOldKey")

; Declare the KVO callback procedure
DeclareC KVO(obj, sel, keyPath, object, change, context)

; Create Key-Value Observer class (PB_KVO)
Global KVO_Class.i = objc_allocateClassPair_(objc_getClass_("NSObject"), "PB_KVO", 0)
class_addMethod_(KVO_Class, sel_registerName_("observeValueForKeyPath:ofObject:change:context:"), @KVO(), "v@:@@@^v")
objc_registerClassPair_(KVO_Class)

; Create PB_KVO class instance (KVO)
Global KVO.i = CocoaMessage(0, 0, "PB_KVO new")

; >> End of Key-Value observer code <<<

Enumeration #PB_Event_FirstCustomValue
  #EventChangeAppearance
EndEnumeration

ProcedureC KVO(obj, sel, keyPath, object, change, context)
  Select PeekS(CocoaMessage(0, keyPath, "UTF8String"), -1, #PB_UTF8)
     
    Case "effectiveAppearance":
      CocoaMessage(0, 0, "NSAppearance setCurrentAppearance:", CocoaMessage(0, change, "objectForKey:", *NSKeyValueChangeNewKey\i))
      PostEvent(#EventChangeAppearance)
     
  EndSelect
EndProcedure

; add observer
Global NSApp.i = CocoaMessage(0, 0, "NSApplication sharedApplication")
CocoaMessage(0, NSApp, "addObserver:", KVO, "forKeyPath:$", @"effectiveAppearance", "options:", #NSKeyValueObservingOptionNew, "context:", #nil)


Global Event, text_color, control_background_color

Procedure UpdateColors()
  text_color = NSColorByNameToRGB("textColor")
  control_background_color = NSColorByNameToRGB("windowBackgroundColor")
 
  If StartDrawing(CanvasOutput(0))
    Box(0, 0, OutputWidth(), OutputHeight(), control_background_color)
    DrawingMode(#PB_2DDrawing_Transparent)
    DrawText(10, 10, "Hello World", text_color)
    StopDrawing()
  EndIf
EndProcedure

OpenWindow(0, #PB_Ignore, #PB_Ignore, 300, 180, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)

CreateStatusBar(0, WindowID(0))
AddStatusBarField(#PB_Ignore)
StatusBarText(0, 0, " StatusBar")

CreateImage(0, 64, 64, 32, $B48246)
CocoaMessage(0, WindowID(0), "setBackgroundColor:", CocoaMessage(0, 0, "NSColor colorWithPatternImage:", ImageID(0)))

CanvasGadget(0, 10, 10, WindowWidth(0) - 20, WindowHeight(0) - 20 - StatusBarHeight(0))

UpdateColors()

Repeat
  Event = WaitWindowEvent()
  If Event = #EventChangeAppearance
    UpdateColors()
  EndIf
Until Event = #PB_Event_CloseWindow

; remove observer

CocoaMessage(0, NSApp, "removeObserver:", KVO, "forKeyPath:$", @"effectiveAppearance")
CocoaMessage(0, KVO, "release")

_________________
My Projects ThreadToGUI / OOP-BaseClass / OOP-BaseClassDispatch / EventDesigner V3
PB v3.30 / v5.70 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace


Top
 Profile  
Reply with quote  
 Post subject: Re: [Mac] How to handle Dark Mode toggle?
PostPosted: Mon Feb 22, 2021 5:01 am 
Offline
Addict
Addict
User avatar

Joined: Tue Dec 23, 2003 3:54 am
Posts: 1873
Thank you both for your advice. If I knew Cocoa better, I probably could have solved it before from the threads you linked.
:D

Now I have a Key Value Observer to intercept theme changes, instead of constantly polling. Good!

Also, calling setCurrentAppearance results in the new, updated colors being reported. Good!!

All that remains, is the StatusBar background color doesn't update. I did not try copying the 5.70 StatusBar library yet. I would prefer a Cocoa workaround... but I have no idea how to start. Thanks for reporting the bug already.


EDIT: Can confirm, that copying over the 5.70 x64 StatusBar purelibrary "fixes" the StatusBar (its background color updates when light/dark mode changes). Thanks for all your help!!!

_________________
On GitHub: PB Includes - IDE Tools - Color Themes - IDE Branches - TabBarGadget Mods


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 4 posts ] 

All times are UTC + 1 hour


Who is online

Users browsing this forum: No registered users and 5 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
Jump to:  

 


Powered by phpBB © 2008 phpBB Group
subSilver+ theme by Canver Software, sponsor Sanal Modifiye