From 40f348195bd50ac313949717f1be7397573fe34e Mon Sep 17 00:00:00 2001 From: dayvster Date: Sat, 14 Feb 2026 12:16:22 +0100 Subject: [PATCH 1/4] feat(prefs): add per-app preferences (pin/hide) storage --- CMakeLists.txt | 3 +++ src/core/preferences.cpp | 51 ++++++++++++++++++++++++++++++++++++++++ src/core/preferences.h | 26 ++++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 src/core/preferences.cpp create mode 100644 src/core/preferences.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7093d10..fe3d6d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,6 +57,9 @@ list(FILTER HEADERS EXCLUDE REGEX "${CMAKE_SOURCE_DIR}/src/apps/.*") add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS}) +# preferences storage +list(APPEND SOURCES ${CMAKE_SOURCE_DIR}/src/core/preferences.cpp) + target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Widgets) target_include_directories(${PROJECT_NAME} PRIVATE diff --git a/src/core/preferences.cpp b/src/core/preferences.cpp new file mode 100644 index 0000000..c55e183 --- /dev/null +++ b/src/core/preferences.cpp @@ -0,0 +1,51 @@ +#include "preferences.h" +#include "xdg.h" +#include +#include + +using json = nlohmann::json; + +namespace prefs { + +Preferences::Preferences(const std::filesystem::path &path) : file(path) { load(); } +Preferences::~Preferences() { } + +void Preferences::load() { + map.clear(); + try { + if (!std::filesystem::exists(file)) return; + std::ifstream f(file); + if (!f.is_open()) return; + json j; f >> j; + for (auto it = j.begin(); it != j.end(); ++it) { + Entry e; + if (it.value().contains("pinned")) e.pinned = it.value()["pinned"].get(); + if (it.value().contains("hidden")) e.hidden = it.value()["hidden"].get(); + map[it.key()] = e; + } + } catch(...) {} +} + +void Preferences::save() { + try { + json j; + for (auto &p : map) { + j[p.first] = { {"pinned", p.second.pinned}, {"hidden", p.second.hidden} }; + } + std::filesystem::create_directories(file.parent_path()); + std::ofstream f(file, std::ios::trunc); + f << j.dump(2); + } catch(...) {} +} + +bool Preferences::isPinned(const std::string &key) const { + auto it = map.find(key); if (it == map.end()) return false; return it->second.pinned; +} +void Preferences::setPinned(const std::string &key, bool v) { + map[key].pinned = v; +} + +bool Preferences::isHidden(const std::string &key) const { auto it = map.find(key); if (it == map.end()) return false; return it->second.hidden; } +void Preferences::setHidden(const std::string &key, bool v) { map[key].hidden = v; } + +} // namespace prefs diff --git a/src/core/preferences.h b/src/core/preferences.h new file mode 100644 index 0000000..df3cc52 --- /dev/null +++ b/src/core/preferences.h @@ -0,0 +1,26 @@ +#pragma once +#include +#include +#include + +namespace prefs { +class Preferences { +public: + explicit Preferences(const std::filesystem::path &path); + ~Preferences(); + + bool isPinned(const std::string &key) const; + void setPinned(const std::string &key, bool v); + + bool isHidden(const std::string &key) const; + void setHidden(const std::string &key, bool v); + + void save(); + void load(); + +private: + std::filesystem::path file; + struct Entry { bool pinned = false; bool hidden = false; }; + std::unordered_map map; +}; +} // namespace prefs From 275bd2ed9a5c01edb80994b8b683d92fc7604faf Mon Sep 17 00:00:00 2001 From: dayvster Date: Sat, 14 Feb 2026 12:16:41 +0100 Subject: [PATCH 2/4] feat(ui): add per-app pin/hide prefs and AppRow hooks (storage + stubs) --- src/ui/components/appRow.cpp | 10 +++++++++- src/ui/components/appRow.h | 3 +++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/ui/components/appRow.cpp b/src/ui/components/appRow.cpp index f0e180f..563f9dd 100644 --- a/src/ui/components/appRow.cpp +++ b/src/ui/components/appRow.cpp @@ -90,5 +90,13 @@ AppRow::AppRow(QWidget *parent, const DesktopApp &app) : QWidget(parent), app(ap layout->addStretch(); - this->setLayout(layout); + this->setLayout(layout); +} + +void AppRow::setPinned(bool /*pinned*/) { + // placeholder: visual pin state not yet implemented +} + +void AppRow::setHiddenFlag(bool /*hidden*/) { + // placeholder: visual hide state not yet implemented } diff --git a/src/ui/components/appRow.h b/src/ui/components/appRow.h index cfbadfe..79b8b3a 100644 --- a/src/ui/components/appRow.h +++ b/src/ui/components/appRow.h @@ -13,6 +13,9 @@ class AppRow : public QWidget public: DesktopApp app; explicit AppRow(QWidget *parent = nullptr, const DesktopApp &app = DesktopApp()); + // expose hooks for pin/hide UI + void setPinned(bool pinned); + void setHiddenFlag(bool hidden); private: const unsigned int ICON_SIZE = 24; From 6e104f579b1041f3cdf0bd92c636f8e77e3819b7 Mon Sep 17 00:00:00 2001 From: dayvster Date: Sat, 14 Feb 2026 12:17:47 +0100 Subject: [PATCH 3/4] test(prefs): add persistence test for per-app pin/hide --- test/preferences_test.cpp | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 test/preferences_test.cpp diff --git a/test/preferences_test.cpp b/test/preferences_test.cpp new file mode 100644 index 0000000..e88b71b --- /dev/null +++ b/test/preferences_test.cpp @@ -0,0 +1,32 @@ +// Test prefs persistence: set pinned/hidden flags, save, reload, verify +#include +#include +#include +#include "../src/core/preferences.h" + +int main() { + namespace fs = std::filesystem; + fs::path tmp = fs::temp_directory_path() / "dlauncher_prefs_test"; + fs::create_directories(tmp.parent_path()); + fs::path prefsFile = tmp; + + // Ensure clean + if (fs::exists(prefsFile)) fs::remove(prefsFile); + + prefs::Preferences p(prefsFile); + // initially false + assert(!p.isPinned("/bin/testapp")); + assert(!p.isHidden("/bin/testapp")); + + p.setPinned("/bin/testapp", true); + p.setHidden("/bin/testapp", true); + p.save(); + + prefs::Preferences p2(prefsFile); + assert(p2.isPinned("/bin/testapp")); + assert(p2.isHidden("/bin/testapp")); + + // cleanup + fs::remove(prefsFile); + return 0; +} From c528fda1a23e73413b7c36540f7f2327ed204ec9 Mon Sep 17 00:00:00 2001 From: dayvster Date: Sat, 14 Feb 2026 13:27:34 +0100 Subject: [PATCH 4/4] debug: gate runtime prints behind Debug::log; use Debug::log for verbose output; keep CLI diagnostic dumps on stdout; include themes as tracked files --- Testing/Temporary/CTestCostData.txt | 1 + main.cpp | 182 +++++++++++++++++--- src/components/appRow/appRow.cpp | 14 +- src/components/list/list.cpp | 5 +- src/core/apps/readApps.cpp | 19 ++- src/core/preferences.cpp | 38 ++--- src/theme/theme.cpp | 255 ++++++++++++++++++++++++++++ src/theme/theme.h | 22 +++ src/ui/components/appRow.cpp | 30 ++-- src/ui/components/appRow.h | 11 +- src/ui/components/list.cpp | 15 +- src/utils/debug.cpp | 10 +- src/utils/debug.h | 2 + themes/ayu.qss | 5 + themes/catpp.qss | 2 + themes/catppuccin.qss | 5 + themes/dark.qss | 11 ++ themes/dracula.qss | 5 + themes/light.qss | 10 ++ themes/solarized.qss | 5 + themes/tokyo-night.qss | 5 + 21 files changed, 568 insertions(+), 84 deletions(-) create mode 100644 Testing/Temporary/CTestCostData.txt create mode 100644 themes/ayu.qss create mode 100644 themes/catpp.qss create mode 100644 themes/catppuccin.qss create mode 100644 themes/dark.qss create mode 100644 themes/dracula.qss create mode 100644 themes/light.qss create mode 100644 themes/solarized.qss create mode 100644 themes/tokyo-night.qss diff --git a/Testing/Temporary/CTestCostData.txt b/Testing/Temporary/CTestCostData.txt new file mode 100644 index 0000000..ed97d53 --- /dev/null +++ b/Testing/Temporary/CTestCostData.txt @@ -0,0 +1 @@ +--- diff --git a/main.cpp b/main.cpp index fe6651d..9ca6441 100644 --- a/main.cpp +++ b/main.cpp @@ -40,11 +40,16 @@ int main(int argc, char *argv[]) // frequency store FrequencyStore freqStore(freqPath); - Debug::log("Debug run string: " + Debug::generateRandomString()); + // We'll print a small debug run string after parsing args so its output + // respects the -v/-vvv flags (verbosity is set while parsing argv). + // Set default verbosity from env/args later; Debug::setVerbosity() will be + // called while parsing -v flags. AppReader appReader; QApplication app(argc, argv); ThemeManager themeManager; const Theme &theme = themeManager.currentTheme(); + // theme selection may be provided via CLI (--theme ); default stylesheet + // from ~/.config/dlauncher/style.qss will be loaded later if present. GlobalEventListener globalKbListener(app); // By default include all .desktop entries (don't skip NoDisplay/Hidden/OnlyShowIn) @@ -55,11 +60,19 @@ int main(int argc, char *argv[]) std::string debugAppPattern; bool rebuildCache = false; bool dumpInMem = false; + std::string themeName; + bool testQss = false; std::vector> menuItems; // label, command for (int i = 1; i < argc; ++i) { std::string arg(argv[i]); + if (arg == "-v") { + Debug::setVerbosity(std::max(Debug::verbosity(), 1)); + } else if (arg == "-vvv") { + Debug::setVerbosity(std::max(Debug::verbosity(), 3)); + } if (arg == "--include-hidden" || arg == "--show-hidden") includeHidden = true; else if (arg == "--show-system") showSystem = true; + else if (arg == "--theme" && i + 1 < argc) { themeName = argv[++i]; } else if (arg == "--dump") { // Diagnostic mode: print each .desktop path and whether it would be included dumpMode = true; @@ -115,14 +128,53 @@ int main(int argc, char *argv[]) else if (arg == "--rebuild-cache") { rebuildCache = true; } + else if (arg == "--test-qss") { + testQss = true; + } else if (arg == "--dump-inmem") { dumpInMem = true; } } + // Print a per-run debug identifier now that we've parsed args and set + // verbosity via -v/-vvv. This respects the user's requested verbosity. + Debug::log(std::string("Debug run string: ") + Debug::generateRandomString()); // If rebuildCache is set, disable cache reads so we force a fresh rescan appReader.SetCacheEnabled(!rebuildCache); appReader.LoadApps(includeHidden, showSystem); - std::cout << "[DEBUG] Loaded apps (count) = " << appReader.GetAllApps().size() << std::endl; + Debug::log(std::string("[DEBUG] Loaded apps (count) = ") + std::to_string(appReader.GetAllApps().size())); + + // Apply a built-in theme if requested via --theme (or persisted QSS). Track + // whether a stylesheet is active so we avoid conflicting palette/attributes. + bool usingQss = false; + QString appliedQss; + + // If a forced QSS test was requested, use an aggressive global stylesheet to + // verify whether application-level QSS is honored by the environment. + if (testQss) { + usingQss = true; + appliedQss = QString("QWidget { background: red !important; color: white !important; }"); + Debug::log("[DEBUG] Test QSS will be applied (aggressive red background)"); + } + if (!themeName.empty()) { + QString qname = QString::fromStdString(themeName); + QString qss = themeManager.builtinStyle(qname); + if (!qss.isEmpty()) { + usingQss = true; + appliedQss = qss; + } + } else { + // fallback: load persisted style if present + QString globalQss = themeManager.styleSheet(); + if (!globalQss.isEmpty()) { + usingQss = true; + appliedQss = globalQss; + } + } + + // If we decided to use QSS, we will apply it after creating the root + // container so that both application and root-level selectors are set by + // ThemeManager::applyStyle. (Avoid applying the QSS here before widgets + // exist; ThemeManager will set the app stylesheet and palette later.) if (dumpMode) { // Print diagnostics and exit. If debugAppPattern is set only matching lines are printed. @@ -169,13 +221,30 @@ int main(int argc, char *argv[]) }); QWidget window; + window.setObjectName("MainWindow"); window.setWindowFlags(devFlags()); window.setFixedSize(theme.windowWidth, theme.windowHeight); - window.setAttribute(Qt::WA_TranslucentBackground); - QPalette pal = window.palette(); - pal.setColor(QPalette::Window, theme.backgroundColor); - window.setPalette(pal); - window.setAutoFillBackground(true); + // If a stylesheet is active, prefer stylesheet painting; don't force + // translucent background or override the palette which can prevent QSS + // background rules from taking effect. + if (usingQss) { + window.setAttribute(Qt::WA_TranslucentBackground, false); + window.setAttribute(Qt::WA_StyledBackground, true); // ensure stylesheet paints background + window.setAutoFillBackground(false); + if (!appliedQss.isEmpty()) { + // also apply stylesheet directly to the window to ensure top-level + // background rules are honored on all platforms + window.setStyleSheet(appliedQss); + // Debug info + qDebug("Applied QSS (len=%d): %s", appliedQss.size(), appliedQss.left(200).toUtf8().constData()); + } + } else { + window.setAttribute(Qt::WA_TranslucentBackground); + QPalette pal = window.palette(); + pal.setColor(QPalette::Window, theme.backgroundColor); + window.setPalette(pal); + window.setAutoFillBackground(true); + } if (theme.windowPosX >= 0 && theme.windowPosY >= 0) { @@ -189,25 +258,19 @@ int main(int argc, char *argv[]) window.move(x, y); } - ListView *list = new ListView(&window); + // Create a root container inside the top-level window so stylesheet painting + // reliably applies to a normal child widget (some platforms don't allow + // QSS to paint top-level window backgrounds when using translucent flags). + QWidget *root = new QWidget(&window); + root->setObjectName("MainWindow"); - QVBoxLayout *layout = new QVBoxLayout(&window); + ListView *list = new ListView(root); - LockedLineEdit *input = new LockedLineEdit(&window, list->listWidget); - input->setStyleSheet(QString(R"( - QLineEdit { - background-color: %1; - border: 2px solid %2; - border-radius: 10px; - padding: 10px; - color: %3; - font-size: %4px; - } - )") - .arg(theme.inputBackground.name(QColor::HexArgb)) - .arg(theme.inputBorder.name(QColor::HexArgb)) - .arg(theme.textColor.name(QColor::HexArgb)) - .arg(theme.font.pointSize())); + QVBoxLayout *layout = new QVBoxLayout(root); + + LockedLineEdit *input = new LockedLineEdit(root, list->listWidget); + // Input styling is provided by the global QSS generated from ThemeManager; + // keep the font so widgets without QSS still render consistently. input->setFont(theme.font); input->setPlaceholderText("Search..."); input->setFocus(); @@ -236,12 +299,24 @@ int main(int argc, char *argv[]) QListWidgetItem *item = list->listWidget->item(row); AppRow *appRow = dynamic_cast(list->listWidget->itemWidget(item)); if (appRow) { - std::cout << "Exec: " << appRow->app.exec << std::endl; + Debug::log(std::string("Exec: ") + appRow->app.exec); } } }); layout->addWidget(list); + // place the root container inside the window + root->setLayout(layout); + root->setGeometry(0, 0, window.width(), window.height()); + + // Now apply the theme (global QSS + palette) to the application and root. + if (usingQss && !appliedQss.isEmpty()) { + // Write the applied qss to the user's config stylesheet so it's visible + // in ~/.config/dlauncher/style.qss for inspection. + themeManager.applyStyle(app, root, appliedQss, themeManager.currentTheme()); + window.setStyleSheet(appliedQss); + } + QObject::connect(input, &QLineEdit::textChanged, [&](const QString &text) { std::string search = text.toStdString(); @@ -301,9 +376,11 @@ int main(int argc, char *argv[]) cleanedExec.remove(fieldCode); auto [program, args, envAssignments] = parseExecCommand(cleanedExec); if (program.isEmpty()) return; - std::cout << "Launching: " << program.toStdString(); - for (const auto &arg : args) std::cout << " " << arg.toStdString(); - std::cout << std::endl; + { + std::string launchMsg = std::string("Launching: ") + program.toStdString(); + for (const auto &arg : args) { launchMsg += std::string(" ") + arg.toStdString(); } + Debug::log(launchMsg); + } freqStore.inc(appRow->app.exec); freqStore.save(); if (envAssignments.isEmpty()) { @@ -335,6 +412,55 @@ int main(int argc, char *argv[]) window.show(); + // If we have a stylesheet to apply, aggressively set it on the window and + // all child widgets to ensure platforms/compositors that don't honor the + // application stylesheet still pick up theming. + if (usingQss && !appliedQss.isEmpty()) { + std::function applyRec = [&](QObject *o) { + QWidget *w = qobject_cast(o); + if (w) { + w->setStyleSheet(appliedQss); + } + const QObjectList &kids = o->children(); + for (QObject *c : kids) applyRec(c); + }; + applyRec(&window); + qDebug("Theme applied recursively to window tree (len=%d)", appliedQss.size()); + // Dump diagnostics so the user can see what was actually applied at runtime + qDebug() << "APPLICATION styleSheet length=" << qApp->styleSheet().size(); + qDebug() << "WINDOW styleSheet length=" << window.styleSheet().size(); + qDebug() << "APPLICATION palette Window color=" << qApp->palette().color(QPalette::Window).name(); + if (list->listWidget->count() > 0) { + QListWidgetItem *it = list->listWidget->item(0); + QWidget *w = list->listWidget->itemWidget(it); + if (w) { + qDebug() << "First row objectName=" << w->objectName() << " styleSheetLen=" << w->styleSheet().size(); + qDebug() << "First row styleSheet (prefix):" << w->styleSheet().left(200); + } + } + } + + // Additionally apply a coherent palette and font derived from Theme so that + // widgets that don't pick up QSS still match the theme. + if (usingQss) { + // Use current Theme values as a base + Theme base = themeManager.currentTheme(); + // if a builtin was requested, override base with it + if (!themeName.empty()) base = ThemeManager::themeForBuiltin(QString::fromStdString(themeName)); + QPalette pal = app.palette(); + pal.setColor(QPalette::Window, base.backgroundColor); + pal.setColor(QPalette::WindowText, base.textColor); + pal.setColor(QPalette::Button, base.rowBackground); + pal.setColor(QPalette::ButtonText, base.textColor); + pal.setColor(QPalette::Base, base.inputBackground); + pal.setColor(QPalette::Text, base.textColor); + pal.setColor(QPalette::Highlight, base.selectionColor); + app.setPalette(pal); + app.setFont(base.font); + window.setPalette(pal); + window.setAutoFillBackground(true); + } + // Safety: ensure initial rows are populated (some environments/layout timing // issues can leave the list empty). If list is empty, populate from master. if (!menuMode && list->listWidget->count() == 0) { diff --git a/src/components/appRow/appRow.cpp b/src/components/appRow/appRow.cpp index 7d6f04a..a9faf55 100644 --- a/src/components/appRow/appRow.cpp +++ b/src/components/appRow/appRow.cpp @@ -1,4 +1,5 @@ #include "appRow.h" +#include "appRow.h" #include #include #include @@ -7,6 +8,7 @@ #include const QString AppRow::rowStyle = R"( + /* kept for reference; prefer global QSS from ThemeManager */ AppRow { background-color: rgba(255, 255, 255, 10); border-radius: 8px; @@ -32,11 +34,17 @@ const QString AppRow::nameStyleTemplate = R"( AppRow::AppRow(QWidget *parent, const DesktopApp &app) : QWidget(parent), app(app) { - this->setObjectName(QString::fromStdString(app.name)); + // Use a stable object name so theme QSS selectors can target rows + this->setObjectName("AppRow"); this->setToolTip(QString::fromStdString(app.comment.value_or(""))); this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - this->setStyleSheet(rowStyle); + // Prefer global QSS where possible; only keep minimal inline styling that + // doesn't conflict with the global stylesheet. Move row visuals into the + // ThemeManager-generated QSS so themes can control appearance consistently. + // Remove the fixed inline stylesheet to allow application-level QSS to take + // effect. + // this->setStyleSheet(rowStyle); QHBoxLayout *layout = new QHBoxLayout(this); layout->setSpacing(4); @@ -86,6 +94,8 @@ AppRow::AppRow(QWidget *parent, const DesktopApp &app) : QWidget(parent), app(ap layout->addWidget(iconLabel); QLabel *nameLabel = new QLabel(QString::fromStdString(app.name), this); + // Rely on the global stylesheet for font size/color. Keep a minimal local + // override only if the theme hasn't provided a rule (fallback to default). nameLabel->setStyleSheet(nameStyleTemplate.arg(TEXT_SIZE + 4)); nameLabel->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); layout->addWidget(nameLabel); diff --git a/src/components/list/list.cpp b/src/components/list/list.cpp index 2e83a6a..22b0a3b 100644 --- a/src/components/list/list.cpp +++ b/src/components/list/list.cpp @@ -28,7 +28,8 @@ ListView::ListView(QWidget *parent) : QWidget(parent) { listWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); listWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); listWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - listWidget->setStyleSheet(StyleSheet); + // Remove local stylesheet so the global/app stylesheet can control visuals. + // listWidget->setStyleSheet(StyleSheet); QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(listWidget); @@ -38,7 +39,7 @@ ListView::ListView(QWidget *parent) : QWidget(parent) { ListView::ListView(QWidget *parent, const QList &rows) : QWidget(parent) { listWidget = new QListWidget(this); - listWidget->setStyleSheet(StyleSheet); + // listWidget->setStyleSheet(StyleSheet); QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(listWidget); setLayout(layout); diff --git a/src/core/apps/readApps.cpp b/src/core/apps/readApps.cpp index b8722be..b7c687c 100644 --- a/src/core/apps/readApps.cpp +++ b/src/core/apps/readApps.cpp @@ -294,13 +294,18 @@ void AppReader::DumpAndPrint(bool includeHidden, bool showSystem, const std::str { out = entry.path().string() + "\tINCLUDED\t" + app.name; } - if (pattern.empty()) { - std::cout << out << std::endl; - } else { - // case-insensitive match - std::string low = toLower(out); - std::string pat = toLower(pattern); - if (low.find(pat) != std::string::npos) std::cout << out << std::endl; + if (/* debug output controlled by flag */ true) { + if (pattern.empty()) { + // use std::cout for diagnostic dump mode (this function is a CLI + // diagnostic helper; keep output on stdout regardless of -v so + // scripts can consume it). Use std::cout here instead of Debug::log. + std::cout << out << std::endl; + } else { + // case-insensitive match + std::string low = toLower(out); + std::string pat = toLower(pattern); + if (low.find(pat) != std::string::npos) std::cout << out << std::endl; + } } } } diff --git a/src/core/preferences.cpp b/src/core/preferences.cpp index c55e183..35ced85 100644 --- a/src/core/preferences.cpp +++ b/src/core/preferences.cpp @@ -1,9 +1,7 @@ #include "preferences.h" #include "xdg.h" #include -#include - -using json = nlohmann::json; +#include namespace prefs { @@ -16,34 +14,34 @@ void Preferences::load() { if (!std::filesystem::exists(file)) return; std::ifstream f(file); if (!f.is_open()) return; - json j; f >> j; - for (auto it = j.begin(); it != j.end(); ++it) { - Entry e; - if (it.value().contains("pinned")) e.pinned = it.value()["pinned"].get(); - if (it.value().contains("hidden")) e.hidden = it.value()["hidden"].get(); - map[it.key()] = e; + std::string line; + while (std::getline(f, line)) { + if (line.empty()) continue; + // format: key\t\t + std::istringstream iss(line); + std::string key; int pinned = 0; int hidden = 0; + if (!std::getline(iss, key, '\t')) continue; + std::string s1; if (!std::getline(iss, s1, '\t')) continue; pinned = std::stoi(s1); + std::string s2; if (!std::getline(iss, s2, '\t')) continue; hidden = std::stoi(s2); + Entry e; e.pinned = (pinned != 0); e.hidden = (hidden != 0); + map[key] = e; } } catch(...) {} } void Preferences::save() { try { - json j; - for (auto &p : map) { - j[p.first] = { {"pinned", p.second.pinned}, {"hidden", p.second.hidden} }; - } std::filesystem::create_directories(file.parent_path()); std::ofstream f(file, std::ios::trunc); - f << j.dump(2); + if (!f.is_open()) return; + for (auto &p : map) { + f << p.first << '\t' << (p.second.pinned ? 1 : 0) << '\t' << (p.second.hidden ? 1 : 0) << '\n'; + } } catch(...) {} } -bool Preferences::isPinned(const std::string &key) const { - auto it = map.find(key); if (it == map.end()) return false; return it->second.pinned; -} -void Preferences::setPinned(const std::string &key, bool v) { - map[key].pinned = v; -} +bool Preferences::isPinned(const std::string &key) const { auto it = map.find(key); if (it == map.end()) return false; return it->second.pinned; } +void Preferences::setPinned(const std::string &key, bool v) { map[key].pinned = v; } bool Preferences::isHidden(const std::string &key) const { auto it = map.find(key); if (it == map.end()) return false; return it->second.hidden; } void Preferences::setHidden(const std::string &key, bool v) { map[key].hidden = v; } diff --git a/src/theme/theme.cpp b/src/theme/theme.cpp index a2401f7..3083905 100644 --- a/src/theme/theme.cpp +++ b/src/theme/theme.cpp @@ -2,11 +2,17 @@ #include #include #include +#include +#include +#include +#include ThemeManager::ThemeManager() { setDefaults(); loadTheme(defaultConfigPath()); + // Ensure builtin QSS files exist so users can tweak them if desired + ensureBuiltinQssFiles(); } void ThemeManager::setDefaults() @@ -93,6 +99,76 @@ bool ThemeManager::loadTheme(const QString &path) return true; } +bool ThemeManager::loadStyleSheet(const QString &path) +{ + QFile f(path); + if (!f.exists()) return false; + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return false; + QTextStream in(&f); + QString contents = in.readAll(); + // store stylesheet in a simple file under ~/.config/dlauncher/style.qss + try { + QString outPath = ThemeManager::defaultStylePath(); + QFile out(outPath); + QFile::remove(outPath); + if (out.open(QIODevice::WriteOnly | QIODevice::Text)) { + QTextStream o(&out); + o << contents; + out.close(); + } + } catch(...) {} + return true; +} + +QString ThemeManager::defaultThemesDir() +{ + return QDir::homePath() + "/.config/dlauncher/themes"; +} + +void ThemeManager::ensureBuiltinQssFiles() +{ + QDir d(defaultThemesDir()); + if (!d.exists()) d.mkpath("."); + // For each builtin, write its generated QSS if the file doesn't already exist + // Instead of generating QSS here, copy packaged theme files from the + // repository 'themes' directory (if present in the working tree) so that + // designers can ship curated QSS files rather than autogenerated ones. + QString repoThemesPath = QDir::current().filePath("themes"); + QDir repoThemesDir(repoThemesPath); + if (repoThemesDir.exists()) { + for (const QString &fileName : repoThemesDir.entryList(QStringList() << "*.qss", QDir::Files)) { + QString src = repoThemesDir.filePath(fileName); + QString dst = d.filePath(fileName); + if (QFile::exists(dst)) continue; // don't overwrite + QFile::copy(src, dst); + } + } else { + // Fallback: generate QSS from Theme structs + for (const QString &name : ThemeManager::builtinNames()) { + QString fname = d.filePath(name + ".qss"); + QFile f(fname); + if (f.exists()) continue; // don't overwrite + Theme t = ThemeManager::themeForBuiltin(name); + QString qss = ThemeManager::qssForTheme(t); + if (f.open(QIODevice::WriteOnly | QIODevice::Text)) { + QTextStream out(&f); + out << qss; + f.close(); + } + } + } +} + +QString ThemeManager::styleSheet() const +{ + QString path = ThemeManager::defaultStylePath(); + QFile f(path); + if (!f.exists()) return ""; + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return ""; + QTextStream in(&f); + return in.readAll(); +} + const Theme &ThemeManager::currentTheme() const { return theme; @@ -102,3 +178,182 @@ QString ThemeManager::defaultConfigPath() { return QDir::homePath() + "/.config/dlauncher/theme"; } + +QString ThemeManager::defaultStylePath() +{ + return QDir::homePath() + "/.config/dlauncher/style.qss"; +} + +QString ThemeManager::loadQssFile(const QString &path) +{ + QFile f(path); + if (!f.exists()) return QString(); + if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return QString(); + QTextStream in(&f); + return in.readAll(); +} + +void ThemeManager::applyStyle(QApplication &app, QWidget *root, const QString &qss, const Theme &t) +{ + if (!qss.isEmpty()) { + app.setStyleSheet(qss); + if (root) root->setStyleSheet(qss); + } + // Apply palette and font derived from Theme + QPalette pal = app.palette(); + pal.setColor(QPalette::Window, t.backgroundColor); + pal.setColor(QPalette::WindowText, t.textColor); + pal.setColor(QPalette::Button, t.rowBackground); + pal.setColor(QPalette::ButtonText, t.textColor); + pal.setColor(QPalette::Base, t.inputBackground); + pal.setColor(QPalette::Text, t.textColor); + pal.setColor(QPalette::Highlight, t.selectionColor); + app.setPalette(pal); + app.setFont(t.font); + if (root) { root->setPalette(pal); root->setAutoFillBackground(true); } +} + +QString ThemeManager::builtinStyle(const QString &name) +{ + // Prefer generating QSS from the Theme struct so it can target widget object + // names and be consistent with the Theme values used elsewhere in code. + Theme t = ThemeManager::themeForBuiltin(name); + return ThemeManager::qssForTheme(t); +} + +QString ThemeManager::qssForTheme(const Theme &t) +{ + auto rgba = [](const QColor &c){ + int r,g,b,a; c.getRgb(&r,&g,&b,&a); + // use alpha as fractional value in CSS rgba() + double af = a / 255.0; + return QString("rgba(%1,%2,%3,%4)").arg(r).arg(g).arg(b).arg(QString::number(af,'f',2)); + }; + + QString bg = rgba(t.backgroundColor); + QString txt = rgba(t.textColor); + QString row = rgba(t.rowBackground); + QString rowHover = rgba(t.rowHover); + QString inputBg = rgba(t.inputBackground); + QString inputBorder = rgba(t.inputBorder); + QString selection = rgba(t.selectionColor); + + // Generate a concise QSS that covers global fallbacks and targeted rules + QString q = QString(R"( +/* global fallbacks */ +QWidget { background: %1; color: %2; font-family: "%9"; } +QListWidget#AppListWidget, QListWidget { background: transparent; color: %2; border: none; } +QLineEdit { background: %6; color: %2; border: 2px solid %7; border-radius: %4px; padding: 6px; font-size: %10px; } +QPushButton { background: rgba(0,0,0,0); color: %2; border: 1px solid rgba(255,255,255,0.12); border-radius: 6px; padding: 4px; } +QScrollBar:vertical { background: transparent; width: 10px; } +QListWidget::item:selected { background: %8; color: %2; } + +/* targeted rules for our app */ +QWidget#MainWindow { background: %1; color: %2; } +QWidget#AppRow { background: %3; border-radius: %4px; padding: 6px; } +QWidget#AppRow QLabel { color: %2; font-size: %10px; } +/* pin/hide buttons removed */ + + )") + .arg(bg) + .arg(txt) + .arg(row) + .arg(t.borderRadius) + .arg(rowHover) + .arg(inputBg) + .arg(inputBorder) + .arg(selection) + .arg(t.font.family()) + .arg(t.font.pointSize()); + return q; +} + +Theme ThemeManager::themeForBuiltin(const QString &name) +{ + Theme t; + if (name == "light") { + t.backgroundColor = QColor("#F7F7F7"); + t.textColor = QColor("#222222"); + t.inputBackground = QColor("#FFFFFF"); + t.inputBorder = QColor("#CCCCCC"); + t.rowBackground = QColor(255,255,255,180); + t.rowHover = QColor(230,230,230,200); + t.selectionColor = QColor("#BFD7FF"); + t.font = QFont("Sans", 14); + return t; + } + if (name == "solarized") { + t.backgroundColor = QColor("#002b36"); + t.textColor = QColor("#839496"); + t.inputBackground = QColor("#073642"); + t.inputBorder = QColor("#586e75"); + t.rowBackground = QColor(0,43,54,200); + t.rowHover = QColor(7,54,66,200); + t.selectionColor = QColor("#586e75"); + t.font = QFont("Sans", 14); + return t; + } + if (name == "tokyo-night" || name == "tokyo") { + // Tokyo Night (storm-like dark blue/purple palette) + t.backgroundColor = QColor("#1a1b26"); + t.textColor = QColor("#c0caf5"); + t.inputBackground = QColor("#24283b"); + t.inputBorder = QColor("#394264"); + t.rowBackground = QColor(31,35,53,200); // slightly lighter than bg + t.rowHover = QColor(42,46,66,220); + t.selectionColor = QColor("#7aa2f7"); + t.font = QFont("FiraCode", 15); + return t; + } + if (name == "catppuccin" || name == "catpp") { + // Catppuccin (Mocha-like warm dark theme) + t.backgroundColor = QColor("#1e1e2e"); + t.textColor = QColor("#cdd6f4"); + t.inputBackground = QColor("#11111b"); + t.inputBorder = QColor("#313244"); + t.rowBackground = QColor(49,50,68,220); + t.rowHover = QColor(68,71,90,230); + t.selectionColor = QColor("#89b4fa"); + t.font = QFont("FiraCode", 15); + return t; + } + if (name == "dracula") { + // Dracula theme + t.backgroundColor = QColor("#282a36"); + t.textColor = QColor("#f8f8f2"); + t.inputBackground = QColor("#44475a"); + t.inputBorder = QColor("#6272a4"); + t.rowBackground = QColor(68,71,90,200); + t.rowHover = QColor(98,114,164,220); + t.selectionColor = QColor("#bd93f9"); + t.font = QFont("FiraCode", 15); + return t; + } + if (name == "ayu") { + // Ayu (dark) theme + t.backgroundColor = QColor("#0F1419"); + t.textColor = QColor("#e6e1cf"); + t.inputBackground = QColor("#0b0f14"); + t.inputBorder = QColor("#2b2f33"); + t.rowBackground = QColor(20,24,30,200); + t.rowHover = QColor(34,40,48,220); + t.selectionColor = QColor("#f07178"); + t.font = QFont("FiraCode", 15); + return t; + } + // default dark + t.backgroundColor = QColor(30,30,30,220); + t.textColor = QColor(255,255,255); + t.inputBackground = QColor(34,34,34); + t.inputBorder = QColor(68,68,68); + t.rowBackground = QColor(255,255,255,10); + t.rowHover = QColor(155,255,255,30); + t.selectionColor = QColor(0,188,212,80); + t.font = QFont("FiraCode", 16); + return t; +} + +QStringList ThemeManager::builtinNames() +{ + return QStringList() << "dark" << "light" << "solarized" << "tokyo-night" << "catppuccin" << "dracula" << "ayu"; +} diff --git a/src/theme/theme.h b/src/theme/theme.h index bf172c7..0abd47e 100644 --- a/src/theme/theme.h +++ b/src/theme/theme.h @@ -3,6 +3,7 @@ #include #include #include +#include struct Theme { @@ -30,10 +31,31 @@ class ThemeManager public: ThemeManager(); bool loadTheme(const QString &path); + // Load a Qt stylesheet (.qss) from disk; returns true if loaded + bool loadStyleSheet(const QString &path); + // Return loaded stylesheet (may be empty) + QString styleSheet() const; + // Return a built-in stylesheet by name ("dark", "light", "solarized") + static QString builtinStyle(const QString &name); + // List of available built-in theme names + static QStringList builtinNames(); + // Generate a QSS string from a Theme struct that targets our app object names + static QString qssForTheme(const Theme &t); + // Return a Theme struct for one of the built-in theme names + static Theme themeForBuiltin(const QString &name); + // Load a QSS from a file path (returns empty on failure) + static QString loadQssFile(const QString &path); + // Apply a stylesheet and theme values to the application and a root widget + static void applyStyle(QApplication &app, QWidget *root, const QString &qss, const Theme &t); const Theme ¤tTheme() const; static QString defaultConfigPath(); + static QString defaultStylePath(); + // Directory where builtin QSS files are written for user inspection/customization + static QString defaultThemesDir(); private: Theme theme; void setDefaults(); + // Ensure builtin QSS files are present on disk (~/.config/dlauncher/themes) + void ensureBuiltinQssFiles(); }; diff --git a/src/ui/components/appRow.cpp b/src/ui/components/appRow.cpp index 563f9dd..01574a0 100644 --- a/src/ui/components/appRow.cpp +++ b/src/ui/components/appRow.cpp @@ -28,13 +28,17 @@ const QString AppRow::nameStyleTemplate = " font-size: %1px;\n" "}\n"; -AppRow::AppRow(QWidget *parent, const DesktopApp &app) : QWidget(parent), app(app) +AppRow::AppRow(QWidget *parent, const DesktopApp &app, prefs::Preferences *prefs) : QWidget(parent), app(app) { - this->setObjectName(QString::fromStdString(app.name)); + // Use a stable object name so QSS can target AppRow widgets + this->setObjectName("AppRow"); + // Store the app name for accessibility/debugging + this->setProperty("appName", QString::fromStdString(app.name)); this->setToolTip(QString::fromStdString(app.comment.value_or(""))); this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - this->setStyleSheet(rowStyle); + // Do not set per-instance stylesheet here; prefer global QSS generated by ThemeManager + // so styles apply consistently. Keep rowStyle as a fallback but don't apply it. QHBoxLayout *layout = new QHBoxLayout(this); layout->setSpacing(4); @@ -83,20 +87,16 @@ AppRow::AppRow(QWidget *parent, const DesktopApp &app) : QWidget(parent), app(ap iconLabel->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); layout->addWidget(iconLabel); - QLabel *nameLabel = new QLabel(QString::fromStdString(app.name), this); - nameLabel->setStyleSheet(nameStyleTemplate.arg(TEXT_SIZE + 4)); - nameLabel->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); - layout->addWidget(nameLabel); + QLabel *nameLabel = new QLabel(QString::fromStdString(app.name), this); + // Styling for the name label is provided by global QSS; avoid per-widget + // styles that override the stylesheet so themes can apply consistently. + nameLabel->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); + layout->addWidget(nameLabel); - layout->addStretch(); + // No pin/hide buttons — removed (they were unnecessary UX noise) + layout->addStretch(); this->setLayout(layout); } -void AppRow::setPinned(bool /*pinned*/) { - // placeholder: visual pin state not yet implemented -} - -void AppRow::setHiddenFlag(bool /*hidden*/) { - // placeholder: visual hide state not yet implemented -} +// pin/hide hooks removed diff --git a/src/ui/components/appRow.h b/src/ui/components/appRow.h index 79b8b3a..fe0f0ea 100644 --- a/src/ui/components/appRow.h +++ b/src/ui/components/appRow.h @@ -5,6 +5,8 @@ #include "core/apps/readApps.h" #include #include +#include +#include "../../core/preferences.h" class AppRow : public QWidget { @@ -12,10 +14,10 @@ class AppRow : public QWidget public: DesktopApp app; - explicit AppRow(QWidget *parent = nullptr, const DesktopApp &app = DesktopApp()); - // expose hooks for pin/hide UI - void setPinned(bool pinned); - void setHiddenFlag(bool hidden); + explicit AppRow(QWidget *parent = nullptr, const DesktopApp &app = DesktopApp(), prefs::Preferences *prefs = nullptr); + // Pin/hide hooks removed — UI no longer exposes these actions + void setPinned(bool /*pinned*/) {} + void setHiddenFlag(bool /*hidden*/) {} private: const unsigned int ICON_SIZE = 24; @@ -24,6 +26,7 @@ class AppRow : public QWidget static const QString rowStyle; static const QString iconStyle; static const QString nameStyleTemplate; + // buttons removed }; #endif diff --git a/src/ui/components/list.cpp b/src/ui/components/list.cpp index e7223fc..71d275f 100644 --- a/src/ui/components/list.cpp +++ b/src/ui/components/list.cpp @@ -1,4 +1,5 @@ #include "list.h" +#include "../../utils/debug.h" #include #include "list.h" @@ -26,11 +27,14 @@ QString StyleSheet = R"( ListView::ListView(QWidget *parent) : QWidget(parent) { + this->setObjectName("ListView"); listWidget = new QListWidget(this); + listWidget->setObjectName("AppListWidget"); listWidget->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); listWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); listWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - listWidget->setStyleSheet(StyleSheet); + // Defer styling to global QSS/theme. Avoid per-widget stylesheet here so + // application stylesheet can control appearance. QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(listWidget); @@ -40,8 +44,11 @@ ListView::ListView(QWidget *parent) : QWidget(parent) ListView::ListView(QWidget *parent, const QList &rows) : QWidget(parent) { + this->setObjectName("ListView"); listWidget = new QListWidget(this); - listWidget->setStyleSheet(StyleSheet); + listWidget->setObjectName("AppListWidget"); + // Defer styling to global QSS/theme. Avoid per-widget stylesheet here so + // application stylesheet can control appearance. QVBoxLayout *layout = new QVBoxLayout(this); layout->addWidget(listWidget); setLayout(layout); @@ -59,7 +66,7 @@ void ListView::addRow(QWidget *row) listWidget->setItemWidget(item, row); count++; // Debug: log when rows are added to help diagnose empty UI issues - std::cout << "[ListView] addRow: now has " << count << " rows" << std::endl; + Debug::log(std::string("[ListView] addRow: now has ") + std::to_string(count) + " rows"); } void ListView::removeAllRows() @@ -68,7 +75,7 @@ void ListView::removeAllRows() { listWidget->clear(); count = 0; - std::cout << "[ListView] removeAllRows: cleared" << std::endl; + Debug::log("[ListView] removeAllRows: cleared"); } } diff --git a/src/utils/debug.cpp b/src/utils/debug.cpp index 26285a6..a7f6644 100644 --- a/src/utils/debug.cpp +++ b/src/utils/debug.cpp @@ -2,14 +2,20 @@ #include #include +static int gVerbosity = 0; + +void Debug::setVerbosity(int v) { gVerbosity = v; } + +int Debug::verbosity() { return gVerbosity; } + void Debug::log(const char *message) { - std::cout << message << std::endl; + if (gVerbosity > 0) std::cout << message << std::endl; } void Debug::log(const std::string &message) { - std::cout << message << std::endl; + if (gVerbosity > 0) std::cout << message << std::endl; } /* diff --git a/src/utils/debug.h b/src/utils/debug.h index 11c4334..fadabfb 100644 --- a/src/utils/debug.h +++ b/src/utils/debug.h @@ -9,6 +9,8 @@ class Debug public: static void log(const char *message); static void log(const std::string &message); + static void setVerbosity(int v); + static int verbosity(); static const std::vector adjectives; static const std::vector nouns; diff --git a/themes/ayu.qss b/themes/ayu.qss new file mode 100644 index 0000000..41b9ad6 --- /dev/null +++ b/themes/ayu.qss @@ -0,0 +1,5 @@ +/* Ayu (dark) theme */ +QWidget { background: #0F1419; color: #e6e1cf; font-family: "FiraCode"; } +QLineEdit { background: #0b0f14; color: #e6e1cf; border: 2px solid #2b2f33; border-radius: 8px; padding: 6px; } +QWidget#AppRow { background: rgba(20,24,30,0.78); border-radius: 8px; padding: 6px; } +QListWidget::item:selected { background: #f07178; } diff --git a/themes/catpp.qss b/themes/catpp.qss new file mode 100644 index 0000000..7f1b857 --- /dev/null +++ b/themes/catpp.qss @@ -0,0 +1,2 @@ +/* Alias for catppuccin */ +@import "catppuccin.qss"; diff --git a/themes/catppuccin.qss b/themes/catppuccin.qss new file mode 100644 index 0000000..2f7b9ad --- /dev/null +++ b/themes/catppuccin.qss @@ -0,0 +1,5 @@ +/* Catppuccin (mocha) theme */ +QWidget { background: #1e1e2e; color: #cdd6f4; font-family: "FiraCode"; } +QLineEdit { background: #11111b; color: #cdd6f4; border: 2px solid #313244; border-radius: 8px; padding: 6px; } +QWidget#AppRow { background: rgba(49,50,68,0.86); border-radius: 8px; padding: 6px; } +QListWidget::item:selected { background: #89b4fa; } diff --git a/themes/dark.qss b/themes/dark.qss new file mode 100644 index 0000000..32639b1 --- /dev/null +++ b/themes/dark.qss @@ -0,0 +1,11 @@ +/* Dark built-in theme */ +QWidget { background: rgba(30,30,30,0.86); color: rgba(255,255,255,1); font-family: "FiraCode"; } +QListWidget#AppListWidget, QListWidget { background: transparent; color: rgba(255,255,255,1); border: none; } +QLineEdit { background: rgba(34,34,34,1); color: rgba(255,255,255,1); border: 2px solid rgba(68,68,68,1); border-radius: 10px; padding: 6px; font-size: 16px; } +QPushButton { background: rgba(0,0,0,0); color: rgba(255,255,255,1); border: 1px solid rgba(255,255,255,0.12); border-radius: 6px; padding: 4px; } +QScrollBar:vertical { background: transparent; width: 10px; } +QListWidget::item:selected { background: rgba(0,188,212,0.31); color: rgba(255,255,255,1); } + +QWidget#MainWindow { background: rgba(30,30,30,0.86); color: rgba(255,255,255,1); } +QWidget#AppRow { background: rgba(255,255,255,0.04); border-radius: 10px; padding: 6px; } +QWidget#AppRow QLabel { color: rgba(255,255,255,1); font-size: 16px; } diff --git a/themes/dracula.qss b/themes/dracula.qss new file mode 100644 index 0000000..5f374a7 --- /dev/null +++ b/themes/dracula.qss @@ -0,0 +1,5 @@ +/* Dracula theme */ +QWidget { background: #282a36; color: #f8f8f2; font-family: "FiraCode"; } +QLineEdit { background: #44475a; color: #f8f8f2; border: 2px solid #6272a4; border-radius: 8px; padding: 6px; } +QWidget#AppRow { background: rgba(68,71,90,0.78); border-radius: 8px; padding: 6px; } +QListWidget::item:selected { background: #bd93f9; } diff --git a/themes/light.qss b/themes/light.qss new file mode 100644 index 0000000..55afa7f --- /dev/null +++ b/themes/light.qss @@ -0,0 +1,10 @@ +/* Light built-in theme */ +QWidget { background: #F7F7F7; color: #222222; font-family: "Sans"; } +QListWidget#AppListWidget, QListWidget { background: transparent; color: #222222; border: none; } +QLineEdit { background: #FFFFFF; color: #222222; border: 2px solid #CCCCCC; border-radius: 10px; padding: 6px; font-size: 14px; } +QPushButton { background: rgba(0,0,0,0); color: #222222; border: 1px solid rgba(0,0,0,0.08); border-radius: 6px; padding: 4px; } +QListWidget::item:selected { background: #BFD7FF; } + +QWidget#MainWindow { background: #F7F7F7; color: #222222; } +QWidget#AppRow { background: rgba(255,255,255,0.7); border-radius: 10px; padding: 6px; } +QWidget#AppRow QLabel { color: #222222; font-size: 14px; } diff --git a/themes/solarized.qss b/themes/solarized.qss new file mode 100644 index 0000000..098903e --- /dev/null +++ b/themes/solarized.qss @@ -0,0 +1,5 @@ +/* Solarized built-in theme */ +QWidget { background: #002b36; color: #839496; font-family: "Sans"; } +QLineEdit { background: #073642; color: #839496; border: 2px solid #586e75; border-radius: 10px; padding: 6px; } +QWidget#AppRow { background: rgba(0,43,54,0.78); border-radius: 10px; padding: 6px; } +QListWidget::item:selected { background: #586e75; } diff --git a/themes/tokyo-night.qss b/themes/tokyo-night.qss new file mode 100644 index 0000000..3343409 --- /dev/null +++ b/themes/tokyo-night.qss @@ -0,0 +1,5 @@ +/* Tokyo Night theme */ +QWidget { background: #1a1b26; color: #c0caf5; font-family: "FiraCode"; } +QLineEdit { background: #24283b; color: #c0caf5; border: 2px solid #394264; border-radius: 8px; padding: 6px; } +QWidget#AppRow { background: rgba(31,35,53,0.78); border-radius: 8px; padding: 6px; } +QListWidget::item:selected { background: #7aa2f7; }