add light/dark theme with global persistence

- AppTheme.pas: новый файл, TAppTheme record + DarkTheme()/LightTheme()
- FlatSlider: цвета через SetThemeColors вместо хардкода
- SpectrumView: все цвета из TAppTheme (SetTheme)
- SettingsForm: ApplyTheme(T) с рекурсивным обходом контролов
- DeviceForm: SetTheme(T) — перекраска всех элементов включая TEdit
- Settings: SaveTheme/LoadTheme в корневой секции preferences (не привязано к устройству)
- MainForm: тема загружается при старте, сохраняется при изменении

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-02 19:00:45 +03:00
co-authored by Claude Sonnet 4.6
parent f89b15730e
commit d9a2157719
8 changed files with 785 additions and 173 deletions
+101 -14
View File
@@ -27,7 +27,7 @@ uses
Classes, SysUtils, Math,
Forms, Controls, Graphics, Dialogs,
StdCtrls, ExtCtrls, Spin,
FlatButton, AudioOutput, AudioInput;
FlatButton, AudioOutput, AudioInput, AppTheme;
type
// PA: MaxPower в Вт, BandCal[0..10] — калибровка 38.8..100.0 на диапазон
@@ -43,6 +43,7 @@ type
TOnAudioInDevChange = procedure(DevIndex: Integer; const DevName: string) of object;
TOnVisibilityChange = procedure(ShowSpectrum, ShowWaterfall: Boolean) of object;
TOnFPSChange = procedure(FPS: Integer) of object;
TOnThemeChange = procedure(LightTheme: Boolean) of object;
TOnCATChange = procedure(
const SerEnabled: array of Boolean;
const SerPort: array of string;
@@ -101,6 +102,7 @@ type
FChkShowSpectrum: TCheckBox;
FChkShowWaterfall: TCheckBox;
FCmbFPS: TComboBox;
FChkLightTheme: TCheckBox;
// ---- PA Settings tab controls ----
FEdMaxPower: TSpinEdit;
@@ -129,6 +131,7 @@ type
FOnAudioInDevChange: TOnAudioInDevChange;
FOnVisibilityChange: TOnVisibilityChange;
FOnFPSChange: TOnFPSChange;
FOnThemeChange: TOnThemeChange;
FLoading: Boolean;
@@ -138,7 +141,7 @@ type
procedure BuildRX1Tab;
procedure BuildPATab;
procedure BuildCATTab;
procedure ApplyTheme;
procedure ApplyTheme(const T: TAppTheme);
procedure NavClick(Sender: TObject);
procedure SelectPage(APage: TWinControl; ANav: TFlatButton);
@@ -167,6 +170,7 @@ type
procedure OnTXDevChange(Sender: TObject);
procedure OnVisibilityChkChange(Sender: TObject);
procedure OnFPSCmbChange(Sender: TObject);
procedure OnLightThemeChkChange(Sender: TObject);
procedure OnMaxPowerChange(Sender: TObject);
procedure OnBandCalChange(Sender: TObject);
procedure OnCATSerialChange(Sender: TObject);
@@ -203,6 +207,7 @@ type
procedure RefreshAudioDevices(AudioOut: TAudioOutput; AudioIn: TAudioInput);
procedure LoadVisibility(ShowSpectrum, ShowWaterfall: Boolean);
procedure LoadFPS(FPS: Integer);
procedure LoadLightTheme(ALight: Boolean);
procedure LoadPASettings(MaxPower: Double; const BandCal: array of Double);
procedure LoadCATSettings(
const SerEnabled: array of Boolean;
@@ -219,6 +224,7 @@ type
property OnAudioInDevChange: TOnAudioInDevChange read FOnAudioInDevChange write FOnAudioInDevChange;
property OnVisibilityChange: TOnVisibilityChange read FOnVisibilityChange write FOnVisibilityChange;
property OnFPSChange: TOnFPSChange read FOnFPSChange write FOnFPSChange;
property OnThemeChange: TOnThemeChange read FOnThemeChange write FOnThemeChange;
end;
implementation
@@ -287,7 +293,7 @@ begin
FLoading := False;
FRXDevCount := 0;
BuildUI;
ApplyTheme;
ApplyTheme(DarkTheme);
end;
// ---------------------------------------------------------------------------
@@ -611,6 +617,18 @@ begin
for i := 0 to High(FPS_NAMES) do
FCmbFPS.Items.Add(FPS_NAMES[i]);
FCmbFPS.ItemIndex := 8;
Grp := MakeGroupPanel(FPageDisplay, 'Appearance', MARGIN, 292, 650, 68);
FChkLightTheme := TCheckBox.Create(Self);
FChkLightTheme.Parent := Grp;
FChkLightTheme.Caption := 'Light theme';
FChkLightTheme.SetBounds(PAD, R1, CHKW, 22);
FChkLightTheme.Checked := False;
FChkLightTheme.Font.Color := CLR_TEXT;
FChkLightTheme.Font.Name := UI_FONT;
FChkLightTheme.Font.Size := 9;
FChkLightTheme.OnChange := OnLightThemeChkChange;
end;
// ---------------------------------------------------------------------------
@@ -888,18 +906,66 @@ begin
end;
end;
procedure TSettingsForm.ApplyTheme;
procedure TSettingsForm.ApplyTheme(const T: TAppTheme);
procedure StyleBtn(B: TFlatButton);
begin
B.ClrNorm := T.Panel;
B.ClrBorder := T.Panel;
B.ClrHot := T.BG;
B.ClrActive := T.Panel;
B.ClrText := T.Text;
B.ClrTextAct := T.FreqActive;
B.Invalidate;
end;
procedure WalkControls(C: TWinControl);
var i: Integer; Ctrl: TControl;
begin
for i := 0 to C.ControlCount - 1 do
begin
Ctrl := C.Controls[i];
if Ctrl is TFlatButton then
StyleBtn(TFlatButton(Ctrl))
else if Ctrl is TScrollBox then
begin
TScrollBox(Ctrl).Color := T.BG;
WalkControls(TScrollBox(Ctrl));
end
else if Ctrl is TPanel then
begin
if TPanel(Ctrl).Height = 1 then
TPanel(Ctrl).Color := T.Border
else
TPanel(Ctrl).Color := T.Panel;
WalkControls(TPanel(Ctrl));
end
else if Ctrl is TLabel then
begin
if Ctrl.Font.Size >= 12 then
TLabel(Ctrl).Font.Color := T.Text
else if fsBold in Ctrl.Font.Style then
TLabel(Ctrl).Font.Color := T.FreqActive
else
TLabel(Ctrl).Font.Color := T.TextDim;
end
else if Ctrl is TCheckBox then
TCheckBox(Ctrl).Font.Color := T.Text
else if Ctrl is TListBox then
begin
TListBox(Ctrl).Color := T.BG;
TListBox(Ctrl).Font.Color := T.Text;
end
else if Ctrl is TWinControl then
WalkControls(TWinControl(Ctrl));
end;
end;
begin
Color := CLR_BG;
if Assigned(FNavPanel) then FNavPanel.Color := TColor($00151515);
if Assigned(FContentPanel) then FContentPanel.Color := CLR_BG;
if Assigned(FPageAudio) then FPageAudio.Color := CLR_BG;
if Assigned(FPageDisplay) then FPageDisplay.Color := CLR_BG;
if Assigned(FPageSpectrum) then FPageSpectrum.Color := CLR_BG;
if Assigned(FPageWaterfall) then FPageWaterfall.Color := CLR_BG;
if Assigned(FPagePA) then FPagePA.Color := CLR_BG;
if Assigned(FPageAdvanced) then FPageAdvanced.Color := CLR_BG;
if Assigned(FPageCAT) then FPageCAT.Color := CLR_BG;
Color := T.BG;
Font.Color := T.Text;
WalkControls(Self);
Invalidate;
end;
// ---------------------------------------------------------------------------
@@ -1265,6 +1331,15 @@ begin
FOnVisibilityChange(FChkShowSpectrum.Checked, FChkShowWaterfall.Checked);
end;
procedure TSettingsForm.OnLightThemeChkChange(Sender: TObject);
begin
if FLoading then Exit;
if FChkLightTheme.Checked then ApplyTheme(LightTheme)
else ApplyTheme(DarkTheme);
if Assigned(FOnThemeChange) then
FOnThemeChange(FChkLightTheme.Checked);
end;
procedure TSettingsForm.OnFPSCmbChange(Sender: TObject);
var
Idx: Integer;
@@ -1291,6 +1366,18 @@ begin
end;
end;
procedure TSettingsForm.LoadLightTheme(ALight: Boolean);
begin
FLoading := True;
try
FChkLightTheme.Checked := ALight;
finally
FLoading := False;
end;
if ALight then ApplyTheme(LightTheme)
else ApplyTheme(DarkTheme);
end;
procedure TSettingsForm.LoadPASettings(MaxPower: Double;
const BandCal: array of Double);
var i, n: Integer;