Difference between revisions of "Dark theme"

From Lazarus wiki
Jump to navigationJump to search
m (→‎Example 1: Fix typo in code)
(→‎Demo: Simple demo)
Line 113: Line 113:
 
     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 }
 +
 +
function IsMinMacOS(Maj, Min: integer): boolean;
 +
  { returns true, if this app runs on a macOS version as specified or newer }
 +
var
 +
  Major, Minor: SInt32;
 +
  theError: SInt16;
 +
begin
 +
  result := false;
 +
  theError := Gestalt(gestaltSystemVersionMajor, Major);
 +
  if theError = 0 then
 +
    theError := Gestalt(gestaltSystemVersionMinor, Minor);
 +
  if theError = 0 then
 +
    if (Major = Maj) and (Minor >= Min) or (Major > Maj) then
 +
      Result := True;
 +
end;
 +
 +
function MojaveOrNewer: boolean;
 +
  { returns true, if this app runs on macOS X 10.14 Mojave or newer }
 +
begin
 +
  result := IsMinMacOS(10, 14);
 +
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 197: Line 277:
 
=== 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].
  

Revision as of 08:56, 29 August 2020

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

Universal method

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 "dbannon" from Lazarus forum
function IsDarkTheme(F: TForm): boolean;
var
  Col : string;
begin
  // if char 3, 5 and 7 are all 'A' or above, we are not in a DarkTheme
  Col := HexStr(qword(F.GetRGBColorResolvingParent()), 8);
  Result := (Col[3] < 'A') and (Col[5] < 'A') and (Col[7] < 'A');
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 "jwdietrich" from Lazarus forum
uses
  MacOSAll;
     
function IsMinMacOS(Maj, Min: integer): boolean;
  { returns true, if this app runs on a macOS version as specified or newer }
var
  Major, Minor: SInt32;
  theError: SInt16;
begin
  result := false;
  theError := Gestalt(gestaltSystemVersionMajor, Major);
  if theError = 0 then
    theError := Gestalt(gestaltSystemVersionMinor, Minor);
  if theError = 0 then
    if (Major = Maj) and (Minor >= Min) or (Major > Maj) then
      Result := True;
end;
     
function MojaveOrNewer: boolean;
  { returns true, if this app runs on macOS X 10.14 Mojave or newer }
begin
  result := IsMinMacOS(10, 14);
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 }

function IsMinMacOS(Maj, Min: integer): boolean;
  { returns true, if this app runs on a macOS version as specified or newer }
var
  Major, Minor: SInt32;
  theError: SInt16;
begin
  result := false;
  theError := Gestalt(gestaltSystemVersionMajor, Major);
  if theError = 0 then
    theError := Gestalt(gestaltSystemVersionMinor, Minor);
  if theError = 0 then
    if (Major = Maj) and (Minor >= Min) or (Major > Maj) then
      Result := True;
end;

function MojaveOrNewer: boolean;
  { returns true, if this app runs on macOS X 10.14 Mojave or newer }
begin
  result := IsMinMacOS(10, 14);
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.

See also

External links