Recent

Author Topic: How to detect if macOS changed Theme (Dark vs Light) while running?  (Read 2500 times)

Hansaplast

  • Hero Member
  • *****
  • Posts: 516
  • Tweaking4All.com
    • Tweaking4All
The latest SVN version (r59451) seems to include some impressive work on support for Dark and Light Theme under Mojave. When switching theme, the IDE changes as well. This even works with my own applications now! Awesome!!  8)


I was wondering if there is an event that I can tie a procedure to so I can have my application respond to the color changes (for custom drawn controls etc)?

CCRDude

  • Sr. Member
  • ****
  • Posts: 490
Re: How to detect if macOS changed Theme (Dark vs Light) while running?
« Reply #1 on: November 05, 2018, 05:15:36 pm »
I would like to second that request :)

Or simply a way to query as a first step.

Hansaplast

  • Hero Member
  • *****
  • Posts: 516
  • Tweaking4All.com
    • Tweaking4All
Re: How to detect if macOS changed Theme (Dark vs Light) while running?
« Reply #2 on: November 05, 2018, 05:29:27 pm »
Hi CCRDude!


I found a way to query if Dark Theme is active or not, even if it's not the most elegant way, but it's fast and it works.
The preferences value updates right away when a user switched back and forth between dark and light theme.
You can glue this in one function of course, I just use the GetPrefString function for other purposes as well.



Code: Pascal  [Select]
  1. // Retrieve key's string value from user preferences. Result is encoded using NSStrToStr's default encoding.
  2. function GetPrefString(const KeyName : string) : string;
  3. begin
  4.   Result := NSStringToString(NSUserDefaults.standardUserDefaults.stringForKey(NSStr(@KeyName[1])));
  5. end;
  6.  
  7.  
  8. // IsDarkTheme: Detects if the Dark Theme (true) has been enabled or not (false)
  9. function IsDarkTheme:boolean;
  10. begin
  11.   Result := pos('DARK',UpperCase(GetPrefString('AppleInterfaceStyle')))>0;
  12. end;
  13.  
   


Note: I got the function to get the preference keyword from one of Phil's units if I recall correctly - there are some very neat and useful tools in his units.
The biggest problem I run into is that I need to update other controls as well to match the new colors, but have no means to get alerted about the change  :o

Hansaplast

  • Hero Member
  • *****
  • Posts: 516
  • Tweaking4All.com
    • Tweaking4All
Re: How to detect if macOS changed Theme (Dark vs Light) while running?
« Reply #3 on: November 09, 2018, 03:28:10 pm »
Using the function above, I did find a way to detect the change by using the onPaint event.
However I'm not happy, or maybe I should say "concerned", with how often the onPaint event is fired, especially when resizing a form.


Code: Pascal  [Select]
  1. procedure TForm1.FormPaint(Sender: TObject);
  2. begin
  3.   if IsDarkTheme then
  4.     label1.Caption:='Dark Theme'
  5.   else
  6.     label1.Caption:='Light Theme';
  7. end;


I'd rather see a specific event, based on theme change. I've tried looking at something like this (usign addObserver, with themeChanged() being the notification function);


Code: C  [Select]
  1. [NSDistributedNotificationCenter.defaultCenter addObserver:self selector:@selector(themeChanged:) name:@"AppleInterfaceThemeChangedNotification" object: nil];
  2.  
  3.  
  4. -(void)themeChanged:(NSNotification *) notification {
  5.     NSLog (@"%@", notification);
  6. }


But I have not been successful in translating this to Pascal, even with Dmitry's help who suggested looking at (in cocoawsforms)
Code: Pascal  [Select]
  1. NSNotificationCenter.defaultCenter.addObserver_selector_name_object(cnt, objcselector('didResignKeyNotification:'), NSWindowDidResignKeyNotification, cnt.window);


Any help would be much appreciated.
« Last Edit: November 11, 2018, 12:19:39 pm by Hansaplast »

jwdietrich

  • Hero Member
  • *****
  • Posts: 994
    • formatio reticularis
Re: How to detect if macOS changed Theme (Dark vs Light) while running?
« Reply #4 on: November 25, 2018, 11:19:53 pm »
For me, it works great. The onPaint event seems to be automatically fired every time the theme is changed.
function GetRandomNumber: integer; // xkcd.com
begin
  GetRandomNumber := 4; // chosen by fair dice roll. Guaranteed to be random.
