diff --git a/src/ApplicationSettings.cpp b/src/ApplicationSettings.cpp index 17def0ca6..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) @@ -71,3 +94,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, Theme, theme, ApplicationSettings::ThemeMode, ApplicationSettings::SystemTheme) diff --git a/src/ApplicationSettings.h b/src/ApplicationSettings.h index cd4089801..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(); } @@ -119,4 +130,6 @@ class ApplicationSettings : public QSettings DEFINE_SETTING(DefaultEOLMode, defaultEOLMode, QString) DEFINE_SETTING(URLHighlighting, urlHighlighting, bool) DEFINE_SETTING(ShowLineNumbers, showLineNumbers, bool) + + DEFINE_SETTING(Theme, theme, ThemeMode) }; diff --git a/src/EditorManager.cpp b/src/EditorManager.cpp index e60f2911f..3361ad1c4 100644 --- a/src/EditorManager.cpp +++ b/src/EditorManager.cpp @@ -128,6 +128,11 @@ EditorManager::EditorManager(ApplicationSettings *settings, QObject *parent) } } }); + + connect(settings, &ApplicationSettings::effectiveDarkModeChanged, 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,55 @@ void EditorManager::setupEditor(ScintillaNext *editor) new HTMLAutoCompleteDecorator(editor); } +void EditorManager::applyEditorTheme(ScintillaNext *editor) +{ + const bool dark = settings->effectiveDarkMode(); + + // 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; 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 (not affected by styleClearAll) + 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(); + + // 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->effectiveDarkMode(); + + 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..92e30279a 100644 --- a/src/EditorManager.h +++ b/src/EditorManager.h @@ -41,6 +41,11 @@ class EditorManager : public QObject void manageEditor(ScintillaNext *editor); + void applyEditorTheme(ScintillaNext *editor); + void applyEditorNamedStyles(ScintillaNext *editor); + + QList> getEditors(); + signals: void editorCreated(ScintillaNext *editor); void editorClosed(ScintillaNext *editor); @@ -48,7 +53,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..32d6e5d55 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->effectiveDarkMode()); luaState->executeFile(":/scripts/init.lua"); LuaExtension::Instance().Initialise(luaState->L, Q_NULLPTR); + connect(settings, &ApplicationSettings::effectiveDarkModeChanged, this, &NotepadNextApplication::refreshEditorTheme); + createNewWindow(); connect(editorManager, &EditorManager::editorCreated, window, &MainWindow::addEditor); @@ -295,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 @@ -508,3 +515,14 @@ QStringList NotepadNextApplication::debugInfo() const return info; } + +void NotepadNextApplication::refreshEditorTheme() +{ + getLuaState()->setVariable("dark_mode", settings->effectiveDarkMode()); + 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..84cf9b9f5 100644 --- a/src/dialogs/MainWindow.cpp +++ b/src/dialogs/MainWindow.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #ifdef Q_OS_WIN @@ -882,7 +883,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 +915,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::effectiveDarkModeChanged, this, &MainWindow::applyStyleSheet); connect(ui->statusBar, &EditorInfoStatusBar::customContextMenuRequestedForEOLLabel, this, [=](const QPoint &pos){ ui->menuEOLConversion->popup(pos); }); @@ -1799,10 +1801,56 @@ void MainWindow::applyStyleSheet() { qInfo(Q_FUNC_INFO); + auto *settings = app->getSettings(); + const bool dark = settings->effectiveDarkMode(); + + if (settings->theme() == ApplicationSettings::SystemTheme) { + // 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 palette 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") + : 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..0b14e2a15 100644 --- a/src/dialogs/PreferencesDialog.cpp +++ b/src/dialogs/PreferencesDialog.cpp @@ -44,6 +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); + 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 2d98c2230..fb692c4c7 100644 --- a/src/dialogs/PreferencesDialog.ui +++ b/src/dialogs/PreferencesDialog.ui @@ -52,6 +52,49 @@ + + + + + + Theme: + + + + + + + + Follow system + + + + + Light + + + + + Dark + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + diff --git a/src/docks/LuaConsoleDock.cpp b/src/docks/LuaConsoleDock.cpp index a9acefbcb..caf3bfd00 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->effectiveDarkMode() ? 0xFF6B6B : 0x0000FF); // For error messages input->setExtraAscent(2); input->setExtraDescent(2); input->setMaximumHeight(input->textHeight(0)); input->installEventFilter(this); + connect(settings, &ApplicationSettings::effectiveDarkModeChanged, this, [=](bool) { + setupStyle(input); + setupStyle(output); + output->styleSetFore(39, settings->effectiveDarkMode() ? 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->effectiveDarkMode(); + 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); +}