Skip to content

Commit a41f5d5

Browse files
committed
Refresh styled widget palettes when switching themes and fix Windows install prefix
1 parent cbb6bd3 commit a41f5d5

4 files changed

Lines changed: 59 additions & 2 deletions

File tree

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ jobs:
6262
shell: bash
6363
run: |
6464
for scale in 1 2; do
65-
QT_SCALE_FACTOR="$scale" TODOBENCH_THEME_SCREENSHOTS="$PWD/build/screenshots/$scale" build/release/test_main_window themesRender
65+
QT_SCALE_FACTOR="$scale" TODOBENCH_THEME_SCREENSHOTS="$PWD/build/screenshots/$scale" build/release/test_main_window themesRender themeSwitchUpdatesExistingWidgets
6666
done
6767
- if: runner.os == 'Linux'
6868
name: Linux packages

native/src/app/theme.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <QRegularExpression>
66
#include <QStyle>
77
#include <QStyleFactory>
8+
#include <QWidget>
89
#include <algorithm>
910
#include <cmath>
1011

@@ -165,11 +166,19 @@ QPalette theme_palette(const std::string& id, const ColorOverrides& overrides, c
165166
void apply_theme(const Settings& settings) {
166167
const auto& system = system_appearance();
167168
const auto& overrides = theme_overrides(settings);
169+
// Qt stylesheet styles cache resolved widget palettes. Unpolish them before
170+
// changing the application palette, then resolve their rules against it again.
171+
std::vector<std::pair<QWidget*, QString>> stylesheets;
172+
for (auto* widget : QApplication::allWidgets()) {
173+
if (!widget->styleSheet().isEmpty()) stylesheets.emplace_back(widget, widget->styleSheet());
174+
}
175+
for (const auto& [widget, sheet] : stylesheets) widget->setStyleSheet({});
168176
const auto style_name = settings.theme == "system" && overrides.empty() ? system.style : QString("fusion");
169177
if (QApplication::style()->objectName().compare(style_name, Qt::CaseInsensitive) != 0) {
170178
if (auto* style = QStyleFactory::create(style_name)) QApplication::setStyle(style);
171179
}
172180
QApplication::setPalette(theme_palette(settings.theme, overrides, system.palette));
181+
for (const auto& [widget, sheet] : stylesheets) widget->setStyleSheet(sheet);
173182
}
174183

175184
double color_contrast(const QColor& foreground, const QColor& background) {

native/tests/test_main_window.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ private slots:
4646
void projectFilterNamesSurviveEditingAndTabSwitching();
4747
void themesRender_data();
4848
void themesRender();
49+
void themeSwitchUpdatesExistingWidgets();
4950
void cancelThemeCustomizationPreservesSettings();
5051
void sampleWorkflowRenders_data();
5152
void sampleWorkflowRenders();
@@ -192,6 +193,53 @@ void MainWindowTest::newWorkspaceUsesSelectedFolder_data() {
192193
QTest::newRow("tutorial") << true;
193194
}
194195

196+
namespace {
197+
void select_theme(MainWindow& window, const char* preset) {
198+
for (auto* action : window.findChildren<QAction*>()) {
199+
if (action->data().toString() == preset && action->isCheckable()) action->trigger();
200+
}
201+
QApplication::processEvents();
202+
}
203+
204+
bool widgets_match_theme(MainWindow& window, const char* preset) {
205+
const auto expected = theme_palette(preset, {}, system_theme_palette());
206+
auto* filter = window.findChild<QLineEdit*>("taskFilter");
207+
return filter->palette().color(QPalette::Base) == expected.color(QPalette::Base)
208+
&& filter->palette().color(QPalette::Text) == expected.color(QPalette::Text)
209+
&& window.findChild<QStackedWidget*>("detailStack")->palette().color(QPalette::WindowText) == expected.color(QPalette::WindowText)
210+
&& window.findChild<QTextEdit*>()->palette().color(QPalette::Base) == expected.color(QPalette::Base);
211+
}
212+
}
213+
214+
void MainWindowTest::themeSwitchUpdatesExistingWidgets() {
215+
QTemporaryDir temporary;
216+
const auto root = std::filesystem::path(temporary.path().toStdString()) / "theme-switch";
217+
QVERIFY(seed_task(root));
218+
{
219+
MainWindow window;
220+
window.open_workspace(root);
221+
window.resize(1280, 820);
222+
auto* tree = window.findChild<QTreeView*>("taskTree");
223+
tree->setCurrentIndex(tree->model()->index(0, 0));
224+
window.show();
225+
for (const auto* preset : {"brown", "light", "brown", "rose", "light"}) {
226+
select_theme(window, preset);
227+
QVERIFY(widgets_match_theme(window, preset));
228+
}
229+
const auto directory = qEnvironmentVariable("TODOBENCH_THEME_SCREENSHOTS");
230+
if (!directory.isEmpty()) {
231+
std::filesystem::create_directories(directory.toStdString());
232+
QVERIFY(window.grab().save(directory + "/switched-light.png"));
233+
}
234+
}
235+
QCOMPARE(std::get<Settings>(load_settings(root / "settings.json")).theme, std::string("light"));
236+
MainWindow reopened;
237+
reopened.open_workspace(root);
238+
reopened.show();
239+
QApplication::processEvents();
240+
QVERIFY(widgets_match_theme(reopened, "light"));
241+
}
242+
195243
void MainWindowTest::newWorkspaceUsesSelectedFolder() {
196244
QFETCH(bool, tutorial);
197245
QTemporaryDir temporary;

scripts/package-windows.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
1616
cmake --build $BuildDirectory --parallel
1717
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
1818

19-
$Stage = Join-Path $BuildDirectory "windows-stage"
19+
$Stage = [System.IO.Path]::GetFullPath((Join-Path $BuildDirectory "windows-stage"))
2020
if (Test-Path $Stage) { Remove-Item -Recurse -Force $Stage }
2121
cmake --install $BuildDirectory --prefix $Stage
2222
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

0 commit comments

Comments
 (0)