Skip to content

feat: Visual theme system (Dark, Light, High-Contrast, custom colour schemes) #165

Description

@ten9876

What

Allow users to switch between visual themes in AetherSDR — for example a classic
dark theme (current default), a light theme, a high-contrast accessibility theme,
and optionally a "radio green phosphor" theme reminiscent of traditional ham radio
displays. The selected theme persists across sessions via AppSettings.


Why

AetherSDR currently ships with a single hard-coded dark theme
(#0f0f1a background, #00b4d8 accent, #203040 borders). This works well
for most desktop environments, but creates problems in several real-world
scenarios:

  • Outdoor / daylight operation: Dark UIs with low-contrast cyan text are
    difficult to read in bright sunlight. A high-contrast or light theme would
    dramatically improve usability at a portable/SOTA station.
  • Visual accessibility: Users with certain colour-vision deficiencies may
    find the cyan-on-dark palette difficult. A high-contrast theme (white text,
    bold borders, no subtle gradients) is a common accessibility accommodation.
  • Desktop integration: On KDE Plasma or GNOME with a light system theme,
    AetherSDR looks jarring. A light theme brings it in line with the desktop.
  • Personal preference: Many operators simply prefer a different look, and
    providing this choice is standard practice in professional radio software.

How Other Clients Do It

SmartSDR for Windows

SmartSDR does not offer theme switching — it uses a single fixed dark theme.
This is a known pain point in the FlexRadio community for outdoor and
accessibility use.

SDR# (SDRSharp)

SDR# uses WPF resource dictionaries. Themes are selectable from
View → Theme and swap the entire colour palette instantly without restart.
Ships with Dark (default), Light, and a High-Contrast mode. Theme files are XML
resource dictionaries, making community themes easy to contribute.

GQRX

GQRX inherits the system Qt/GTK theme automatically because it uses standard
Qt widgets without heavy custom painting. Users on light desktops get a light
UI for free. This works well for simple controls but doesn't extend to the
spectrum/waterfall widget colours.

CubicSDR

CubicSDR ships with multiple colour presets for the waterfall (the most
visually prominent element) selectable from a dropdown in the display settings.
Offers: Default, BW (greyscale), Solar, and Gradient presets. Spectrum and
waterfall are coupled to the same preset.

SDRangel

SDRangel uses Qt stylesheets (.qss files) loaded at startup via a
--stylesheet command-line flag. Multiple .qss files ship with the
application (Dark, Light, Night). Advanced users can write their own.


Suggested Behavior

Theme Selection

  • Add a View → Theme menu (or a Theme sub-menu under the existing
    Settings menu in Settings menu: all remaining items #56) with radio-button items:

    • ☑ Dark (default, current palette)
    • ○ Light
    • ○ High Contrast
    • ○ Night (deep red accent, for dark-adapted eyes)
    • ○ Custom…
  • The selected theme applies immediately without restart by calling
    qApp->setStyleSheet(...) and triggering a repaint of custom-painted widgets
    (SpectrumWidget, SMeterWidget, HGauge, etc.).

Persistence

// Save
AppSettings::instance().setValue("ActiveTheme", "Light");

// Load at startup in main.cpp / MainWindow constructor
QString theme = AppSettings::instance().value("ActiveTheme", "Dark").toString();
ThemeManager::instance().applyTheme(theme);

Implementation Architecture

Introduce a ThemeManager singleton in src/core/:

struct Theme {
    QColor background;      // e.g. #0f0f1a (Dark) / #f5f5f5 (Light)
    QColor surface;         // widget panel backgrounds
    QColor text;
    QColor accent;          // VFO freq colour, slice line colour
    QColor border;
    QColor spectrumLine;    // FFT trace colour
    QColor waterfallLow;    // cold end of waterfall gradient
    QColor waterfallHigh;   // hot end of waterfall gradient
    QString qssOverride;    // optional full Qt stylesheet string
};

class ThemeManager : public QObject {
    Q_OBJECT
public:
    static ThemeManager& instance();
    void applyTheme(const QString& name);
    const Theme& current() const;
signals:
    void themeChanged(const Theme& theme);
};

Custom-painted widgets (SpectrumWidget, SMeterWidget, HGauge) connect to
themeChanged and call update(). Qt-widget-based panels get the .qss
stylesheet via qApp->setStyleSheet(...).

Built-in Themes (suggested palettes)

Token Dark (current) Light High Contrast Night
background #0f0f1a #f0f4f8 #000000 #0a0005
surface #1a2030 #ffffff #000000 #120010
text #c8d8e8 #1a2030 #ffffff #ff9900
accent #00b4d8 #0077aa #ffff00 #ff3300
border #203040 #b0c0d0 #ffffff #440022
spectrumLine #00b4d8 #005577 #00ff00 #ff6600

Custom Theme

"Custom…" opens a colour-picker dialog that lets the user tweak each token
individually, then saves the result under the name Custom in AppSettings.


Protocol Hints

None required. Theming is entirely client-side. No FlexLib API calls are
involved. The radio has no concept of client UI colours.

Metadata

Metadata

Assignees

No one assigned

    Labels

    GUIUser interfaceenhancementImprovement to existing featurehelp wantedExtra attention neededmaintainer-reviewRequires maintainer review before any action is takenpriority: lowLow priority

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions