forked from quickshell/quickshell
		
	core/variants: restructure Variants to match the design of Repeater
This commit is contained in:
		
							parent
							
								
									ffbdac9977
								
							
						
					
					
						commit
						48156a55b3
					
				
					 3 changed files with 111 additions and 52 deletions
				
			
		| 
						 | 
					@ -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
 | 
				
			||||||
 | 
						///     }
 | 
				
			||||||
	///   }
 | 
						///   }
 | 
				
			||||||
	/// }
 | 
						/// }
 | 
				
			||||||
	/// ```
 | 
						/// ```
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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,12 +20,18 @@ void Variants::onReload(QObject* oldInstance) {
 | 
				
			||||||
		if (old != nullptr) {
 | 
							if (old != nullptr) {
 | 
				
			||||||
			auto& values = old->instances.values;
 | 
								auto& values = old->instances.values;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (variant.canConvert<QVariantMap>()) {
 | 
				
			||||||
 | 
									auto variantMap = variant.value<QVariantMap>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				int matchcount = 0;
 | 
									int matchcount = 0;
 | 
				
			||||||
				int matchi = 0;
 | 
									int matchi = 0;
 | 
				
			||||||
				int i = 0;
 | 
									int i = 0;
 | 
				
			||||||
			for (auto& [valueSet, _]: values) {
 | 
									for (auto& [value, _]: values) {
 | 
				
			||||||
 | 
										if (!value.canConvert<QVariantMap>()) continue;
 | 
				
			||||||
 | 
										auto valueSet = value.value<QVariantMap>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					int count = 0;
 | 
										int count = 0;
 | 
				
			||||||
				for (auto& [k, v]: variant.toStdMap()) {
 | 
										for (auto [k, v]: variantMap.asKeyValueRange()) {
 | 
				
			||||||
						if (valueSet.contains(k) && valueSet.value(k) == v) {
 | 
											if (valueSet.contains(k) && valueSet.value(k) == v) {
 | 
				
			||||||
							count++;
 | 
												count++;
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
| 
						 | 
					@ -39,6 +48,17 @@ void Variants::onReload(QObject* oldInstance) {
 | 
				
			||||||
				if (matchcount > 0) {
 | 
									if (matchcount > 0) {
 | 
				
			||||||
					oldInstance = values.takeAt(matchi).second;
 | 
										oldInstance = values.takeAt(matchi).second;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									int i = 0;
 | 
				
			||||||
 | 
									for (auto& [value, _]: values) {
 | 
				
			||||||
 | 
										if (variant == value) {
 | 
				
			||||||
 | 
											oldInstance = values.takeAt(i).second;
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										i++;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		auto* instance = qobject_cast<Reloadable*>(instanceObj);
 | 
							auto* instance = qobject_cast<Reloadable*>(instanceObj);
 | 
				
			||||||
| 
						 | 
					@ -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 {
 | 
					 | 
				
			||||||
			auto variant = variantObj.value<QVariantMap>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			for (auto iter2 = this->mVariants.begin(); iter2 < iter; iter2++) {
 | 
					 | 
				
			||||||
				if (*iter2 == variantObj) {
 | 
					 | 
				
			||||||
				qWarning() << "same value specified twice in Variants, duplicates will be ignored:"
 | 
									qWarning() << "same value specified twice in Variants, duplicates will be ignored:"
 | 
				
			||||||
					           << variantObj;
 | 
									           << variant;
 | 
				
			||||||
				goto outer;
 | 
									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;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue