Browse Source

theme: support hot-reload via --watch-themes cmd option

Le Tan 5 months ago
parent
commit
62f6b7f3c5

+ 52 - 0
src/application.cpp

@@ -2,6 +2,11 @@
 
 #include <QFileOpenEvent>
 #include <QDebug>
+#include <QDir>
+#include <QStyle>
+#include <QFileSystemWatcher>
+#include <QTimer>
+#include <core/vnotex.h>
 
 using namespace vnotex;
 
@@ -10,6 +15,53 @@ Application::Application(int &p_argc, char **p_argv)
 {
 }
 
+void Application::watchThemeFolder(const QString &p_themeFolderPath)
+{
+    if (p_themeFolderPath.isEmpty()) {
+        return;
+    }
+
+    // Initialize watchers only when needed
+    if (!m_styleWatcher) {
+        m_styleWatcher = new QFileSystemWatcher(this);
+    }
+    if (!m_reloadTimer) {
+        m_reloadTimer = new QTimer(this);
+        m_reloadTimer->setSingleShot(true);
+        m_reloadTimer->setInterval(500); // 500ms debounce delay
+        connect(m_reloadTimer, &QTimer::timeout,
+                this, &Application::reloadThemeResources);
+
+        // Connect file watcher to timer
+        connect(m_styleWatcher, &QFileSystemWatcher::directoryChanged,
+                m_reloadTimer, qOverload<>(&QTimer::start));
+        connect(m_styleWatcher, &QFileSystemWatcher::fileChanged,
+                m_reloadTimer, qOverload<>(&QTimer::start));
+    }
+
+    // Watch the theme folder and its files
+    m_styleWatcher->addPath(p_themeFolderPath);
+
+    // Also watch individual files in the theme folder
+    QDir themeDir(p_themeFolderPath);
+    QStringList files = themeDir.entryList(QDir::Files);
+    for (const QString &file : files) {
+        m_styleWatcher->addPath(themeDir.filePath(file));
+    }
+}
+
+void Application::reloadThemeResources()
+{
+    VNoteX::getInst().getThemeMgr().refreshCurrentTheme();
+
+    auto stylesheet = VNoteX::getInst().getThemeMgr().fetchQtStyleSheet();
+    if (!stylesheet.isEmpty()) {
+        setStyleSheet(stylesheet);
+        style()->unpolish(this);
+        style()->polish(this);
+    }
+}
+
 bool Application::event(QEvent *p_event)
 {
     // On macOS, we need this to open file from Finder.

+ 13 - 1
src/application.h

@@ -1,8 +1,10 @@
 #ifndef APPLICATION_H
 #define APPLICATION_H
-
 #include <QApplication>
 
+class QFileSystemWatcher;
+class QTimer;
+
 namespace vnotex
 {
     class Application : public QApplication
@@ -11,11 +13,21 @@ namespace vnotex
     public:
         Application(int &p_argc, char **p_argv);
 
+        // Set up theme folder watcher for hot-reload
+        void watchThemeFolder(const QString &p_themeFolderPath);
+
+        // Reload the theme resources (stylesheet, icons, etc)
+        void reloadThemeResources();
+
     signals:
         void openFileRequested(const QString &p_filePath);
 
     protected:
         bool event(QEvent *p_event) Q_DECL_OVERRIDE;
+
+    private:
+        QFileSystemWatcher *m_styleWatcher = nullptr;
+        QTimer *m_reloadTimer = nullptr;
     };
 }
 

+ 7 - 0
src/commandlineoptions.cpp

@@ -25,6 +25,9 @@ CommandLineOptions::ParseResult CommandLineOptions::parse(const QStringList &p_a
     const QCommandLineOption logStderrOpt("log-stderr", MainWindow::tr("Log to stderr."));
     parser.addOption(logStderrOpt);
 
+    const QCommandLineOption watchThemesOpt("watch-themes", MainWindow::tr("Watch theme folder for changes."));
+    parser.addOption(watchThemesOpt);
+
     // WebEngine options.
     // No need to handle them. Just add them to the parser to avoid parse error.
     {
@@ -70,5 +73,9 @@ CommandLineOptions::ParseResult CommandLineOptions::parse(const QStringList &p_a
         m_logToStderr = true;
     }
 
+    if (parser.isSet(watchThemesOpt)) {
+        m_watchThemes = true;
+    }
+
     return ParseResult::Ok;
 }

+ 3 - 0
src/commandlineoptions.h

@@ -27,6 +27,9 @@ public:
     bool m_verbose = false;
 
     bool m_logToStderr = false;
+
+    // Whether to watch theme folder for changes
+    bool m_watchThemes = false;
 };
 
 #endif // COMMANDLINEOPTIONS_H

