From ac97898c8ed9fadb444abbcd0fb2810e5fd1bf5e Mon Sep 17 00:00:00 2001 From: Leon Date: Fri, 21 Aug 2026 19:06:37 +0800 Subject: [PATCH 1/2] T-0025(fix): improve dark Compare and tab visibility --- src/AppearanceManager.cpp | 4 +-- src/DockedEditor.cpp | 12 ++++++++ src/DockedEditor.h | 4 +++ src/compare/CompareToolBar.cpp | 2 +- src/dialogs/MainWindow.cpp | 3 ++ src/stylesheets/npp.css | 4 --- tests/AppearanceManagerTests.cpp | 47 ++++++++++++++++++++++++++++++++ tests/CompareSessionTests.cpp | 44 ++++++++++++++++++++++++++++++ 8 files changed, 113 insertions(+), 7 deletions(-) diff --git a/src/AppearanceManager.cpp b/src/AppearanceManager.cpp index 4cf0168..e30664a 100644 --- a/src/AppearanceManager.cpp +++ b/src/AppearanceManager.cpp @@ -311,8 +311,8 @@ AppearanceTokens AppearanceManager::darkTokens() QColor(QStringLiteral("#FFA657")), QColor(QStringLiteral("#79C0FF")), QColor(QStringLiteral("#7EE787")), QColor(QStringLiteral("#9CDCFE")), QColor(QStringLiteral("#72C892")), QColor(QStringLiteral("#0078D4")), - QColor(QStringLiteral("#F28772")), QColor(52, 125, 57, 38), - QColor(201, 60, 55, 38) + QColor(QStringLiteral("#F28772")), QColor(52, 125, 57, 89), + QColor(201, 60, 55, 89) }; } diff --git a/src/DockedEditor.cpp b/src/DockedEditor.cpp index 09c11b2..7d331c0 100644 --- a/src/DockedEditor.cpp +++ b/src/DockedEditor.cpp @@ -95,6 +95,18 @@ DockedEditor::DockedEditor(QWidget *parent) : QObject(parent) }); } +QString DockedEditor::tabTitleStyleSheet(const QColor &active, const QColor &inactive) +{ + auto cssColor = [](const QColor &color) { + return QStringLiteral("rgba(%1, %2, %3, %4)") + .arg(color.red()).arg(color.green()).arg(color.blue()).arg(color.alpha()); + }; + return QStringLiteral( + "ads--CDockWidgetTab[activeTab=\"true\"] ads--CElidingLabel { color: %1; }\n" + "ads--CDockWidgetTab[activeTab=\"false\"] ads--CElidingLabel { color: %2; }\n") + .arg(cssColor(active), cssColor(inactive)); +} + ScintillaNext *DockedEditor::getCurrentEditor() const { diff --git a/src/DockedEditor.h b/src/DockedEditor.h index 577b7f3..1b5cf40 100644 --- a/src/DockedEditor.h +++ b/src/DockedEditor.h @@ -20,7 +20,9 @@ #ifndef DOCKEDEDITOR_H #define DOCKEDEDITOR_H +#include #include +#include #include "DockManager.h" #include "ScintillaNext.h" @@ -37,6 +39,8 @@ class DockedEditor : public QObject public: explicit DockedEditor(QWidget *parent); + static QString tabTitleStyleSheet(const QColor &active, const QColor &inactive); + ScintillaNext *getCurrentEditor() const; ads::CDockAreaWidget *currentDockArea() const; diff --git a/src/compare/CompareToolBar.cpp b/src/compare/CompareToolBar.cpp index 49f777c..a081546 100644 --- a/src/compare/CompareToolBar.cpp +++ b/src/compare/CompareToolBar.cpp @@ -200,7 +200,7 @@ void CompareToolBar::applyAppearance() "QLabel#compareModifiedDot { background: %9; border-radius: 3px; }") .arg(cssColor(tokens.surfaceShell), cssColor(tokens.borderDefault), cssColor(tokens.textPrimary), cssColor(tokens.surfaceHover), - cssColor(tokens.surfaceRaised), cssColor(tokens.textSecondary), + cssColor(tokens.surfaceRaised), cssColor(tokens.textPrimary), cssColor(tokens.diffAddedMarker), cssColor(tokens.diffDeletedMarker), cssColor(tokens.diffModifiedMarker))); diff --git a/src/dialogs/MainWindow.cpp b/src/dialogs/MainWindow.cpp index 87b2063..7891428 100644 --- a/src/dialogs/MainWindow.cpp +++ b/src/dialogs/MainWindow.cpp @@ -2014,6 +2014,9 @@ void MainWindow::applyStyleSheet() sheet = f.readAll(); f.close(); + const AppearanceTokens &tokens = app->getAppearanceManager()->tokens(); + sheet += DockedEditor::tabTitleStyleSheet(tokens.textPrimary, tokens.textSecondary); + // If there is a "custom.css" file where the ini is located, load it as a style sheet addition QString directoryPath = QFileInfo(app->getSettings()->fileName()).absolutePath(); QString fullPath = QDir(directoryPath).filePath("custom.css"); diff --git a/src/stylesheets/npp.css b/src/stylesheets/npp.css index f7ddef5..35ace27 100644 --- a/src/stylesheets/npp.css +++ b/src/stylesheets/npp.css @@ -117,10 +117,6 @@ ads--CDockAreaTitleBar padding-bottom: 0px; } -ads--CDockWidgetTab[activeTab="false"] QLabel { - color: palette(placeholder-text); -} - #tabCloseButton { background: transparent; diff --git a/tests/AppearanceManagerTests.cpp b/tests/AppearanceManagerTests.cpp index 2e9d53a..6bcbea8 100644 --- a/tests/AppearanceManagerTests.cpp +++ b/tests/AppearanceManagerTests.cpp @@ -2,8 +2,39 @@ #include #include #include +#include #include +namespace { + +QColor composite(const QColor &foreground, const QColor &background) +{ + const double alpha = foreground.alphaF(); + return QColor::fromRgbF( + foreground.redF() * alpha + background.redF() * (1.0 - alpha), + foreground.greenF() * alpha + background.greenF() * (1.0 - alpha), + foreground.blueF() * alpha + background.blueF() * (1.0 - alpha)); +} + +double luminance(const QColor &color) +{ + auto linear = [](double channel) { + return channel <= 0.04045 ? channel / 12.92 : qPow((channel + 0.055) / 1.055, 2.4); + }; + return 0.2126 * linear(color.redF()) + + 0.7152 * linear(color.greenF()) + + 0.0722 * linear(color.blueF()); +} + +double contrast(const QColor &first, const QColor &second) +{ + const double lighter = qMax(luminance(first), luminance(second)); + const double darker = qMin(luminance(first), luminance(second)); + return (lighter + 0.05) / (darker + 0.05); +} + +} // namespace + #include "AppearanceManager.h" #include "ApplicationSettings.h" @@ -23,6 +54,7 @@ private slots: void ignoresWidgetPaletteEventsWhenApplicationPaletteIsUnchanged(); void refreshesOnceWhenApplicationPaletteChanges(); void writesOptInPerformanceTrace(); + void darkCompareLineFillsRemainVisible(); }; void AppearanceManagerTests::parsesOnlyCanonicalWireValues() @@ -230,6 +262,21 @@ void AppearanceManagerTests::writesOptInPerformanceTrace() QVERIFY(trace.contains(QStringLiteral(" end elapsed-ms="))); } +void AppearanceManagerTests::darkCompareLineFillsRemainVisible() +{ + QTemporaryDir directory; + ApplicationSettings settings(directory.filePath(QStringLiteral("settings.ini")), QSettings::IniFormat); + AppearanceManager manager(&settings, [] { return Qt::ColorScheme::Dark; }); + manager.setRequestedMode(AppearanceManager::Mode::Dark); + + const AppearanceTokens &tokens = manager.tokens(); + const QColor added = composite(tokens.diffAddedFill, tokens.surfaceEditor); + const QColor deleted = composite(tokens.diffDeletedFill, tokens.surfaceEditor); + + QVERIFY(contrast(added, tokens.surfaceEditor) >= 1.35); + QVERIFY(contrast(deleted, tokens.surfaceEditor) >= 1.35); +} + QTEST_MAIN(AppearanceManagerTests) #include "AppearanceManagerTests.moc" \ No newline at end of file diff --git a/tests/CompareSessionTests.cpp b/tests/CompareSessionTests.cpp index d7c14e9..9571d40 100644 --- a/tests/CompareSessionTests.cpp +++ b/tests/CompareSessionTests.cpp @@ -13,6 +13,9 @@ #include "DockedEditor.h" #include "ScintillaNext.h" +#include "DockWidgetTab.h" +#include "ElidingLabel.h" + #include #include #include @@ -24,6 +27,8 @@ #include #include +#include + using Compare::ChangeKind; using Compare::DiffHunk; using Compare::Navigator; @@ -34,6 +39,45 @@ class CompareSessionTests : public QObject Q_OBJECT private slots: + void darkStyleKeepsInactiveTabTitlesReadable() + { + QFile styleSheet(QStringLiteral(":/stylesheets/npp.css")); + QVERIFY(styleSheet.open(QIODevice::ReadOnly | QIODevice::Text)); + const QByteArray css = styleSheet.readAll(); + QWidget host; + QPalette darkPalette = host.palette(); + darkPalette.setColor(QPalette::Window, QColor(QStringLiteral("#191A1B"))); + darkPalette.setColor(QPalette::WindowText, QColor(QStringLiteral("#BFBFBF"))); + darkPalette.setColor(QPalette::Base, QColor(QStringLiteral("#121314"))); + darkPalette.setColor(QPalette::Text, QColor(QStringLiteral("#BBBEBF"))); + darkPalette.setColor(QPalette::PlaceholderText, QColor(QStringLiteral("#555555"))); + host.setPalette(darkPalette); + host.resize(900, 600); + + DockedEditor dockedEditor(&host); + dockedEditor.addEditor(new ScintillaNext(QStringLiteral("first.cpp"))); + dockedEditor.addEditor(new ScintillaNext(QStringLiteral("second.cpp"))); + host.setStyleSheet(QString::fromUtf8(css) + + DockedEditor::tabTitleStyleSheet( + QColor(QStringLiteral("#BFBFBF")), QColor(QStringLiteral("#8C8C8C")))); + host.show(); + QCoreApplication::processEvents(); + + const QList tabs = host.findChildren(); + QCOMPARE(tabs.size(), 2); + auto inactive = std::find_if(tabs.cbegin(), tabs.cend(), [](const auto *tab) { + return !tab->isActiveTab(); + }); + QVERIFY(inactive != tabs.cend()); + + const QList labels = + (*inactive)->findChildren(); + QCOMPARE(labels.size(), 1); + labels.first()->ensurePolished(); + QCOMPARE(labels.first()->palette().color(QPalette::WindowText), + QColor(QStringLiteral("#8C8C8C"))); + } + void navigationWrapsInBothDirections() { Navigator navigator; From 0144737000e16c32a7ad3b207d0d832ce9157a26 Mon Sep 17 00:00:00 2001 From: Leon Date: Fri, 21 Aug 2026 21:32:34 +0800 Subject: [PATCH 2/2] T-0025(fix): theme the main Compare action icon --- src/ThemedIcon.h | 32 ++++++++++++++++++++++ src/dialogs/MainWindow.cpp | 11 +++++++- tests/CompareSessionTests.cpp | 51 ++++++++++++++++++++++++++++++++++- 3 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 src/ThemedIcon.h diff --git a/src/ThemedIcon.h b/src/ThemedIcon.h new file mode 100644 index 0000000..59321e5 --- /dev/null +++ b/src/ThemedIcon.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace ThemedIcon { + +inline QPixmap tintedPixmap(const QIcon &source, int size, const QColor &color) +{ + QPixmap pixmap = source.pixmap(size, size, QIcon::Normal, QIcon::Off); + QPainter painter(&pixmap); + painter.setCompositionMode(QPainter::CompositionMode_SourceIn); + painter.fillRect(pixmap.rect(), color); + return pixmap; +} + +inline QIcon monochrome(const QString &resource, const QColor &normal, + const QColor &disabled) +{ + const QIcon source(resource); + QIcon result; + for (const int size : {16, 20, 24, 32}) { + result.addPixmap(tintedPixmap(source, size, normal), QIcon::Normal); + result.addPixmap(tintedPixmap(source, size, disabled), QIcon::Disabled); + } + return result; +} + +} // namespace ThemedIcon \ No newline at end of file diff --git a/src/dialogs/MainWindow.cpp b/src/dialogs/MainWindow.cpp index 7891428..e9c924b 100644 --- a/src/dialogs/MainWindow.cpp +++ b/src/dialogs/MainWindow.cpp @@ -96,6 +96,7 @@ #include "FadingIndicator.h" #include "ActionUtils.h" +#include "ThemedIcon.h" MainWindow::MainWindow(NotepadSharpApplication *app) : @@ -130,8 +131,11 @@ MainWindow::MainWindow(NotepadSharpApplication *app) : QMenu *compareMenu = new QMenu(tr("&Compare"), this); ui->menuBar->insertMenu(ui->menuSettings->menuAction(), compareMenu); + const AppearanceTokens &appearanceTokens = app->getAppearanceManager()->tokens(); comparePreviousAction = new QAction( - QIcon(QStringLiteral(":/icons/git-compare-arrows.svg")), + ThemedIcon::monochrome(QStringLiteral(":/icons/git-compare-arrows.svg"), + appearanceTokens.textPrimary, + appearanceTokens.textDisabled), tr("Compare with Previous Tab"), this); comparePreviousAction->setObjectName(QStringLiteral("actionComparePrevious")); @@ -2015,6 +2019,11 @@ void MainWindow::applyStyleSheet() f.close(); const AppearanceTokens &tokens = app->getAppearanceManager()->tokens(); + if (comparePreviousAction) { + comparePreviousAction->setIcon(ThemedIcon::monochrome( + QStringLiteral(":/icons/git-compare-arrows.svg"), + tokens.textPrimary, tokens.textDisabled)); + } sheet += DockedEditor::tabTitleStyleSheet(tokens.textPrimary, tokens.textSecondary); // If there is a "custom.css" file where the ini is located, load it as a style sheet addition diff --git a/tests/CompareSessionTests.cpp b/tests/CompareSessionTests.cpp index 9571d40..4c03583 100644 --- a/tests/CompareSessionTests.cpp +++ b/tests/CompareSessionTests.cpp @@ -12,6 +12,7 @@ #include "CompareToolBar.h" #include "DockedEditor.h" #include "ScintillaNext.h" +#include "ThemedIcon.h" #include "DockWidgetTab.h" #include "ElidingLabel.h" @@ -117,11 +118,59 @@ private slots: dockedEditor.splitToRightOf(secondEditor, thirdEditor); QVERIFY(dockedEditor.previousEditor(thirdEditor) == nullptr); QCOMPARE(dockedEditor.previousEditor(secondEditor), firstEditor); - const QIcon compareIcon(QStringLiteral(":/icons/git-compare-arrows.svg")); QVERIFY(!compareIcon.isNull()); } + void newlyAddedActiveTabCanCompareWithPrevious() + { + QWidget host; + host.resize(900, 600); + DockedEditor dockedEditor(&host); + auto *firstEditor = new ScintillaNext(QStringLiteral("New 1")); + auto *secondEditor = new ScintillaNext(QStringLiteral("New 2")); + + dockedEditor.addEditor(firstEditor); + dockedEditor.addEditor(secondEditor); + host.show(); + QCoreApplication::processEvents(); + + QCOMPARE(dockedEditor.getCurrentEditor(), secondEditor); + QCOMPARE(dockedEditor.previousEditor(dockedEditor.getCurrentEditor()), firstEditor); + } + + void darkCompareActionIconUsesThemeColors() + { + const QColor normal(QStringLiteral("#BFBFBF")); + const QColor disabled(QStringLiteral("#555555")); + const QIcon icon = ThemedIcon::monochrome( + QStringLiteral(":/icons/git-compare-arrows.svg"), normal, disabled); + + QVERIFY(!icon.isNull()); + auto firstOpaquePixel = [](const QPixmap &pixmap) { + const QImage image = pixmap.toImage().convertToFormat(QImage::Format_ARGB32); + for (int y = 0; y < image.height(); ++y) { + for (int x = 0; x < image.width(); ++x) { + const QColor pixel = image.pixelColor(x, y); + if (pixel.alpha() > 0) + return pixel; + } + } + return QColor(); + }; + + const QColor normalPixel = firstOpaquePixel(icon.pixmap(24, 24, QIcon::Normal)); + const QColor disabledPixel = firstOpaquePixel(icon.pixmap(24, 24, QIcon::Disabled)); + QVERIFY(normalPixel.isValid()); + QVERIFY(disabledPixel.isValid()); + QCOMPARE(normalPixel.red(), normal.red()); + QCOMPARE(normalPixel.green(), normal.green()); + QCOMPARE(normalPixel.blue(), normal.blue()); + QVERIFY(qAbs(disabledPixel.red() - disabled.red()) <= 8); + QVERIFY(qAbs(disabledPixel.green() - disabled.green()) <= 8); + QVERIFY(qAbs(disabledPixel.blue() - disabled.blue()) <= 8); + } + void mapsInsertedRowsToTheirNearestBoundary() { const QVector hunks = {