Page 1 of 1

Getting window color with dark mode active?

Posted: Fri Jan 03, 2020 4:27 pm
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?

Re: Getting window color with dark mode active?

Posted: Fri Jan 03, 2020 5:04 pm
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.

Re: Getting window color with dark mode active?

Posted: Fri Jan 03, 2020 5:20 pm
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.

Re: Getting window color with dark mode active?

Posted: Fri Jan 03, 2020 5:40 pm
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")

Re: Getting window color with dark mode active?

Posted: Fri Jan 03, 2020 5:43 pm
by deseven
This is exactly what i'm doing (i even looked at your NSColor module) and it doesn't work:
Image

Re: Getting window color with dark mode active?

Posted: Fri Jan 03, 2020 5:57 pm
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.

Re: Getting window color with dark mode active?

Posted: Fri Jan 03, 2020 6:05 pm
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)))
...

Re: Getting window color with dark mode active?

Posted: Fri Jan 03, 2020 6:16 pm
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")

Re: Getting window color with dark mode active?

Posted: Fri Jan 03, 2020 6:23 pm
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

Re: Getting window color with dark mode active?

Posted: Fri Jan 03, 2020 6:24 pm
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 :?

Re: Getting window color with dark mode active?

Posted: Fri Jan 03, 2020 6:26 pm
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.

Re: Getting window color with dark mode active?

Posted: Sun Jan 05, 2020 4:11 pm
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?

Re: Getting window color with dark mode active?

Posted: Sun Jan 05, 2020 7:40 pm
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

Re: Getting window color with dark mode active?

Posted: Wed Jan 08, 2020 6:01 am
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