83 lines
3.7 KiB
C++
83 lines
3.7 KiB
C++
#pragma once
|
|
|
|
#include <QObject>
|
|
#include <QJsonObject>
|
|
#include <QJsonValue>
|
|
#include <QMetaProperty>
|
|
#include <QMetaMethod>
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CFG_PROPERTY / CFG_SECTION
|
|
//
|
|
// These generate exactly the boilerplate you'd write by hand for a
|
|
// Q_PROPERTY with a backing member, getter, setter and NOTIFY signal - just
|
|
// in one line instead of ~8. Because everything they generate is a normal
|
|
// compiled getter/setter, there is *zero* extra runtime cost vs hand-written
|
|
// code: reading Config.appearance.anim.sessionGifSpeed from QML calls a
|
|
// plain inline getter. Reflection (QMetaObject) is only walked at load time,
|
|
// save time, and once at construction time to wire change signals - never
|
|
// on the read/write path itself.
|
|
//
|
|
// CFG_PROPERTY(type, name, Name)
|
|
// - type: C++ type (bool, int, qreal, QString, QVariantList, ...)
|
|
// - name: property name as it must appear in the JSON file (camelCase,
|
|
// or whatever the JSON actually uses - it MUST match exactly)
|
|
// - Name: same name, capitalized, used to build setName()/NameChanged()
|
|
//
|
|
// CFG_SECTION(Type, name, Name)
|
|
// - Declares a child ConfigSection* property (e.g. Anim inside Appearance)
|
|
// - The pointer itself is CONSTANT (never reassigned); the object it
|
|
// points to is what actually changes.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#define CFG_PROPERTY(type, name, Name) \
|
|
Q_PROPERTY(type name READ name WRITE set##Name NOTIFY Name##Changed) \
|
|
public: \
|
|
type name() const { return m_##name; } \
|
|
void set##Name(const type &value) { \
|
|
if (m_##name == value) return; \
|
|
m_##name = value; \
|
|
Q_EMIT Name##Changed(); \
|
|
} \
|
|
Q_SIGNALS: \
|
|
void Name##Changed(); \
|
|
private: \
|
|
type m_##name{};
|
|
|
|
#define CFG_SECTION(Type, name, Name) \
|
|
public: \
|
|
Q_PROPERTY(Type *name READ name CONSTANT) \
|
|
Type *name() const { return m_##name; } \
|
|
private: \
|
|
Type *m_##name = nullptr;
|
|
|
|
class ConfigSection : public QObject {
|
|
Q_OBJECT
|
|
public:
|
|
explicit ConfigSection(QObject *parent = nullptr);
|
|
|
|
// Populate this section (and all child sections) from JSON. Unknown
|
|
// keys in obj are ignored; missing keys keep their current value.
|
|
void fromJson(const QJsonObject &obj);
|
|
// Serialize this section (and all child sections) to JSON.
|
|
QJsonObject toJson() const;
|
|
|
|
Q_SIGNALS:
|
|
// Emitted whenever any property in this section, or any nested section,
|
|
// changes. Config listens on the root section to know a save is needed,
|
|
// without every leaf class needing to know about Config.
|
|
void changed();
|
|
|
|
protected:
|
|
// Call once, at the end of every subclass constructor, after all child
|
|
// ConfigSection* members have been constructed (new'd with `this` as
|
|
// parent). Sets up the reflection-based signal forwarding.
|
|
void wireSignals();
|
|
|
|
private Q_SLOTS:
|
|
void onPropertyChanged();
|
|
|
|
private:
|
|
bool m_wired = false;
|
|
};
|