Difference between revisions of "Dark theme"

From Free Pascal wiki
Jump to navigationJump to search
 
(21 intermediate revisions by 4 users not shown)
Line 1: Line 1:
 
How to detect whether the operating system is using a dark theme?
 
How to detect whether the operating system is using a dark theme?
  
=Universal method=
+
==Universal method==
==Example 1==
+
The following method is based on the GUI colors. It works on all platforms but those colors are not updated when the theme is changed while the application is running. Thus it cannot allow to detect if the dark mode is toggled.
 +
 
 +
===Example 1===
  
 
<syntaxhighlight lang="pascal">
 
<syntaxhighlight lang="pascal">
Line 17: Line 19:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
==Example 2==
+
===Example 2===
 
<syntaxhighlight lang="pascal">
 
<syntaxhighlight lang="pascal">
// by "dbannon" from Lazarus forum
+
// by "Hansaplast" & "Alextp" from Lazarus forum
function IsDarkTheme(F: TForm): boolean;
+
function IsDarkTheme: boolean;
var
+
  function _Level(C: TColor): double;
   Col : string;
+
  begin
 +
    Result:= Red(C)*0.3 + Green(C)*0.59 + Blue(C)*0.11;
 +
   end;
 
begin
 
begin
   // if char 3, 5 and 7 are all 'A' or above, we are not in a DarkTheme
+
   Result:= _Level(ColorToRGB(clWindow)) < _Level(ColorToRGB(clWindowText));
  Col := HexStr(qword(F.GetRGBColorResolvingParent()), 8);
 
  Result := (Col[3] < 'A') and (Col[5] < 'A') and (Col[7] < 'A');
 
 
end;
 
end;
 
</syntaxhighlight>
 
</syntaxhighlight>
Line 40: Line 42:
 
      
 
      
 
// IsDarkTheme: Detects if the Dark Theme (true) has been enabled or not (false)
 
// IsDarkTheme: Detects if the Dark Theme (true) has been enabled or not (false)
function DarkTheme: boolean;
+
function IsDarkTheme: boolean;
 
const
 
const
 
   KEYPATH = '\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize';
 
   KEYPATH = '\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize';
Line 61: Line 63:
 
     else
 
     else
 
       LightKey := true;
 
       LightKey := true;
      Result := not LightKey
+
    Result := not LightKey
 
   finally
 
   finally
 
     Registry.Free;
 
     Registry.Free;
Line 67: Line 69:
 
end;
 
end;
 
</syntaxhighlight>
 
</syntaxhighlight>
   
+
 
 
== macOS method ==
 
== macOS method ==
  
Line 73: Line 75:
  
 
<syntaxhighlight lang="pascal">
 
<syntaxhighlight lang="pascal">
// by "jwdietrich" from Lazarus forum
+
// by "trev" from Lazarus forum
uses
+
 
  MacOSAll;
+
{ returns true, if this app runs on macOS 10.14.0 Mojave or newer }
   
+
function MojaveOrNewer: boolean;
function IsMinMacOS(Maj, Min: integer): boolean;
 
  { returns true, if this app runs on a macOS version as specified or newer }
 
 
var
 
var
   Major, Minor: SInt32;
+
   minOsVer: NSOperatingSystemVersion;
  theError: SInt16;
 
 
begin
 
begin
   result := false;
+
   //Setup minimum version (Mojave)
   theError := Gestalt(gestaltSystemVersionMajor, Major);
+
  minOsVer.majorVersion:= 10;
   if theError = 0 then
+
   minOsVer.minorVersion:= 14;
    theError := Gestalt(gestaltSystemVersionMinor, Minor);
+
   minOsVer.patchVersion:= 0;
   if theError = 0 then
+
 
    if (Major = Maj) and (Minor >= Min) or (Major > Maj) then
+
   // Check minimum version
      Result := True;
+
  if(NSProcessInfo.ProcessInfo.isOperatingSystemAtLeastVersion(minOSVer)) then
end;
+
    Result := True
   
+
   else
function MojaveOrNewer: boolean;
+
    Result := False;
   { returns true, if this app runs on macOS X 10.14 Mojave or newer }
 
begin
 
  result := IsMinMacOS(10, 14);
 
 
end;
 
end;
 
      
 
      
Line 107: Line 103:
 
      
 
      
 
// IsDarkTheme: Detects if the Dark Theme (true) has been enabled or not (false)
 
