fix: move scale back to normal config

This commit is contained in:
2 * r + 2 * t 2026-04-11 02:38:03 +10:00
parent d530e209b3
commit 8d73d6037d
8 changed files with 445 additions and 168 deletions

View file

@ -1,10 +1,10 @@
qml_module(caelestia-config qml_module(caelestia-config
URI Caelestia.Config URI Caelestia.Config
SOURCES SOURCES
config.hpp config.cpp config.cpp
configobject.hpp configobject.cpp
appearanceconfig.cpp
advancedconfig.hpp advancedconfig.hpp
appearanceconfig.hpp
backgroundconfig.hpp backgroundconfig.hpp
barconfig.hpp barconfig.hpp
borderconfig.hpp borderconfig.hpp
@ -21,7 +21,6 @@ qml_module(caelestia-config
userpaths.hpp userpaths.hpp
utilitiesconfig.hpp utilitiesconfig.hpp
winfoconfig.hpp winfoconfig.hpp
configobject.cpp
LIBRARIES LIBRARIES
Qt::Quick Qt::Quick
) )

View file

@ -32,14 +32,91 @@ public:
, m_expressiveSlowSpatial({ 0.39, 1.29, 0.35, 0.98, 1, 1 }) {} , 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 { class AdvancedAppearance : public ConfigObject {
Q_OBJECT Q_OBJECT
CONFIG_SUBOBJECT(AnimCurves, curves) 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: public:
explicit AdvancedAppearance(QObject* parent = nullptr) explicit AdvancedAppearance(QObject* parent = nullptr)
: ConfigObject(parent) : 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 { class BarSizes : public ConfigObject {

View file

@ -0,0 +1,167 @@
#include "appearanceconfig.hpp"
#include "advancedconfig.hpp"
#include <qmetaobject.h>
namespace caelestia::config {
// Helper: connect all changed signals from a token object to a single valuesChanged signal,
// plus connect the local scaleChanged signal.
template <typename Source, typename Target> 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<int>(m_tokens->small() * m_scale) : 0;
}
int AppearanceRounding::normal() const {
return m_tokens ? static_cast<int>(m_tokens->normal() * m_scale) : 0;
}
int AppearanceRounding::large() const {
return m_tokens ? static_cast<int>(m_tokens->large() * m_scale) : 0;
}
int AppearanceRounding::full() const {
return m_tokens ? static_cast<int>(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<int>(m_tokens->small() * m_scale) : 0;
}
int AppearanceSpacing::smaller() const {
return m_tokens ? static_cast<int>(m_tokens->smaller() * m_scale) : 0;
}
int AppearanceSpacing::normal() const {
return m_tokens ? static_cast<int>(m_tokens->normal() * m_scale) : 0;
}
int AppearanceSpacing::larger() const {
return m_tokens ? static_cast<int>(m_tokens->larger() * m_scale) : 0;
}
int AppearanceSpacing::large() const {
return m_tokens ? static_cast<int>(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<int>(m_tokens->small() * m_scale) : 0;
}
int AppearancePadding::smaller() const {
return m_tokens ? static_cast<int>(m_tokens->smaller() * m_scale) : 0;
}
int AppearancePadding::normal() const {
return m_tokens ? static_cast<int>(m_tokens->normal() * m_scale) : 0;
}
int AppearancePadding::larger() const {
return m_tokens ? static_cast<int>(m_tokens->larger() * m_scale) : 0;
}
int AppearancePadding::large() const {
return m_tokens ? static_cast<int>(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<int>(m_tokens->small() * m_scale) : 0;
}
int FontSize::smaller() const {
return m_tokens ? static_cast<int>(m_tokens->smaller() * m_scale) : 0;
}
int FontSize::normal() const {
return m_tokens ? static_cast<int>(m_tokens->normal() * m_scale) : 0;
}
int FontSize::larger() const {
return m_tokens ? static_cast<int>(m_tokens->larger() * m_scale) : 0;
}
int FontSize::large() const {
return m_tokens ? static_cast<int>(m_tokens->large() * m_scale) : 0;
}
int FontSize::extraLarge() const {
return m_tokens ? static_cast<int>(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<int>(m_tokens->small() * m_scale) : 0;
}
int AnimDurations::normal() const {
return m_tokens ? static_cast<int>(m_tokens->normal() * m_scale) : 0;
}
int AnimDurations::large() const {
return m_tokens ? static_cast<int>(m_tokens->large() * m_scale) : 0;
}
int AnimDurations::extraLarge() const {
return m_tokens ? static_cast<int>(m_tokens->extraLarge() * m_scale) : 0;
}
int AnimDurations::expressiveFastSpatial() const {
return m_tokens ? static_cast<int>(m_tokens->expressiveFastSpatial() * m_scale) : 0;
}
int AnimDurations::expressiveDefaultSpatial() const {
return m_tokens ? static_cast<int>(m_tokens->expressiveDefaultSpatial() * m_scale) : 0;
}
int AnimDurations::expressiveSlowSpatial() const {
return m_tokens ? static_cast<int>(m_tokens->expressiveSlowSpatial() * m_scale) : 0;
}
} // namespace caelestia::config

View file

@ -6,76 +6,93 @@
namespace caelestia::config { namespace caelestia::config {
// Forward declare token types from advancedconfig.hpp
class RoundingTokens;
class SpacingTokens;
class PaddingTokens;
class FontSizeTokens;
class AnimDurationTokens;
class AppearanceRounding : public ConfigObject { class AppearanceRounding : public ConfigObject {
Q_OBJECT Q_OBJECT
CONFIG_PROPERTY(qreal, scale, 1) CONFIG_PROPERTY(qreal, scale, 1)
Q_PROPERTY(int small READ small NOTIFY scaleChanged) Q_PROPERTY(int small READ small NOTIFY valuesChanged)
Q_PROPERTY(int normal READ normal NOTIFY scaleChanged) Q_PROPERTY(int normal READ normal NOTIFY valuesChanged)
Q_PROPERTY(int large READ large NOTIFY scaleChanged) Q_PROPERTY(int large READ large NOTIFY valuesChanged)
Q_PROPERTY(int full READ full NOTIFY scaleChanged) Q_PROPERTY(int full READ full NOTIFY valuesChanged)
public: public:
explicit AppearanceRounding(QObject* parent = nullptr) explicit AppearanceRounding(QObject* parent = nullptr)
: ConfigObject(parent) {} : ConfigObject(parent) {}
[[nodiscard]] int small() const { return static_cast<int>(12 * m_scale); } void bindTokens(RoundingTokens* tokens);
[[nodiscard]] int normal() const { return static_cast<int>(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<int>(25 * m_scale); } Q_SIGNAL void valuesChanged();
[[nodiscard]] int full() const { return static_cast<int>(1000 * m_scale); } private:
RoundingTokens* m_tokens = nullptr;
}; };
class AppearanceSpacing : public ConfigObject { class AppearanceSpacing : public ConfigObject {
Q_OBJECT Q_OBJECT
CONFIG_PROPERTY(qreal, scale, 1) CONFIG_PROPERTY(qreal, scale, 1)
Q_PROPERTY(int small READ small NOTIFY scaleChanged) Q_PROPERTY(int small READ small NOTIFY valuesChanged)
Q_PROPERTY(int smaller READ smaller NOTIFY scaleChanged) Q_PROPERTY(int smaller READ smaller NOTIFY valuesChanged)
Q_PROPERTY(int normal READ normal NOTIFY scaleChanged) Q_PROPERTY(int normal READ normal NOTIFY valuesChanged)
Q_PROPERTY(int larger READ larger NOTIFY scaleChanged) Q_PROPERTY(int larger READ larger NOTIFY valuesChanged)
Q_PROPERTY(int large READ large NOTIFY scaleChanged) Q_PROPERTY(int large READ large NOTIFY valuesChanged)
public: public:
explicit AppearanceSpacing(QObject* parent = nullptr) explicit AppearanceSpacing(QObject* parent = nullptr)
: ConfigObject(parent) {} : ConfigObject(parent) {}
[[nodiscard]] int small() const { return static_cast<int>(7 * m_scale); } void bindTokens(SpacingTokens* tokens);
[[nodiscard]] int smaller() const { return static_cast<int>(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<int>(12 * m_scale); } Q_SIGNAL void valuesChanged();
[[nodiscard]] int larger() const { return static_cast<int>(15 * m_scale); } private:
SpacingTokens* m_tokens = nullptr;
[[nodiscard]] int large() const { return static_cast<int>(20 * m_scale); }
}; };
class AppearancePadding : public ConfigObject { class AppearancePadding : public ConfigObject {
Q_OBJECT Q_OBJECT
CONFIG_PROPERTY(qreal, scale, 1) CONFIG_PROPERTY(qreal, scale, 1)
Q_PROPERTY(int small READ small NOTIFY scaleChanged) Q_PROPERTY(int small READ small NOTIFY valuesChanged)
Q_PROPERTY(int smaller READ smaller NOTIFY scaleChanged) Q_PROPERTY(int smaller READ smaller NOTIFY valuesChanged)
Q_PROPERTY(int normal READ normal NOTIFY scaleChanged) Q_PROPERTY(int normal READ normal NOTIFY valuesChanged)
Q_PROPERTY(int larger READ larger NOTIFY scaleChanged) Q_PROPERTY(int larger READ larger NOTIFY valuesChanged)
Q_PROPERTY(int large READ large NOTIFY scaleChanged) Q_PROPERTY(int large READ large NOTIFY valuesChanged)
public: public:
explicit AppearancePadding(QObject* parent = nullptr) explicit AppearancePadding(QObject* parent = nullptr)
: ConfigObject(parent) {} : ConfigObject(parent) {}
[[nodiscard]] int small() const { return static_cast<int>(5 * m_scale); } void bindTokens(PaddingTokens* tokens);
[[nodiscard]] int smaller() const { return static_cast<int>(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<int>(10 * m_scale); } Q_SIGNAL void valuesChanged();
[[nodiscard]] int larger() const { return static_cast<int>(12 * m_scale); } private:
PaddingTokens* m_tokens = nullptr;
[[nodiscard]] int large() const { return static_cast<int>(15 * m_scale); }
}; };
class FontFamily : public ConfigObject { class FontFamily : public ConfigObject {
@ -94,28 +111,30 @@ class FontSize : public ConfigObject {
Q_OBJECT Q_OBJECT
CONFIG_PROPERTY(qreal, scale, 1) CONFIG_PROPERTY(qreal, scale, 1)
Q_PROPERTY(int small READ small NOTIFY scaleChanged) Q_PROPERTY(int small READ small NOTIFY valuesChanged)
Q_PROPERTY(int smaller READ smaller NOTIFY scaleChanged) Q_PROPERTY(int smaller READ smaller NOTIFY valuesChanged)
Q_PROPERTY(int normal READ normal NOTIFY scaleChanged) Q_PROPERTY(int normal READ normal NOTIFY valuesChanged)
Q_PROPERTY(int larger READ larger NOTIFY scaleChanged) Q_PROPERTY(int larger READ larger NOTIFY valuesChanged)
Q_PROPERTY(int large READ large NOTIFY scaleChanged) Q_PROPERTY(int large READ large NOTIFY valuesChanged)
Q_PROPERTY(int extraLarge READ extraLarge NOTIFY scaleChanged) Q_PROPERTY(int extraLarge READ extraLarge NOTIFY valuesChanged)
public: public:
explicit FontSize(QObject* parent = nullptr) explicit FontSize(QObject* parent = nullptr)
: ConfigObject(parent) {} : ConfigObject(parent) {}
[[nodiscard]] int small() const { return static_cast<int>(11 * m_scale); } void bindTokens(FontSizeTokens* tokens);
[[nodiscard]] int smaller() const { return static_cast<int>(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<int>(13 * m_scale); } Q_SIGNAL void valuesChanged();
[[nodiscard]] int larger() const { return static_cast<int>(15 * m_scale); } private:
FontSizeTokens* m_tokens = nullptr;
[[nodiscard]] int large() const { return static_cast<int>(18 * m_scale); }
[[nodiscard]] int extraLarge() const { return static_cast<int>(28 * m_scale); }
}; };
class AppearanceFont : public ConfigObject { class AppearanceFont : public ConfigObject {
@ -134,31 +153,32 @@ class AnimDurations : public ConfigObject {
Q_OBJECT Q_OBJECT
CONFIG_PROPERTY(qreal, scale, 1) CONFIG_PROPERTY(qreal, scale, 1)
Q_PROPERTY(int small READ small NOTIFY scaleChanged) Q_PROPERTY(int small READ small NOTIFY valuesChanged)
Q_PROPERTY(int normal READ normal NOTIFY scaleChanged) Q_PROPERTY(int normal READ normal NOTIFY valuesChanged)
Q_PROPERTY(int large READ large NOTIFY scaleChanged) Q_PROPERTY(int large READ large NOTIFY valuesChanged)
Q_PROPERTY(int extraLarge READ extraLarge NOTIFY scaleChanged) Q_PROPERTY(int extraLarge READ extraLarge NOTIFY valuesChanged)
Q_PROPERTY(int expressiveFastSpatial READ expressiveFastSpatial NOTIFY scaleChanged) Q_PROPERTY(int expressiveFastSpatial READ expressiveFastSpatial NOTIFY valuesChanged)
Q_PROPERTY(int expressiveDefaultSpatial READ expressiveDefaultSpatial NOTIFY scaleChanged) Q_PROPERTY(int expressiveDefaultSpatial READ expressiveDefaultSpatial NOTIFY valuesChanged)
Q_PROPERTY(int expressiveSlowSpatial READ expressiveSlowSpatial NOTIFY scaleChanged) Q_PROPERTY(int expressiveSlowSpatial READ expressiveSlowSpatial NOTIFY valuesChanged)
public: public:
explicit AnimDurations(QObject* parent = nullptr) explicit AnimDurations(QObject* parent = nullptr)
: ConfigObject(parent) {} : ConfigObject(parent) {}
[[nodiscard]] int small() const { return static_cast<int>(200 * m_scale); } void bindTokens(AnimDurationTokens* tokens);
[[nodiscard]] int normal() const { return static_cast<int>(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<int>(600 * m_scale); } Q_SIGNAL void valuesChanged();
[[nodiscard]] int extraLarge() const { return static_cast<int>(1000 * m_scale); } private:
AnimDurationTokens* m_tokens = nullptr;
[[nodiscard]] int expressiveFastSpatial() const { return static_cast<int>(350 * m_scale); }
[[nodiscard]] int expressiveDefaultSpatial() const { return static_cast<int>(500 * m_scale); }
[[nodiscard]] int expressiveSlowSpatial() const { return static_cast<int>(650 * m_scale); }
}; };
class AppearanceAnim : public ConfigObject { class AppearanceAnim : public ConfigObject {

View file

@ -1,17 +1,15 @@
#include "config.hpp" #include "config.hpp"
#include <qdir.h>
#include <qfile.h>
#include <qfileinfo.h>
#include <qjsondocument.h>
#include <qjsonobject.h>
#include <qlogging.h>
#include <qstandardpaths.h> #include <qstandardpaths.h>
namespace caelestia::config { namespace caelestia::config {
static GlobalConfig* s_instance = nullptr; static GlobalConfig* s_instance = nullptr;
static QString configDir() {
return QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QStringLiteral("/caelestia/");
}
GlobalConfig::GlobalConfig(QObject* parent) GlobalConfig::GlobalConfig(QObject* parent)
: ConfigObject(parent) : ConfigObject(parent)
, m_appearance(new AppearanceConfig(this)) , m_appearance(new AppearanceConfig(this))
@ -34,91 +32,32 @@ GlobalConfig::GlobalConfig(QObject* parent)
, m_advanced(new AdvancedConfig(this)) { , m_advanced(new AdvancedConfig(this)) {
s_instance = this; s_instance = this;
m_configPath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + // Bind token base values from advanced config to appearance computed properties
QStringLiteral("/caelestia/shell.json"); 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); // Each has its own file backend
m_saveTimer.setInterval(500); setupFileBackend(configDir() + QStringLiteral("shell.json"));
connect(&m_saveTimer, &QTimer::timeout, this, &GlobalConfig::writeToFile); m_advanced->setupFileBackend(configDir() + QStringLiteral("advanced.json"));
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);
}
} }
GlobalConfig* GlobalConfig::instance() { GlobalConfig* GlobalConfig::instance() {
return s_instance; return s_instance;
} }
bool GlobalConfig::recentlySaved() const {
return m_recentlySaved;
}
void GlobalConfig::save() { void GlobalConfig::save() {
m_saveTimer.start(); saveToFile();
m_recentlySaved = true;
emit recentlySavedChanged();
m_cooldownTimer.start();
} }
void GlobalConfig::reload() { void GlobalConfig::reload() {
QFile file(m_configPath); reloadFromFile();
if (!file.open(QIODevice::ReadOnly)) {
qWarning() << "Config: failed to open" << m_configPath;
return;
} }
load(file.readAll()); // Config (attached type)
}
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::Config(QQuickItem* parent) Config::Config(QQuickItem* parent)
: QQuickItem(parent) {} : QQuickItem(parent) {}

View file

@ -1,12 +1,7 @@
#pragma once #pragma once
#include <qfilesystemwatcher.h>
#include <qjsondocument.h>
#include <qjsonobject.h>
#include <qobject.h>
#include <qqmlintegration.h> #include <qqmlintegration.h>
#include <qquickitem.h> #include <qquickitem.h>
#include <qtimer.h>
#include "advancedconfig.hpp" #include "advancedconfig.hpp"
#include "appearanceconfig.hpp" #include "appearanceconfig.hpp"
@ -51,33 +46,21 @@ class GlobalConfig : public ConfigObject {
CONFIG_SUBOBJECT(SidebarConfig, sidebar) CONFIG_SUBOBJECT(SidebarConfig, sidebar)
CONFIG_SUBOBJECT(ServiceConfig, services) CONFIG_SUBOBJECT(ServiceConfig, services)
CONFIG_SUBOBJECT(UserPaths, paths) CONFIG_SUBOBJECT(UserPaths, paths)
CONFIG_SUBOBJECT(AdvancedConfig, advanced) // advanced is NOT a CONFIG_SUBOBJECT — it has its own file backend
Q_PROPERTY(AdvancedConfig* advanced READ advanced CONSTANT)
Q_PROPERTY(bool recentlySaved READ recentlySaved NOTIFY recentlySavedChanged)
public: public:
explicit GlobalConfig(QObject* parent = nullptr); explicit GlobalConfig(QObject* parent = nullptr);
static GlobalConfig* instance(); static GlobalConfig* instance();
[[nodiscard]] bool recentlySaved() const; [[nodiscard]] AdvancedConfig* advanced() const { return m_advanced; }
Q_INVOKABLE void save(); Q_INVOKABLE void save();
Q_INVOKABLE void reload(); Q_INVOKABLE void reload();
signals:
void recentlySavedChanged();
private: private:
void load(const QByteArray& contents); AdvancedConfig* m_advanced = nullptr;
void onFileChanged();
void writeToFile();
bool m_recentlySaved = false;
QString m_configPath;
QFileSystemWatcher m_watcher;
QTimer m_saveTimer;
QTimer m_cooldownTimer;
}; };
class Config : public QQuickItem { class Config : public QQuickItem {

View file

@ -1,6 +1,10 @@
#include "configobject.hpp" #include "configobject.hpp"
#include <qdir.h>
#include <qfile.h>
#include <qfileinfo.h>
#include <qjsonarray.h> #include <qjsonarray.h>
#include <qjsondocument.h>
#include <qjsonvalue.h> #include <qjsonvalue.h>
#include <qloggingcategory.h> #include <qloggingcategory.h>
#include <qmetaobject.h> #include <qmetaobject.h>
@ -104,4 +108,74 @@ QJsonObject ConfigObject::toJsonObject() const {
return obj; 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 } // namespace caelestia::config

View file

@ -1,10 +1,10 @@
#pragma once #pragma once
#include <qfilesystemwatcher.h>
#include <qjsonobject.h> #include <qjsonobject.h>
#include <qobject.h> #include <qobject.h>
#include <qqmlintegration.h> #include <qqmlintegration.h>
#include <qtimer.h>
#include <type_traits>
// Declares a serialized config property with getter, setter (change-detected), signal, and member. // Declares a serialized config property with getter, setter (change-detected), signal, and member.
#define CONFIG_PROPERTY(Type, name, ...) \ #define CONFIG_PROPERTY(Type, name, ...) \
@ -16,7 +16,7 @@ public:
} \ } \
void set_##name(const Type& val) { \ void set_##name(const Type& val) { \
if (caelestia::config::ConfigObject::updateMember(m_##name, val)) \ if (caelestia::config::ConfigObject::updateMember(m_##name, val)) \
emit name##Changed(); \ Q_EMIT name##Changed(); \
} \ } \
Q_SIGNAL void name##Changed(); \ Q_SIGNAL void name##Changed(); \
\ \
@ -47,6 +47,14 @@ public:
void loadFromJson(const QJsonObject& obj); void loadFromJson(const QJsonObject& obj);
[[nodiscard]] QJsonObject toJsonObject() const; [[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 <typename T> static bool updateMember(T& member, const T& value) { template <typename T> static bool updateMember(T& member, const T& value) {
if constexpr (std::is_floating_point_v<T>) { if constexpr (std::is_floating_point_v<T>) {
if (qFuzzyCompare(member + 1.0, value + 1.0)) if (qFuzzyCompare(member + 1.0, value + 1.0))
@ -58,6 +66,16 @@ public:
member = value; member = value;
return true; 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 } // namespace caelestia::config