diff --git a/src/AppearanceManager.cpp b/src/AppearanceManager.cpp
index e30664a..489abb0 100644
--- a/src/AppearanceManager.cpp
+++ b/src/AppearanceManager.cpp
@@ -17,6 +17,12 @@
namespace {
+const QPalette &initialApplicationPalette()
+{
+ static const QPalette palette = QApplication::palette();
+ return palette;
+}
+
double relativeLuminance(const QColor &color)
{
auto linearChannel = [](int channel) {
@@ -46,6 +52,7 @@ AppearanceManager::AppearanceManager(ApplicationSettings *settings,
})
, mode(modeFromString(settings->appearance()))
, effective(EffectiveAppearance::Light)
+ , lightPalette(initialApplicationPalette())
{
AppearanceTrace::initialize();
if (mode == Mode::System)
@@ -197,6 +204,16 @@ void AppearanceManager::restoreSystemPalette()
hasSystemPaletteSnapshot = true;
}
+void AppearanceManager::restoreLightPalette()
+{
+ if (QApplication::palette() == lightPalette)
+ return;
+
+ applyingPalette = true;
+ QApplication::setPalette(lightPalette);
+ applyingPalette = false;
+}
+
void AppearanceManager::applyApplicationAppearance()
{
if (mode == Mode::System) {
@@ -206,6 +223,11 @@ void AppearanceManager::applyApplicationAppearance()
}
currentTokens = effective == EffectiveAppearance::Dark ? darkTokens() : lightTokens();
+ if (effective == EffectiveAppearance::Light) {
+ restoreLightPalette();
+ return;
+ }
+
applyingPalette = true;
QApplication::setPalette(explicitPalette(currentTokens));
applyingPalette = false;
@@ -328,7 +350,7 @@ AppearanceTokens AppearanceManager::lightTokens()
QColor(0, 120, 212, 128), QColor(128, 128, 128, 80),
QColor(QStringLiteral("#A1260D")), QColor(QStringLiteral("#8A6D00")),
QColor(QStringLiteral("#107C10")), QColor(QStringLiteral("#0067B8")),
- QColor(QStringLiteral("#000000")), QColor(QStringLiteral("#FFF4CE")),
+ QColor(QStringLiteral("#000000")), QColor(QStringLiteral("#E8E8FF")),
QColor(QStringLiteral("#A0A0A0")), QColor(QStringLiteral("#C0C0C0")),
QColor(QStringLiteral("#707070")), QColor(QStringLiteral("#0078D4")),
QColor(QStringLiteral("#A1260D")), QColor(QStringLiteral("#008000")),
diff --git a/src/AppearanceManager.h b/src/AppearanceManager.h
index 6b9aa92..fdf5fc3 100644
--- a/src/AppearanceManager.h
+++ b/src/AppearanceManager.h
@@ -107,6 +107,7 @@ public slots:
EffectiveAppearance resolveEffectiveAppearance() const;
EffectiveAppearance paletteFallback() const;
void restoreSystemPalette();
+ void restoreLightPalette();
void applyApplicationAppearance();
void applyNativeAppearance();
void applyNativeAppearance(QWidget *window);
@@ -124,6 +125,7 @@ public slots:
Mode mode;
EffectiveAppearance effective;
AppearanceTokens currentTokens;
+ QPalette lightPalette;
QPalette systemPaletteSnapshot;
bool hasSystemPaletteSnapshot = false;
bool applyingPalette = false;
diff --git a/src/dialogs/MainWindow.cpp b/src/dialogs/MainWindow.cpp
index e9c924b..241a9ac 100644
--- a/src/dialogs/MainWindow.cpp
+++ b/src/dialogs/MainWindow.cpp
@@ -2011,7 +2011,10 @@ void MainWindow::applyStyleSheet()
bool hasCustomStyleSheet = false;
{
AppearanceTrace::Scope readTrace(QStringLiteral("stylesheet-read"));
- QFile f(":/stylesheets/npp.css");
+ const QString styleSheetPath = app->getAppearanceManager()->isDark()
+ ? QStringLiteral(":/stylesheets/npp.css")
+ : QStringLiteral(":/stylesheets/npp-light.css");
+ QFile f(styleSheetPath);
qInfo() << "Loading stylesheet:" << f.fileName();
f.open(QFile::ReadOnly);
diff --git a/src/resources.qrc b/src/resources.qrc
index 4929146..b7245cd 100644
--- a/src/resources.qrc
+++ b/src/resources.qrc
@@ -19,6 +19,7 @@
icons/wrap.png
icons/find.png
stylesheets/npp.css
+ stylesheets/npp-light.css
icons/findReplace.png
icons/readonly.png
icons/startRecord.png
diff --git a/src/stylesheets/npp-light.css b/src/stylesheets/npp-light.css
new file mode 100644
index 0000000..5b70e2f
--- /dev/null
+++ b/src/stylesheets/npp-light.css
@@ -0,0 +1,77 @@
+QStatusBar {
+ border-top: 1px solid darkgray;
+}
+
+ads--CDockWidgetTab ads--CElidingLabel {
+ color : black;
+}
+
+ads--CDockWidgetTab {
+ background: rgb(192, 192, 192);
+ margin-top: 2px;
+ border-left: 1px solid white;
+ border-top: 2px solid white;
+ border-right: 2px solid gray;
+ padding: 2px 2px 0px 2px;
+ border-top-left-radius: 2px;
+ border-top-right-radius: 2px;
+}
+
+ads--CDockWidgetTab[activeTab="true"]
+{
+ background: rgb(240, 240, 240);
+ border-top: 4px solid rgb(255, 202, 176);
+ padding-bottom: 2px;
+ margin-top: 1px;
+}
+
+ads--CDockWidgetTab[focused="true"]
+{
+ background: rgb(240, 240, 240);
+ border-top: 4px solid orange;
+ padding-bottom: 2px;
+ margin-top: 1px;
+}
+
+ads--CDockWidgetTab:hover[activeTab="false"] {
+ background: rgb(210, 210, 210);
+}
+
+ads--CDockAreaTitleBar
+{
+ background: transparent;
+ border-bottom: 1px solid darkgray;
+ padding-bottom: 0px;
+}
+
+ads--CDockWidgetTab[activeTab="false"] QLabel {
+ color: gray;
+}
+
+#tabCloseButton
+{
+ background: rgba(0, 0, 0, 16);
+ margin-bottom: 1px;
+ margin-left: 4px;
+ border: none;
+ padding: 0px -2px;
+}
+
+#tabCloseButton:hover
+{
+ border: 1px solid rgba(0, 0, 0, 32);
+ background-color: rgb(251, 115, 115);
+ color: green;
+}
+
+#tabCloseButton:pressed
+{
+ background: red;
+}
+
+#QuickFindWidget {
+ border-left: 1px solid darkgray;
+ border-right: 1px solid darkgray;
+ border-bottom: 3px solid blue;
+ background: palette(window);
+}
\ No newline at end of file
diff --git a/src/stylesheets/npp.css b/src/stylesheets/npp.css
index 35ace27..f9a7983 100644
--- a/src/stylesheets/npp.css
+++ b/src/stylesheets/npp.css
@@ -13,7 +13,9 @@ QMenuBar::item:selected, QMenu::item:selected {
}
QMenu::separator {
+ height: 1px;
background: palette(mid);
+ margin: 4px 6px;
}
QToolTip {
@@ -22,6 +24,25 @@ QToolTip {
border: 1px solid palette(mid);
}
+QMessageBox {
+ background: palette(window);
+}
+
+QMessageBox QLabel {
+ background: transparent;
+ color: palette(window-text);
+}
+
+QMessageBox QDialogButtonBox {
+ background: palette(button);
+ border-top: 1px solid palette(mid);
+}
+
+QMessageBox QPushButton {
+ min-width: 72px;
+ padding: 5px 12px;
+}
+
QLineEdit, QComboBox, QSpinBox, QDoubleSpinBox, QTextEdit, QPlainTextEdit,
QListView, QTreeView, QTableView {
background: palette(base);
diff --git a/tests/AppearanceManagerTests.cpp b/tests/AppearanceManagerTests.cpp
index 6bcbea8..eeb0f63 100644
--- a/tests/AppearanceManagerTests.cpp
+++ b/tests/AppearanceManagerTests.cpp
@@ -45,6 +45,7 @@ class AppearanceManagerTests : public QObject
private slots:
void parsesOnlyCanonicalWireValues();
void defaultsMissingSettingsToClassicLight();
+ void explicitLightRestoresNativePalette();
void defaultsInvalidSettingsToSystem();
void persistsExplicitMode();
void emitsOnlyWhenEffectiveAppearanceChanges();
@@ -76,10 +77,36 @@ void AppearanceManagerTests::defaultsMissingSettingsToClassicLight()
ApplicationSettings settings(directory.filePath(QStringLiteral("settings.ini")), QSettings::IniFormat);
QCOMPARE(settings.appearance(), QStringLiteral("light"));
+ QVERIFY(!QApplication::testAttribute(Qt::AA_SetPalette));
AppearanceManager manager(&settings, [] { return Qt::ColorScheme::Dark; });
QCOMPARE(manager.requestedMode(), AppearanceManager::Mode::Light);
QCOMPARE(manager.effectiveAppearance(), AppearanceManager::EffectiveAppearance::Light);
+ QVERIFY(!QApplication::testAttribute(Qt::AA_SetPalette));
+}
+
+void AppearanceManagerTests::explicitLightRestoresNativePalette()
+{
+ QTemporaryDir directory;
+ QVERIFY(directory.isValid());
+
+ const QPalette nativePalette = QApplication::palette();
+ ApplicationSettings settings(directory.filePath(QStringLiteral("settings.ini")), QSettings::IniFormat);
+ AppearanceManager manager(&settings, [] { return Qt::ColorScheme::Light; });
+
+ manager.setRequestedMode(AppearanceManager::Mode::Dark);
+ QVERIFY(QApplication::palette() != nativePalette);
+
+ manager.setRequestedMode(AppearanceManager::Mode::Light);
+ const QPalette actualPalette = QApplication::palette();
+ for (QPalette::ColorGroup group : {QPalette::Active, QPalette::Inactive, QPalette::Disabled}) {
+ for (int role = 0; role < QPalette::NColorRoles; ++role) {
+ if (role == QPalette::NoRole)
+ continue;
+ QCOMPARE(actualPalette.color(group, static_cast(role)),
+ nativePalette.color(group, static_cast(role)));
+ }
+ }
}
void AppearanceManagerTests::defaultsInvalidSettingsToSystem()
diff --git a/tests/CompareSessionTests.cpp b/tests/CompareSessionTests.cpp
index 4c03583..9e4edec 100644
--- a/tests/CompareSessionTests.cpp
+++ b/tests/CompareSessionTests.cpp
@@ -21,9 +21,14 @@
#include
#include
#include
+#include
#include
#include
+#include
+#include
#include
+#include
+#include
#include
#include
#include
@@ -40,6 +45,122 @@ class CompareSessionTests : public QObject
Q_OBJECT
private slots:
+ void lightStylePreservesNativeControls()
+ {
+ QFile styleSheet(QStringLiteral(":/stylesheets/npp-light.css"));
+ QVERIFY(styleSheet.open(QIODevice::ReadOnly | QIODevice::Text));
+ const QString css = QString::fromUtf8(styleSheet.readAll())
+ + DockedEditor::tabTitleStyleSheet(Qt::black, Qt::gray);
+
+ QWidget nativeHost;
+ QWidget lightHost;
+ lightHost.setStyleSheet(css);
+
+ QMessageBox nativeMessageBox(&nativeHost);
+ QMessageBox lightMessageBox(&lightHost);
+ auto addButtons = [](QMessageBox &messageBox) {
+ QPushButton *saveButton = messageBox.addButton(
+ QStringLiteral("Save"), QMessageBox::AcceptRole);
+ messageBox.addButton(QStringLiteral("Discard"), QMessageBox::DestructiveRole);
+ messageBox.addButton(QMessageBox::Cancel);
+ return saveButton;
+ };
+ QPushButton *nativeSaveButton = addButtons(nativeMessageBox);
+ QPushButton *lightSaveButton = addButtons(lightMessageBox);
+
+ QMenu nativeMenu(&nativeHost);
+ QMenu lightMenu(&lightHost);
+ nativeMenu.addAction(QStringLiteral("First"));
+ QAction *nativeSeparator = nativeMenu.addSeparator();
+ nativeMenu.addAction(QStringLiteral("Second"));
+ lightMenu.addAction(QStringLiteral("First"));
+ QAction *lightSeparator = lightMenu.addSeparator();
+ lightMenu.addAction(QStringLiteral("Second"));
+
+ nativeMessageBox.show();
+ lightMessageBox.show();
+ nativeMenu.show();
+ lightMenu.show();
+ QCoreApplication::processEvents();
+
+ QCOMPARE(lightSaveButton->size(), nativeSaveButton->size());
+ QCOMPARE(lightMenu.actionGeometry(lightSeparator).height(),
+ nativeMenu.actionGeometry(nativeSeparator).height());
+ }
+
+ void darkStyleKeepsMenuSeparatorsCompact()
+ {
+ QFile styleSheet(QStringLiteral(":/stylesheets/npp.css"));
+ QVERIFY(styleSheet.open(QIODevice::ReadOnly | QIODevice::Text));
+
+ QWidget host;
+ QPalette palette = host.palette();
+ palette.setColor(QPalette::Window, QColor(QStringLiteral("#101010")));
+ palette.setColor(QPalette::Mid, QColor(QStringLiteral("#5A5A5A")));
+ host.setPalette(palette);
+ host.setStyleSheet(QString::fromUtf8(styleSheet.readAll()));
+ QMenu menu(&host);
+ menu.addAction(QStringLiteral("First"));
+ QAction *separator = menu.addSeparator();
+ menu.addAction(QStringLiteral("Second"));
+ menu.show();
+ QCoreApplication::processEvents();
+
+ QImage image(menu.size(), QImage::Format_ARGB32);
+ image.fill(Qt::transparent);
+ menu.render(&image);
+
+ const QRect separatorGeometry = menu.actionGeometry(separator);
+ const int sampleX = separatorGeometry.center().x();
+ const QColor backgroundColor = image.pixelColor(sampleX, separatorGeometry.top());
+ int separatorRows = 0;
+ for (int y = separatorGeometry.top(); y <= separatorGeometry.bottom(); ++y) {
+ if (image.pixelColor(sampleX, y) != backgroundColor)
+ ++separatorRows;
+ }
+ QCOMPARE(separatorRows, 1);
+ }
+
+ void darkStyleKeepsMessageBoxesDark()
+ {
+ QFile styleSheet(QStringLiteral(":/stylesheets/npp.css"));
+ QVERIFY(styleSheet.open(QIODevice::ReadOnly | QIODevice::Text));
+
+ QWidget host;
+ QPalette darkPalette = host.palette();
+ darkPalette.setColor(QPalette::Window, QColor(QStringLiteral("#191A1B")));
+ darkPalette.setColor(QPalette::WindowText, QColor(QStringLiteral("#BFBFBF")));
+ darkPalette.setColor(QPalette::Button, QColor(QStringLiteral("#202122")));
+ darkPalette.setColor(QPalette::ButtonText, QColor(QStringLiteral("#BFBFBF")));
+ darkPalette.setColor(QPalette::Mid, QColor(QStringLiteral("#333536")));
+ const QPalette originalPalette = QApplication::palette();
+ const auto restorePalette = qScopeGuard([originalPalette] {
+ QApplication::setPalette(originalPalette);
+ });
+ QApplication::setPalette(darkPalette);
+ host.setPalette(darkPalette);
+ host.setStyleSheet(QString::fromUtf8(styleSheet.readAll()));
+
+ QMessageBox messageBox(&host);
+ messageBox.setIcon(QMessageBox::Question);
+ messageBox.setText(QStringLiteral("Save changes to New 1?"));
+ messageBox.addButton(QStringLiteral("Save"), QMessageBox::AcceptRole);
+ messageBox.addButton(QStringLiteral("Discard"), QMessageBox::DestructiveRole);
+ messageBox.addButton(QMessageBox::Cancel);
+ messageBox.show();
+ QCoreApplication::processEvents();
+
+ QImage image(messageBox.size(), QImage::Format_ARGB32);
+ image.fill(Qt::transparent);
+ messageBox.render(&image);
+
+ const QColor bodyPixel = image.pixelColor(messageBox.width() / 2, 12);
+ const QColor footerPixel = image.pixelColor(12, messageBox.height() - 12);
+ QCOMPARE(bodyPixel, darkPalette.color(QPalette::Window));
+ QVERIFY(footerPixel.lightness() < 80);
+ QVERIFY(messageBox.buttons().first()->width() >= 72);
+ }
+
void darkStyleKeepsInactiveTabTitlesReadable()
{
QFile styleSheet(QStringLiteral(":/stylesheets/npp.css"));
diff --git a/tests/EditorAppearanceTests.cpp b/tests/EditorAppearanceTests.cpp
index 1eb3ff1..ac7376b 100644
--- a/tests/EditorAppearanceTests.cpp
+++ b/tests/EditorAppearanceTests.cpp
@@ -95,6 +95,8 @@ void EditorAppearanceTests::switchesTheSameEditorBackToLight()
EditorAppearance::apply(&editor, manager.tokens(), QStringLiteral("Courier New"), 11);
QCOMPARE(editor.styleBack(STYLE_DEFAULT),
static_cast(AppearanceManager::scintillaColor(QColor(QStringLiteral("#FFFFFF")))));
+ QCOMPARE(static_cast(editor.elementColour(SC_ELEMENT_CARET_LINE_BACK)),
+ 0xFFFFE8E8u);
}
void EditorAppearanceTests::preservesExistingLexerStyleAssignments()