// IsDarkTheme: Detects if the Dark Theme (true) has been enabled or not (false)
function DarkTheme: boolean;
+
function IsDarkTheme: boolean;
 
begin
 
begin
 
   Result := false;
 
   Result := false;
Line 113: Line 109:
 
     Result := pos('DARK',UpperCase(GetPrefString('AppleInterfaceStyle'))) > 0;
 
     Result := pos('DARK',UpperCase(GetPrefString('AppleInterfaceStyle'))) > 0;
 
end;
 
end;
 +
</syntaxhighlight>
 +
 +
==== Demo ====
 +
 +
<syntaxhighlight lang=pascal>
 +
unit Unit1;
 +
 +
{$mode objfpc}{$H+}
 +
{$modeswitch objectivec1}
 +
 +
interface
 +
 +
uses
 +
  Classes, Forms, Dialogs, StdCtrls, SysUtils,
 +
  CocoaAll, CocoaUtils, MacOSAll;
 +
 +
type
 +
 +
  { TForm1 }
 +
 +
  TForm1 = class(TForm)
 +
    Button1: TButton;
 +
    procedure Button1Click(Sender: TObject);
 +
  private
 +
 +
  public
 +
 +
  end;
 +
 +
var
 +
  Form1: TForm1;
 +
 +
implementation
 +
 +
{$R *.lfm}
 +
 +
{ TForm1 }
 +
 +
{ returns true, if this app runs on macOS 10.14 Mojave or newer }
 +
function MojaveOrNewer: boolean;
 +
var
 +
  minOsVer: NSOperatingSystemVersion;
 +
begin
 +
  // Setup minimum version (Mojave)
 +
  minOsVer.majorVersion:= 10;
 +
  minOsVer.minorVersion:= 14;
 +
  minOsVer.patchVersion:= 0;
 +
 +
  // Check minimum version
 +
  if(NSProcessInfo.ProcessInfo.isOperatingSystemAtLeastVersion(minOSVer)) then
 +
    Result := True
 +
  else
 +
    Result := False;
 +
end;
 +
 +
function GetPrefString(const KeyName : string) : string;
 +
begin
 +
  Result := NSStringToString(NSUserDefaults.standardUserDefaults.stringForKey(NSStr(@KeyName[1])));
 +
end;
 +
 +
function IsDarkTheme: boolean;
 +
begin
 +
  Result := false;
 +
  if MojaveOrNewer then
 +
    Result := pos('DARK',UpperCase(GetPrefString('AppleInterfaceStyle'))) > 0;
 +
end;
 +
 +
procedure TForm1.Button1Click(Sender: TObject);
 +
begin
 +
  if(IsDarkTheme) then
 +
    ShowMessage('Dark mode')
 +
  else
 +
    ShowMessage('Light mode');
 +
end;
 +
 +
end.
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Line 136: Line 208:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== See also ===
+
==== Demo ====
 +
 
 +
<syntaxhighlight lang=pascal>
 +
unit Unit1;
 +
 
 +
{$mode objfpc}{$H+}
 +
{$modeswitch objectivec1}
 +
 
 +
interface
 +
 
 +
uses
 +
  Classes, Forms, Dialogs, StdCtrls,
 +
  CocoaAll, CocoaUtils, MacOSAll;
 +
 
 +
type
 +
 
 +
  { TForm1 }
 +
 
 +
  TForm1 = class(TForm)
 +
    Button1: TButton;
 +
    procedure Button1Click(Sender: TObject);
 +
  private
 +
 
 +
  public
 +
 
 +
  end;
 +
 
 +
var
 +
  Form1: TForm1;
 +
 
 +
implementation
 +
 
 +
{$R *.lfm}
 +
 
 +
{ TForm1 }
 +
 
 +
function IsMacDarkMode: Boolean;
 +
var
 +
  sMode: string;
 +
begin
 +
  sMode  := CFStringToStr( CFStringRef( NSApp.effectiveAppearance.name ));
 +
  Result := Pos('Dark', sMode) > 0;
 +
end;
 +
 
 +
procedure TForm1.Button1Click(Sender: TObject);
 +
begin
 +
  if(IsMacDarkMode) then
 +
    ShowMessage('Dark mode')
 +
  else
 +
    ShowMessage('Light mode');
 +
end;
 +
 
 +
end.
 +
</syntaxhighlight>
 +
 
 +
