Getting window color with dark mode active?

Mac OSX specific forum
User avatar
deseven
Enthusiast
Enthusiast
Posts: 367
Joined: Wed Jan 12, 2011 3:48 pm
Location: Serbia
Contact:

Getting window color with dark mode active?

Post by deseven »

So, since PB IDE is opensource now, i'm trying to add the support for macos dark mode.
The IDE uses a custom control called TabBarGadget and of course it doesn't use system colors. I thought that i'd just make it use them, sounds easy, right?

Well, it turned out that the real problem is to get a window color.

Consider this example (this code is based on various answers in various topics - 1, 2, 3):

Code: Select all

Procedure SetTextColorABGR(EditorGadget, Color, StartPosition, Length = -1, BackColor = #NO)
  Protected.CGFloat r,g,b,a
  Protected range.NSRange, textStorage.i
  If StartPosition > 0
    textStorage = CocoaMessage(0, GadgetID(EditorGadget), "textStorage")
    range\location = StartPosition - 1
    range\length = CocoaMessage(0, textStorage, "length") - range\location
    If range\length > 0
      If Length >= 0 And Length < range\length
        range\length = Length
      EndIf
      r = Red(Color) / 255
      g = Green(Color) / 255
      b = Blue(Color) / 255
      a = Alpha(Color) / 255
      Color = CocoaMessage(0, 0, "NSColor colorWithDeviceRed:@", @r, "green:@", @g, "blue:@", @b, "alpha:@", @a)
      If BackColor
        CocoaMessage(0, textStorage, "addAttribute:$", @"NSBackgroundColor", "value:", Color, "range:@", @range)
      Else
        CocoaMessage(0, textStorage, "addAttribute:$", @"NSColor", "value:", Color, "range:@", @range)
      EndIf
    EndIf
  EndIf
EndProcedure

OpenWindow(0,#PB_Ignore,#PB_Ignore,500,800,"colors test",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
EditorGadget(0,10,10,480,780)

ColorList = CocoaMessage(0, 0, "NSColorList colorListNamed:$", @"System")
If ColorList
  ColorSpace = CocoaMessage(0, 0, "NSColorSpace deviceRGBColorSpace")
  Keys = CocoaMessage(0, ColorList, "allKeys")
  NumKeys = CocoaMessage(0, Keys, "count")
  For k = 1 To NumKeys
    Key = CocoaMessage(0, Keys, "objectAtIndex:", k - 1)
    Color = CocoaMessage(0, ColorList, "colorWithKey:", Key)
    Color = CocoaMessage(0, Color, "colorUsingColorSpace:", ColorSpace)
    If Color
      KeyName.s = PeekS(CocoaMessage(0, Key, "UTF8String"), -1, #PB_UTF8)
      CocoaMessage(@r.CGFloat, Color, "redComponent")
      CocoaMessage(@g.CGFloat, Color, "greenComponent")
      CocoaMessage(@b.CGFloat, Color, "blueComponent")
      CocoaMessage(@a.CGFloat, Color, "alphaComponent")
      ;Debug KeyName + " = RGBA(" + Str(r*255) + "," + Str(g*255) + "," + Str(b*255) + "," + Str(a*255) + ")"
      AddGadgetItem(0,-1,KeyName + ", RGBA(" + Str(r*255) + "," + Str(g*255) + "," + Str(b*255) + "," + Str(a*255) + "):")
      AddGadgetItem(0,-1,"█ █ █ █ █")
      SetTextColorABGR(0,RGBA(r*255,g*255,b*255,a*255),Len(GetGadgetText(0))-8,9)
      AddGadgetItem(0,-1,"")
    EndIf
  Next
EndIf

Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
It works totally fine without dark mode enabled, windowBackgroundColor matches real window background color. However, if you enable dark mode, it clearly fails to do so:
Image

ControlAccentColor also looks a little wrong.

It doesn't matter if you build your color from NSColor components or by drawing a pixel with drawSwatchInRect. Changing ColorSpaceName also doesn't fix it. Looks like system applies some sort of a tint on top of a base color, but i can't find any information about that whatsoever.

Can anyone help?
User avatar
Bisonte
Addict
Addict
Posts: 1305
Joined: Tue Oct 09, 2007 2:15 am

Re: Getting window color with dark mode active?

Post by Bisonte »

Maybe these "old" trick works ?

Code: Select all

Procedure.l GetOsWinColor()
  
  Protected Window = OpenWindow(#PB_Any, 0, 0, 10, 10, "", #PB_Window_BorderLess)
  Protected Color.l = -1
  
  If Window
    While WindowEvent() : Wend
    If StartDrawing(WindowOutput(Window))
      Color = Point(2, 2)
      StopDrawing()
    EndIf
  EndIf
  
  ProcedureReturn Color
  
EndProcedure

Debug Hex(GetOsWinColor())
If it work for you , you should use it on programstart, to avoid focus problems.
PureBasic 6.21 (Windows x64) | Windows 11 Pro | AsRock B850 Steel Legend Wifi | R7 9800x3D | 64GB RAM | RTX 5080 | ThermaltakeView 270 TG ARGB | build by vannicom​​
English is not my native language... (I often use DeepL.)
User avatar
deseven
Enthusiast
Enthusiast
Posts: 367
Joined: Wed Jan 12, 2011 3:48 pm
Location: Serbia
Contact:

Re: Getting window color with dark mode active?

Post by deseven »

Doesn't look like WindowOutput() on macos contains actual rendered window, because Point() always return zero for all possible pixels.
But thanks for the idea, it's an interesting one.
User avatar
mk-soft
Always Here
Always Here
Posts: 6209
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Getting window color with dark mode active?

Post by mk-soft »

with change colors on running app.

Code: Select all

;-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, 150, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)

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

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")
Last edited by mk-soft on Fri Jan 03, 2020 5:43 pm, edited 1 time in total.
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
deseven
Enthusiast
Enthusiast
Posts: 367
Joined: Wed Jan 12, 2011 3:48 pm
Location: Serbia
Contact:

Re: Getting window color with dark mode active?

Post by deseven »

This is exactly what i'm doing (i even looked at your NSColor module) and it doesn't work:
Image
User avatar
mk-soft
Always Here
Always Here
Posts: 6209
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Getting window color with dark mode active?

Post by mk-soft »

Now I understand your problem.

I also noticed that the background color is not quite right.

That's because the window background color of the apps has a certain transparency and depending on the position behind the window the background color changes slightly.

This is very much to program and because the object must have this property.
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
mk-soft
Always Here
Always Here
Posts: 6209
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Getting window color with dark mode active?

Post by mk-soft »

Add Code...

Code: Select all

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

CreateImage(0, 64, 64, 32, $B48246)
CocoaMessage(0, WindowID(0), "setBackgroundColor:",
                   CocoaMessage(0, 0, "NSColor colorWithPatternImage:", ImageID(0)))
...
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
mestnyi
Addict
Addict
Posts: 1098
Joined: Mon Nov 25, 2013 6:41 am

Re: Getting window color with dark mode active?

Post by mestnyi »

try it, what do you say?

Code: Select all

;-TOP

Procedure NSColorByNameToRGB(NSColorName.s)
  Protected.cgfloat red, green, blue, alpha
  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")
    CocoaMessage(@alpha, nscolorspace, "alphaComponent")
    rgb = RGBA(red * 260.0, green * 260.0, blue * 260.0, alpha * 260.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, 150, "", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)

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

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")
User avatar
deseven
Enthusiast
Enthusiast
Posts: 367
Joined: Wed Jan 12, 2011 3:48 pm
Location: Serbia
Contact:

Re: Getting window color with dark mode active?

Post by deseven »

Setting a window background manually or adding a correction to background color may work, but this is too hacky and i want to use system colors without losing any transparency.

I think i better go another way and make tab controls in TabBarGadget have a transparent background. There are some problems with that, but it looks nice:
Image

Thanks for your help!
If you want to play with dark mode yourself to get a better idea check out my fork here - https://github.com/deseven/purebasic
User avatar
mk-soft
Always Here
Always Here
Posts: 6209
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Getting window color with dark mode active?

Post by mk-soft »


rgb = RGBA(red * 260.0, green * 260.0, blue * 260.0, alpha * 260.0)


A byte have only a range of 0..255 :?
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
User avatar
deseven
Enthusiast
Enthusiast
Posts: 367
Joined: Wed Jan 12, 2011 3:48 pm
Location: Serbia
Contact:

Re: Getting window color with dark mode active?

Post by deseven »

Yeah, he just tried to make a correction that way. As i said this may work, but i don't want to do it like that. IDE should look like a native app.
Wolfram
Enthusiast
Enthusiast
Posts: 604
Joined: Thu May 30, 2013 4:39 pm

Re: Getting window color with dark mode active?

Post by Wolfram »

deseven wrote: If you want to play with dark mode yourself to get a better idea check out my fork here - https://github.com/deseven/purebasic
Thanks for sharing your code.
Unfortunately I get some errors. For example the "BuildInfo.pb" is missing.
Could you share these file too please?
macOS Catalina 10.15.7
User avatar
deseven
Enthusiast
Enthusiast
Posts: 367
Joined: Wed Jan 12, 2011 3:48 pm
Location: Serbia
Contact:

Re: Getting window color with dark mode active?

Post by deseven »

Wolfram wrote: Thanks for sharing your code.
Unfortunately I get some errors. For example the "BuildInfo.pb" is missing.
Could you share these file too please?
How exactly are you trying to build it?

Let me describe the whole (correct) process so you can see if anything is wrong.
1. Copy OSX-x64.bash to OSX-x64-local.bash (or any other name, it doesn't matter).
2. Edit PUREBASIC_HOME variable inside, it should point to your PB Resources dir (for me it's "/Applications/PureBasic.app/Contents/Resources").
3. Open your Terminal, cd to the repo root and type

Code: Select all

. OSX-x64-local.bash
4. Next cd to PureBasicIDE dir.
5. Type

Code: Select all

make clean && make
New IDE app should be located in your PureBasic Home dir from #2.

UPD: After that if you're changing pb files only you can open .pbp project and work on it like on any other PB project, more info about that here at the bottom - https://github.com/fantaisie-software/p ... r/BUILD.md
User avatar
kenmo
Addict
Addict
Posts: 2033
Joined: Tue Dec 23, 2003 3:54 am

Re: Getting window color with dark mode active?

Post by kenmo »

Tonight I got the IDE repo building on my Mac, thanks for your tips. Much simpler than the Windows build system.

I also pulled in your Dark Mode changes, but I did not get to test them yet. Tomorrow I hope! :D
Post Reply