end;

http://www.formatio-reticularis.de

Lazarus 2.0.0 | FPC 3.0.4 | PPC, Intel, ARM | macOS, Windows, Linux

Hansaplast

  • Hero Member
  • *****
  • Posts: 516
  • Tweaking4All.com
    • Tweaking4All
Re: How to detect if macOS changed Theme (Dark vs Light) while running?
« Reply #5 on: November 26, 2018, 10:53:41 am »
It works fine indeed, just the when resizing a form this event gets fired tons of times - that was my only concern.
You can try with a simple example; add a TLabel to your form, and do the following on the onPaint event:


Code: Pascal  [Select]
  1.   Label1.Tag := Label1.Tag+1;
  2.   Label1.Caption:=IntToStr(Label1.Tag);


Now run you application and resize the window ...



On top of that; Cocoa has a function to have the OS inform your application that the theme has changed - which would be only one single call when the theme changes.
I have not been able to get that call to work with lazarus though, due to my lack of experience with converting Obj-C calls.
Disk Arbitration has a similar function (called in a different way) to let you application know if a disk was inserted, ejected, mounted or unmounted as well, and that one I did get to work.

lucamar

  • Hero Member
  • *****
  • Posts: 1299
Re: How to detect if macOS changed Theme (Dark vs Light) while running?
« Reply #6 on: November 26, 2018, 02:20:22 pm »
Hansaplast, I think at least part of your problem is that if you set the caption in the OnPaint handler it will trigger another repaint and another call to OnPaint.
Turbo Pascal 3 CP/M - Amstrad PCW 8256 (512 KB !!!) :P
Lazarus 1.8.4 & 2.0.2 w/FPC 3.0.4 on:
(K|L)Ubuntu 12..16, Windows XP SP3, various DOSes.

Hansaplast

  • Hero Member
  • *****
  • Posts: 516
  • Tweaking4All.com
    • Tweaking4All
Re: How to detect if macOS changed Theme (Dark vs Light) while running?
« Reply #7 on: November 26, 2018, 02:36:08 pm »
Oh duh! Good call lucamar!  :-[

I'll test it without changing the label and see what happens.

Hansaplast

  • Hero Member
  • *****
  • Posts: 516
  • Tweaking4All.com
    • Tweaking4All
Re: How to detect if macOS changed Theme (Dark vs Light) while running?
« Reply #8 on: November 26, 2018, 02:53:37 pm »
Hansaplast, I think at least part of your problem is that if you set the caption in the OnPaint handler it will trigger another repaint and another call to OnPaint.



I did a simple test, and even though it's not 100% accurate, it seems that changing the caption does not trigger the onPaint event of the form itself (only a repaint of TLabel?).


Simple resizing of the form to a little bigger, triggered onPaint around 20 times, without updating the TLabel Caption.
Doing the same test, however with updating the TLabel caption, triggered it around 20 times as well.
Since it's kinda hard to measure, it all depends how steady my hand is and if I have the mouse travel the exact same direction and distance at the same speed, I'm guessing onPaint triggers about the same amount of times during a resize, with ot without changing TLabel.caption.


I tried playing with TForm.IsResizing, but that doesn't seem to be doing anything (ie. this value doesn't appear to be "true" at any time while resizing).


Example code I used - simple form, with a TLabel on it:


Code: Pascal  [Select]
  1. unit Unit1;
  2. {$mode objfpc}{$H+}
  3. interface
  4. uses
  5.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls;
  6. type
  7.   { TForm1 }
  8.   TForm1 = class(TForm)
  9.     Label1: TLabel;
  10.     procedure FormClose(Sender: TObject; var CloseAction: TCloseAction);
  11.     procedure FormCreate(Sender: TObject);
  12.     procedure FormPaint(Sender: TObject);
  13.   private
  14.     counter:integer;
  15.   public
  16.   end;
  17. var
  18.   Form1: TForm1;
  19. implementation
  20. {$R *.lfm}
  21. { TForm1 }
  22. procedure TForm1.FormClose(Sender: TObject; var CloseAction: TCloseAction);
  23. begin
  24.   ShowMessage(IntToStr(counter)); // show count
  25. end;
  26. procedure TForm1.FormCreate(Sender: TObject);
  27. begin
  28.   Counter:=0;
  29. end;
  30. procedure TForm1.FormPaint(Sender: TObject);
  31. begin
  32.   inc(Counter);
  33.   //label1.Caption:=IntToStr(counter);
  34. end;
  35. end.

