core/variants: restructure Variants to match the design of Repeater

This commit is contained in:
outfoxxed 2024-03-14 04:46:44 -07:00
parent ffbdac9977
commit 48156a55b3
Signed by: outfoxxed
GPG key ID: 4C88A185FB89301E
3 changed files with 111 additions and 52 deletions

View file

@ -83,12 +83,12 @@ class QuickshellGlobal: public QObject {
/// ```qml /// ```qml
/// ShellRoot { /// ShellRoot {
/// Variants { /// Variants {
/// ShellWindow {
/// // ...
/// }
///
/// // see Variants for details /// // see Variants for details
/// variants: Quickshell.screens.map(screen => ({ screen })) /// variants: Quickshell.screens
/// PanelWindow {
/// property var modelData
/// screen: modelData
/// }
/// } /// }
/// } /// }
/// ``` /// ```

View file

@ -6,6 +6,9 @@
#include <qlogging.h> #include <qlogging.h>
#include <qobject.h> #include <qobject.h>
#include <qqmlengine.h> #include <qqmlengine.h>
#include <qqmllist.h>
#include <qtmetamacros.h>
#include <qvariant.h>
#include "reload.hpp" #include "reload.hpp"
@ -17,27 +20,44 @@ void Variants::onReload(QObject* oldInstance) {
if (old != nullptr) { if (old != nullptr) {
auto& values = old->instances.values; auto& values = old->instances.values;
int matchcount = 0; if (variant.canConvert<QVariantMap>()) {
int matchi = 0; auto variantMap = variant.value<QVariantMap>();
int i = 0;
for (auto& [valueSet, _]: values) { int matchcount = 0;
int count = 0; int matchi = 0;
for (auto& [k, v]: variant.toStdMap()) { int i = 0;
if (valueSet.contains(k) && valueSet.value(k) == v) { for (auto& [value, _]: values) {
count++; if (!value.canConvert<QVariantMap>()) continue;
auto valueSet = value.value<QVariantMap>();
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) { if (matchcount > 0) {
matchcount = count; oldInstance = values.takeAt(matchi).second;
matchi = i;
} }
} else {
int i = 0;
for (auto& [value, _]: values) {
if (variant == value) {
oldInstance = values.takeAt(i).second;
break;
}
i++; i++;
} }
if (matchcount > 0) {
oldInstance = values.takeAt(matchi).second;
} }
} }
@ -50,9 +70,32 @@ void Variants::onReload(QObject* oldInstance) {
this->loaded = true; this->loaded = true;
} }
void Variants::setVariants(QVariantList variants) { QVariant Variants::model() const { return QVariant::fromValue(this->mModel); }
this->mVariants = std::move(variants);
void Variants::setModel(const QVariant& model) {
if (model.canConvert<QVariantList>()) {
this->mModel = model.value<QVariantList>();
} else if (model.canConvert<QQmlListReference>()) {
auto list = model.value<QQmlListReference>();
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(); this->updateVariants();
emit this->modelChanged();
} }
void Variants::componentComplete() { void Variants::componentComplete() {
@ -61,14 +104,14 @@ void Variants::componentComplete() {
} }
void Variants::updateVariants() { void Variants::updateVariants() {
if (this->mComponent == nullptr) { if (this->mDelegate == nullptr) {
qWarning() << "Variants instance does not have a component specified"; qWarning() << "Variants instance does not have a component specified";
return; return;
} }
// clean up removed entries // clean up removed entries
for (auto iter = this->instances.values.begin(); iter < this->instances.values.end();) { 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++; iter++;
} else { } else {
iter->second->deleteLater(); iter->second->deleteLater();
@ -76,32 +119,31 @@ void Variants::updateVariants() {
} }
} }
for (auto iter = this->mVariants.begin(); iter < this->mVariants.end(); iter++) { for (auto iter = this->mModel.begin(); iter < this->mModel.end(); iter++) {
auto& variantObj = *iter; auto& variant = *iter;
if (!variantObj.canConvert<QVariantMap>()) { for (auto iter2 = this->mModel.begin(); iter2 < iter; iter2++) {
qWarning() << "value passed to Variants is not an object and will be ignored:" << variantObj; if (*iter2 == variant) {
} else { qWarning() << "same value specified twice in Variants, duplicates will be ignored:"
auto variant = variantObj.value<QVariantMap>(); << variant;
goto outer;
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;
}
} }
}
{
if (this->instances.contains(variant)) { if (this->instances.contains(variant)) {
continue; // we dont need to recreate this one continue; // we dont need to recreate this one
} }
auto* instance = this->mComponent->createWithInitialProperties( auto variantMap = QVariantMap();
variant, variantMap.insert("modelData", variant);
QQmlEngine::contextForObject(this->mComponent)
auto* instance = this->mDelegate->createWithInitialProperties(
variantMap,
QQmlEngine::contextForObject(this->mDelegate)
); );
if (instance == nullptr) { 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; qWarning() << "failed to create variant with object" << variant;
continue; continue;
} }

View file

@ -8,7 +8,9 @@
#include <qqmlcomponent.h> #include <qqmlcomponent.h>
#include <qqmlparserstatus.h> #include <qqmlparserstatus.h>
#include <qtmetamacros.h> #include <qtmetamacros.h>
#include <qvariant.h>
#include "doc.hpp"
#include "reload.hpp" #include "reload.hpp"
// extremely inefficient map // extremely inefficient map
@ -22,39 +24,54 @@ public:
QList<QPair<K, V>> values; QList<QPair<K, V>> 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. /// 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 /// See [Quickshell.screens] for an example of using `Variants` to create copies of a window per
/// screen. /// screen.
/// ///
/// > [!WARNING] BUG: Variants currently fails to reload children if the variant set is changed as /// > [!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) /// > 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 /// [Quickshell.screens]: ../quickshell#prop.screens
class Variants: public Reloadable { class Variants: public Reloadable {
Q_OBJECT; Q_OBJECT;
/// The component to create instances of /// The component to create instances of.
Q_PROPERTY(QQmlComponent* component MEMBER mComponent); ///
/// 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. /// 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. /// Each set creates an instance of the component, which are updated when the input sets update.
Q_PROPERTY(QList<QVariant> variants MEMBER mVariants WRITE setVariants); QSDOC_PROPERTY_OVERRIDE(QList<QVariant> model READ model WRITE setModel NOTIFY modelChanged);
Q_CLASSINFO("DefaultProperty", "component"); QSDOC_HIDE Q_PROPERTY(QVariant model READ model WRITE setModel NOTIFY modelChanged);
Q_CLASSINFO("DefaultProperty", "delegate");
QML_ELEMENT; QML_ELEMENT;
public: public:
explicit Variants(QObject* parent = nullptr): Reloadable(parent) {} explicit Variants(QObject* parent = nullptr): Reloadable(parent) {}
void onReload(QObject* oldInstance) override; void onReload(QObject* oldInstance) override;
void componentComplete() override; void componentComplete() override;
[[nodiscard]] QVariant model() const;
void setModel(const QVariant& model);
signals:
void modelChanged();
private: private:
void setVariants(QVariantList variants);
void updateVariants(); void updateVariants();
QQmlComponent* mComponent = nullptr; QQmlComponent* mDelegate = nullptr;
QVariantList mVariants; QVariantList mModel;
AwfulMap<QVariantMap, QObject*> instances; AwfulMap<QVariant, QObject*> instances;
bool loaded = false; bool loaded = false;
}; };