source: trunk/Packages/Common/Theme.pas

Last change on this file was 341, checked in by chronos, 37 hours ago
  • Modified: Improved dark theme support on Windows.
File size: 7.3 KB
Line 
1unit Theme;
2
3interface
4
5uses
6 Classes, SysUtils, Graphics, ComCtrls, Controls, ExtCtrls, Menus, StdCtrls,
7 Spin, Forms, Generics.Collections, Grids, Registry, LCLType;
8
9type
10 TTheme = class
11 Name: string;
12 ColorWindow: TColor;
13 ColorWindowText: TColor;
14 ColorControl: TColor;
15 ColorControlText: TColor;
16 ColorControlSelected: TColor;
17 end;
18
19 { TThemes }
20
21 TThemes = class(TObjectList<TTheme>)
22 function AddNew(Name: string): TTheme;
23 function FindByName(Name: string): TTheme;
24 procedure LoadToStrings(Strings: TStrings);
25 end;
26
27 TDwmSetWindowAttribute = function(hwnd: HWND; dwAttribute: DWORD; pvAttribute: Pointer; cbAttribute: DWORD): HRESULT; stdcall;
28
29 { TThemeManager }
30
31 TThemeManager = class(TComponent)
32 private
33 FTheme: TTheme;
34 FActualTheme: TTheme;
35 DwmapiLib: TLibHandle;
36 DwmSetWindowAttribute: TDwmSetWindowAttribute;
37 function Gray(C: TColor): Byte;
38 procedure SetTheme(AValue: TTheme);
39 procedure SetThemeName(Name: string);
40 procedure SetThemedTitleBar(AForm: TForm; Active: Bool);
41 function IsWindows10OrGreater(BuildNumber: Integer): Boolean;
42 public
43 Used: Boolean;
44 Themes: TThemes;
45 function IsDarkTheme: Boolean;
46 procedure ApplyTheme(Component: TComponent);
47 constructor Create(AOwner: TComponent); override;
48 destructor Destroy; override;
49 procedure UseTheme(Form: TForm);
50 property Theme: TTheme read FTheme write SetTheme;
51 property ActualTheme: TTheme read FActualTheme;
52 end;
53
54const
55 ThemeNameSystem = 'System';
56 ThemeNameLight = 'Light';
57 ThemeNameDark = 'Dark';
58 DwmapiLibName = 'dwmapi.dll';
59 DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1 = 19;
60 DWMWA_USE_IMMERSIVE_DARK_MODE = 20;
61
62procedure Register;
63
64
65implementation
66
67{ TThemes }
68
69procedure Register;
70begin
71 RegisterComponents('Common', [TThemeManager]);
72end;
73
74function TThemes.AddNew(Name: string): TTheme;
75begin
76 Result := TTheme.Create;
77 Result.Name := Name;
78 Add(Result);
79end;
80
81function TThemes.FindByName(Name: string): TTheme;
82var
83 Theme: TTheme;
84begin
85 Result := nil;
86 for Theme in Self do
87 if Theme.Name = Name then begin
88 Result := Theme;
89 Exit;
90 end;
91end;
92
93procedure TThemes.LoadToStrings(Strings: TStrings);
94var
95 I: Integer;
96begin
97 Strings.BeginUpdate;
98 try
99 while Strings.Count < Count do Strings.Add('');
100 while Strings.Count > Count do Strings.Delete(Strings.Count - 1);
101 for I := 0 to Count - 1 do begin
102 Strings[I] := Items[I].Name;
103 Strings.Objects[I] := Items[I];
104 end;
105 finally
106 Strings.EndUpdate;
107 end;
108end;
109
110{ TThemeManager }
111
112function TThemeManager.Gray(C: TColor): Byte;
113begin
114 Result := Trunc(Red(C) * 0.3 + Green(C) * 0.59 + Blue(C) * 0.11);
115end;
116
117function TThemeManager.IsDarkTheme: Boolean;
118{$IFDEF WINDOWS}
119var
120 LightKey: Boolean;
121 Registry: TRegistry;
122const
123 KeyPath = '\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize';
124 KeyName = 'AppsUseLightTheme';
125{$ELSE}
126var
127 ColorWindow: TColor;
128 ColorWindowText: TColor;
129{$ENDIF}
130begin
131 Result := False;
132 {$IFDEF WINDOWS}
133 Registry := TRegistry.Create;
134 try
135 Registry.RootKey := HKEY_CURRENT_USER;
136 if Registry.OpenKeyReadOnly(KeyPath) then begin
137 if Registry.ValueExists(KeyName) then
138 LightKey := Registry.ReadBool(KeyName)
139 else LightKey := True;
140 end else LightKey := True;
141 Result := not LightKey;
142 finally
143 Registry.Free;
144 end;
145 {$ELSE}
146 ColorWindow := ColorToRGB(clWindow);
147 ColorWindowText := ColorToRGB(clWindowText);
148 Result := Gray(ColorWindow) < Gray(ColorWindowText);
149 {$ENDIF}
150end;
151
152procedure TThemeManager.SetThemeName(Name: string);
153begin
154 Theme := Themes.FindByName(Name);
155end;
156
157function TThemeManager.IsWindows10OrGreater(BuildNumber: Integer): Boolean;
158begin
159 {$IFDEF WINDOWS}
160 Result := (Win32MajorVersion >= 10) and (Win32BuildNumber >= BuildNumber);
161 {$ELSE}
162 Result := False;
163 {$ENDIF}
164end;
165
166procedure TThemeManager.SetThemedTitleBar(AForm: TForm; Active: Bool);
167var
168 Attr: DWord;
169begin
170 if Assigned(DwmSetWindowAttribute) and IsWindows10OrGreater(17763) then begin
171 Attr := DWMWA_USE_IMMERSIVE_DARK_MODE_BEFORE_20H1;
172 if IsWindows10OrGreater(18985) then Attr := DWMWA_USE_IMMERSIVE_DARK_MODE;
173
174 DwmSetWindowAttribute(AForm.Handle, Attr, @Active, SizeOf(Active));
175 end;
176end;
177
178procedure TThemeManager.SetTheme(AValue: TTheme);
179begin
180 if FTheme = AValue then Exit;
181 FTheme := AValue;
182 FActualTheme := FTheme;
183 {$IFDEF WINDOWS}
184 if Assigned(FTheme) and (FTheme = Themes.FindByName(ThemeNameSystem)) and IsDarkTheme then
185 FActualTheme := Themes.FindByName(ThemeNameDark);
186 {$ENDIF}
187end;
188
189constructor TThemeManager.Create(AOwner: TComponent);
190begin
191 inherited;
192 {$IFDEF WINDOWS}
193 DwmapiLib := LoadLibrary(DwmapiLibName);
194 if DwmapiLib <> 0 then DwmSetWindowAttribute := GetProcAddress(DwmapiLib, 'DwmSetWindowAttribute')
195 else DwmSetWindowAttribute := nil;
196 {$ENDIF}
197
198 Themes := TThemes.Create;
199 with Themes.AddNew(ThemeNameSystem) do begin
200 ColorWindow := clWindow;
201 ColorWindowText := clWindowText;
202 ColorControl := clMenu;
203 ColorControlText := clWindowText;
204 ColorControlSelected := clWindow;
205 end;
206 with Themes.AddNew(ThemeNameDark) do begin
207 ColorWindow := RGBToColor($20, $20, $20);
208 ColorWindowText := clWhite;
209 ColorControl := RGBToColor($40, $40, $40);
210 ColorControlText := clWhite;
211 ColorControlSelected := RGBToColor(96, 125, 155);
212 end;
213 with Themes.AddNew(ThemeNameLight) do begin
214 ColorWindow := clWhite;
215 ColorWindowText := clBlack;
216 ColorControl := RGBToColor($e0, $e0, $e0);
217 ColorControlText := clBlack;
218 ColorControlSelected := RGBToColor(196, 225, 255);
219 end;
220 Theme := TTheme(Themes.First);
221end;
222
223destructor TThemeManager.Destroy;
224begin
225 FreeAndNil(Themes);
226 {$IFDEF WINDOWS}
227 if DwmapiLib <> 0 then FreeLibrary(DwmapiLib);
228 {$ENDIF}
229 inherited;
230end;
231
232procedure TThemeManager.ApplyTheme(Component: TComponent);
233var
234 Control: TControl;
235 I: Integer;
236begin
237 if Component is TWinControl then begin
238 for I := 0 to TWinControl(Component).ControlCount - 1 do
239 ApplyTheme(TWinControl(Component).Controls[I]);
240 end;
241
242 if Component is TControl then begin
243 Control := (Component as TControl);
244 if (Control is TEdit) or (Control is TSpinEdit) or (Control is TComboBox) and
245 (Control is TMemo) or (Control is TListView) or (Control is TCustomDrawGrid) or
246 (Control is TCheckBox) or (Control is TPageControl) or (Control is TRadioButton) then begin
247 Control.Color := FActualTheme.ColorWindow;
248 Control.Font.Color := FActualTheme.ColorWindowText;
249 end else begin
250 Control.Color := FActualTheme.ColorControl;
251 Control.Font.Color := FActualTheme.ColorControlText;
252 end;
253
254 if Control is TCustomDrawGrid then begin
255 (Control as TCustomDrawGrid).Editor.Color := FActualTheme.ColorWindow;
256 (Control as TCustomDrawGrid).Editor.Font.Color := FActualTheme.ColorWindowText;
257 end;
258
259 if Control is TPageControl then begin
260 for I := 0 to TPageControl(Component).PageCount - 1 do
261 ApplyTheme(TPageControl(Component).Pages[I]);
262 end;
263
264 if Control is TCoolBar then begin
265 (Control as TCoolBar).Themed := False;
266 end;
267 end;
268end;
269
270procedure TThemeManager.UseTheme(Form: TForm);
271begin
272 if not Used and (FActualTheme.Name = ThemeNameSystem) then Exit;
273 ApplyTheme(Form);
274 SetThemedTitleBar(Form, FActualTheme.Name = ThemeNameDark);
275 Used := True;
276end;
277
278end.
Note: See TracBrowser for help on using the repository browser.