#pragma once #include #include #include #include #include // --------------------------------------------------------------------------- // 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; };