=== Opting out of dark mode ===
 +
 
 +
Mac systems automatically opt in any application linked against the macOS 10.14 or later SDK to both light and dark appearances. You can opt out of dark mode by including the NSRequiresAquaSystemAppearance key (with a value of YES) in your application’s [[macOS_property_list_files#Info.plist|Info.plist]] property list file. Setting this key to YES causes the system to ignore the user's preference and always apply a light appearance to your application.
 +
 
 +
=== Opting in to dark mode ===
 +
 
 +
If you build your application against an earlier SDK but still want to support Dark Mode, include the NSRequiresAquaSystemAppearance key (with a value of NO) in your application's [[macOS_property_list_files#Info.plist|Info.plist]] property list file. Do so only if your application's appearance looks correct when running in macOS 10.14 and later with Dark Mode enabled.
 +
 
 +
==See Also==
  
* [[macOS extensions]]  
+
* [[macOS extensions]]
  
 
=== External links ===
 
=== External links ===
  
* [https://developer.apple.com/documentation/appkit/nsappearance Apple: NSAppearnce].
 
* [https://developer.apple.com/documentation/appkit/nsappearance/2980972-bestmatchfromappearanceswithname Apple: bestMatchFromAppearancesWithName]
 
 
* [https://developer.apple.com/design/human-interface-guidelines/macos/visual-design/dark-mode/ Apple: Mojave Dark Mode].
 
* [https://developer.apple.com/design/human-interface-guidelines/macos/visual-design/dark-mode/ Apple: Mojave Dark Mode].
  
Line 150: Line 283:
 
[[Category:Windows]]
 
[[Category:Windows]]
 
[[Category:Code Snippets]]
 
[[Category:Code Snippets]]
 +
[[Category:Platform-sensitive development]]

Latest revision as of 12:19, 31 January 2021

How to detect whether the operating system is using a dark theme?

Universal method

The following method is based on the GUI colors. It works on all platforms but those colors are not updated when the theme is changed while the application is running. Thus it cannot allow to detect if the dark mode is toggled.

Example 1

// by "Alextp" from Lazarus forum
function IsDarkTheme: boolean;
const
  cMax = $A0;
var
  N: TColor;
begin
  N:= ColorToRGB(clWindow);
  Result:= (Red(N)<cMax) and (Green(N)<cMax) and (Blue(N)<cMax);
end;

Example 2

// by "Hansaplast" & "Alextp" from Lazarus forum
function IsDarkTheme: boolean;
  function _Level(C: TColor): double;
  begin
    Result:= Red(C)*0.3 + Green(C)*0.59 + Blue(C)*0.11;
  end;
begin
  Result:= _Level(ColorToRGB(clWindow)) < _Level(ColorToRGB(clWindowText));
end;

Windows method

Example 1

// by "jwdietrich" from Lazarus forum
uses
  Windows, Win32Proc, Registry;
     
// IsDarkTheme: Detects if the Dark Theme (true) has been enabled or not (false)
function IsDarkTheme: boolean;
const
  KEYPATH = '\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize';
  KEYNAME = 'AppsUseLightTheme';
var
  LightKey: boolean;
  Registry: TRegistry;
begin
  Result := false;
  Registry := TRegistry.Create;
  try
    Registry.RootKey := HKEY_CURRENT_USER;
    if Registry.OpenKeyReadOnly(KEYPATH) then
      begin
        if Registry.ValueExists(KEYNAME) then
          LightKey := Registry.ReadBool(KEYNAME)
        else
          LightKey := true;
      end
    else
      LightKey := true;
    Result := not LightKey
  finally
    Registry.Free;
  end;
end;

macOS method

Example 1

// by "trev" from Lazarus forum
   
{ returns true, if this app runs on macOS 10.14.0 Mojave or newer }
function MojaveOrNewer: boolean;
var
  minOsVer: NSOperatingSystemVersion;
begin
  //Setup minimum version (Mojave)
  minOsVer.majorVersion:= 10;
  minOsVer.minorVersion:= 14;
  minOsVer.patchVersion:= 0;

  // Check minimum version
  if(NSProcessInfo.ProcessInfo.isOperatingSystemAtLeastVersion(minOSVer)) then
    Result := True
  else
    Result := False;
end;
     
{ The following two functions were suggested by Hansaplast at https://forum.lazarus.freepascal.org/index.php/topic,43111.msg304366.html }
     
// Retrieve key's string value from user preferences. Result is encoded using NSStrToStr's default encoding.
function GetPrefString(const KeyName : string) : string;
begin
  Result := NSStringToString(NSUserDefaults.standardUserDefaults.stringForKey(NSStr(@KeyName[1])));
end;
     
// IsDarkTheme: Detects if the Dark Theme (true) has been enabled or not (false)
function IsDarkTheme: boolean;
begin
  Result := false;
  if MojaveOrNewer then
    Result := pos('DARK',UpperCase(GetPrefString('AppleInterfaceStyle'))) > 0;
end;

Demo

unit Unit1;

{$mode objfpc}{$H+}
{$modeswitch objectivec1}

interface

uses
  Classes, Forms, Dialogs, StdCtrls, SysUtils,
  CocoaAll, CocoaUtils, MacOSAll;

type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private

  public

  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

{ returns true, if this app runs on macOS 10.14 Mojave or newer }
function MojaveOrNewer: boolean;
var
  minOsVer: NSOperatingSystemVersion;
begin
  // Setup minimum version (Mojave)
  minOsVer.majorVersion:= 10;
  minOsVer.minorVersion:= 14;
  minOsVer.patchVersion:= 0;

  // Check minimum version
  if(NSProcessInfo.ProcessInfo.isOperatingSystemAtLeastVersion(minOSVer)) then
    Result := True
  else
    Result := False;
end;

function GetPrefString(const KeyName : string) : string;
begin
  Result := NSStringToString(NSUserDefaults.standardUserDefaults.stringForKey(NSStr(@KeyName[1])));
end;

function IsDarkTheme: boolean;
begin
  Result := false;
  if MojaveOrNewer then
    Result := pos('DARK',UpperCase(GetPrefString('AppleInterfaceStyle'))) > 0;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  if(IsDarkTheme) then
    ShowMessage('Dark mode')
  else
    ShowMessage('Light mode');
end;

end.

Example 2

Catalina added an "Auto" option where the computer switches between Light and Dark modes depending on time of day. The 'AppleInterfaceStyle' method apparently doesn't work if auto is enabled.

The solution is to get the NSApp.effectiveAppearance string which will be one of a number of values including 'NSAppearanceNameAqua' (standard light mode) and 'NSAppearanceNameDarkAqua' (standard dark mode). Google these to see the whole list which includes other light and dark modes with added contrast. The effectiveAppearance is correct in all modes including when auto mode kicks in.

Tested on Mojave, Catalina, Big Sur.

// by "Clover" from Lazarus forum
function IsMacDarkMode: Boolean;
var
  sMode: string;
begin
  //sMode := CFStringToStr( CFStringRef( NSUserDefaults.StandardUserDefaults.stringForKey( NSSTR('AppleInterfaceStyle') ))); // Doesn't work in auto mode
  sMode  := CFStringToStr( CFStringRef( NSApp.effectiveAppearance.name ));
  Result := Pos('Dark', sMode) > 0;
end;

Demo

unit Unit1;

{$mode objfpc}{$H+}
{$modeswitch objectivec1}

interface

uses
  Classes, Forms, Dialogs, StdCtrls,
  CocoaAll, CocoaUtils, MacOSAll;

type

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private

  public

  end;

var
  Form1: TForm1;

implementation

{$R *.lfm}

{ TForm1 }

function IsMacDarkMode: Boolean;
var
  sMode: string;
begin
  sMode  := CFStringToStr( CFStringRef( NSApp.effectiveAppearance.name ));
  Result := Pos('Dark', sMode) > 0;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  if(IsMacDarkMode) then
    ShowMessage('Dark mode')
  else
    ShowMessage('Light mode');
end;

end.

Opting out of dark mode

Mac systems automatically opt in any application linked against the macOS 10.14 or later SDK to both light and dark appearances. You can opt out of dark mode by including the NSRequiresAquaSystemAppearance key (with a value of YES) in your application’s Info.plist property list file. Setting this key to YES causes the system to ignore the user's preference and always apply a light appearance to your application.

Opting in to dark mode

If you build your application against an earlier SDK but still want to support Dark Mode, include the NSRequiresAquaSystemAppearance key (with a value of NO) in your application's Info.plist property list file. Do so only if your application's appearance looks correct when running in macOS 10.14 and later with Dark Mode enabled.

See Also

External links