From 5ec4f78248e7ec687cdea9c4e4ae73daa798dff3 Mon Sep 17 00:00:00 2001 From: Andrei Shevchenko Date: Mon, 4 May 2026 19:48:38 +0200 Subject: [PATCH 1/6] Add dark mode Implements a clean dark mode that drives all theming from a single ApplicationSettings::darkMode boolean. Live toggle: no restart required. The Lua side defines a `theme` table that is rebuilt from a `dark_mode` global injected by C++. SetStyle() sets STYLE_DEFAULT from theme and calls StyleClearAll, then auto-translates canonical light-mode colors (black text, white background) to theme equivalents per-style. Language `.lua` files therefore work in either mode without modification: the vast majority specify black on white, which gets converted, while deliberate syntax colors (blue keywords, green comments, etc.) pass through unchanged. EditorManager::applyEditorTheme() handles all Scintilla UI surfaces (margins, caret line, fold markers, line numbers, brace match) for both modes. NotepadNextApplication::refreshEditorTheme() updates the Lua theme and re-applies styles to every open editor when the setting changes. For Qt widgets, MainWindow::applyStyleSheet() switches between Fusion-based light and dark QPalettes and loads either npp.css or the new npp-dark.css for ADS dock tabs and the QuickFindWidget. No third-party stylesheet dependency. LuaConsoleDock takes ApplicationSettings and reapplies its lexer styles on darkModeChanged so the console's Lua syntax highlighting tracks the theme. A "Dark mode" checkbox is added to the GUI section of Preferences. --- src/ApplicationSettings.cpp | 2 + src/ApplicationSettings.h | 2 + src/EditorManager.cpp | 83 +++++++++++++++++-------------- src/EditorManager.h | 5 +- src/NotepadNextApplication.cpp | 14 ++++++ src/NotepadNextApplication.h | 3 ++ src/dialogs/MainWindow.cpp | 37 ++++++++++++-- src/dialogs/PreferencesDialog.cpp | 1 + src/dialogs/PreferencesDialog.ui | 7 +++ src/docks/LuaConsoleDock.cpp | 64 ++++++++++++++---------- src/docks/LuaConsoleDock.h | 4 +- src/resources.qrc | 1 + src/scripts/init.lua | 41 ++++++++++++++- src/stylesheets/npp-dark.css | 77 ++++++++++++++++++++++++++++ 14 files changed, 272 insertions(+), 69 deletions(-) create mode 100644 src/stylesheets/npp-dark.css diff --git a/src/ApplicationSettings.cpp b/src/ApplicationSettings.cpp index 17def0ca6..1d01bcb74 100644 --- a/src/ApplicationSettings.cpp +++ b/src/ApplicationSettings.cpp @@ -71,3 +71,5 @@ CREATE_SETTING(Editor, AdditionalWordChars, additionalWordChars, QString, QStrin CREATE_SETTING(Editor, DefaultEOLMode, defaultEOLMode, QString, QStringLiteral("")) CREATE_SETTING(Editor, URLHighlighting, urlHighlighting, bool, true) CREATE_SETTING(Editor, ShowLineNumbers, showLineNumbers, bool, true) + +CREATE_SETTING(Gui, DarkMode, darkMode, bool, false) diff --git a/src/ApplicationSettings.h b/src/ApplicationSettings.h index cd4089801..b2e3e5739 100644 --- a/src/ApplicationSettings.h +++ b/src/ApplicationSettings.h @@ -119,4 +119,6 @@ class ApplicationSettings : public QSettings DEFINE_SETTING(DefaultEOLMode, defaultEOLMode, QString) DEFINE_SETTING(URLHighlighting, urlHighlighting, bool) DEFINE_SETTING(ShowLineNumbers, showLineNumbers, bool) + + DEFINE_SETTING(DarkMode, darkMode, bool) }; diff --git a/src/EditorManager.cpp b/src/EditorManager.cpp index e60f2911f..924ca8447 100644 --- a/src/EditorManager.cpp +++ b/src/EditorManager.cpp @@ -128,6 +128,11 @@ EditorManager::EditorManager(ApplicationSettings *settings, QObject *parent) } } }); + + connect(settings, &ApplicationSettings::darkModeChanged, this, [=](bool) { + for (auto &editor : getEditors()) + applyEditorTheme(editor); + }); } ScintillaNext *EditorManager::createEditor(const QString &name) @@ -225,54 +230,18 @@ void EditorManager::setupEditor(ScintillaNext *editor) editor->setEdgeColour(0x80FFFF); - // https://www.scintilla.org/ScintillaDoc.html#ElementColours - // SC_ELEMENT_SELECTION_TEXT - // SC_ELEMENT_SELECTION_BACK - // SC_ELEMENT_SELECTION_ADDITIONAL_TEXT - // SC_ELEMENT_SELECTION_ADDITIONAL_BACK - // SC_ELEMENT_SELECTION_SECONDARY_TEXT - // SC_ELEMENT_SELECTION_SECONDARY_BACK - // SC_ELEMENT_SELECTION_INACTIVE_TEXT - editor->setElementColour(SC_ELEMENT_SELECTION_INACTIVE_BACK, 0xFFE0E0E0); - // SC_ELEMENT_CARET - // SC_ELEMENT_CARET_ADDITIONAL - editor->setElementColour(SC_ELEMENT_CARET_LINE_BACK, 0xFFFFE8E8); - editor->setElementColour(SC_ELEMENT_WHITE_SPACE, 0xFFD0D0D0); - // SC_ELEMENT_WHITE_SPACE_BACK - // SC_ELEMENT_HOT_SPOT_ACTIVE - // SC_ELEMENT_HOT_SPOT_ACTIVE_BACK - editor->setElementColour(SC_ELEMENT_FOLD_LINE, 0xFFA0A0A0); - // SC_ELEMENT_HIDDEN_LINE - editor->setWhitespaceSize(2); - editor->setFoldMarginColour(true, 0xFFFFFF); - editor->setFoldMarginHiColour(true, 0xE9E9E9); - editor->setAutomaticFold(SC_AUTOMATICFOLD_SHOW | SC_AUTOMATICFOLD_CLICK | SC_AUTOMATICFOLD_CHANGE); editor->markerEnableHighlight(true); editor->setCharsDefault(); editor->setWordChars(editor->wordChars() + settings->additionalWordChars().toLatin1()); - editor->styleSetFore(STYLE_DEFAULT, 0x000000); - editor->styleSetBack(STYLE_DEFAULT, 0xFFFFFF); editor->styleSetSize(STYLE_DEFAULT, settings->fontSize()); editor->styleSetFont(STYLE_DEFAULT, settings->fontName().toUtf8().data()); - editor->styleClearAll(); - - editor->styleSetFore(STYLE_LINENUMBER, 0x808080); - editor->styleSetBack(STYLE_LINENUMBER, 0xE4E4E4); - editor->styleSetBold(STYLE_LINENUMBER, false); - editor->styleSetFore(STYLE_BRACELIGHT, 0x0000FF); - editor->styleSetBack(STYLE_BRACELIGHT, 0xFFFFFF); - - editor->styleSetFore(STYLE_BRACEBAD, 0x000080); - editor->styleSetBack(STYLE_BRACEBAD, 0xFFFFFF); - - editor->styleSetFore(STYLE_INDENTGUIDE, 0xC0C0C0); - editor->styleSetBack(STYLE_INDENTGUIDE, 0xFFFFFF); + applyEditorTheme(editor); // STYLE_CONTROLCHAR // STYLE_CALLTIP @@ -334,6 +303,46 @@ void EditorManager::setupEditor(ScintillaNext *editor) new HTMLAutoCompleteDecorator(editor); } +void EditorManager::applyEditorTheme(ScintillaNext *editor) +{ + const bool dark = settings->darkMode(); + + // Fold markers + for (int i = SC_MARKNUM_FOLDEREND; i <= SC_MARKNUM_FOLDEROPEN; ++i) { + editor->markerSetFore(i, dark ? 0x3C3C3C : 0xF3F3F3); + editor->markerSetBack(i, 0x808080); + editor->markerSetBackSelected(i, dark ? 0xCC7A00 : 0x0000FF); + } + + // Element colors (ARGB 0xAARRGGBB) + editor->setElementColour(SC_ELEMENT_SELECTION_INACTIVE_BACK, dark ? 0xFF3A3D41 : 0xFFE0E0E0); + editor->setElementColour(SC_ELEMENT_CARET_LINE_BACK, dark ? 0xFF2A2D2E : 0xFFFFE8E8); + editor->setElementColour(SC_ELEMENT_WHITE_SPACE, dark ? 0xFF505050 : 0xFFD0D0D0); + editor->setElementColour(SC_ELEMENT_FOLD_LINE, dark ? 0xFF505050 : 0xFFA0A0A0); + + // Fold margin + editor->setFoldMarginColour(true, dark ? 0x1E1E1E : 0xFFFFFF); + editor->setFoldMarginHiColour(true, dark ? 0x252526 : 0xE9E9E9); + + // STYLE_DEFAULT sets the base for styleClearAll() + editor->styleSetFore(STYLE_DEFAULT, dark ? 0xD4D4D4 : 0x000000); + editor->styleSetBack(STYLE_DEFAULT, dark ? 0x1E1E1E : 0xFFFFFF); + editor->styleClearAll(); + + editor->styleSetFore(STYLE_LINENUMBER, dark ? 0x858585 : 0x808080); + editor->styleSetBack(STYLE_LINENUMBER, dark ? 0x252526 : 0xE4E4E4); + editor->styleSetBold(STYLE_LINENUMBER, false); + + editor->styleSetFore(STYLE_BRACELIGHT, dark ? 0xD4D4D4 : 0x0000FF); + editor->styleSetBack(STYLE_BRACELIGHT, dark ? 0x1E1E1E : 0xFFFFFF); + + editor->styleSetFore(STYLE_BRACEBAD, dark ? 0x0000FF : 0x000080); + editor->styleSetBack(STYLE_BRACEBAD, dark ? 0x1E1E1E : 0xFFFFFF); + + editor->styleSetFore(STYLE_INDENTGUIDE, dark ? 0x404040 : 0xC0C0C0); + editor->styleSetBack(STYLE_INDENTGUIDE, dark ? 0x1E1E1E : 0xFFFFFF); +} + void EditorManager::purgeOldEditorPointers() { QMutableListIterator> it(editors); diff --git a/src/EditorManager.h b/src/EditorManager.h index 0aebb1ebb..fca0cc394 100644 --- a/src/EditorManager.h +++ b/src/EditorManager.h @@ -41,6 +41,10 @@ class EditorManager : public QObject void manageEditor(ScintillaNext *editor); + void applyEditorTheme(ScintillaNext *editor); + + QList> getEditors(); + signals: void editorCreated(ScintillaNext *editor); void editorClosed(ScintillaNext *editor); @@ -48,7 +52,6 @@ class EditorManager : public QObject private: void setupEditor(ScintillaNext *editor); void purgeOldEditorPointers(); - QList> getEditors(); int detectEOLMode(ScintillaNext *editor) const; QList> editors; diff --git a/src/NotepadNextApplication.cpp b/src/NotepadNextApplication.cpp index 3c4726d03..68badf7ad 100644 --- a/src/NotepadNextApplication.cpp +++ b/src/NotepadNextApplication.cpp @@ -149,9 +149,12 @@ bool NotepadNextApplication::init() MarkerAppDecorator *mad = new MarkerAppDecorator(this); mad->setEnabled(true); + luaState->setVariable("dark_mode", settings->darkMode()); luaState->executeFile(":/scripts/init.lua"); LuaExtension::Instance().Initialise(luaState->L, Q_NULLPTR); + connect(settings, &ApplicationSettings::darkModeChanged, this, &NotepadNextApplication::refreshEditorTheme); + createNewWindow(); connect(editorManager, &EditorManager::editorCreated, window, &MainWindow::addEditor); @@ -508,3 +511,14 @@ QStringList NotepadNextApplication::debugInfo() const return info; } + +void NotepadNextApplication::refreshEditorTheme() +{ + getLuaState()->setVariable("dark_mode", settings->darkMode()); + getLuaState()->execute("UpdateTheme()"); + + for (auto &editor : editorManager->getEditors()) { + if (!editor->languageName.isEmpty()) + setEditorLanguage(editor, editor->languageName); + } +} diff --git a/src/NotepadNextApplication.h b/src/NotepadNextApplication.h index b44af9673..130279a98 100644 --- a/src/NotepadNextApplication.h +++ b/src/NotepadNextApplication.h @@ -73,6 +73,9 @@ class NotepadNextApplication : public SingleApplication protected: bool event(QEvent *event) override; +public slots: + void refreshEditorTheme(); + private slots: void saveSettings(); void receiveInfoFromSecondaryInstance(quint32 instanceId, QByteArray message); diff --git a/src/dialogs/MainWindow.cpp b/src/dialogs/MainWindow.cpp index daf1d5b45..77496b6b0 100644 --- a/src/dialogs/MainWindow.cpp +++ b/src/dialogs/MainWindow.cpp @@ -42,6 +42,8 @@ #include #include #include +#include +#include #ifdef Q_OS_WIN @@ -882,7 +884,7 @@ MainWindow::MainWindow(NotepadNextApplication *app) : languageInspectorDock->hide(); addDockWidget(Qt::RightDockWidgetArea, languageInspectorDock); - LuaConsoleDock *luaConsoleDock = new LuaConsoleDock(app->getLuaState(), this); + LuaConsoleDock *luaConsoleDock = new LuaConsoleDock(app->getLuaState(), app->getSettings(), this); luaConsoleDock->hide(); addDockWidget(Qt::BottomDockWidgetArea, luaConsoleDock); @@ -914,6 +916,7 @@ MainWindow::MainWindow(NotepadNextApplication *app) : }); connect(app->getSettings(), &ApplicationSettings::showToolBarChanged, ui->mainToolBar, &QToolBar::setVisible); connect(app->getSettings(), &ApplicationSettings::showStatusBarChanged, ui->statusBar, &QStatusBar::setVisible); + connect(app->getSettings(), &ApplicationSettings::darkModeChanged, this, &MainWindow::applyStyleSheet); connect(ui->statusBar, &EditorInfoStatusBar::customContextMenuRequestedForEOLLabel, this, [=](const QPoint &pos){ ui->menuEOLConversion->popup(pos); }); @@ -1799,10 +1802,38 @@ void MainWindow::applyStyleSheet() { qInfo(Q_FUNC_INFO); + const bool dark = app->getSettings()->darkMode(); + + // Apply Fusion palette for all standard Qt widgets + QApplication::setStyle(QStyleFactory::create("Fusion")); + + if (dark) { + QPalette p; + p.setColor(QPalette::Window, QColor(0x1E, 0x1E, 0x1E)); + p.setColor(QPalette::WindowText, QColor(0xD4, 0xD4, 0xD4)); + p.setColor(QPalette::Base, QColor(0x25, 0x25, 0x26)); + p.setColor(QPalette::AlternateBase, QColor(0x2D, 0x2D, 0x2D)); + p.setColor(QPalette::Text, QColor(0xD4, 0xD4, 0xD4)); + p.setColor(QPalette::Button, QColor(0x3C, 0x3C, 0x3C)); + p.setColor(QPalette::ButtonText, QColor(0xD4, 0xD4, 0xD4)); + p.setColor(QPalette::Highlight, QColor(0x00, 0x7A, 0xCC)); + p.setColor(QPalette::HighlightedText, QColor(0xFF, 0xFF, 0xFF)); + p.setColor(QPalette::ToolTipBase, QColor(0x25, 0x25, 0x26)); + p.setColor(QPalette::ToolTipText, QColor(0xD4, 0xD4, 0xD4)); + p.setColor(QPalette::Link, QColor(0x40, 0xA0, 0xFF)); + p.setColor(QPalette::Disabled, QPalette::Text, QColor(0x66, 0x66, 0x66)); + p.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(0x66, 0x66, 0x66)); + QApplication::setPalette(p); + } else { + QApplication::setPalette(QApplication::style()->standardPalette()); + } + + // Targeted CSS for custom widgets (ADS dock tabs, QuickFindWidget, status bar) + const QString cssResource = dark ? QStringLiteral(":/stylesheets/npp-dark.css") + : QStringLiteral(":/stylesheets/npp.css"); QString sheet; - QFile f(":/stylesheets/npp.css"); + QFile f(cssResource); qInfo() << "Loading stylesheet:" << f.fileName(); - f.open(QFile::ReadOnly); sheet = f.readAll(); f.close(); diff --git a/src/dialogs/PreferencesDialog.cpp b/src/dialogs/PreferencesDialog.cpp index 54296f482..dad5e2034 100644 --- a/src/dialogs/PreferencesDialog.cpp +++ b/src/dialogs/PreferencesDialog.cpp @@ -44,6 +44,7 @@ PreferencesDialog::PreferencesDialog(ApplicationSettings *settings, QWidget *par MapSettingToCheckBox(ui->checkBoxMenuBar, &ApplicationSettings::showMenuBar, &ApplicationSettings::setShowMenuBar, &ApplicationSettings::showMenuBarChanged); MapSettingToCheckBox(ui->checkBoxToolBar, &ApplicationSettings::showToolBar, &ApplicationSettings::setShowToolBar, &ApplicationSettings::showToolBarChanged); MapSettingToCheckBox(ui->checkBoxStatusBar, &ApplicationSettings::showStatusBar, &ApplicationSettings::setShowStatusBar, &ApplicationSettings::showStatusBarChanged); + MapSettingToCheckBox(ui->checkBoxDarkMode, &ApplicationSettings::darkMode, &ApplicationSettings::setDarkMode, &ApplicationSettings::darkModeChanged); MapSettingToCheckBox(ui->checkBoxRecenterSearchDialog, &ApplicationSettings::centerSearchDialog, &ApplicationSettings::setCenterSearchDialog, &ApplicationSettings::centerSearchDialogChanged); MapSettingToGroupBox(ui->gbxRestorePreviousSession, &ApplicationSettings::restorePreviousSession, &ApplicationSettings::setRestorePreviousSession, &ApplicationSettings::restorePreviousSessionChanged); diff --git a/src/dialogs/PreferencesDialog.ui b/src/dialogs/PreferencesDialog.ui index 2d98c2230..f3f8debbe 100644 --- a/src/dialogs/PreferencesDialog.ui +++ b/src/dialogs/PreferencesDialog.ui @@ -52,6 +52,13 @@ + + + + Dark mode + + + diff --git a/src/docks/LuaConsoleDock.cpp b/src/docks/LuaConsoleDock.cpp index a9acefbcb..814b5ac7f 100644 --- a/src/docks/LuaConsoleDock.cpp +++ b/src/docks/LuaConsoleDock.cpp @@ -20,6 +20,7 @@ #include "LuaConsoleDock.h" #include "ui_LuaConsoleDock.h" +#include "ApplicationSettings.h" #include "ScintillaNext.h" #include "ILexer.h" #include "Lexilla.h" @@ -83,9 +84,10 @@ static int cf_global_print(lua_State *L) { } -LuaConsoleDock::LuaConsoleDock(LuaState *l, QWidget *parent) : +LuaConsoleDock::LuaConsoleDock(LuaState *l, ApplicationSettings *settings, QWidget *parent) : QDockWidget(parent), - ui(new Ui::LuaConsoleDock) + ui(new Ui::LuaConsoleDock), + settings(settings) { L = l; @@ -183,13 +185,19 @@ LuaConsoleDock::LuaConsoleDock(LuaState *l, QWidget *parent) : setupStyle(input); setupStyle(output); - output->styleSetFore(39, 0x0000FF); // For error messages + output->styleSetFore(39, settings->darkMode() ? 0xFF6B6B : 0x0000FF); // For error messages input->setExtraAscent(2); input->setExtraDescent(2); input->setMaximumHeight(input->textHeight(0)); input->installEventFilter(this); + connect(settings, &ApplicationSettings::darkModeChanged, this, [=](bool) { + setupStyle(input); + setupStyle(output); + output->styleSetFore(39, settings->darkMode() ? 0xFF6B6B : 0x0000FF); + }); + connect(input, &ScintillaNext::updateUi, [=](Scintilla::Update flags) { Q_UNUSED(flags); int curPos = input->currentPos(); @@ -377,10 +385,12 @@ bool LuaConsoleDock::eventFilter(QObject *obj, QEvent *event) void LuaConsoleDock::setupStyle(ScintillaNext *editor) { + const bool dark = settings->darkMode(); + editor->setEOLMode(SC_EOL_CRLF); - editor->styleSetFore(STYLE_DEFAULT, 0x000000); - editor->styleSetBack(STYLE_DEFAULT, 0xFFFFFF); + editor->styleSetFore(STYLE_DEFAULT, dark ? 0xD4D4D4 : 0x000000); + editor->styleSetBack(STYLE_DEFAULT, dark ? 0x1E1E1E : 0xFFFFFF); editor->styleSetFont(STYLE_DEFAULT, "Courier New"); editor->styleSetSize(STYLE_DEFAULT, 10); editor->styleClearAll(); @@ -392,29 +402,33 @@ void LuaConsoleDock::setupStyle(ScintillaNext *editor) editor->setMarginWidthN(4, 0); editor->setCodePage(SC_CP_UTF8); - editor->styleSetFore(SCE_LUA_COMMENT, 0x008000); - editor->styleSetFore(SCE_LUA_COMMENTLINE, 0x008000); - editor->styleSetFore(SCE_LUA_COMMENTDOC, 0x808000); - editor->styleSetFore(SCE_LUA_LITERALSTRING, 0x4A0095); - editor->styleSetFore(SCE_LUA_PREPROCESSOR, 0x004080); // Technically not used since this is lua 5+ - editor->styleSetFore(SCE_LUA_WORD, 0xFF0000); - editor->styleSetBold(SCE_LUA_WORD, 1); // for SCI_SETKEYWORDS, 0 - editor->styleSetFore(SCE_LUA_NUMBER, 0x0080FF); - editor->styleSetFore(SCE_LUA_STRING, 0x808080); - editor->styleSetFore(SCE_LUA_CHARACTER, 0x808080); - editor->styleSetFore(SCE_LUA_OPERATOR, 0x800000); + editor->styleSetFore(SCE_LUA_COMMENT, dark ? 0x6A9955 : 0x008000); + editor->styleSetFore(SCE_LUA_COMMENTLINE, dark ? 0x6A9955 : 0x008000); + editor->styleSetFore(SCE_LUA_COMMENTDOC, dark ? 0x808000 : 0x808000); + editor->styleSetFore(SCE_LUA_LITERALSTRING, dark ? 0xCE9178 : 0x4A0095); + editor->styleSetFore(SCE_LUA_PREPROCESSOR, dark ? 0xB5CEA8 : 0x004080); + editor->styleSetFore(SCE_LUA_WORD, dark ? 0xD7BA7D : 0xFF0000); + editor->styleSetBold(SCE_LUA_WORD, 1); + editor->styleSetFore(SCE_LUA_NUMBER, dark ? 0xB5CEA8 : 0x0080FF); + editor->styleSetFore(SCE_LUA_STRING, dark ? 0xCE9178 : 0x808080); + editor->styleSetFore(SCE_LUA_CHARACTER, dark ? 0xCE9178 : 0x808080); + editor->styleSetFore(SCE_LUA_OPERATOR, dark ? 0xD4D4D4 : 0x800000); editor->styleSetBold(SCE_LUA_OPERATOR, 1); - editor->styleSetFore(SCE_LUA_WORD2, 0xC08000); - editor->styleSetBold(SCE_LUA_WORD2, 1); // for SCI_SETKEYWORDS, 1 - editor->styleSetFore(SCE_LUA_WORD3, 0xFF0080); - editor->styleSetBold(SCE_LUA_WORD3, 1); // for SCI_SETKEYWORDS, 2 - editor->styleSetFore(SCE_LUA_WORD4, 0xA00000); + editor->styleSetFore(SCE_LUA_WORD2, dark ? 0xDCDCAA : 0xC08000); + editor->styleSetBold(SCE_LUA_WORD2, 1); + editor->styleSetFore(SCE_LUA_WORD3, dark ? 0xC586C0 : 0xFF0080); + editor->styleSetBold(SCE_LUA_WORD3, 1); + editor->styleSetFore(SCE_LUA_WORD4, dark ? 0x4EC9B0 : 0xA00000); editor->styleSetBold(SCE_LUA_WORD4, 1); - editor->styleSetItalic(SCE_LUA_WORD4, 1); // for SCI_SETKEYWORDS, 3 - editor->styleSetFore(SCE_LUA_LABEL, 0x008080); + editor->styleSetItalic(SCE_LUA_WORD4, 1); + editor->styleSetFore(SCE_LUA_LABEL, dark ? 0x4FC1FF : 0x008080); editor->styleSetBold(SCE_LUA_LABEL, 1); - editor->styleSetFore(SCE_LUA_WORD5, 0x004080); // for SCI_SETKEYWORDS, 4, Scintilla defines + editor->styleSetFore(SCE_LUA_WORD5, dark ? 0x9CDCFE : 0x004080); editor->styleSetBold(SCE_LUA_WORD5, 1); - editor->styleSetFore(SCE_LUA_WORD6, 0x004080); // for SCI_SETKEYWORDS, 5, Notepad++ defines + editor->styleSetFore(SCE_LUA_WORD6, dark ? 0x9CDCFE : 0x004080); editor->styleSetBold(SCE_LUA_WORD6, 1); + + editor->styleSetFore(STYLE_LINENUMBER, dark ? 0x858585 : 0x808080); + editor->styleSetBack(STYLE_LINENUMBER, dark ? 0x252526 : 0xE4E4E4); + editor->styleSetBold(STYLE_LINENUMBER, true); } diff --git a/src/docks/LuaConsoleDock.h b/src/docks/LuaConsoleDock.h index 2707c3fac..f41789af3 100644 --- a/src/docks/LuaConsoleDock.h +++ b/src/docks/LuaConsoleDock.h @@ -24,6 +24,7 @@ class ScintillaNext; class LuaState; +class ApplicationSettings; namespace Ui { class LuaConsoleDock; @@ -34,7 +35,7 @@ class LuaConsoleDock : public QDockWidget Q_OBJECT public: - explicit LuaConsoleDock(LuaState *l, QWidget *parent = 0); + explicit LuaConsoleDock(LuaState *l, ApplicationSettings *settings, QWidget *parent = 0); ~LuaConsoleDock(); void writeToOutput(const char *s); @@ -55,6 +56,7 @@ public slots: private: Ui::LuaConsoleDock *ui; + ApplicationSettings *settings; ScintillaNext *output; ScintillaNext *input; diff --git a/src/resources.qrc b/src/resources.qrc index d373be086..7043376d3 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -19,6 +19,7 @@ icons/wrap.png icons/find.png stylesheets/npp.css + stylesheets/npp-dark.css icons/findReplace.png icons/readonly.png icons/startRecord.png diff --git a/src/scripts/init.lua b/src/scripts/init.lua index a872688c3..ae670caee 100644 --- a/src/scripts/init.lua +++ b/src/scripts/init.lua @@ -2,6 +2,30 @@ function rgb(x) return ((x & 0xFF) << 16) | (x & 0xFF00) | ((x & 0xFF0000) >> 16) end +local STYLE_DEFAULT = 32 + +-- Build the active theme table from the dark_mode global injected by C++. +-- Also called by NotepadNextApplication::refreshEditorTheme() on toggle. +function UpdateTheme() + if dark_mode then + theme = { + default_fg = rgb(0xD4D4D4), + default_bg = rgb(0x1E1E1E), + light_fg = rgb(0x000000), + light_bg = rgb(0xFFFFFF), + } + else + theme = { + default_fg = rgb(0x000000), + default_bg = rgb(0xFFFFFF), + light_fg = rgb(0x000000), + light_bg = rgb(0xFFFFFF), + } + end +end + +UpdateTheme() + function DetectLanguageFromContents(contents) for name, L in pairs(languages) do if L.first_line then @@ -49,10 +73,23 @@ function DialogFilters() end function SetStyle(L) + -- Apply theme base: STYLE_DEFAULT sets the canvas for styleClearAll(). + -- This ensures every style slot starts with the correct background color + -- even for styles not explicitly listed in the language definition. + editor.StyleFore[STYLE_DEFAULT] = theme.default_fg + editor.StyleBack[STYLE_DEFAULT] = theme.default_bg + editor:StyleClearAll() + if L.styles then for _, style in pairs(L.styles) do - editor.StyleFore[style.id] = style.fgColor - editor.StyleBack[style.id] = style.bgColor + -- Translate canonical light-mode colors to theme equivalents. + -- Language files that specify black text on white background get + -- auto-converted; deliberate syntax colors (blue, green, etc.) + -- pass through unchanged since they are readable on dark backgrounds. + local fg = (style.fgColor == theme.light_fg) and theme.default_fg or style.fgColor + local bg = (style.bgColor == theme.light_bg) and theme.default_bg or style.bgColor + editor.StyleFore[style.id] = fg + editor.StyleBack[style.id] = bg if style.fontStyle then editor.StyleBold[style.id] = (style.fontStyle & 1 == 1) diff --git a/src/stylesheets/npp-dark.css b/src/stylesheets/npp-dark.css new file mode 100644 index 000000000..ec2936e08 --- /dev/null +++ b/src/stylesheets/npp-dark.css @@ -0,0 +1,77 @@ +QStatusBar { + border-top: 1px solid #3C3C3C; +} + +ads--CDockWidgetTab ads--CElidingLabel { + color: #D4D4D4; +} + +ads--CDockWidgetTab { + background: #2D2D2D; + margin-top: 2px; + border-left: 1px solid #3C3C3C; + border-top: 2px solid #3C3C3C; + border-right: 2px solid #252526; + padding: 2px 2px 0px 2px; + border-top-left-radius: 2px; + border-top-right-radius: 2px; +} + +ads--CDockWidgetTab[activeTab="true"] +{ + background: #1E1E1E; + border-top: 4px solid #007ACC; + padding-bottom: 2px; + margin-top: 1px; +} + +ads--CDockWidgetTab[focused="true"] +{ + background: #1E1E1E; + border-top: 4px solid #FFA500; + padding-bottom: 2px; + margin-top: 1px; +} + +ads--CDockWidgetTab:hover[activeTab="false"] { + background: #353535; +} + +ads--CDockAreaTitleBar +{ + background: transparent; + border-bottom: 1px solid #3C3C3C; + padding-bottom: 0px; +} + +ads--CDockWidgetTab[activeTab="false"] QLabel { + color: #858585; +} + +#tabCloseButton +{ + background: rgba(255, 255, 255, 16); + margin-bottom: 1px; + margin-left: 4px; + border: none; + padding: 0px -2px; +} + +#tabCloseButton:hover +{ + border: 1px solid rgba(255, 255, 255, 32); + background-color: #993333; + color: white; +} + +#tabCloseButton:pressed +{ + background: #CC3333; +} + +#QuickFindWidget { + border-left: 1px solid #3C3C3C; + border-right: 1px solid #3C3C3C; + border-bottom: 3px solid #007ACC; + background: palette(window); +} From f2a89525fe8331a3cbd50cbac339ca2064d756d2 Mon Sep 17 00:00:00 2001 From: Andrei Shevchenko Date: Mon, 4 May 2026 19:54:21 +0200 Subject: [PATCH 2/6] Preserve line number and brace match colors after language load Lua SetStyle() calls StyleClearAll on language load, which resets STYLE_LINENUMBER, STYLE_BRACELIGHT, STYLE_BRACEBAD, and STYLE_INDENTGUIDE to STYLE_DEFAULT values, wiping the colors set by applyEditorTheme. Split applyEditorTheme into two methods: the full theme (including STYLE_DEFAULT + StyleClearAll) and a separate applyEditorNamedStyles for the colors that need re-applying after each language load. NotepadNextApplication::setEditorLanguage calls applyEditorNamedStyles after the Lua SetLanguage call so line numbers and brace match retain their themed colors regardless of which language is active. --- src/EditorManager.cpp | 15 ++++++++++++--- src/EditorManager.h | 1 + src/NotepadNextApplication.cpp | 4 ++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/EditorManager.cpp b/src/EditorManager.cpp index 924ca8447..f3e7d8e60 100644 --- a/src/EditorManager.cpp +++ b/src/EditorManager.cpp @@ -307,20 +307,20 @@ void EditorManager::applyEditorTheme(ScintillaNext *editor) { const bool dark = settings->darkMode(); - // Fold markers + // Fold markers (not affected by styleClearAll) for (int i = SC_MARKNUM_FOLDEREND; i <= SC_MARKNUM_FOLDEROPEN; ++i) { editor->markerSetFore(i, dark ? 0x3C3C3C : 0xF3F3F3); editor->markerSetBack(i, 0x808080); editor->markerSetBackSelected(i, dark ? 0xCC7A00 : 0x0000FF); } - // Element colors (ARGB 0xAARRGGBB) + // Element colors (ARGB 0xAARRGGBB; not affected by styleClearAll) editor->setElementColour(SC_ELEMENT_SELECTION_INACTIVE_BACK, dark ? 0xFF3A3D41 : 0xFFE0E0E0); editor->setElementColour(SC_ELEMENT_CARET_LINE_BACK, dark ? 0xFF2A2D2E : 0xFFFFE8E8); editor->setElementColour(SC_ELEMENT_WHITE_SPACE, dark ? 0xFF505050 : 0xFFD0D0D0); editor->setElementColour(SC_ELEMENT_FOLD_LINE, dark ? 0xFF505050 : 0xFFA0A0A0); - // Fold margin + // Fold margin (not affected by styleClearAll) editor->setFoldMarginColour(true, dark ? 0x1E1E1E : 0xFFFFFF); editor->setFoldMarginHiColour(true, dark ? 0x252526 : 0xE9E9E9); @@ -329,6 +329,15 @@ void EditorManager::applyEditorTheme(ScintillaNext *editor) editor->styleSetBack(STYLE_DEFAULT, dark ? 0x1E1E1E : 0xFFFFFF); editor->styleClearAll(); + // Named styles must be re-applied after any subsequent styleClearAll + // (Lua SetStyle does one when a language is loaded) + applyEditorNamedStyles(editor); +} + +void EditorManager::applyEditorNamedStyles(ScintillaNext *editor) +{ + const bool dark = settings->darkMode(); + editor->styleSetFore(STYLE_LINENUMBER, dark ? 0x858585 : 0x808080); editor->styleSetBack(STYLE_LINENUMBER, dark ? 0x252526 : 0xE4E4E4); editor->styleSetBold(STYLE_LINENUMBER, false); diff --git a/src/EditorManager.h b/src/EditorManager.h index fca0cc394..92e30279a 100644 --- a/src/EditorManager.h +++ b/src/EditorManager.h @@ -42,6 +42,7 @@ class EditorManager : public QObject void manageEditor(ScintillaNext *editor); void applyEditorTheme(ScintillaNext *editor); + void applyEditorNamedStyles(ScintillaNext *editor); QList> getEditors(); diff --git a/src/NotepadNextApplication.cpp b/src/NotepadNextApplication.cpp index 68badf7ad..bb5fb7792 100644 --- a/src/NotepadNextApplication.cpp +++ b/src/NotepadNextApplication.cpp @@ -298,6 +298,10 @@ void NotepadNextApplication::setEditorLanguage(ScintillaNext *editor, const QStr getLuaState()->setVariable("skip_tabwidth", skipTabWidth); getLuaState()->execute("SetLanguage(languageName)"); + + // Restore line number, brace match, and indent guide colors that the + // Lua-side styleClearAll wiped. + editorManager->applyEditorNamedStyles(editor); } QString NotepadNextApplication::detectLanguage(ScintillaNext *editor) const From cfb31e6e5e8be6e0afb7c9e443cfbac4bf1cb8a8 Mon Sep 17 00:00:00 2001 From: Andrei Shevchenko Date: Mon, 4 May 2026 20:14:23 +0200 Subject: [PATCH 3/6] Set explicit light Fusion palette in light mode Qt 6.5+ has Fusion::standardPalette() honor QStyleHints::colorScheme(), so on systems where the user has selected dark mode at the OS level (GNOME, KDE, etc.) Fusion::standardPalette() returns dark colors. Setting that as the application palette in light mode left menu bar, toolbar, and dock tabs dark while only the editor and stylesheet-targeted widgets switched to light. Define an explicit light palette parallel to the existing dark one so the palette is fully driven by the user-selected DarkMode setting and ignores the system color scheme. Verified visually: menu bar, toolbar, and ADS tabs render light when DarkMode=false and dark when DarkMode=true, regardless of the GNOME theme. --- src/dialogs/MainWindow.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/dialogs/MainWindow.cpp b/src/dialogs/MainWindow.cpp index 77496b6b0..611a3bb22 100644 --- a/src/dialogs/MainWindow.cpp +++ b/src/dialogs/MainWindow.cpp @@ -1807,8 +1807,8 @@ void MainWindow::applyStyleSheet() // Apply Fusion palette for all standard Qt widgets QApplication::setStyle(QStyleFactory::create("Fusion")); + QPalette p; if (dark) { - QPalette p; p.setColor(QPalette::Window, QColor(0x1E, 0x1E, 0x1E)); p.setColor(QPalette::WindowText, QColor(0xD4, 0xD4, 0xD4)); p.setColor(QPalette::Base, QColor(0x25, 0x25, 0x26)); @@ -1823,10 +1823,24 @@ void MainWindow::applyStyleSheet() p.setColor(QPalette::Link, QColor(0x40, 0xA0, 0xFF)); p.setColor(QPalette::Disabled, QPalette::Text, QColor(0x66, 0x66, 0x66)); p.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(0x66, 0x66, 0x66)); - QApplication::setPalette(p); } else { - QApplication::setPalette(QApplication::style()->standardPalette()); + // Explicit light palette so Qt does not pick up the system dark scheme + p.setColor(QPalette::Window, QColor(0xF0, 0xF0, 0xF0)); + p.setColor(QPalette::WindowText, QColor(0x00, 0x00, 0x00)); + p.setColor(QPalette::Base, QColor(0xFF, 0xFF, 0xFF)); + p.setColor(QPalette::AlternateBase, QColor(0xF7, 0xF7, 0xF7)); + p.setColor(QPalette::Text, QColor(0x00, 0x00, 0x00)); + p.setColor(QPalette::Button, QColor(0xE6, 0xE6, 0xE6)); + p.setColor(QPalette::ButtonText, QColor(0x00, 0x00, 0x00)); + p.setColor(QPalette::Highlight, QColor(0x33, 0x99, 0xFF)); + p.setColor(QPalette::HighlightedText, QColor(0xFF, 0xFF, 0xFF)); + p.setColor(QPalette::ToolTipBase, QColor(0xFF, 0xFF, 0xDC)); + p.setColor(QPalette::ToolTipText, QColor(0x00, 0x00, 0x00)); + p.setColor(QPalette::Link, QColor(0x00, 0x00, 0xEE)); + p.setColor(QPalette::Disabled, QPalette::Text, QColor(0x99, 0x99, 0x99)); + p.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(0x99, 0x99, 0x99)); } + QApplication::setPalette(p); // Targeted CSS for custom widgets (ADS dock tabs, QuickFindWidget, status bar) const QString cssResource = dark ? QStringLiteral(":/stylesheets/npp-dark.css") From a7d999e0e805c7a445ee9042555274b559aeebce Mon Sep 17 00:00:00 2001 From: Andrei Shevchenko Date: Mon, 4 May 2026 20:28:48 +0200 Subject: [PATCH 4/6] Make theme tristate: System / Light / Dark Replace the DarkMode boolean with a Theme enum (System / Light / Dark, default System). System mode reads QGuiApplication::styleHints()->colorScheme() and listens for colorSchemeChanged so the app follows the desktop environment's light/dark preference, including live changes (e.g. GNOME night-mode auto-switching at sunset). Light and Dark remain explicit overrides for users who want their editor independent of the desktop theme. ApplicationSettings exposes a single effectiveDarkMode() helper plus an effectiveDarkModeChanged(bool) signal that fires on either theme change or system color-scheme change while in System mode. All consumers (EditorManager, NotepadNextApplication, MainWindow, LuaConsoleDock) call effectiveDarkMode() and connect to that signal, so the resolution-to-dark/light logic stays in one place. Preferences gets a "Theme" combo box with three options replacing the previous checkbox. --- src/ApplicationSettings.cpp | 25 ++++++++++++++++- src/ApplicationSettings.h | 13 ++++++++- src/EditorManager.cpp | 6 ++-- src/NotepadNextApplication.cpp | 6 ++-- src/dialogs/MainWindow.cpp | 4 +-- src/dialogs/PreferencesDialog.cpp | 8 +++++- src/dialogs/PreferencesDialog.ui | 46 +++++++++++++++++++++++++++---- src/docks/LuaConsoleDock.cpp | 8 +++--- 8 files changed, 96 insertions(+), 20 deletions(-) diff --git a/src/ApplicationSettings.cpp b/src/ApplicationSettings.cpp index 1d01bcb74..ef4e7aa11 100644 --- a/src/ApplicationSettings.cpp +++ b/src/ApplicationSettings.cpp @@ -20,6 +20,7 @@ #include #include +#include #define CREATE_SETTING(group, name, lname, type, default) \ ApplicationSetting name{#group "/" #name, default};\ @@ -38,6 +39,28 @@ ApplicationSetting name{#group "/" #name, default};\ ApplicationSettings::ApplicationSettings(QObject *parent) : QSettings{parent} { + // Translate user-facing theme changes and OS-level color-scheme changes + // into a single effectiveDarkModeChanged signal that consumers listen for. + connect(this, &ApplicationSettings::themeChanged, this, [this](ThemeMode) { + emit effectiveDarkModeChanged(effectiveDarkMode()); + }); + if (auto *hints = QGuiApplication::styleHints()) { + connect(hints, &QStyleHints::colorSchemeChanged, this, [this](Qt::ColorScheme) { + if (theme() == SystemTheme) + emit effectiveDarkModeChanged(effectiveDarkMode()); + }); + } +} + +bool ApplicationSettings::effectiveDarkMode() const +{ + switch (theme()) { + case LightTheme: return false; + case DarkTheme: return true; + case SystemTheme: + default: + return QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark; + } } CREATE_SETTING(Gui, ShowMenuBar, showMenuBar, bool, true) @@ -72,4 +95,4 @@ CREATE_SETTING(Editor, DefaultEOLMode, defaultEOLMode, QString, QStringLiteral(" CREATE_SETTING(Editor, URLHighlighting, urlHighlighting, bool, true) CREATE_SETTING(Editor, ShowLineNumbers, showLineNumbers, bool, true) -CREATE_SETTING(Gui, DarkMode, darkMode, bool, false) +CREATE_SETTING(Gui, Theme, theme, ApplicationSettings::ThemeMode, ApplicationSettings::SystemTheme) diff --git a/src/ApplicationSettings.h b/src/ApplicationSettings.h index b2e3e5739..e0ed29dc7 100644 --- a/src/ApplicationSettings.h +++ b/src/ApplicationSettings.h @@ -76,6 +76,17 @@ class ApplicationSettings : public QSettings }; Q_ENUM(DefaultDirectoryBehaviorEnum) + enum ThemeMode { + SystemTheme, + LightTheme, + DarkTheme + }; + Q_ENUM(ThemeMode) + + // Resolves Theme=System against the current QStyleHints color scheme. + bool effectiveDarkMode() const; + Q_SIGNAL void effectiveDarkModeChanged(bool dark); + template T get(const char *key, const T &defaultValue) const { return value(QLatin1String(key), defaultValue).template value(); } @@ -120,5 +131,5 @@ class ApplicationSettings : public QSettings DEFINE_SETTING(URLHighlighting, urlHighlighting, bool) DEFINE_SETTING(ShowLineNumbers, showLineNumbers, bool) - DEFINE_SETTING(DarkMode, darkMode, bool) + DEFINE_SETTING(Theme, theme, ThemeMode) }; diff --git a/src/EditorManager.cpp b/src/EditorManager.cpp index f3e7d8e60..3361ad1c4 100644 --- a/src/EditorManager.cpp +++ b/src/EditorManager.cpp @@ -129,7 +129,7 @@ EditorManager::EditorManager(ApplicationSettings *settings, QObject *parent) } }); - connect(settings, &ApplicationSettings::darkModeChanged, this, [=](bool) { + connect(settings, &ApplicationSettings::effectiveDarkModeChanged, this, [=](bool) { for (auto &editor : getEditors()) applyEditorTheme(editor); }); @@ -305,7 +305,7 @@ void EditorManager::setupEditor(ScintillaNext *editor) void EditorManager::applyEditorTheme(ScintillaNext *editor) { - const bool dark = settings->darkMode(); + const bool dark = settings->effectiveDarkMode(); // Fold markers (not affected by styleClearAll) for (int i = SC_MARKNUM_FOLDEREND; i <= SC_MARKNUM_FOLDEROPEN; ++i) { @@ -336,7 +336,7 @@ void EditorManager::applyEditorTheme(ScintillaNext *editor) void EditorManager::applyEditorNamedStyles(ScintillaNext *editor) { - const bool dark = settings->darkMode(); + const bool dark = settings->effectiveDarkMode(); editor->styleSetFore(STYLE_LINENUMBER, dark ? 0x858585 : 0x808080); editor->styleSetBack(STYLE_LINENUMBER, dark ? 0x252526 : 0xE4E4E4); diff --git a/src/NotepadNextApplication.cpp b/src/NotepadNextApplication.cpp index bb5fb7792..32d6e5d55 100644 --- a/src/NotepadNextApplication.cpp +++ b/src/NotepadNextApplication.cpp @@ -149,11 +149,11 @@ bool NotepadNextApplication::init() MarkerAppDecorator *mad = new MarkerAppDecorator(this); mad->setEnabled(true); - luaState->setVariable("dark_mode", settings->darkMode()); + luaState->setVariable("dark_mode", settings->effectiveDarkMode()); luaState->executeFile(":/scripts/init.lua"); LuaExtension::Instance().Initialise(luaState->L, Q_NULLPTR); - connect(settings, &ApplicationSettings::darkModeChanged, this, &NotepadNextApplication::refreshEditorTheme); + connect(settings, &ApplicationSettings::effectiveDarkModeChanged, this, &NotepadNextApplication::refreshEditorTheme); createNewWindow(); connect(editorManager, &EditorManager::editorCreated, window, &MainWindow::addEditor); @@ -518,7 +518,7 @@ QStringList NotepadNextApplication::debugInfo() const void NotepadNextApplication::refreshEditorTheme() { - getLuaState()->setVariable("dark_mode", settings->darkMode()); + getLuaState()->setVariable("dark_mode", settings->effectiveDarkMode()); getLuaState()->execute("UpdateTheme()"); for (auto &editor : editorManager->getEditors()) { diff --git a/src/dialogs/MainWindow.cpp b/src/dialogs/MainWindow.cpp index 611a3bb22..99db3cad3 100644 --- a/src/dialogs/MainWindow.cpp +++ b/src/dialogs/MainWindow.cpp @@ -916,7 +916,7 @@ MainWindow::MainWindow(NotepadNextApplication *app) : }); connect(app->getSettings(), &ApplicationSettings::showToolBarChanged, ui->mainToolBar, &QToolBar::setVisible); connect(app->getSettings(), &ApplicationSettings::showStatusBarChanged, ui->statusBar, &QStatusBar::setVisible); - connect(app->getSettings(), &ApplicationSettings::darkModeChanged, this, &MainWindow::applyStyleSheet); + connect(app->getSettings(), &ApplicationSettings::effectiveDarkModeChanged, this, &MainWindow::applyStyleSheet); connect(ui->statusBar, &EditorInfoStatusBar::customContextMenuRequestedForEOLLabel, this, [=](const QPoint &pos){ ui->menuEOLConversion->popup(pos); }); @@ -1802,7 +1802,7 @@ void MainWindow::applyStyleSheet() { qInfo(Q_FUNC_INFO); - const bool dark = app->getSettings()->darkMode(); + const bool dark = app->getSettings()->effectiveDarkMode(); // Apply Fusion palette for all standard Qt widgets QApplication::setStyle(QStyleFactory::create("Fusion")); diff --git a/src/dialogs/PreferencesDialog.cpp b/src/dialogs/PreferencesDialog.cpp index dad5e2034..0b14e2a15 100644 --- a/src/dialogs/PreferencesDialog.cpp +++ b/src/dialogs/PreferencesDialog.cpp @@ -44,7 +44,13 @@ PreferencesDialog::PreferencesDialog(ApplicationSettings *settings, QWidget *par MapSettingToCheckBox(ui->checkBoxMenuBar, &ApplicationSettings::showMenuBar, &ApplicationSettings::setShowMenuBar, &ApplicationSettings::showMenuBarChanged); MapSettingToCheckBox(ui->checkBoxToolBar, &ApplicationSettings::showToolBar, &ApplicationSettings::setShowToolBar, &ApplicationSettings::showToolBarChanged); MapSettingToCheckBox(ui->checkBoxStatusBar, &ApplicationSettings::showStatusBar, &ApplicationSettings::setShowStatusBar, &ApplicationSettings::showStatusBarChanged); - MapSettingToCheckBox(ui->checkBoxDarkMode, &ApplicationSettings::darkMode, &ApplicationSettings::setDarkMode, &ApplicationSettings::darkModeChanged); + ui->comboBoxTheme->setCurrentIndex(static_cast(settings->theme())); + connect(ui->comboBoxTheme, QOverload::of(&QComboBox::currentIndexChanged), this, [=](int index) { + settings->setTheme(static_cast(index)); + }); + connect(settings, &ApplicationSettings::themeChanged, this, [=](ApplicationSettings::ThemeMode mode) { + ui->comboBoxTheme->setCurrentIndex(static_cast(mode)); + }); MapSettingToCheckBox(ui->checkBoxRecenterSearchDialog, &ApplicationSettings::centerSearchDialog, &ApplicationSettings::setCenterSearchDialog, &ApplicationSettings::centerSearchDialogChanged); MapSettingToGroupBox(ui->gbxRestorePreviousSession, &ApplicationSettings::restorePreviousSession, &ApplicationSettings::setRestorePreviousSession, &ApplicationSettings::restorePreviousSessionChanged); diff --git a/src/dialogs/PreferencesDialog.ui b/src/dialogs/PreferencesDialog.ui index f3f8debbe..fb692c4c7 100644 --- a/src/dialogs/PreferencesDialog.ui +++ b/src/dialogs/PreferencesDialog.ui @@ -53,11 +53,47 @@ - - - Dark mode - - + + + + + Theme: + + + + + + + + Follow system + + + + + Light + + + + + Dark + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + diff --git a/src/docks/LuaConsoleDock.cpp b/src/docks/LuaConsoleDock.cpp index 814b5ac7f..caf3bfd00 100644 --- a/src/docks/LuaConsoleDock.cpp +++ b/src/docks/LuaConsoleDock.cpp @@ -185,17 +185,17 @@ LuaConsoleDock::LuaConsoleDock(LuaState *l, ApplicationSettings *settings, QWidg setupStyle(input); setupStyle(output); - output->styleSetFore(39, settings->darkMode() ? 0xFF6B6B : 0x0000FF); // For error messages + output->styleSetFore(39, settings->effectiveDarkMode() ? 0xFF6B6B : 0x0000FF); // For error messages input->setExtraAscent(2); input->setExtraDescent(2); input->setMaximumHeight(input->textHeight(0)); input->installEventFilter(this); - connect(settings, &ApplicationSettings::darkModeChanged, this, [=](bool) { + connect(settings, &ApplicationSettings::effectiveDarkModeChanged, this, [=](bool) { setupStyle(input); setupStyle(output); - output->styleSetFore(39, settings->darkMode() ? 0xFF6B6B : 0x0000FF); + output->styleSetFore(39, settings->effectiveDarkMode() ? 0xFF6B6B : 0x0000FF); }); connect(input, &ScintillaNext::updateUi, [=](Scintilla::Update flags) { @@ -385,7 +385,7 @@ bool LuaConsoleDock::eventFilter(QObject *obj, QEvent *event) void LuaConsoleDock::setupStyle(ScintillaNext *editor) { - const bool dark = settings->darkMode(); + const bool dark = settings->effectiveDarkMode(); editor->setEOLMode(SC_EOL_CRLF); From 8691213804059e69f721abb5c5c1f7d409f3ff9d Mon Sep 17 00:00:00 2001 From: Andrei Shevchenko Date: Mon, 4 May 2026 20:31:38 +0200 Subject: [PATCH 5/6] Use Qt's standard palette for system theme In System theme mode, applyStyleSheet now applies QApplication::style()->standardPalette() instead of our hardcoded Fusion palette. Qt 6.5+ Fusion already adapts standardPalette to QStyleHints::colorScheme(), so this lets the Qt widgets follow the desktop environment's palette rather than imposing our own interpretation of "dark" or "light". Light and Dark explicit modes still use the hardcoded palettes since the user has chosen to override the system, and Qt's standardPalette would leak through the system color scheme there. Scintilla editor surfaces still resolve through effectiveDarkMode() since they cannot read the Qt palette directly. --- src/dialogs/MainWindow.cpp | 77 +++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/src/dialogs/MainWindow.cpp b/src/dialogs/MainWindow.cpp index 99db3cad3..1277dde3e 100644 --- a/src/dialogs/MainWindow.cpp +++ b/src/dialogs/MainWindow.cpp @@ -1802,45 +1802,52 @@ void MainWindow::applyStyleSheet() { qInfo(Q_FUNC_INFO); - const bool dark = app->getSettings()->effectiveDarkMode(); + auto *settings = app->getSettings(); + const bool dark = settings->effectiveDarkMode(); - // Apply Fusion palette for all standard Qt widgets QApplication::setStyle(QStyleFactory::create("Fusion")); - QPalette p; - if (dark) { - p.setColor(QPalette::Window, QColor(0x1E, 0x1E, 0x1E)); - p.setColor(QPalette::WindowText, QColor(0xD4, 0xD4, 0xD4)); - p.setColor(QPalette::Base, QColor(0x25, 0x25, 0x26)); - p.setColor(QPalette::AlternateBase, QColor(0x2D, 0x2D, 0x2D)); - p.setColor(QPalette::Text, QColor(0xD4, 0xD4, 0xD4)); - p.setColor(QPalette::Button, QColor(0x3C, 0x3C, 0x3C)); - p.setColor(QPalette::ButtonText, QColor(0xD4, 0xD4, 0xD4)); - p.setColor(QPalette::Highlight, QColor(0x00, 0x7A, 0xCC)); - p.setColor(QPalette::HighlightedText, QColor(0xFF, 0xFF, 0xFF)); - p.setColor(QPalette::ToolTipBase, QColor(0x25, 0x25, 0x26)); - p.setColor(QPalette::ToolTipText, QColor(0xD4, 0xD4, 0xD4)); - p.setColor(QPalette::Link, QColor(0x40, 0xA0, 0xFF)); - p.setColor(QPalette::Disabled, QPalette::Text, QColor(0x66, 0x66, 0x66)); - p.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(0x66, 0x66, 0x66)); + if (settings->theme() == ApplicationSettings::SystemTheme) { + // Follow the desktop environment: Fusion's standardPalette() in Qt 6.5+ + // adapts to QStyleHints::colorScheme(). + QApplication::setPalette(QApplication::style()->standardPalette()); } else { - // Explicit light palette so Qt does not pick up the system dark scheme - p.setColor(QPalette::Window, QColor(0xF0, 0xF0, 0xF0)); - p.setColor(QPalette::WindowText, QColor(0x00, 0x00, 0x00)); - p.setColor(QPalette::Base, QColor(0xFF, 0xFF, 0xFF)); - p.setColor(QPalette::AlternateBase, QColor(0xF7, 0xF7, 0xF7)); - p.setColor(QPalette::Text, QColor(0x00, 0x00, 0x00)); - p.setColor(QPalette::Button, QColor(0xE6, 0xE6, 0xE6)); - p.setColor(QPalette::ButtonText, QColor(0x00, 0x00, 0x00)); - p.setColor(QPalette::Highlight, QColor(0x33, 0x99, 0xFF)); - p.setColor(QPalette::HighlightedText, QColor(0xFF, 0xFF, 0xFF)); - p.setColor(QPalette::ToolTipBase, QColor(0xFF, 0xFF, 0xDC)); - p.setColor(QPalette::ToolTipText, QColor(0x00, 0x00, 0x00)); - p.setColor(QPalette::Link, QColor(0x00, 0x00, 0xEE)); - p.setColor(QPalette::Disabled, QPalette::Text, QColor(0x99, 0x99, 0x99)); - p.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(0x99, 0x99, 0x99)); - } - QApplication::setPalette(p); + // Explicit Light or Dark override - hardcoded palettes so the app + // theme is independent of the system color scheme. + QPalette p; + if (dark) { + p.setColor(QPalette::Window, QColor(0x1E, 0x1E, 0x1E)); + p.setColor(QPalette::WindowText, QColor(0xD4, 0xD4, 0xD4)); + p.setColor(QPalette::Base, QColor(0x25, 0x25, 0x26)); + p.setColor(QPalette::AlternateBase, QColor(0x2D, 0x2D, 0x2D)); + p.setColor(QPalette::Text, QColor(0xD4, 0xD4, 0xD4)); + p.setColor(QPalette::Button, QColor(0x3C, 0x3C, 0x3C)); + p.setColor(QPalette::ButtonText, QColor(0xD4, 0xD4, 0xD4)); + p.setColor(QPalette::Highlight, QColor(0x00, 0x7A, 0xCC)); + p.setColor(QPalette::HighlightedText, QColor(0xFF, 0xFF, 0xFF)); + p.setColor(QPalette::ToolTipBase, QColor(0x25, 0x25, 0x26)); + p.setColor(QPalette::ToolTipText, QColor(0xD4, 0xD4, 0xD4)); + p.setColor(QPalette::Link, QColor(0x40, 0xA0, 0xFF)); + p.setColor(QPalette::Disabled, QPalette::Text, QColor(0x66, 0x66, 0x66)); + p.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(0x66, 0x66, 0x66)); + } else { + p.setColor(QPalette::Window, QColor(0xF0, 0xF0, 0xF0)); + p.setColor(QPalette::WindowText, QColor(0x00, 0x00, 0x00)); + p.setColor(QPalette::Base, QColor(0xFF, 0xFF, 0xFF)); + p.setColor(QPalette::AlternateBase, QColor(0xF7, 0xF7, 0xF7)); + p.setColor(QPalette::Text, QColor(0x00, 0x00, 0x00)); + p.setColor(QPalette::Button, QColor(0xE6, 0xE6, 0xE6)); + p.setColor(QPalette::ButtonText, QColor(0x00, 0x00, 0x00)); + p.setColor(QPalette::Highlight, QColor(0x33, 0x99, 0xFF)); + p.setColor(QPalette::HighlightedText, QColor(0xFF, 0xFF, 0xFF)); + p.setColor(QPalette::ToolTipBase, QColor(0xFF, 0xFF, 0xDC)); + p.setColor(QPalette::ToolTipText, QColor(0x00, 0x00, 0x00)); + p.setColor(QPalette::Link, QColor(0x00, 0x00, 0xEE)); + p.setColor(QPalette::Disabled, QPalette::Text, QColor(0x99, 0x99, 0x99)); + p.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(0x99, 0x99, 0x99)); + } + QApplication::setPalette(p); + } // Targeted CSS for custom widgets (ADS dock tabs, QuickFindWidget, status bar) const QString cssResource = dark ? QStringLiteral(":/stylesheets/npp-dark.css") From 67646c0904aa0727b5d9b790e0bf7fb18c56e00a Mon Sep 17 00:00:00 2001 From: Andrei Shevchenko Date: Tue, 5 May 2026 01:40:28 +0200 Subject: [PATCH 6/6] Don't override Qt style or palette in system theme mode Stop forcing QApplication::setStyle(Fusion) globally and skip QApplication::setPalette() entirely when the user has selected System theme. Qt 6.5+ already syncs the palette with QStyleHints::colorScheme() and the platform integration picks the right style on its own; re-applying both on every applyStyleSheet call only re-emits paletteChange events and overrides legitimate platform choices. Light and Dark explicit modes still apply their hardcoded Fusion-derived palettes since the user has chosen to override the system there. --- src/dialogs/MainWindow.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/dialogs/MainWindow.cpp b/src/dialogs/MainWindow.cpp index 1277dde3e..84cf9b9f5 100644 --- a/src/dialogs/MainWindow.cpp +++ b/src/dialogs/MainWindow.cpp @@ -43,7 +43,6 @@ #include #include #include -#include #ifdef Q_OS_WIN @@ -1805,14 +1804,11 @@ void MainWindow::applyStyleSheet() auto *settings = app->getSettings(); const bool dark = settings->effectiveDarkMode(); - QApplication::setStyle(QStyleFactory::create("Fusion")); - if (settings->theme() == ApplicationSettings::SystemTheme) { - // Follow the desktop environment: Fusion's standardPalette() in Qt 6.5+ - // adapts to QStyleHints::colorScheme(). - QApplication::setPalette(QApplication::style()->standardPalette()); + // Follow the desktop environment - leave the Qt-default palette alone. + // Qt 6.5+ already syncs the palette with QStyleHints::colorScheme(). } else { - // Explicit Light or Dark override - hardcoded palettes so the app + // Explicit Light or Dark override - hardcoded palette so the app // theme is independent of the system color scheme. QPalette p; if (dark) {