How to Customize the Theme System with Qt Stylesheets in Fincept
Fincept's theme system uses a token-based architecture where ThemeManager generates global QSS from ThemeTokens and applies it via qApp->setStyleSheet(), allowing runtime customization without recompiling UI code.
The FinceptTerminal repository provides a sophisticated theming engine built on Qt's stylesheet (QSS) system that enables complete UI customization through centralized tokens. By leveraging the singleton ThemeManager and token-based color helpers defined in fincept-qt/src/ui/theme/, developers can modify application appearance, add new color palettes, or create entirely new themes while keeping widget code clean and maintainable.
Understanding Fincept's Theme Architecture
Fincept implements a centralized token system that separates visual values from UI implementation. The architecture follows a strict pipeline:
ThemeTokens.hdefines theThemeTokensstruct containing every visual property (colors, fonts, spacing) as plain data members likeconst char* bg_baseorint font_size_base.ThemeManager(singleton) stores the active token set incurrent_and generates the global QSS string throughbuild_global_qss().- Token helpers in
Theme.hprovideColorTokenandfontsobjects that resolve live values lazily viaoperator const char*, ensuring widgets always read current theme data. - Global application occurs through
qApp->setStyleSheet(), followed by emission of thetheme_changed(const ThemeTokens&)signal to notify custom-painted widgets.
The Advanced Docking System (ADS) receives separate styling through ThemeManager::build_ads_qss() (lines 86-104 in ThemeManager.cpp), ensuring dock widgets match the application theme.
Adding Custom Color Tokens
To introduce a new purple accent color for custom UI elements:
First, extend the token structure in fincept-qt/src/ui/theme/ThemeTokens.h:
struct ThemeTokens {
// ... existing tokens ...
const char* purple; // new token
};
Next, populate the value in fincept-qt/src/ui/theme/ThemeManager.cpp:
const ThemeTokens THEME_OBSIDIAN = {
// ... existing colors ...
.purple = "#7e22ce",
};
Finally, expose the token via fincept-qt/src/ui/theme/Theme.h:
namespace colors {
// ... existing tokens ...
inline constexpr ColorToken PURPLE{&ThemeTokens::purple};
}
You can now use the token in any stylesheet builder:
QString customButtonStyle() {
return QString(
"QPushButton { background: %1; color: %2; border: 1px solid %3; }"
"QPushButton:hover { background: %4; }"
)
.arg(colors::PURPLE(), colors::WHITE(), colors::BORDER_DIM(), colors::PURPLE());
}
Creating Widget-Specific Stylesheet Overrides
For targeted style changes without modifying the global theme, concatenate custom rules to the existing application stylesheet. This approach preserves the global theme while overriding specific widgets:
// In your widget implementation (e.g., NewsScreen.cpp)
QString newsCommandBarOverride = QString(
"#newsCommandBar { background: %1; color: %2; border-bottom: 1px solid %3; }"
).arg(colors::PURPLE(), colors::WHITE(), colors::BORDER_BRIGHT());
// Append to existing global stylesheet
qApp->setStyleSheet(qApp->styleSheet() + newsCommandBarOverride);
Because Qt evaluates stylesheets in order, later rules take precedence. This technique allows module-specific styling while maintaining the token system for color values.
Implementing New Theme Presets
Fincept currently ships with the Obsidian theme. To add a light theme (e.g., "Alabaster"), define a new constant in ThemeManager.cpp:
const ThemeTokens THEME_ALABASTER = {
.name = "Alabaster",
.bg_base = "#ffffff",
.bg_surface = "#f5f5f5",
.bg_raised = "#e0e0e0",
.bg_hover = "#dddddd",
.border_dim = "#c0c0c0",
.border_med = "#a0a0a0",
.border_bright = "#808080",
.text_primary = "#000000",
.text_secondary = "#333333",
.text_tertiary = "#666666",
.text_dim = "#999999",
.accent = "#0066cc",
.accent_dim = "#003366",
.text_on_accent = "#ffffff",
.icon_dim = "#444444",
.icon_hover = "#111111",
.positive = "#28a745",
.positive_dim = "#1e7e34",
.negative = "#dc3545",
.negative_dim = "#a71d2a",
.warning = "#ffc107",
.info = "#17a2b8",
.cyan = "#17a2b8",
.accent_bg = "#e6f0ff",
.positive_bg = "#e6ffea",
.negative_bg = "#ffe6e6",
.row_alt = "#fafafa",
.font_family = "'Consolas','Courier New',monospace",
.font_size_base = 14,
.chart_colors = {"#0066cc", "#17a2b8", "#28a745", "#dc3545", "#ffc107", "#6c757d"},
};
Then extend the theme switching logic:
void ThemeManager::apply_theme(const QString& name) {
if (name == "Alabaster")
current_ = THEME_ALABASTER;
else
current_ = THEME_OBSIDIAN;
rebuild_and_apply(); // Regenerates QSS and applies to qApp
}
Users can now switch themes at runtime:
ThemeManager::instance().apply_theme("Alabaster");
Adjusting Content Density
Fincept supports Compact, Default, and Comfortable density modes that affect padding tokens. Modify density via:
ThemeManager::instance().apply_density("Compact"); // 2px padding
The density setting updates spacing tokens (%7 and %8 placeholders in build_global_qss) without requiring code changes in individual widgets. For one-off padding adjustments in custom components, use hardcoded values in your local QSS snippets rather than the global density tokens.
Handling Theme Changes in Custom-Painted Widgets
Widgets that perform custom painting must connect to the theme change signal to trigger repaints when colors update:
// MyChartWidget.cpp
MyChartWidget::MyChartWidget(QWidget* parent) : QWidget(parent) {
connect(&fincept::ui::ThemeManager::instance(),
&fincept::ui::ThemeManager::theme_changed,
this, [this](const fincept::ui::ThemeTokens&) {
update(); // Trigger repaint
});
}
void MyChartWidget::paintEvent(QPaintEvent*) {
QPainter p(this);
// Query live tokens directly
p.fillRect(rect(), colors::BG_SURFACE());
p.setPen(colors::POSITIVE());
// Draw data series...
}
This pattern ensures custom graphics stay synchronized with the active theme, as implemented in fincept-qt/src/ui/widgets/StatusBar.cpp.
Summary
- Token pipeline: Visual values flow from
ThemeTokens→ThemeManager→ global QSS string →qApp->setStyleSheet(). - Key files: Define tokens in
ThemeTokens.h, expose viaTheme.h, and build QSS inThemeManager.cpporStyleSheets.cpp. - Runtime switching: Call
ThemeManager::instance().apply_theme()to switch presets without restarting. - Widget integration: Connect to
theme_changedsignals for custom-painted widgets, or concatenate specific QSS rules for one-off overrides. - ADS support: Docking system styling is handled separately via
build_ads_qss()inThemeManager.cpp(lines 86-104).
Frequently Asked Questions
Where are theme tokens defined in the Fincept source code?
Theme tokens are defined in fincept-qt/src/ui/theme/ThemeTokens.h as a plain struct with color, font, and spacing members. The active values are stored in ThemeManager and accessed through token helpers like colors::BG_BASE() defined in fincept-qt/src/ui/theme/Theme.h.
How do I apply a custom Qt stylesheet to a single widget without affecting the global theme?
Concatenate your custom QSS to the existing application stylesheet using qApp->setStyleSheet(qApp->styleSheet() + customQss), or call setStyleSheet() directly on the specific widget. Use the token helpers (e.g., colors::PURPLE()) to maintain consistency with the active theme.
Can I switch themes at runtime in FinceptTerminal?
Yes. Call ThemeManager::instance().apply_theme("ThemeName") to switch presets dynamically. The method updates the internal current_ token set, rebuilds the global QSS via rebuild_and_apply(), and emits theme_changed to notify all listening widgets to refresh their appearance.
How do custom-painted widgets update when the theme changes?
Connect to the ThemeManager::theme_changed(const ThemeTokens&) signal in your widget constructor. When emitted, call update() to trigger a repaint, then query current colors through ThemeManager::instance().tokens() or the colors:: helper functions in your paintEvent handler.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →