diff --git a/src/core/qmlglobal.hpp b/src/core/qmlglobal.hpp index df852a1..8d43bb8 100644 --- a/src/core/qmlglobal.hpp +++ b/src/core/qmlglobal.hpp @@ -83,12 +83,12 @@ class QuickshellGlobal: public QObject { /// ```qml /// ShellRoot { /// Variants { - /// ShellWindow { - /// // ... - /// } - /// /// // see Variants for details - /// variants: Quickshell.screens.map(screen => ({ screen })) + /// variants: Quickshell.screens + /// PanelWindow { + /// property var modelData + /// screen: modelData + /// } /// } /// } /// ``` diff --git a/src/core/variants.cpp b/src/core/variants.cpp index eafd90a..8f251c1 100644 --- a/src/core/variants.cpp +++ b/src/core/variants.cpp @@ -6,6 +6,9 @@ #include #include #include +#include +#include +#include #include "reload.hpp" @@ -17,27 +20,44 @@ void Variants::onReload(QObject* oldInstance) { if (old != nullptr) { auto& values = old->instances.values; - int matchcount = 0; - int matchi = 0; - int i = 0; - for (auto& [valueSet, _]: values) { - int count = 0; - for (auto& [k, v]: variant.toStdMap()) { - if (valueSet.contains(k) && valueSet.value(k) == v) { - count++; + if (variant.canConvert()) { + auto variantMap = variant.value(); + + int matchcount = 0; + int matchi = 0; + int i = 0; + for (auto& [value, _]: values) { + if (!value.canConvert()) continue; + auto valueSet = value.value(); + + int count = 0; + for (auto [k, v]: variantMap.asKeyValueRange()) { + if (valueSet.contains(k) && valueSet.value(k) == v) { + count++; + } } + + if (count > matchcount) { + matchcount = count; + matchi = i; + } + + i++; } - if (count > matchcount) { - matchcount = count; - matchi = i; + if (matchcount > 0) { + oldInstance = values.takeAt(matchi).second; } + } else { + int i = 0; + for (auto& [value, _]: values) { + if (variant == value) { + oldInstance = values.takeAt(i).second; + break; + } - i++; - } - - if (matchcount > 0) { - oldInstance = values.takeAt(matchi).second; + i++; + } } } @@ -50,9 +70,32 @@ void Variants::onReload(QObject* oldInstance) { this->loaded = true; } -void Variants::setVariants(QVariantList variants) { - this->mVariants = std::move(variants); +QVariant Variants::model() const { return QVariant::fromValue(this->mModel); } + +void Variants::setModel(const QVariant& model) { + if (model.canConvert()) { + this->mModel = model.value(); + } else if (model.canConvert()) { + auto list = model.value(); + if (!list.isReadable()) { + qWarning() << "Non readable list" << model << "assigned to Variants.model, Ignoring."; + return; + } + + QVariantList model; + auto size = list.count(); + for (auto i = 0; i < size; i++) { + model.push_back(QVariant::fromValue(list.at(i))); + } + + this->mModel = std::move(model); + } else { + qWarning() << "Non list data" << model << "assigned to Variants.model, Ignoring."; + return; + } + this->updateVariants(); + emit this->modelChanged(); } void Variants::componentComplete() { @@ -61,14 +104,14 @@ void Variants::componentComplete() { } void Variants::updateVariants() { - if (this->mComponent == nullptr) { + if (this->mDelegate == nullptr) { qWarning() << "Variants instance does not have a component specified"; return; } // clean up removed entries for (auto iter = this->instances.values.begin(); iter < this->instances.values.end();) { - if (this->mVariants.contains(iter->first)) { + if (this->mModel.contains(iter->first)) { iter++; } else { iter->second->deleteLater(); @@ -76,32 +119,31 @@ void Variants::updateVariants() { } } - for (auto iter = this->mVariants.begin(); iter < this->mVariants.end(); iter++) { - auto& variantObj = *iter; - if (!variantObj.canConvert()) { - qWarning() << "value passed to Variants is not an object and will be ignored:" << variantObj; - } else { - auto variant = variantObj.value(); - - for (auto iter2 = this->mVariants.begin(); iter2 < iter; iter2++) { - if (*iter2 == variantObj) { - qWarning() << "same value specified twice in Variants, duplicates will be ignored:" - << variantObj; - goto outer; - } + for (auto iter = this->mModel.begin(); iter < this->mModel.end(); iter++) { + auto& variant = *iter; + for (auto iter2 = this->mModel.begin(); iter2 < iter; iter2++) { + if (*iter2 == variant) { + qWarning() << "same value specified twice in Variants, duplicates will be ignored:" + << variant; + goto outer; } + } + { if (this->instances.contains(variant)) { continue; // we dont need to recreate this one } - auto* instance = this->mComponent->createWithInitialProperties( - variant, - QQmlEngine::contextForObject(this->mComponent) + auto variantMap = QVariantMap(); + variantMap.insert("modelData", variant); + + auto* instance = this->mDelegate->createWithInitialProperties( + variantMap, + QQmlEngine::contextForObject(this->mDelegate) ); if (instance == nullptr) { - qWarning() << this->mComponent->errorString().toStdString().c_str(); + qWarning() << this->mDelegate->errorString().toStdString().c_str(); qWarning() << "failed to create variant with object" << variant; continue; } diff --git a/src/core/variants.hpp b/src/core/variants.hpp index f84a1d7..cdfea7e 100644 --- a/src/core/variants.hpp +++ b/src/core/variants.hpp @@ -8,7 +8,9 @@ #include #include #include +#include +#include "doc.hpp" #include "reload.hpp" // extremely inefficient map @@ -22,39 +24,54 @@ public: QList> values; }; -///! Creates instances of a component based on a given set of variants. +///! Creates instances of a component based on a given model. /// Creates and destroys instances of the given component when the given property changes. /// +/// `Variants` is similar to [Repeater] except it is for *non Item* objects, and acts as +/// a reload scope. +/// +/// Each non duplicate value passed to [model](#prop.model) will create a new instance of +/// [delegate](#prop.delegate) with its `modelData` property set to that value. +/// /// See [Quickshell.screens] for an example of using `Variants` to create copies of a window per /// screen. /// /// > [!WARNING] BUG: Variants currently fails to reload children if the variant set is changed as /// > it is instantiated. (usually due to a mutation during variant creation) /// +/// [Repeater]: https://doc.qt.io/qt-6/qml-qtquick-repeater.html /// [Quickshell.screens]: ../quickshell#prop.screens class Variants: public Reloadable { Q_OBJECT; - /// The component to create instances of - Q_PROPERTY(QQmlComponent* component MEMBER mComponent); + /// The component to create instances of. + /// + /// The delegate should define a `modelData` property that will be popuplated with a value + /// from the [model](#prop.model). + Q_PROPERTY(QQmlComponent* delegate MEMBER mDelegate); /// The list of sets of properties to create instances with. /// Each set creates an instance of the component, which are updated when the input sets update. - Q_PROPERTY(QList variants MEMBER mVariants WRITE setVariants); - Q_CLASSINFO("DefaultProperty", "component"); + QSDOC_PROPERTY_OVERRIDE(QList model READ model WRITE setModel NOTIFY modelChanged); + QSDOC_HIDE Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged); + Q_CLASSINFO("DefaultProperty", "delegate"); QML_ELEMENT; public: explicit Variants(QObject* parent = nullptr): Reloadable(parent) {} void onReload(QObject* oldInstance) override; - void componentComplete() override; + [[nodiscard]] QVariant model() const; + void setModel(const QVariant& model); + +signals: + void modelChanged(); + private: - void setVariants(QVariantList variants); void updateVariants(); - QQmlComponent* mComponent = nullptr; - QVariantList mVariants; - AwfulMap instances; + QQmlComponent* mDelegate = nullptr; + QVariantList mModel; + AwfulMap instances; bool loaded = false; };