+ 5 - 0
src/core/theme.cpp

@@ -24,6 +24,11 @@ Theme::Theme(const QString &p_themeFolderPath,
 {
 }
 
+QString vnotex::Theme::getThemeFolder() const
+{
+    return m_themeFolderPath;
+}
+
 bool Theme::isValidThemeFolder(const QString &p_folder)
 {
     QDir dir(p_folder);

+ 2 - 0
src/core/theme.h

@@ -47,6 +47,8 @@ namespace vnotex
 
         QString name() const;
 
+        QString getThemeFolder() const;
+
         static bool isValidThemeFolder(const QString &p_folder);
 
         static Theme *fromFolder(const QString &p_folder);

+ 11 - 2
src/core/thememgr.cpp

@@ -24,8 +24,6 @@ ThemeMgr::ThemeMgr(const QString &p_currentThemeName, QObject *p_parent)
     loadAvailableThemes();
 
     loadCurrentTheme(p_currentThemeName);
-
-    IconUtils::setDefaultIconForeground(paletteColor("base#icon#fg"), paletteColor("base#icon#disabled#fg"));
 }
 
 QString ThemeMgr::getIconFile(const QString &p_icon) const
@@ -91,6 +89,7 @@ const Theme &ThemeMgr::getCurrentTheme() const
 
 void ThemeMgr::loadCurrentTheme(const QString &p_themeName)
 {
+    m_currentTheme.reset();
     auto themeFolder = findThemeFolder(p_themeName);
     if (themeFolder.isNull()) {
         qWarning() << "failed to locate theme" << p_themeName;
@@ -104,6 +103,8 @@ void ThemeMgr::loadCurrentTheme(const QString &p_themeName)
         qWarning() << "fall back to default theme" << defaultTheme;
         m_currentTheme.reset(loadTheme(findThemeFolder(defaultTheme)));
     }
+
+    IconUtils::setDefaultIconForeground(paletteColor("base#icon#fg"), paletteColor("base#icon#disabled#fg"));
 }
 
 Theme *ThemeMgr::loadTheme(const QString &p_themeFolder)
@@ -211,6 +212,14 @@ QPixmap ThemeMgr::getThemePreview(const QString &p_name) const
 void ThemeMgr::refresh()
 {
     loadAvailableThemes();
+    refreshCurrentTheme();
+}
+
+void vnotex::ThemeMgr::refreshCurrentTheme()
+{
+    if (m_currentTheme) {
+        loadCurrentTheme(m_currentTheme->name());
+    }
 }
 
 void ThemeMgr::addWebStylesSearchPath(const QString &p_path)

+ 3 - 2
src/core/thememgr.h

@@ -60,10 +60,11 @@ namespace vnotex
 
         const ThemeInfo *findTheme(const QString &p_name) const;
 
-        // Refresh the themes list.
-        // Won't affect current theme since we do not support changing theme real time for now.
+        // Refresh the themes list and reload current theme.
         void refresh();
 
+        void refreshCurrentTheme();
+
         // Return all web stylesheets available, including those from themes and web styles search paths.
         // <DisplayName, FilePath>.
         QVector<QPair<QString, QString>> getWebStyles() const;

+ 5 - 0
src/main.cpp

@@ -161,6 +161,11 @@ int main(int argc, char *argv[])
         auto style = VNoteX::getInst().getThemeMgr().fetchQtStyleSheet();
         if (!style.isEmpty()) {
             app.setStyleSheet(style);
+            // Set up hot-reload for the theme folder if enabled via command line
+            if (cmdOptions.m_watchThemes) {
+                const auto themeFolderPath = VNoteX::getInst().getThemeMgr().getCurrentTheme().getThemeFolder();
+                app.watchThemeFolder(themeFolderPath);
+            }
         }
     }