From 8d73d6037deb78a4abc74a15adf705a93c324cd4 Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Sat, 11 Apr 2026 02:38:03 +1000 Subject: [PATCH] fix: move scale back to normal config --- plugin/src/Caelestia/Config/CMakeLists.txt | 7 +- .../src/Caelestia/Config/advancedconfig.hpp | 79 ++++++++- .../src/Caelestia/Config/appearanceconfig.cpp | 167 ++++++++++++++++++ .../src/Caelestia/Config/appearanceconfig.hpp | 142 ++++++++------- plugin/src/Caelestia/Config/config.cpp | 95 ++-------- plugin/src/Caelestia/Config/config.hpp | 25 +-- plugin/src/Caelestia/Config/configobject.cpp | 74 ++++++++ plugin/src/Caelestia/Config/configobject.hpp | 24 ++- 8 files changed, 445 insertions(+), 168 deletions(-) create mode 100644 plugin/src/Caelestia/Config/appearanceconfig.cpp diff --git a/plugin/src/Caelestia/Config/CMakeLists.txt b/plugin/src/Caelestia/Config/CMakeLists.txt index 9351ec41..93f94b06 100644 --- a/plugin/src/Caelestia/Config/CMakeLists.txt +++ b/plugin/src/Caelestia/Config/CMakeLists.txt @@ -1,10 +1,10 @@ qml_module(caelestia-config URI Caelestia.Config SOURCES - config.hpp config.cpp - configobject.hpp + config.cpp + configobject.cpp + appearanceconfig.cpp advancedconfig.hpp - appearanceconfig.hpp backgroundconfig.hpp barconfig.hpp borderconfig.hpp @@ -21,7 +21,6 @@ qml_module(caelestia-config userpaths.hpp utilitiesconfig.hpp winfoconfig.hpp - configobject.cpp LIBRARIES Qt::Quick ) diff --git a/plugin/src/Caelestia/Config/advancedconfig.hpp b/plugin/src/Caelestia/Config/advancedconfig.hpp index 57352a32..181ba0bc 100644 --- a/plugin/src/Caelestia/Config/advancedconfig.hpp +++ b/plugin/src/Caelestia/Config/advancedconfig.hpp @@ -32,14 +32,91 @@ public: , m_expressiveSlowSpatial({ 0.39, 1.29, 0.35, 0.98, 1, 1 }) {} }; +class RoundingTokens : public ConfigObject { + Q_OBJECT + CONFIG_PROPERTY(int, small, 12) + CONFIG_PROPERTY(int, normal, 17) + CONFIG_PROPERTY(int, large, 25) + CONFIG_PROPERTY(int, full, 1000) + +public: + explicit RoundingTokens(QObject* parent = nullptr) + : ConfigObject(parent) {} +}; + +class SpacingTokens : public ConfigObject { + Q_OBJECT + CONFIG_PROPERTY(int, small, 7) + CONFIG_PROPERTY(int, smaller, 10) + CONFIG_PROPERTY(int, normal, 12) + CONFIG_PROPERTY(int, larger, 15) + CONFIG_PROPERTY(int, large, 20) + +public: + explicit SpacingTokens(QObject* parent = nullptr) + : ConfigObject(parent) {} +}; + +class PaddingTokens : public ConfigObject { + Q_OBJECT + CONFIG_PROPERTY(int, small, 5) + CONFIG_PROPERTY(int, smaller, 7) + CONFIG_PROPERTY(int, normal, 10) + CONFIG_PROPERTY(int, larger, 12) + CONFIG_PROPERTY(int, large, 15) + +public: + explicit PaddingTokens(QObject* parent = nullptr) + : ConfigObject(parent) {} +}; + +class FontSizeTokens : public ConfigObject { + Q_OBJECT + CONFIG_PROPERTY(int, small, 11) + CONFIG_PROPERTY(int, smaller, 12) + CONFIG_PROPERTY(int, normal, 13) + CONFIG_PROPERTY(int, larger, 15) + CONFIG_PROPERTY(int, large, 18) + CONFIG_PROPERTY(int, extraLarge, 28) + +public: + explicit FontSizeTokens(QObject* parent = nullptr) + : ConfigObject(parent) {} +}; + +class AnimDurationTokens : public ConfigObject { + Q_OBJECT + CONFIG_PROPERTY(int, small, 200) + CONFIG_PROPERTY(int, normal, 400) + CONFIG_PROPERTY(int, large, 600) + CONFIG_PROPERTY(int, extraLarge, 1000) + CONFIG_PROPERTY(int, expressiveFastSpatial, 350) + CONFIG_PROPERTY(int, expressiveDefaultSpatial, 500) + CONFIG_PROPERTY(int, expressiveSlowSpatial, 650) + +public: + explicit AnimDurationTokens(QObject* parent = nullptr) + : ConfigObject(parent) {} +}; + class AdvancedAppearance : public ConfigObject { Q_OBJECT CONFIG_SUBOBJECT(AnimCurves, curves) + CONFIG_SUBOBJECT(RoundingTokens, rounding) + CONFIG_SUBOBJECT(SpacingTokens, spacing) + CONFIG_SUBOBJECT(PaddingTokens, padding) + CONFIG_SUBOBJECT(FontSizeTokens, fontSize) + CONFIG_SUBOBJECT(AnimDurationTokens, animDurations) public: explicit AdvancedAppearance(QObject* parent = nullptr) : ConfigObject(parent) - , m_curves(new AnimCurves(this)) {} + , m_curves(new AnimCurves(this)) + , m_rounding(new RoundingTokens(this)) + , m_spacing(new SpacingTokens(this)) + , m_padding(new PaddingTokens(this)) + , m_fontSize(new FontSizeTokens(this)) + , m_animDurations(new AnimDurationTokens(this)) {} }; class BarSizes : public ConfigObject { diff --git a/plugin/src/Caelestia/Config/appearanceconfig.cpp b/plugin/src/Caelestia/Config/appearanceconfig.cpp new file mode 100644 index 00000000..1b45f8f6 --- /dev/null +++ b/plugin/src/Caelestia/Config/appearanceconfig.cpp @@ -0,0 +1,167 @@ +#include "appearanceconfig.hpp" +#include "advancedconfig.hpp" + +#include + +namespace caelestia::config { + +// Helper: connect all changed signals from a token object to a single valuesChanged signal, +// plus connect the local scaleChanged signal. +template static void connectTokenSignals(Source* source, Target* target) { + const auto* meta = source->metaObject(); + + for (int i = meta->propertyOffset(); i < meta->propertyCount(); ++i) { + auto prop = meta->property(i); + + if (prop.hasNotifySignal()) + QObject::connect(source, prop.notifySignal(), target, + target->metaObject()->method(target->metaObject()->indexOfSignal("valuesChanged()"))); + } + + QObject::connect(target, &Target::scaleChanged, target, &Target::valuesChanged); +} + +// AppearanceRounding + +void AppearanceRounding::bindTokens(RoundingTokens* tokens) { + m_tokens = tokens; + connectTokenSignals(tokens, this); +} + +int AppearanceRounding::small() const { + return m_tokens ? static_cast(m_tokens->small() * m_scale) : 0; +} + +int AppearanceRounding::normal() const { + return m_tokens ? static_cast(m_tokens->normal() * m_scale) : 0; +} + +int AppearanceRounding::large() const { + return m_tokens ? static_cast(m_tokens->large() * m_scale) : 0; +} + +int AppearanceRounding::full() const { + return m_tokens ? static_cast(m_tokens->full() * m_scale) : 0; +} + +// AppearanceSpacing + +void AppearanceSpacing::bindTokens(SpacingTokens* tokens) { + m_tokens = tokens; + connectTokenSignals(tokens, this); +} + +int AppearanceSpacing::small() const { + return m_tokens ? static_cast(m_tokens->small() * m_scale) : 0; +} + +int AppearanceSpacing::smaller() const { + return m_tokens ? static_cast(m_tokens->smaller() * m_scale) : 0; +} + +int AppearanceSpacing::normal() const { + return m_tokens ? static_cast(m_tokens->normal() * m_scale) : 0; +} + +int AppearanceSpacing::larger() const { + return m_tokens ? static_cast(m_tokens->larger() * m_scale) : 0; +} + +int AppearanceSpacing::large() const { + return m_tokens ? static_cast(m_tokens->large() * m_scale) : 0; +} + +// AppearancePadding + +void AppearancePadding::bindTokens(PaddingTokens* tokens) { + m_tokens = tokens; + connectTokenSignals(tokens, this); +} + +int AppearancePadding::small() const { + return m_tokens ? static_cast(m_tokens->small() * m_scale) : 0; +} + +int AppearancePadding::smaller() const { + return m_tokens ? static_cast(m_tokens->smaller() * m_scale) : 0; +} + +int AppearancePadding::normal() const { + return m_tokens ? static_cast(m_tokens->normal() * m_scale) : 0; +} + +int AppearancePadding::larger() const { + return m_tokens ? static_cast(m_tokens->larger() * m_scale) : 0; +} + +int AppearancePadding::large() const { + return m_tokens ? static_cast(m_tokens->large() * m_scale) : 0; +} + +// FontSize + +void FontSize::bindTokens(FontSizeTokens* tokens) { + m_tokens = tokens; + connectTokenSignals(tokens, this); +} + +int FontSize::small() const { + return m_tokens ? static_cast(m_tokens->small() * m_scale) : 0; +} + +int FontSize::smaller() const { + return m_tokens ? static_cast(m_tokens->smaller() * m_scale) : 0; +} + +int FontSize::normal() const { + return m_tokens ? static_cast(m_tokens->normal() * m_scale) : 0; +} + +int FontSize::larger() const { + return m_tokens ? static_cast(m_tokens->larger() * m_scale) : 0; +} + +int FontSize::large() const { + return m_tokens ? static_cast(m_tokens->large() * m_scale) : 0; +} + +int FontSize::extraLarge() const { + return m_tokens ? static_cast(m_tokens->extraLarge() * m_scale) : 0; +} + +// AnimDurations + +void AnimDurations::bindTokens(AnimDurationTokens* tokens) { + m_tokens = tokens; + connectTokenSignals(tokens, this); +} + +int AnimDurations::small() const { + return m_tokens ? static_cast(m_tokens->small() * m_scale) : 0; +} + +int AnimDurations::normal() const { + return m_tokens ? static_cast(m_tokens->normal() * m_scale) : 0; +} + +int AnimDurations::large() const { + return m_tokens ? static_cast(m_tokens->large() * m_scale) : 0; +} + +int AnimDurations::extraLarge() const { + return m_tokens ? static_cast(m_tokens->extraLarge() * m_scale) : 0; +} + +int AnimDurations::expressiveFastSpatial() const { + return m_tokens ? static_cast(m_tokens->expressiveFastSpatial() * m_scale) : 0; +} + +int AnimDurations::expressiveDefaultSpatial() const { + return m_tokens ? static_cast(m_tokens->expressiveDefaultSpatial() * m_scale) : 0; +} + +int AnimDurations::expressiveSlowSpatial() const { + return m_tokens ? static_cast(m_tokens->expressiveSlowSpatial() * m_scale) : 0; +} + +} // namespace caelestia::config diff --git a/plugin/src/Caelestia/Config/appearanceconfig.hpp b/plugin/src/Caelestia/Config/appearanceconfig.hpp index 4d4acc08..c0094b26 100644 --- a/plugin/src/Caelestia/Config/appearanceconfig.hpp +++ b/plugin/src/Caelestia/Config/appearanceconfig.hpp @@ -6,76 +6,93 @@ namespace caelestia::config { +// Forward declare token types from advancedconfig.hpp +class RoundingTokens; +class SpacingTokens; +class PaddingTokens; +class FontSizeTokens; +class AnimDurationTokens; + class AppearanceRounding : public ConfigObject { Q_OBJECT CONFIG_PROPERTY(qreal, scale, 1) - Q_PROPERTY(int small READ small NOTIFY scaleChanged) - Q_PROPERTY(int normal READ normal NOTIFY scaleChanged) - Q_PROPERTY(int large READ large NOTIFY scaleChanged) - Q_PROPERTY(int full READ full NOTIFY scaleChanged) + Q_PROPERTY(int small READ small NOTIFY valuesChanged) + Q_PROPERTY(int normal READ normal NOTIFY valuesChanged) + Q_PROPERTY(int large READ large NOTIFY valuesChanged) + Q_PROPERTY(int full READ full NOTIFY valuesChanged) public: explicit AppearanceRounding(QObject* parent = nullptr) : ConfigObject(parent) {} - [[nodiscard]] int small() const { return static_cast(12 * m_scale); } + void bindTokens(RoundingTokens* tokens); - [[nodiscard]] int normal() const { return static_cast(17 * m_scale); } + [[nodiscard]] int small() const; + [[nodiscard]] int normal() const; + [[nodiscard]] int large() const; + [[nodiscard]] int full() const; - [[nodiscard]] int large() const { return static_cast(25 * m_scale); } + Q_SIGNAL void valuesChanged(); - [[nodiscard]] int full() const { return static_cast(1000 * m_scale); } +private: + RoundingTokens* m_tokens = nullptr; }; class AppearanceSpacing : public ConfigObject { Q_OBJECT CONFIG_PROPERTY(qreal, scale, 1) - Q_PROPERTY(int small READ small NOTIFY scaleChanged) - Q_PROPERTY(int smaller READ smaller NOTIFY scaleChanged) - Q_PROPERTY(int normal READ normal NOTIFY scaleChanged) - Q_PROPERTY(int larger READ larger NOTIFY scaleChanged) - Q_PROPERTY(int large READ large NOTIFY scaleChanged) + Q_PROPERTY(int small READ small NOTIFY valuesChanged) + Q_PROPERTY(int smaller READ smaller NOTIFY valuesChanged) + Q_PROPERTY(int normal READ normal NOTIFY valuesChanged) + Q_PROPERTY(int larger READ larger NOTIFY valuesChanged) + Q_PROPERTY(int large READ large NOTIFY valuesChanged) public: explicit AppearanceSpacing(QObject* parent = nullptr) : ConfigObject(parent) {} - [[nodiscard]] int small() const { return static_cast(7 * m_scale); } + void bindTokens(SpacingTokens* tokens); - [[nodiscard]] int smaller() const { return static_cast(10 * m_scale); } + [[nodiscard]] int small() const; + [[nodiscard]] int smaller() const; + [[nodiscard]] int normal() const; + [[nodiscard]] int larger() const; + [[nodiscard]] int large() const; - [[nodiscard]] int normal() const { return static_cast(12 * m_scale); } + Q_SIGNAL void valuesChanged(); - [[nodiscard]] int larger() const { return static_cast(15 * m_scale); } - - [[nodiscard]] int large() const { return static_cast(20 * m_scale); } +private: + SpacingTokens* m_tokens = nullptr; }; class AppearancePadding : public ConfigObject { Q_OBJECT CONFIG_PROPERTY(qreal, scale, 1) - Q_PROPERTY(int small READ small NOTIFY scaleChanged) - Q_PROPERTY(int smaller READ smaller NOTIFY scaleChanged) - Q_PROPERTY(int normal READ normal NOTIFY scaleChanged) - Q_PROPERTY(int larger READ larger NOTIFY scaleChanged) - Q_PROPERTY(int large READ large NOTIFY scaleChanged) + Q_PROPERTY(int small READ small NOTIFY valuesChanged) + Q_PROPERTY(int smaller READ smaller NOTIFY valuesChanged) + Q_PROPERTY(int normal READ normal NOTIFY valuesChanged) + Q_PROPERTY(int larger READ larger NOTIFY valuesChanged) + Q_PROPERTY(int large READ large NOTIFY valuesChanged) public: explicit AppearancePadding(QObject* parent = nullptr) : ConfigObject(parent) {} - [[nodiscard]] int small() const { return static_cast(5 * m_scale); } + void bindTokens(PaddingTokens* tokens); - [[nodiscard]] int smaller() const { return static_cast(7 * m_scale); } + [[nodiscard]] int small() const; + [[nodiscard]] int smaller() const; + [[nodiscard]] int normal() const; + [[nodiscard]] int larger() const; + [[nodiscard]] int large() const; - [[nodiscard]] int normal() const { return static_cast(10 * m_scale); } + Q_SIGNAL void valuesChanged(); - [[nodiscard]] int larger() const { return static_cast(12 * m_scale); } - - [[nodiscard]] int large() const { return static_cast(15 * m_scale); } +private: + PaddingTokens* m_tokens = nullptr; }; class FontFamily : public ConfigObject { @@ -94,28 +111,30 @@ class FontSize : public ConfigObject { Q_OBJECT CONFIG_PROPERTY(qreal, scale, 1) - Q_PROPERTY(int small READ small NOTIFY scaleChanged) - Q_PROPERTY(int smaller READ smaller NOTIFY scaleChanged) - Q_PROPERTY(int normal READ normal NOTIFY scaleChanged) - Q_PROPERTY(int larger READ larger NOTIFY scaleChanged) - Q_PROPERTY(int large READ large NOTIFY scaleChanged) - Q_PROPERTY(int extraLarge READ extraLarge NOTIFY scaleChanged) + Q_PROPERTY(int small READ small NOTIFY valuesChanged) + Q_PROPERTY(int smaller READ smaller NOTIFY valuesChanged) + Q_PROPERTY(int normal READ normal NOTIFY valuesChanged) + Q_PROPERTY(int larger READ larger NOTIFY valuesChanged) + Q_PROPERTY(int large READ large NOTIFY valuesChanged) + Q_PROPERTY(int extraLarge READ extraLarge NOTIFY valuesChanged) public: explicit FontSize(QObject* parent = nullptr) : ConfigObject(parent) {} - [[nodiscard]] int small() const { return static_cast(11 * m_scale); } + void bindTokens(FontSizeTokens* tokens); - [[nodiscard]] int smaller() const { return static_cast(12 * m_scale); } + [[nodiscard]] int small() const; + [[nodiscard]] int smaller() const; + [[nodiscard]] int normal() const; + [[nodiscard]] int larger() const; + [[nodiscard]] int large() const; + [[nodiscard]] int extraLarge() const; - [[nodiscard]] int normal() const { return static_cast(13 * m_scale); } + Q_SIGNAL void valuesChanged(); - [[nodiscard]] int larger() const { return static_cast(15 * m_scale); } - - [[nodiscard]] int large() const { return static_cast(18 * m_scale); } - - [[nodiscard]] int extraLarge() const { return static_cast(28 * m_scale); } +private: + FontSizeTokens* m_tokens = nullptr; }; class AppearanceFont : public ConfigObject { @@ -134,31 +153,32 @@ class AnimDurations : public ConfigObject { Q_OBJECT CONFIG_PROPERTY(qreal, scale, 1) - Q_PROPERTY(int small READ small NOTIFY scaleChanged) - Q_PROPERTY(int normal READ normal NOTIFY scaleChanged) - Q_PROPERTY(int large READ large NOTIFY scaleChanged) - Q_PROPERTY(int extraLarge READ extraLarge NOTIFY scaleChanged) - Q_PROPERTY(int expressiveFastSpatial READ expressiveFastSpatial NOTIFY scaleChanged) - Q_PROPERTY(int expressiveDefaultSpatial READ expressiveDefaultSpatial NOTIFY scaleChanged) - Q_PROPERTY(int expressiveSlowSpatial READ expressiveSlowSpatial NOTIFY scaleChanged) + Q_PROPERTY(int small READ small NOTIFY valuesChanged) + Q_PROPERTY(int normal READ normal NOTIFY valuesChanged) + Q_PROPERTY(int large READ large NOTIFY valuesChanged) + Q_PROPERTY(int extraLarge READ extraLarge NOTIFY valuesChanged) + Q_PROPERTY(int expressiveFastSpatial READ expressiveFastSpatial NOTIFY valuesChanged) + Q_PROPERTY(int expressiveDefaultSpatial READ expressiveDefaultSpatial NOTIFY valuesChanged) + Q_PROPERTY(int expressiveSlowSpatial READ expressiveSlowSpatial NOTIFY valuesChanged) public: explicit AnimDurations(QObject* parent = nullptr) : ConfigObject(parent) {} - [[nodiscard]] int small() const { return static_cast(200 * m_scale); } + void bindTokens(AnimDurationTokens* tokens); - [[nodiscard]] int normal() const { return static_cast(400 * m_scale); } + [[nodiscard]] int small() const; + [[nodiscard]] int normal() const; + [[nodiscard]] int large() const; + [[nodiscard]] int extraLarge() const; + [[nodiscard]] int expressiveFastSpatial() const; + [[nodiscard]] int expressiveDefaultSpatial() const; + [[nodiscard]] int expressiveSlowSpatial() const; - [[nodiscard]] int large() const { return static_cast(600 * m_scale); } + Q_SIGNAL void valuesChanged(); - [[nodiscard]] int extraLarge() const { return static_cast(1000 * m_scale); } - - [[nodiscard]] int expressiveFastSpatial() const { return static_cast(350 * m_scale); } - - [[nodiscard]] int expressiveDefaultSpatial() const { return static_cast(500 * m_scale); } - - [[nodiscard]] int expressiveSlowSpatial() const { return static_cast(650 * m_scale); } +private: + AnimDurationTokens* m_tokens = nullptr; }; class AppearanceAnim : public ConfigObject { diff --git a/plugin/src/Caelestia/Config/config.cpp b/plugin/src/Caelestia/Config/config.cpp index 01df3704..02014577 100644 --- a/plugin/src/Caelestia/Config/config.cpp +++ b/plugin/src/Caelestia/Config/config.cpp @@ -1,17 +1,15 @@ #include "config.hpp" -#include -#include -#include -#include -#include -#include #include namespace caelestia::config { static GlobalConfig* s_instance = nullptr; +static QString configDir() { + return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/caelestia/"); +} + GlobalConfig::GlobalConfig(QObject* parent) : ConfigObject(parent) , m_appearance(new AppearanceConfig(this)) @@ -34,91 +32,32 @@ GlobalConfig::GlobalConfig(QObject* parent) , m_advanced(new AdvancedConfig(this)) { s_instance = this; - m_configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + - QStringLiteral("/caelestia/shell.json"); + // Bind token base values from advanced config to appearance computed properties + auto* adv = m_advanced->appearance(); + m_appearance->rounding()->bindTokens(adv->rounding()); + m_appearance->spacing()->bindTokens(adv->spacing()); + m_appearance->padding()->bindTokens(adv->padding()); + m_appearance->font()->size()->bindTokens(adv->fontSize()); + m_appearance->anim()->durations()->bindTokens(adv->animDurations()); - m_saveTimer.setSingleShot(true); - m_saveTimer.setInterval(500); - connect(&m_saveTimer, &QTimer::timeout, this, &GlobalConfig::writeToFile); - - m_cooldownTimer.setSingleShot(true); - m_cooldownTimer.setInterval(2000); - connect(&m_cooldownTimer, &QTimer::timeout, this, [this] { - m_recentlySaved = false; - emit recentlySavedChanged(); - }); - - connect(&m_watcher, &QFileSystemWatcher::fileChanged, this, &GlobalConfig::onFileChanged); - - reload(); - - if (QFile::exists(m_configPath)) { - m_watcher.addPath(m_configPath); - } + // Each has its own file backend + setupFileBackend(configDir() + QStringLiteral("shell.json")); + m_advanced->setupFileBackend(configDir() + QStringLiteral("advanced.json")); } GlobalConfig* GlobalConfig::instance() { return s_instance; } -bool GlobalConfig::recentlySaved() const { - return m_recentlySaved; -} - void GlobalConfig::save() { - m_saveTimer.start(); - m_recentlySaved = true; - emit recentlySavedChanged(); - m_cooldownTimer.start(); + saveToFile(); } void GlobalConfig::reload() { - QFile file(m_configPath); - - if (!file.open(QIODevice::ReadOnly)) { - qWarning() << "Config: failed to open" << m_configPath; - return; - } - - load(file.readAll()); + reloadFromFile(); } -void GlobalConfig::load(const QByteArray& contents) { - QJsonParseError error{}; - auto doc = QJsonDocument::fromJson(contents, &error); - - if (error.error != QJsonParseError::NoError) { - qWarning() << "Config: failed to parse JSON:" << error.errorString(); - return; - } - - loadFromJson(doc.object()); -} - -void GlobalConfig::onFileChanged() { - if (!m_watcher.files().contains(m_configPath)) { - m_watcher.addPath(m_configPath); - } - - if (!m_recentlySaved) { - reload(); - } -} - -void GlobalConfig::writeToFile() { - QDir().mkpath(QFileInfo(m_configPath).absolutePath()); - - QFile file(m_configPath); - - if (!file.open(QIODevice::WriteOnly)) { - qWarning() << "Config: failed to write" << m_configPath; - return; - } - - file.write(QJsonDocument(toJsonObject()).toJson(QJsonDocument::Indented)); -} - -// Config +// Config (attached type) Config::Config(QQuickItem* parent) : QQuickItem(parent) {} diff --git a/plugin/src/Caelestia/Config/config.hpp b/plugin/src/Caelestia/Config/config.hpp index e989d4c3..ecd26634 100644 --- a/plugin/src/Caelestia/Config/config.hpp +++ b/plugin/src/Caelestia/Config/config.hpp @@ -1,12 +1,7 @@ #pragma once -#include -#include -#include -#include #include #include -#include #include "advancedconfig.hpp" #include "appearanceconfig.hpp" @@ -51,33 +46,21 @@ class GlobalConfig : public ConfigObject { CONFIG_SUBOBJECT(SidebarConfig, sidebar) CONFIG_SUBOBJECT(ServiceConfig, services) CONFIG_SUBOBJECT(UserPaths, paths) - CONFIG_SUBOBJECT(AdvancedConfig, advanced) - - Q_PROPERTY(bool recentlySaved READ recentlySaved NOTIFY recentlySavedChanged) + // advanced is NOT a CONFIG_SUBOBJECT — it has its own file backend + Q_PROPERTY(AdvancedConfig* advanced READ advanced CONSTANT) public: explicit GlobalConfig(QObject* parent = nullptr); static GlobalConfig* instance(); - [[nodiscard]] bool recentlySaved() const; + [[nodiscard]] AdvancedConfig* advanced() const { return m_advanced; } Q_INVOKABLE void save(); Q_INVOKABLE void reload(); -signals: - void recentlySavedChanged(); - private: - void load(const QByteArray& contents); - void onFileChanged(); - void writeToFile(); - - bool m_recentlySaved = false; - QString m_configPath; - QFileSystemWatcher m_watcher; - QTimer m_saveTimer; - QTimer m_cooldownTimer; + AdvancedConfig* m_advanced = nullptr; }; class Config : public QQuickItem { diff --git a/plugin/src/Caelestia/Config/configobject.cpp b/plugin/src/Caelestia/Config/configobject.cpp index 7533b0f5..cb6624a1 100644 --- a/plugin/src/Caelestia/Config/configobject.cpp +++ b/plugin/src/Caelestia/Config/configobject.cpp @@ -1,6 +1,10 @@ #include "configobject.hpp" +#include +#include +#include #include +#include #include #include #include @@ -104,4 +108,74 @@ QJsonObject ConfigObject::toJsonObject() const { return obj; } +void ConfigObject::setupFileBackend(const QString& path) { + m_filePath = path; + + m_watcher = new QFileSystemWatcher(this); + m_saveTimer = new QTimer(this); + m_cooldownTimer = new QTimer(this); + + m_saveTimer->setSingleShot(true); + m_saveTimer->setInterval(500); + connect(m_saveTimer, &QTimer::timeout, this, [this] { + QDir().mkpath(QFileInfo(m_filePath).absolutePath()); + + QFile file(m_filePath); + if (!file.open(QIODevice::WriteOnly)) { + qCWarning(lcConfig) << "Failed to write" << m_filePath; + return; + } + + file.write(QJsonDocument(toJsonObject()).toJson(QJsonDocument::Indented)); + }); + + m_cooldownTimer->setSingleShot(true); + m_cooldownTimer->setInterval(2000); + connect(m_cooldownTimer, &QTimer::timeout, this, [this] { + m_recentlySaved = false; + }); + + connect(m_watcher, &QFileSystemWatcher::fileChanged, this, &ConfigObject::onFileChanged); + + reloadFromFile(); + + if (QFile::exists(m_filePath)) + m_watcher->addPath(m_filePath); +} + +void ConfigObject::saveToFile() { + if (!m_saveTimer) + return; + m_saveTimer->start(); + m_recentlySaved = true; + m_cooldownTimer->start(); +} + +void ConfigObject::reloadFromFile() { + QFile file(m_filePath); + + if (!file.open(QIODevice::ReadOnly)) { + qCDebug(lcConfig) << "Failed to open" << m_filePath; + return; + } + + QJsonParseError error{}; + auto doc = QJsonDocument::fromJson(file.readAll(), &error); + + if (error.error != QJsonParseError::NoError) { + qCWarning(lcConfig) << "Failed to parse" << m_filePath << ":" << error.errorString(); + return; + } + + loadFromJson(doc.object()); +} + +void ConfigObject::onFileChanged() { + if (!m_watcher->files().contains(m_filePath)) + m_watcher->addPath(m_filePath); + + if (!m_recentlySaved) + reloadFromFile(); +} + } // namespace caelestia::config diff --git a/plugin/src/Caelestia/Config/configobject.hpp b/plugin/src/Caelestia/Config/configobject.hpp index 2f8f443e..d3ef11e9 100644 --- a/plugin/src/Caelestia/Config/configobject.hpp +++ b/plugin/src/Caelestia/Config/configobject.hpp @@ -1,10 +1,10 @@ #pragma once +#include #include #include #include - -#include +#include // Declares a serialized config property with getter, setter (change-detected), signal, and member. #define CONFIG_PROPERTY(Type, name, ...) \ @@ -16,7 +16,7 @@ public: } \ void set_##name(const Type& val) { \ if (caelestia::config::ConfigObject::updateMember(m_##name, val)) \ - emit name##Changed(); \ + Q_EMIT name##Changed(); \ } \ Q_SIGNAL void name##Changed(); \ \ @@ -47,6 +47,14 @@ public: void loadFromJson(const QJsonObject& obj); [[nodiscard]] QJsonObject toJsonObject() const; + // File-backed config support. Call setupFileBackend() to enable + // automatic file watching, debounced saving, and reload. + void setupFileBackend(const QString& path); + void saveToFile(); + void reloadFromFile(); + + [[nodiscard]] bool recentlySaved() const { return m_recentlySaved; } + template static bool updateMember(T& member, const T& value) { if constexpr (std::is_floating_point_v) { if (qFuzzyCompare(member + 1.0, value + 1.0)) @@ -58,6 +66,16 @@ public: member = value; return true; } + +private: + void onFileChanged(); + + QString m_filePath; + bool m_recentlySaved = false; + // These are heap-allocated only when setupFileBackend is called + QFileSystemWatcher* m_watcher = nullptr; + QTimer* m_saveTimer = nullptr; + QTimer* m_cooldownTimer = nullptr; }; } // namespace caelestia::config