VTwin

  • Hero Member
  • *****
  • Posts: 620
  • Former Turbo Pascal 3 user
Re: How to detect if macOS changed Theme (Dark vs Light) while running?
« Reply #9 on: December 09, 2018, 03:16:59 pm »
I notify forms of events, such as changes in file or item selections by sending a message:

Code: Pascal  [Select]
  1. type
  2.   TVMessage = packed record
  3.     Id    : longint;
  4.     Param : word;
  5.     Flag  : word;
  6.     Text  : shortstring;
  7.   end;
  8.  
  9. { DispatchMessage
  10.   Dispatches a message to all forms. This adds a message to the message queue,
  11.   which will be processsed by the handler after the current process finishes.
  12.   Define a message id such as:
  13.     LM_Msg = LM_USER + 1;
  14.   where LM_USER is defined in LMessages, and a handler such as:
  15.     procedure OnMessage(var msg: TLMessage); message LM_Msg; }
  16. procedure DispatchMessage(Id: dword; Param, Flag: word; Txt: string);
  17. var
  18.   i: integer;
  19.   f: TForm;
  20.   m: TVMessage;
  21. begin
  22.   m.Id := Id;
  23.   m.Param := Param;
  24.   m.Flag := Flag;
  25.   m.Text := Txt;
  26.   for i := 0 to Screen.FormCount - 1 do begin
  27.     f := Screen.Forms[i];
  28.     if (f <> nil) then
  29.       f.Dispatch(m);
  30.   end;
  31. end;
  32.  

In my case, when the user selects a data item an image in another form is updated. Of course this could be modified for objects other than forms.

You could test for a change in dark theme in a timer, then dispatch a message. Any forms with custom controls could react as needed. Just a thought, there is probably a better way.

Cheers,
VTwin
“Talk is cheap. Show me the code.” -Linus Torvalds

macOS 10.11.6: Lazarus 2.0.1 svn r60918 (64 bit Cocoa)
Ubuntu 18.04.2: Lazarus 2.0.0 (64 bit on VBox)
Windows 7 Pro SP1: Lazarus 2.0.0 (64 bit on VBox)

Hansaplast

  • Hero Member
  • *****
  • Posts: 516
  • Tweaking4All.com
    • Tweaking4All
Re: How to detect if macOS changed Theme (Dark vs Light) while running?
« Reply #10 on: December 09, 2018, 05:08:02 pm »
Thanks VTwin! I'll give that a try.
Not crazy about using a timer though, but I'll give it a try.
OnPaint does seem to work OK and not bog down the application too much.

VTwin

  • Hero Member
  • *****
  • Posts: 620
  • Former Turbo Pascal 3 user
Re: How to detect if macOS changed Theme (Dark vs Light) while running?
« Reply #11 on: December 09, 2018, 05:37:46 pm »
Not crazy about using a timer though, but I'll give it a try.

I try not to use them too much, but they can be handy. I had been using timers to set and check global flags for changes requiring updates, but that started getting messy and I mostly switched to messages.

I assume people don't switch themes very often, so maybe a small lag is not a big deal? If the timer checked once every 1000 ms (or more) perhaps that is enough. A simpler option than messages is to just set a global flag, "gIsDarkTheme" in the timer. The controls would only get updated when a repaint is triggered, but maybe that is sufficient, and is similar to what you are doing now.
« Last Edit: December 09, 2018, 05:42:07 pm by VTwin »
“Talk is cheap. Show me the code.” -Linus Torvalds

macOS 10.11.6: Lazarus 2.0.1 svn r60918 (64 bit Cocoa)
Ubuntu 18.04.2: Lazarus 2.0.0 (64 bit on VBox)
Windows 7 Pro SP1: Lazarus 2.0.0 (64 bit on VBox)