Viewer: dark theme, switchable live from the View menu

View > Theme offers Follow system / Light / Dark, stored in the settings.
Follow system uses the desktop's colour scheme where Qt reports it (6.5+)
and is light otherwise. The dark theme is a deep indigo (#1A1D3A panels,
#12142B entry fields and charts).

The palettes live in the new ViewerTheme module. A switch applies at once:
the application stylesheet is set again after the palette so every widget
is re-polished (with a stylesheet active, polished widgets otherwise keep
their old palette); widgets that bake colours into stylesheets use
SetThemedStyleSheet, which re-applies them on ThemeNotifier::changed;
toolbar icons draw their glyph at paint time through an icon engine, so the
ink follows the theme (and stays sharp at any size); charts re-theme and
rebuild. Hard-coded white backgrounds on entry fields and tables are
removed in favour of the palette's base colour.

The idle detector-status badge in the status bar is now transparent instead
of an empty default-styled progress bar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Filip Leonarski
2026-09-26 17:51:50 +02:00
co-authored by Claude Opus 5.5
parent f1a8c1d868
commit 0be8eb2bca
23 changed files with 311 additions and 83 deletions
+2
View File
@@ -25,6 +25,8 @@ ADD_EXECUTABLE(jfjoch_viewer jfjoch_viewer.cpp JFJochViewerWindow.cpp JFJochView
JFJochViewerMenu.h
RemoteDisplayMode.cpp
RemoteDisplayMode.h
ViewerTheme.cpp
ViewerTheme.h
image_viewer/JFJochDiffractionImage.cpp
image_viewer/JFJochDiffractionImage.h
image_viewer/JFJochSimpleImage.cpp
+17
View File
@@ -3,6 +3,7 @@
#include "JFJochViewerMenu.h"
#include "RemoteDisplayMode.h"
#include "ViewerTheme.h"
#include <QMessageBox>
#include <QApplication>
@@ -90,6 +91,22 @@ JFJochViewerMenu::JFJochViewerMenu(QWidget *parent) : QMenuBar(parent) {
remoteMode->setChecked(RemoteDisplayMode());
connect(remoteMode, &QAction::toggled, this, [](bool on) { SetRemoteDisplayMode(on); });
viewMenu->addSeparator();
QMenu *themeMenu = viewMenu->addMenu("Theme");
auto *themeGroup = new QActionGroup(this);
const ThemeChoice currentTheme = LoadThemeChoice();
const std::pair<ThemeChoice, QString> themes[] = {{ThemeChoice::System, "Follow system"},
{ThemeChoice::Light, "Light"},
{ThemeChoice::Dark, "Dark"}};
for (const auto &theme: themes) {
const ThemeChoice choice = theme.first;
QAction *action = themeMenu->addAction(theme.second);
action->setCheckable(true);
action->setChecked(choice == currentTheme);
themeGroup->addAction(action);
connect(action, &QAction::triggered, this, [choice] { SetThemeChoice(choice); });
}
viewMenu->addSeparator();
// The desktop's own text scaling stays the primary mechanism, but few users know where it lives,
// so the viewer carries a zoom of its own. It multiplies the font the desktop handed us, so it
+3 -1
View File
@@ -18,6 +18,8 @@ JFJochViewerStatusBar::JFJochViewerStatusBar(QWidget *parent) : QStatusBar(paren
state_box->setTextVisible(true);
state_box->setAlignment(Qt::AlignCenter);
state_box->setFormat("");
// No detector session yet: the badge keeps its place but draws nothing (see setHttpConnection).
state_box->setStyleSheet("QProgressBar { border: none; background: transparent; }");
addPermanentWidget(state_box);
}
@@ -58,7 +60,7 @@ void JFJochViewerStatusBar::setHttpConnection(bool connected, QString addr) {
// No detector session at all -> blank the whole cluster (but keep the reserved space).
state_box->setFormat("");
state_box->setValue(0);
state_box->setStyleSheet("");
state_box->setStyleSheet("QProgressBar { border: none; background: transparent; }");
state_box->setToolTip("");
rate_label->setText("");
return;
+150
View File
@@ -0,0 +1,150 @@
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include "ViewerTheme.h"
#include <QApplication>
#include <QChart>
#include <QPalette>
#include <QSettings>
#include <QStyle>
#include <QStyleHints>
#include <QWidget>
namespace {
bool dark_theme = false;
const QColor DARK_WINDOW(0x1A, 0x1D, 0x3A); // a deep indigo
const QColor DARK_TEXT(0xE3, 0xE7, 0xEC);
bool SystemIsDark() {
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
return QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark;
#else
return false;
#endif
}
// Text roles are set for the active and inactive groups only, so disabled text keeps the greyed
// colour the palette derived for it.
void SetTextColor(QPalette &pal, const QColor &color) {
for (auto group: {QPalette::Active, QPalette::Inactive})
for (auto role: {QPalette::WindowText, QPalette::Text, QPalette::ButtonText, QPalette::ToolTipText})
pal.setColor(group, role, color);
}
// Salmon panels, white entry fields that fall back to salmon when disabled, and a navy accent for
// selections and progress-bar chunks. It starts from Fusion's own (light) palette, not the system
// one, which is dark under a dark OS theme; the text is set to black because on macOS the palette's
// text colours come from the platform theme and are grey.
QPalette LightPalette() {
QPalette pal = QApplication::style()->standardPalette();
pal.setColor(QPalette::Window, QColor(255, 235, 230));
pal.setColor(QPalette::Base, Qt::white);
pal.setColor(QPalette::AlternateBase, QColor(255, 245, 242));
pal.setColor(QPalette::Button, QColor(255, 235, 230));
pal.setColor(QPalette::Disabled, QPalette::Base, QColor(255, 235, 230));
pal.setColor(QPalette::Highlight, QColor(0x1f, 0x3a, 0x5f));
pal.setColor(QPalette::HighlightedText, Qt::white);
SetTextColor(pal, Qt::black);
return pal;
}
// The same layout in the dark: indigo panels, entry fields darker still, and a lighter navy for
// selections so they stand out from the panels. QPalette(button) derives the bevel shades and the
// disabled text from the one colour.
QPalette DarkPalette() {
QPalette pal(DARK_WINDOW);
pal.setColor(QPalette::Window, DARK_WINDOW);
pal.setColor(QPalette::Base, QColor(0x12, 0x14, 0x2B));
pal.setColor(QPalette::AlternateBase, QColor(0x24, 0x28, 0x4A));
pal.setColor(QPalette::Button, DARK_WINDOW);
pal.setColor(QPalette::Disabled, QPalette::Base, DARK_WINDOW);
pal.setColor(QPalette::ToolTipBase, QColor(0x2A, 0x2E, 0x55));
pal.setColor(QPalette::Highlight, QColor(0x2F, 0x5A, 0x8F));
pal.setColor(QPalette::HighlightedText, Qt::white);
pal.setColor(QPalette::Link, QColor(0x8A, 0xB4, 0xF8));
pal.setColor(QPalette::PlaceholderText, QColor(0x80, 0x84, 0xA8));
SetTextColor(pal, DARK_TEXT);
return pal;
}
void ApplyTheme() {
const ThemeChoice choice = LoadThemeChoice();
dark_theme = choice == ThemeChoice::Dark || (choice == ThemeChoice::System && SystemIsDark());
QApplication::setPalette(dark_theme ? DarkPalette() : LightPalette());
// With an application stylesheet in place, widgets take their palette when they are polished
// and do not see a later palette change; setting the stylesheet again re-polishes them all.
// Fusion fills QGroupBox interiors with a flat colour of its own; transparent makes them show
// the window background like the rest of the UI (the dock resize handles are drawn by
// SeparatorHandleStyle, not the stylesheet).
qApp->setStyleSheet("QGroupBox { background-color: transparent; }");
emit ThemeNotifier::Instance()->changed();
}
}
ThemeNotifier *ThemeNotifier::Instance() {
static ThemeNotifier notifier;
return &notifier;
}
ThemeChoice LoadThemeChoice() {
const QString value = QSettings("PSI", "jfjoch_viewer").value("theme", "system").toString();
if (value == "light") return ThemeChoice::Light;
if (value == "dark") return ThemeChoice::Dark;
return ThemeChoice::System;
}
void SetThemeChoice(ThemeChoice choice) {
const char *value = "system";
if (choice == ThemeChoice::Light) value = "light";
if (choice == ThemeChoice::Dark) value = "dark";
QSettings("PSI", "jfjoch_viewer").setValue("theme", value);
ApplyTheme();
}
void InitTheme() {
ApplyTheme();
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
// Re-applied on any desktop scheme change, not only under System: the palette set here must also
// survive the platform reacting to the change with a palette of its own.
QObject::connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged,
ThemeNotifier::Instance(), [] { ApplyTheme(); });
#endif
}
bool DarkTheme() {
return dark_theme;
}
void SetThemedStyleSheet(QWidget *widget, const std::function<QString()> &style) {
widget->setStyleSheet(style());
QObject::connect(ThemeNotifier::Instance(), &ThemeNotifier::changed, widget,
[widget, style] { widget->setStyleSheet(style()); });
}
QColor AccentTextColor() {
return dark_theme ? QColor(0x9D, 0xBB, 0xE3) : QColor(0x1F, 0x3A, 0x5F);
}
QColor IconInkColor() {
return dark_theme ? DARK_TEXT : QColor(0x2B, 0x2B, 0x2B);
}
QString SliderStyle() {
// On the dark panels the navy handle would vanish, so it takes the light blue of the headings.
const QString groove = dark_theme ? "#363A66" : "#F3D9D4";
const QString handle = dark_theme ? "#9DBBE3" : "#1F3A5F";
const QString handle_hover = dark_theme ? "#BCD2EE" : "#16314F";
return QString(
"QSlider::groove:horizontal { height:6px; background:%1; border-radius:3px; }"
"QSlider::sub-page:horizontal { background:#FA7268; border-radius:3px; }"
"QSlider::handle:horizontal { background:%2; width:14px; margin:-5px 0; border-radius:7px; }"
"QSlider::handle:horizontal:hover { background:%3; }").arg(groove, handle, handle_hover);
}
void StyleChart(QChart *chart) {
chart->setTheme(dark_theme ? QChart::ChartThemeDark : QChart::ChartThemeLight);
if (dark_theme)
chart->setBackgroundBrush(QApplication::palette().color(QPalette::Base));
}
+45
View File
@@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include <functional>
#include <QColor>
#include <QObject>
#include <QString>
class QChart;
class QWidget;
// The viewer's colour scheme: the light salmon one, or a dark indigo one. The View menu offers
// System, Light and Dark; System follows the desktop where Qt can read it (Qt 6.5+) and is light
// otherwise. A change applies at once: widgets that follow the palette repaint by themselves, and
// the ones that put colours into stylesheets, icons or charts listen to ThemeNotifier.
enum class ThemeChoice { System, Light, Dark };
ThemeChoice LoadThemeChoice();
void SetThemeChoice(ThemeChoice choice); // stores the choice and applies it
// Sets the palette and starts following the desktop's scheme. Call once, after the Fusion style is
// set and before any window is made.
void InitTheme();
bool DarkTheme();
class ThemeNotifier : public QObject {
Q_OBJECT
public:
static ThemeNotifier *Instance();
signals:
void changed();
};
// Sets the widget's stylesheet from `style`, now and again after every theme change.
void SetThemedStyleSheet(QWidget *widget, const std::function<QString()> &style);
// Colours that widgets put into their own stylesheets and drawings.
QColor AccentTextColor(); // headings and headline values: navy on light, light blue on dark
QColor IconInkColor(); // toolbar glyphs and flat toolbar-button text
QString SliderStyle(); // the coral/navy sliders of the toolbars
// Qt Charts do not follow the widget palette; this gives a chart the current scheme's colours. It
// recolours the series too, so call it before adding series that carry colours of their own.
void StyleChart(QChart *chart);
+8 -1
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only
#include <QMenu>
#include "../ViewerTheme.h"
#include <QApplication>
#include <QClipboard>
#include <QCategoryAxis>
@@ -26,6 +27,12 @@ namespace {
JFJochDatasetInfoChartView::JFJochDatasetInfoChartView(QWidget *parent)
: QChartView(new QChart(), parent) {
StyleChart(chart());
// The theme recolours every series, the run colours included, so the chart is rebuilt after it.
connect(ThemeNotifier::Instance(), &ThemeNotifier::changed, this, [this] {
StyleChart(chart());
updateChart();
});
chart()->legend()->hide();
// Reclaim Qt Charts' outer layout padding so the axis labels keep their room even when the dock
// is short (otherwise they are the first thing Qt drops); the inner margins are set per rebuild.
@@ -400,7 +407,7 @@ void JFJochDatasetInfoChartView::buildTimeDomainChart() {
series->setColor(primary_color_);
setSeriesPenWidth(series, fontMetrics().height());
currentSeries = new QScatterSeries(this);
currentSeries->setColor(Qt::black); // "current image" marker: fixed, not a run colour
currentSeries->setColor(palette().color(QPalette::WindowText)); // "current image" marker: not a run colour
currentSeries->setMarkerSize(9.0);
double dispMin = std::numeric_limits<double>::infinity();
+3
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "JFJochSimpleChartView.h"
#include "../ViewerTheme.h"
#include <QMenu>
#include <QClipboard>
@@ -12,6 +13,8 @@
JFJochSimpleChartView::JFJochSimpleChartView(QWidget *parent)
: QChartView(new QChart(), parent) {
StyleChart(chart());
connect(ThemeNotifier::Instance(), &ThemeNotifier::changed, this, [this] { StyleChart(chart()); });
chart()->legend()->hide();
chart()->layout()->setContentsMargins(0, 0, 0, 0); // reclaim padding for the plot + labels
chart()->setMargins(QMargins(2, 10, 8, 2)); // top room so the highest Y label is not clipped
+2 -21
View File
@@ -10,6 +10,7 @@
#include "JFJochViewerWindow.h"
#include "RemoteDisplayMode.h"
#include "ViewerTheme.h"
#include "../writer/HDF5Objects.h"
#include "../common/GitInfo.h"
@@ -65,28 +66,8 @@ int main(int argc, char *argv[]) {
RegisterHDF5Filter();
QApplication app(argc, argv);
// Theme via the palette (robust across widgets): salmon panels, white entry fields that fall back
// to salmon when disabled, and a navy accent for selections and progress-bar chunks.
// Start from Fusion's own (light) palette, not the system one, which is dark under a dark OS theme.
app.setStyle(new SeparatorHandleStyle("Fusion"));
QPalette pal = app.style()->standardPalette();
pal.setColor(QPalette::Window, QColor(255, 235, 230));
pal.setColor(QPalette::Base, Qt::white);
pal.setColor(QPalette::AlternateBase, QColor(255, 245, 242));
pal.setColor(QPalette::Button, QColor(255, 235, 230));
pal.setColor(QPalette::Disabled, QPalette::Base, QColor(255, 235, 230));
pal.setColor(QPalette::Highlight, QColor(0x1f, 0x3a, 0x5f));
pal.setColor(QPalette::HighlightedText, Qt::white);
// Black text as on Linux and Windows: on macOS the palette's text colours come from the platform
// theme and are grey (or white under a dark OS theme). Disabled text keeps Fusion's grey.
for (auto group: {QPalette::Active, QPalette::Inactive})
for (auto role: {QPalette::WindowText, QPalette::Text, QPalette::ButtonText, QPalette::ToolTipText})
pal.setColor(group, role, Qt::black);
app.setPalette(pal);
// Fusion fills QGroupBox interiors with a flat light colour; make them transparent so they show
// the salmon window background like the rest of the UI (entry widgets stay white via the palette).
// The dock resize handles are drawn by SeparatorHandleStyle above, not the stylesheet.
app.setStyleSheet("QGroupBox { background-color: transparent; }");
InitTheme(); // light or dark palette and the application stylesheet, per the View menu's choice
QIcon appIcon(":/jfjoch.png");
app.setWindowIcon(appIcon);
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "JFJochViewerToolbarDisplay.h"
#include "../ViewerTheme.h"
#include <QLabel>
#include "../../common/ColorScale.h"
#include "../RemoteDisplayMode.h"
@@ -23,11 +24,6 @@ JFJochViewerToolbarDisplay::JFJochViewerToolbarDisplay(QWidget *parent)
connect(background_tail_timer_, &QTimer::timeout,
this, &JFJochViewerToolbarDisplay::ApplyPendingBackground);
const QString sliderStyle =
"QSlider::groove:horizontal { height:6px; background:#F3D9D4; border-radius:3px; }"
"QSlider::sub-page:horizontal { background:#FA7268; border-radius:3px; }"
"QSlider::handle:horizontal { background:#1F3A5F; width:14px; margin:-5px 0; border-radius:7px; }"
"QSlider::handle:horizontal:hover { background:#16314F; }";
// Two sliders share the strip, so each is capped rather than expanding across the window.
const int slider_cap = fontMetrics().averageCharWidth() * 36;
@@ -38,7 +34,7 @@ JFJochViewerToolbarDisplay::JFJochViewerToolbarDisplay(QWidget *parent)
foreground_slider->setValue(10);
foreground_slider->setToolTip("White point of the color map (image contrast); hold F and use"
" the mouse wheel, Ctrl+wheel for coarse steps");
foreground_slider->setStyleSheet(sliderStyle);
SetThemedStyleSheet(foreground_slider, SliderStyle);
foreground_slider->setMaximumWidth(slider_cap);
addWidget(foreground_slider);
@@ -49,7 +45,7 @@ JFJochViewerToolbarDisplay::JFJochViewerToolbarDisplay(QWidget *parent)
background_slider->setValue(0);
background_slider->setToolTip("Black point of the color map (0 = off); hold B and use"
" the mouse wheel");
background_slider->setStyleSheet(sliderStyle);
SetThemedStyleSheet(background_slider, SliderStyle);
background_slider->setMaximumWidth(slider_cap);
addWidget(background_slider);
@@ -60,7 +56,7 @@ JFJochViewerToolbarDisplay::JFJochViewerToolbarDisplay(QWidget *parent)
b->setCheckable(true);
b->setToolTip(tip);
b->setCursor(Qt::PointingHandCursor);
b->setStyleSheet(ToolbarIcons::buttonStyle());
SetThemedStyleSheet(b, ToolbarIcons::buttonStyle);
return b;
};
auto_foreground_button = makeToggle("Auto", "Auto-contrast to the current image");
+3 -6
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "JFJochViewerToolbarImage.h"
#include "../ViewerTheme.h"
#include <algorithm>
#include <QTimer>
#include "../RemoteDisplayMode.h"
@@ -18,7 +19,7 @@ JFJochViewerToolbarImage::JFJochViewerToolbarImage(QWidget *parent) : QToolBar(p
b->setToolTip(tip);
b->setCheckable(checkable);
b->setCursor(Qt::PointingHandCursor);
b->setStyleSheet(ToolbarIcons::buttonStyle());
SetThemedStyleSheet(b, ToolbarIcons::buttonStyle);
return b;
};
@@ -38,11 +39,7 @@ JFJochViewerToolbarImage::JFJochViewerToolbarImage(QWidget *parent) : QToolBar(p
image_number_slider->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
image_number_slider->setMinimumWidth(140);
image_number_slider->setToolTip("Scrub through the dataset");
image_number_slider->setStyleSheet(
"QSlider::groove:horizontal { height:6px; background:#F3D9D4; border-radius:3px; }"
"QSlider::sub-page:horizontal { background:#FA7268; border-radius:3px; }"
"QSlider::handle:horizontal { background:#1F3A5F; width:14px; margin:-5px 0; border-radius:7px; }"
"QSlider::handle:horizontal:hover { background:#16314F; }");
SetThemedStyleSheet(image_number_slider, SliderStyle);
image_number_slider_timer = new QTimer(this);
image_number_slider_timer->setSingleShot(true);
+7 -4
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "CollapsibleSection.h"
#include "../ViewerTheme.h"
#include <QToolButton>
#include <QVBoxLayout>
@@ -18,10 +19,12 @@ CollapsibleSection::CollapsibleSection(const QString &title, QWidget *parent) :
header_->setArrowType(Qt::RightArrow);
header_->setCursor(Qt::PointingHandCursor);
header_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
header_->setStyleSheet(
"QToolButton { color:#1F3A5F; font-weight:bold; text-align:left;"
" padding:3px 6px 3px 6px; border:none;"
" border-left:3px solid #FA7268; border-bottom:1px solid #FA7268; background:transparent; }");
SetThemedStyleSheet(header_, [] {
return QString("QToolButton { color:%1; font-weight:bold; text-align:left;"
" padding:3px 6px 3px 6px; border:none;"
" border-left:3px solid #FA7268; border-bottom:1px solid #FA7268; background:transparent; }")
.arg(AccentTextColor().name());
});
content_ = new QWidget(this);
content_->setVisible(false);
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "JFJochViewerImageStatistics.h"
#include "../ViewerTheme.h"
#include "../../common/time_utc.h"
#include <QFormLayout>
#include <QRegularExpression>
@@ -403,7 +404,7 @@ void JFJochViewerImageStatistics::loadImage(std::shared_ptr<const JFJochReaderIm
auto latt = image->ImageData().indexing_lattice;
if (latt) {
text = mkUnitCell(latt->GetUnitCell(), "purple");
text = mkUnitCell(latt->GetUnitCell(), DarkTheme() ? "violet" : "purple");
auto vec0 = latt->Vec0();
auto vec1 = latt->Vec1();
-1
View File
@@ -54,7 +54,6 @@ JFJochViewerROIList::JFJochViewerROIList(QWidget *parent) : QWidget(parent) {
fedit_[i] = new QLineEdit(this);
fedit_[i]->setAlignment(Qt::AlignRight);
fedit_[i]->setMaximumWidth(90);
fedit_[i]->setStyleSheet("background-color: white; color: black;");
grid->addWidget(flabel_[i], i, 0);
grid->addWidget(fedit_[i], i, 1, Qt::AlignRight);
connect(fedit_[i], &QLineEdit::editingFinished, this, &JFJochViewerROIList::ApplyEditor);
-1
View File
@@ -13,7 +13,6 @@ NumberLineEdit::NumberLineEdit(float minVal,
, decimalCount(decimals)
, savedValue(def_value) {
setStyleSheet("background-color: rgb(255, 255, 255);");
setRange(minVal, maxVal);
setDecimals(decimalCount);
setButtonSymbols(QAbstractSpinBox::ButtonSymbols::NoButtons);
+2 -4
View File
@@ -16,8 +16,6 @@ NumericComboBox::NumericComboBox(std::vector<int> options, int min, int max, QWi
setCurrentText(QString::number(min));
setStyleSheet("background-color: rgb(255, 255, 255);");
connect(this, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, &NumericComboBox::onCurrentIndexChanged);
connect(this, &QComboBox::editTextChanged,
@@ -35,7 +33,7 @@ void NumericComboBox::setValue(int value) {
void NumericComboBox::onCurrentIndexChanged(int index) {
if (index >= 0) {
setStyleSheet("background-color: rgb(255, 255, 255);");
setStyleSheet("");
emit valueChanged(getValue());
}
}
@@ -44,7 +42,7 @@ void NumericComboBox::onEditTextChanged(const QString &text) {
bool ok;
int value = text.toInt(&ok);
if (ok) {
setStyleSheet("background-color: rgb(255, 255, 255);");
setStyleSheet("");
emit valueChanged(value);
} else {
setStyleSheet("background-color: rgb(125, 0, 0);");
-1
View File
@@ -33,7 +33,6 @@ SliderPlusBox::SliderPlusBox(double min, double max, double step, int decimals,
m_doubleSpinBox->setDecimals(decimals);
m_doubleSpinBox->setSingleStep(step);
m_doubleSpinBox->setValue(v);
m_doubleSpinBox->setStyleSheet("background-color: rgb(255, 255, 255);");
m_updateTimer = new QTimer(this);
m_updateTimer->setInterval(500); // 500ms throttle
+9 -6
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "TitleLabel.h"
#include "../ViewerTheme.h"
TitleLabel::TitleLabel(QString text, QWidget *parent) : QLabel(parent) {
// Slim section header: navy bold text with a coral accent rule, in place of a heavy 50px
@@ -10,12 +11,14 @@ TitleLabel::TitleLabel(QString text, QWidget *parent) : QLabel(parent) {
QFont f = font();
f.setBold(true);
setFont(f);
setStyleSheet("QLabel {"
" color: #1F3A5F;"
" background-color: transparent;"
" border-left: 3px solid #FA7268;"
" border-bottom: 1px solid #FA7268;"
" padding: 3px 6px 3px 8px; }");
SetThemedStyleSheet(this, [] {
return QString("QLabel {"
" color: %1;"
" background-color: transparent;"
" border-left: 3px solid #FA7268;"
" border-bottom: 1px solid #FA7268;"
" padding: 3px 6px 3px 8px; }").arg(AccentTextColor().name());
});
setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
setFixedHeight(26);
}
+37 -17
View File
@@ -2,7 +2,9 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "ToolbarIcons.h"
#include "../ViewerTheme.h"
#include <QIconEngine>
#include <QPainter>
#include <QPainterPath>
#include <QPolygonF>
@@ -10,28 +12,49 @@
namespace {
constexpr int S = 32;
const QColor INK("#2B2B2B");
const QColor DISABLED("#9AA6B3");
using DrawFn = std::function<void(QPainter &, const QColor &)>;
QPixmap render(const DrawFn &fn, const QColor &color) {
QPixmap pm(S, S);
// The glyphs are drawn on an S x S canvas; the painter is scaled so they come out sharp at any size.
QPixmap render(const DrawFn &fn, const QColor &color, const QSize &size) {
QPixmap pm(size);
pm.fill(Qt::transparent);
QPainter p(&pm);
p.setRenderHint(QPainter::Antialiasing);
p.scale(size.width() / double(S), size.height() / double(S));
fn(p, color);
return pm;
}
// Toggle-capable icon: ink when off, white when on (checked, navy background), grey when disabled.
// Draws the glyph when the icon is painted rather than when it is made, so the ink follows the
// theme: ink when off, white when on (a toggle on its navy checked background), grey when disabled.
class InkIconEngine final : public QIconEngine {
public:
InkIconEngine(DrawFn fn, bool toggle) : fn_(std::move(fn)), toggle_(toggle) {}
void paint(QPainter *painter, const QRect &rect, QIcon::Mode mode, QIcon::State state) override {
painter->drawPixmap(rect, pixmap(rect.size(), mode, state));
}
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) override {
QColor color = IconInkColor();
if (mode == QIcon::Disabled)
color = DISABLED;
else if (toggle_ && state == QIcon::On)
color = Qt::white;
return render(fn_, color, size);
}
QIconEngine *clone() const override { return new InkIconEngine(fn_, toggle_); }
private:
DrawFn fn_;
bool toggle_;
};
QIcon toggleIcon(const DrawFn &fn) {
QIcon ic;
ic.addPixmap(render(fn, INK), QIcon::Normal, QIcon::Off);
ic.addPixmap(render(fn, Qt::white), QIcon::Normal, QIcon::On);
ic.addPixmap(render(fn, Qt::white), QIcon::Active, QIcon::On);
ic.addPixmap(render(fn, DISABLED), QIcon::Disabled, QIcon::Off);
return ic;
return QIcon(new InkIconEngine(fn, true));
}
void fillTriangle(QPainter &p, const QColor &c, QPointF a, QPointF b, QPointF tip) {
@@ -180,19 +203,16 @@ QIcon httpSync(HttpState state) {
p.setBrush(dot);
p.drawEllipse(QPointF(24, 24), 4.2, 4.2); // status dot
};
QIcon ic;
ic.addPixmap(render(draw, INK), QIcon::Normal, QIcon::Off);
ic.addPixmap(render(draw, DISABLED), QIcon::Disabled, QIcon::Off);
return ic;
return QIcon(new InkIconEngine(draw, false));
}
QString buttonStyle() {
return QStringLiteral(
"QToolButton { border:none; border-radius:4px; padding:4px 6px; color:#2B2B2B;"
return QString(
"QToolButton { border:none; border-radius:4px; padding:4px 6px; color:%1;"
" background:transparent; }"
" QToolButton:hover { background:rgba(250,114,104,0.20); }"
" QToolButton:checked { background:#1F3A5F; color:white; }"
" QToolButton:disabled { color:#9AA6B3; }");
" QToolButton:disabled { color:#9AA6B3; }").arg(IconInkColor().name());
}
} // namespace ToolbarIcons
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "JFJochCalibrationResultWindow.h"
#include "../ViewerTheme.h"
#include <QFrame>
#include <QHBoxLayout>
@@ -27,7 +28,7 @@ namespace {
f.setPointSizeF(f.pointSizeF() * 2.2);
f.setBold(true);
v->setFont(f);
v->setStyleSheet("color: #1F3A5F;");
SetThemedStyleSheet(v, [] { return "color: " + AccentTextColor().name() + ";"; });
v->setAlignment(Qt::AlignCenter);
auto *c = new QLabel(caption, card);
c->setStyleSheet("color: gray;");
+14 -7
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "JFJochMergeStatsWindow.h"
#include "../ViewerTheme.h"
#include <cmath>
#include <limits>
@@ -57,7 +58,7 @@ namespace {
f.setPointSizeF(f.pointSizeF() * 2.2);
f.setBold(true);
v->setFont(f);
v->setStyleSheet("color: #1F3A5F;");
SetThemedStyleSheet(v, [] { return "color: " + AccentTextColor().name() + ";"; });
v->setAlignment(Qt::AlignCenter);
auto *c = new QLabel(caption, card);
c->setStyleSheet("color: gray;");
@@ -170,7 +171,10 @@ JFJochMergeStatsWindow::JFJochMergeStatsWindow(const QString &title, const Merge
auto *banner = new QLabel(text, this);
banner->setContentsMargins(10, 6, 10, 6);
if (twinning.twinning_suspected)
banner->setStyleSheet("color: rgb(160,0,0); border: 1px solid red; border-radius: 4px;");
SetThemedStyleSheet(banner, [] {
return QString("color: %1; border: 1px solid red; border-radius: 4px;")
.arg(DarkTheme() ? "#FF7B72" : "rgb(160,0,0)");
});
else
banner->setStyleSheet("color: gray;");
layout->addWidget(banner);
@@ -183,22 +187,22 @@ JFJochMergeStatsWindow::JFJochMergeStatsWindow(const QString &title, const Merge
plotBtn->setCheckable(true);
plotBtn->setChecked(true);
plotBtn->setToolTip("Per-resolution plot");
plotBtn->setStyleSheet(ToolbarIcons::buttonStyle());
SetThemedStyleSheet(plotBtn, ToolbarIcons::buttonStyle);
auto *tableBtn = new QToolButton(this);
tableBtn->setIcon(ToolbarIcons::table());
tableBtn->setCheckable(true);
tableBtn->setToolTip("Per-shell table");
tableBtn->setStyleSheet(ToolbarIcons::buttonStyle());
SetThemedStyleSheet(tableBtn, ToolbarIcons::buttonStyle);
auto *isaBtn = new QToolButton(this);
isaBtn->setIcon(ToolbarIcons::linePlot());
isaBtn->setCheckable(true);
isaBtn->setToolTip("ISa diagnostic: merged I/σ against I");
isaBtn->setStyleSheet(ToolbarIcons::buttonStyle());
SetThemedStyleSheet(isaBtn, ToolbarIcons::buttonStyle);
auto *reportBtn = new QToolButton(this);
reportBtn->setText("Report");
reportBtn->setCheckable(true);
reportBtn->setToolTip("The rugnux result report, as written to <prefix>_report.txt");
reportBtn->setStyleSheet(ToolbarIcons::buttonStyle());
SetThemedStyleSheet(reportBtn, ToolbarIcons::buttonStyle);
auto *viewGroup = new QButtonGroup(this);
viewGroup->setExclusive(true);
viewGroup->addButton(plotBtn, 0);
@@ -277,7 +281,9 @@ QWidget *JFJochMergeStatsWindow::buildIsaDiagnostic(const std::vector<std::pair<
points->setName("merged reflections");
points->setMarkerSize(4.0);
points->setBorderColor(Qt::transparent);
points->setColor(QColor(0x1F, 0x3A, 0x5F, 0x60));
QColor point_color = AccentTextColor();
point_color.setAlpha(0x60);
points->setColor(point_color);
double i_lo = std::numeric_limits<double>::max(), i_hi = 0.0, r_hi = 0.0;
for (const auto &[I, sigma] : i_sigma) {
@@ -295,6 +301,7 @@ QWidget *JFJochMergeStatsWindow::buildIsaDiagnostic(const std::vector<std::pair<
}
auto *chart = new QChart();
StyleChart(chart);
chart->legend()->setVisible(false);
chart->addSeries(points);
@@ -223,7 +223,7 @@ void JFJochViewerImageListWindow::imageLoaded(std::shared_ptr<const JFJochReader
// Only the row losing the highlight and the row gaining it change. Repainting every cell
// emitted dataChanged for the whole model - and a proxy re-sort - on each frame.
setRowBackground(highlighted_row, Qt::white);
setRowBackground(highlighted_row, QBrush()); // back to the palette's own
setRowBackground(curr_row, Qt::blue);
highlighted_row = curr_row;
}
@@ -29,7 +29,6 @@ JFJochViewerReflectionListWindow::JFJochViewerReflectionListWindow(QWidget* pare
tableView->verticalHeader()->setVisible(false);
tableView->horizontalHeader()->setSectionsClickable(true);
tableView->horizontalHeader()->setSortIndicatorShown(true);
tableView->setStyleSheet("background-color: white;");
tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
tableView->setSelectionMode(QAbstractItemView::SingleSelection);
@@ -29,7 +29,6 @@ JFJochViewerSpotListWindow::JFJochViewerSpotListWindow(QWidget* parent)
tableView->verticalHeader()->setVisible(false);
tableView->horizontalHeader()->setSectionsClickable(true);
tableView->horizontalHeader()->setSortIndicatorShown(true);
tableView->setStyleSheet("background-color: white;");
tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
tableView->setSelectionMode(QAbstractItemView::SingleSelection);