forked from quickshell/quickshell
		
	widgets: add wrapper components and managers
This commit is contained in:
		
							parent
							
								
									79fca3cab8
								
							
						
					
					
						commit
						401ee4cec6
					
				
					 8 changed files with 578 additions and 0 deletions
				
			
		| 
						 | 
				
			
			@ -1,5 +1,7 @@
 | 
			
		|||
qt_add_library(quickshell-widgets STATIC
 | 
			
		||||
	cliprect.cpp
 | 
			
		||||
	wrapper.cpp
 | 
			
		||||
	marginwrapper.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
qt_add_qml_module(quickshell-widgets
 | 
			
		||||
| 
						 | 
				
			
			@ -8,6 +10,8 @@ qt_add_qml_module(quickshell-widgets
 | 
			
		|||
	QML_FILES
 | 
			
		||||
		IconImage.qml
 | 
			
		||||
		ClippingRectangle.qml
 | 
			
		||||
		WrapperItem.qml
 | 
			
		||||
		WrapperRectangle.qml
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
qt6_add_shaders(quickshell-widgets "widgets-cliprect"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										44
									
								
								src/widgets/WrapperItem.qml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/widgets/WrapperItem.qml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,44 @@
 | 
			
		|||
import QtQuick
 | 
			
		||||
import Quickshell.Widgets
 | 
			
		||||
 | 
			
		||||
///! Item that handles sizes and positioning for a single visual child.
 | 
			
		||||
/// This component is useful when you need to wrap a single component in
 | 
			
		||||
/// an item, or give a single component a margin. See [QtQuick.Layouts]
 | 
			
		||||
/// for positioning multiple items.
 | 
			
		||||
///
 | 
			
		||||
/// > [!NOTE] WrapperItem is a @@MarginWrapperManager based component.
 | 
			
		||||
/// > You should read its documentation as well.
 | 
			
		||||
///
 | 
			
		||||
/// ### Example: Adding a margin to an item
 | 
			
		||||
/// The snippet below adds a 10px margin to all sides of the @@QtQuick.Text item.
 | 
			
		||||
///
 | 
			
		||||
/// ```qml
 | 
			
		||||
/// WrapperItem {
 | 
			
		||||
///   margin: 10
 | 
			
		||||
///
 | 
			
		||||
///   @@QtQuick.Text { text: "Hello!" }
 | 
			
		||||
/// }
 | 
			
		||||
/// ```
 | 
			
		||||
///
 | 
			
		||||
/// > [!NOTE] The child item can be specified by writing it inline in the wrapper,
 | 
			
		||||
/// > as in the example above, or by using the @@child property. See
 | 
			
		||||
/// > @@WrapperManager.child for details.
 | 
			
		||||
///
 | 
			
		||||
/// > [!WARNING] You should not set @@Item.x, @@Item.y, @@Item.width,
 | 
			
		||||
/// > @@Item.height or @@Item.anchors on the child item, as they are used
 | 
			
		||||
/// > by WrapperItem to position it. Instead set @@Item.implicitWidth and
 | 
			
		||||
/// > @@Item.implicitHeight.
 | 
			
		||||
///
 | 
			
		||||
/// [QtQuick.Layouts]: https://doc.qt.io/qt-6/qtquicklayouts-index.html
 | 
			
		||||
Item {
 | 
			
		||||
	/// The minimum margin between the child item and the WrapperItem's edges.
 | 
			
		||||
	/// Defaults to 0.
 | 
			
		||||
	property /*real*/alias margin: manager.margin
 | 
			
		||||
	/// If the child item should be resized larger than its implicit size if
 | 
			
		||||
	/// the WrapperItem is resized larger than its implicit size. Defaults to false.
 | 
			
		||||
	property /*bool*/alias resizeChild: manager.resizeChild
 | 
			
		||||
	/// See @@WrapperManager.child for details.
 | 
			
		||||
	property /*Item*/alias child: manager.child
 | 
			
		||||
 | 
			
		||||
	MarginWrapperManager { id: manager }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								src/widgets/WrapperRectangle.qml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/widgets/WrapperRectangle.qml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,35 @@
 | 
			
		|||
import QtQuick
 | 
			
		||||
import Quickshell.Widgets
 | 
			
		||||
 | 
			
		||||
///! Rectangle that handles sizes and positioning for a single visual child.
 | 
			
		||||
/// This component is useful for adding a border or background rectangle to
 | 
			
		||||
/// a child item.
 | 
			
		||||
///
 | 
			
		||||
/// > [!NOTE] WrapperRectangle is a @@MarginWrapperManager based component.
 | 
			
		||||
/// > You should read its documentation as well.
 | 
			
		||||
///
 | 
			
		||||
/// > [!WARNING] You should not set @@Item.x, @@Item.y, @@Item.width,
 | 
			
		||||
/// > @@Item.height or @@Item.anchors on the child item, as they are used
 | 
			
		||||
/// > by WrapperItem to position it. Instead set @@Item.implicitWidth and
 | 
			
		||||
/// > @@Item.implicitHeight.
 | 
			
		||||
Rectangle {
 | 
			
		||||
	id: root
 | 
			
		||||
 | 
			
		||||
	/// If true (default), the rectangle's border width will be added
 | 
			
		||||
	/// to the margin.
 | 
			
		||||
	property bool contentInsideBorder: true
 | 
			
		||||
	/// The minimum margin between the child item and the WrapperRectangle's
 | 
			
		||||
	/// edges. If @@contentInsideBorder is true, this excludes the border,
 | 
			
		||||
	/// otherwise it includes it. Defaults to 0.
 | 
			
		||||
	property real margin: 0
 | 
			
		||||
	/// If the child item should be resized larger than its implicit size if
 | 
			
		||||
	/// the WrapperRectangle is resized larger than its implicit size. Defaults to false.
 | 
			
		||||
	property /*bool*/alias resizeChild: manager.resizeChild
 | 
			
		||||
	/// See @@WrapperManager.child for details.
 | 
			
		||||
	property alias child: manager.child
 | 
			
		||||
 | 
			
		||||
	MarginWrapperManager {
 | 
			
		||||
		id: manager
 | 
			
		||||
		margin: (root.contentInsideBorder ? root.border.width : 0) + root.margin
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										146
									
								
								src/widgets/marginwrapper.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								src/widgets/marginwrapper.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,146 @@
 | 
			
		|||
#include "marginwrapper.hpp"
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qquickitem.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qtypes.h>
 | 
			
		||||
 | 
			
		||||
#include "wrapper.hpp"
 | 
			
		||||
 | 
			
		||||
namespace qs::widgets {
 | 
			
		||||
 | 
			
		||||
MarginWrapperManager::MarginWrapperManager(QObject* parent): WrapperManager(parent) {
 | 
			
		||||
	QObject::connect(
 | 
			
		||||
	    this,
 | 
			
		||||
	    &WrapperManager::initializedChildChanged,
 | 
			
		||||
	    this,
 | 
			
		||||
	    &MarginWrapperManager::onChildChanged
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MarginWrapperManager::componentComplete() {
 | 
			
		||||
	if (this->mWrapper) {
 | 
			
		||||
		QObject::connect(
 | 
			
		||||
		    this->mWrapper,
 | 
			
		||||
		    &QQuickItem::widthChanged,
 | 
			
		||||
		    this,
 | 
			
		||||
		    &MarginWrapperManager::onWrapperWidthChanged
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		QObject::connect(
 | 
			
		||||
		    this->mWrapper,
 | 
			
		||||
		    &QQuickItem::heightChanged,
 | 
			
		||||
		    this,
 | 
			
		||||
		    &MarginWrapperManager::onWrapperHeightChanged
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->WrapperManager::componentComplete();
 | 
			
		||||
 | 
			
		||||
	if (!this->mChild) this->updateGeometry();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
qreal MarginWrapperManager::margin() const { return this->mMargin; }
 | 
			
		||||
 | 
			
		||||
void MarginWrapperManager::setMargin(qreal margin) {
 | 
			
		||||
	if (margin == this->mMargin) return;
 | 
			
		||||
	this->mMargin = margin;
 | 
			
		||||
	this->updateGeometry();
 | 
			
		||||
	emit this->marginChanged();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MarginWrapperManager::resizeChild() const { return this->mResizeChild; }
 | 
			
		||||
 | 
			
		||||
void MarginWrapperManager::setResizeChild(bool resizeChild) {
 | 
			
		||||
	if (resizeChild == this->mResizeChild) return;
 | 
			
		||||
	this->mResizeChild = resizeChild;
 | 
			
		||||
	this->updateGeometry();
 | 
			
		||||
	emit this->resizeChildChanged();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MarginWrapperManager::onChildChanged() {
 | 
			
		||||
	// QObject::disconnect in MarginWrapper handles disconnecting old item
 | 
			
		||||
 | 
			
		||||
	if (this->mChild) {
 | 
			
		||||
		QObject::connect(
 | 
			
		||||
		    this->mChild,
 | 
			
		||||
		    &QQuickItem::implicitWidthChanged,
 | 
			
		||||
		    this,
 | 
			
		||||
		    &MarginWrapperManager::onChildImplicitWidthChanged
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		QObject::connect(
 | 
			
		||||
		    this->mChild,
 | 
			
		||||
		    &QQuickItem::implicitHeightChanged,
 | 
			
		||||
		    this,
 | 
			
		||||
		    &MarginWrapperManager::onChildImplicitHeightChanged
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->updateGeometry();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
qreal MarginWrapperManager::targetChildWidth() const {
 | 
			
		||||
	auto max = this->mWrapper->width() - this->mMargin * 2;
 | 
			
		||||
 | 
			
		||||
	if (this->mResizeChild) return max;
 | 
			
		||||
	else return std::min(this->mChild->implicitWidth(), max);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
qreal MarginWrapperManager::targetChildHeight() const {
 | 
			
		||||
	auto max = this->mWrapper->height() - this->mMargin * 2;
 | 
			
		||||
 | 
			
		||||
	if (this->mResizeChild) return max;
 | 
			
		||||
	else return std::min(this->mChild->implicitHeight(), max);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
qreal MarginWrapperManager::targetChildX() const {
 | 
			
		||||
	if (this->mResizeChild) return this->mMargin;
 | 
			
		||||
	else return this->mWrapper->width() / 2 - this->mChild->implicitWidth() / 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
qreal MarginWrapperManager::targetChildY() const {
 | 
			
		||||
	if (this->mResizeChild) return this->mMargin;
 | 
			
		||||
	else return this->mWrapper->height() / 2 - this->mChild->implicitHeight() / 2;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MarginWrapperManager::onWrapperWidthChanged() {
 | 
			
		||||
	if (!this->mChild || !this->mWrapper) return;
 | 
			
		||||
	this->mChild->setX(this->targetChildX());
 | 
			
		||||
	this->mChild->setWidth(this->targetChildWidth());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MarginWrapperManager::onWrapperHeightChanged() {
 | 
			
		||||
	if (!this->mChild || !this->mWrapper) return;
 | 
			
		||||
	this->mChild->setY(this->targetChildY());
 | 
			
		||||
	this->mChild->setHeight(this->targetChildHeight());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MarginWrapperManager::onChildImplicitWidthChanged() {
 | 
			
		||||
	if (!this->mChild || !this->mWrapper) return;
 | 
			
		||||
	this->mWrapper->setImplicitWidth(this->mChild->implicitWidth() + this->mMargin * 2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MarginWrapperManager::onChildImplicitHeightChanged() {
 | 
			
		||||
	if (!this->mChild || !this->mWrapper) return;
 | 
			
		||||
	this->mWrapper->setImplicitHeight(this->mChild->implicitHeight() + this->mMargin * 2);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MarginWrapperManager::updateGeometry() {
 | 
			
		||||
	if (!this->mWrapper) return;
 | 
			
		||||
 | 
			
		||||
	if (this->mChild) {
 | 
			
		||||
		this->mWrapper->setImplicitWidth(this->mChild->implicitWidth() + this->mMargin * 2);
 | 
			
		||||
		this->mWrapper->setImplicitHeight(this->mChild->implicitHeight() + this->mMargin * 2);
 | 
			
		||||
		this->mChild->setX(this->targetChildX());
 | 
			
		||||
		this->mChild->setY(this->targetChildY());
 | 
			
		||||
		this->mChild->setWidth(this->targetChildWidth());
 | 
			
		||||
		this->mChild->setHeight(this->targetChildHeight());
 | 
			
		||||
	} else {
 | 
			
		||||
		this->mWrapper->setImplicitWidth(this->mMargin * 2);
 | 
			
		||||
		this->mWrapper->setImplicitHeight(this->mMargin * 2);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace qs::widgets
 | 
			
		||||
							
								
								
									
										75
									
								
								src/widgets/marginwrapper.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								src/widgets/marginwrapper.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,75 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qqmlintegration.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qtypes.h>
 | 
			
		||||
 | 
			
		||||
#include "wrapper.hpp"
 | 
			
		||||
 | 
			
		||||
namespace qs::widgets {
 | 
			
		||||
 | 
			
		||||
///! Helper object for applying sizes and margins to a single child item.
 | 
			
		||||
/// > [!NOTE] MarginWrapperManager is an extension of @@WrapperManager.
 | 
			
		||||
/// > You should read its documentation to understand wrapper types.
 | 
			
		||||
///
 | 
			
		||||
/// MarginWrapperManager can be used to apply margins to a child item,
 | 
			
		||||
/// in addition to handling the size / implicit size relationship
 | 
			
		||||
/// between the parent and the child. @@WrapperItem and @@WrapperRectangle
 | 
			
		||||
/// exist for Item and Rectangle implementations respectively.
 | 
			
		||||
///
 | 
			
		||||
/// > [!WARNING] MarginWrapperManager based types set the child item's
 | 
			
		||||
/// > @@QtQuick.Item.x, @@QtQuick.Item.y, @@QtQuick.Item.width, @@QtQuick.Item.height
 | 
			
		||||
/// > or @@QtQuick.Item.anchors properties. Do not set them yourself,
 | 
			
		||||
/// > instead set @@Item.implicitWidth and @@Item.implicitHeight.
 | 
			
		||||
///
 | 
			
		||||
/// ### Implementing a margin wrapper type
 | 
			
		||||
/// Follow the directions in @@WrapperManager$'s documentation, and or
 | 
			
		||||
/// alias the @@margin property if you wish to expose it.
 | 
			
		||||
class MarginWrapperManager: public WrapperManager {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	/// The minimum margin between the child item and the parent item's edges.
 | 
			
		||||
	/// Defaults to 0.
 | 
			
		||||
	Q_PROPERTY(qreal margin READ margin WRITE setMargin NOTIFY marginChanged FINAL);
 | 
			
		||||
	/// If the child item should be resized larger than its implicit size if
 | 
			
		||||
	/// the parent is resized larger than its implicit size. Defaults to false.
 | 
			
		||||
	Q_PROPERTY(bool resizeChild READ resizeChild WRITE setResizeChild NOTIFY resizeChildChanged FINAL);
 | 
			
		||||
	// clang-format on
 | 
			
		||||
	QML_ELEMENT;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit MarginWrapperManager(QObject* parent = nullptr);
 | 
			
		||||
 | 
			
		||||
	void componentComplete() override;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] qreal margin() const;
 | 
			
		||||
	void setMargin(qreal margin);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] bool resizeChild() const;
 | 
			
		||||
	void setResizeChild(bool resizeChild);
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void marginChanged();
 | 
			
		||||
	void resizeChildChanged();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onChildChanged();
 | 
			
		||||
	void onWrapperWidthChanged();
 | 
			
		||||
	void onWrapperHeightChanged();
 | 
			
		||||
	void onChildImplicitWidthChanged();
 | 
			
		||||
	void onChildImplicitHeightChanged();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	void updateGeometry();
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] qreal targetChildX() const;
 | 
			
		||||
	[[nodiscard]] qreal targetChildY() const;
 | 
			
		||||
	[[nodiscard]] qreal targetChildWidth() const;
 | 
			
		||||
	[[nodiscard]] qreal targetChildHeight() const;
 | 
			
		||||
 | 
			
		||||
	qreal mMargin = 0;
 | 
			
		||||
	bool mResizeChild = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace qs::widgets
 | 
			
		||||
| 
						 | 
				
			
			@ -1,7 +1,15 @@
 | 
			
		|||
name = "Quickshell.Widgets"
 | 
			
		||||
description = "Bundled widgets"
 | 
			
		||||
 | 
			
		||||
headers = [
 | 
			
		||||
	"wrapper.hpp",
 | 
			
		||||
	"marginwrapper.hpp",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
qml_files = [
 | 
			
		||||
	"IconImage.qml",
 | 
			
		||||
	"ClippingRectangle.qml",
 | 
			
		||||
	"WrapperItem.qml",
 | 
			
		||||
	"WrapperRectangle.qml",
 | 
			
		||||
]
 | 
			
		||||
-----
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										127
									
								
								src/widgets/wrapper.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								src/widgets/wrapper.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,127 @@
 | 
			
		|||
#include "wrapper.hpp"
 | 
			
		||||
 | 
			
		||||
#include <QtQml/qqmlinfo.h>
 | 
			
		||||
#include <QtQml/qqmllist.h>
 | 
			
		||||
#include <qlogging.h>
 | 
			
		||||
#include <qnamespace.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qquickitem.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
 | 
			
		||||
namespace qs::widgets {
 | 
			
		||||
 | 
			
		||||
void WrapperManager::componentComplete() {
 | 
			
		||||
	this->mWrapper = qobject_cast<QQuickItem*>(this->parent());
 | 
			
		||||
 | 
			
		||||
	if (!this->mWrapper) {
 | 
			
		||||
		QString pstr;
 | 
			
		||||
		QDebug(&pstr) << this->parent();
 | 
			
		||||
 | 
			
		||||
		qmlWarning(this) << "Parent of WrapperManager is not a QQuickItem. Parent: " << pstr;
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	QQuickItem* child = this->mChild;
 | 
			
		||||
	this->mChild = nullptr; // avoids checks for the old item in setChild.
 | 
			
		||||
 | 
			
		||||
	const auto& childItems = this->mWrapper->childItems();
 | 
			
		||||
 | 
			
		||||
	if (childItems.length() == 1) {
 | 
			
		||||
		this->mDefaultChild = childItems.first();
 | 
			
		||||
	} else if (childItems.length() != 0) {
 | 
			
		||||
		this->flags.setFlag(WrapperManager::HasMultipleChildren);
 | 
			
		||||
 | 
			
		||||
		if (!child && !this->flags.testFlags(WrapperManager::NullChild)) {
 | 
			
		||||
			this->printChildCountWarning();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for (auto* item: childItems) {
 | 
			
		||||
		if (item != child) item->setParentItem(nullptr);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (child && !this->flags.testFlag(WrapperManager::NullChild)) {
 | 
			
		||||
		this->setChild(child);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QQuickItem* WrapperManager::child() const { return this->mChild; }
 | 
			
		||||
 | 
			
		||||
void WrapperManager::setChild(QQuickItem* child) {
 | 
			
		||||
	if (child && child == this->mChild) return;
 | 
			
		||||
 | 
			
		||||
	if (this->mChild != nullptr) {
 | 
			
		||||
		QObject::disconnect(this->mChild, nullptr, this, nullptr);
 | 
			
		||||
 | 
			
		||||
		if (this->mChild->parentItem() == this->mWrapper) {
 | 
			
		||||
			this->mChild->setParentItem(nullptr);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->mChild = child;
 | 
			
		||||
	this->flags.setFlag(WrapperManager::NullChild, child == nullptr);
 | 
			
		||||
 | 
			
		||||
	if (child) {
 | 
			
		||||
		QObject::connect(
 | 
			
		||||
		    child,
 | 
			
		||||
		    &QObject::destroyed,
 | 
			
		||||
		    this,
 | 
			
		||||
		    &WrapperManager::onChildDestroyed,
 | 
			
		||||
		    Qt::UniqueConnection
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		if (auto* wrapper = this->mWrapper) {
 | 
			
		||||
			child->setParentItem(wrapper);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	emit this->initializedChildChanged();
 | 
			
		||||
	emit this->childChanged();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WrapperManager::setProspectiveChild(QQuickItem* child) {
 | 
			
		||||
	if (child && child == this->mChild) return;
 | 
			
		||||
 | 
			
		||||
	if (!this->mWrapper) {
 | 
			
		||||
		if (this->mChild) {
 | 
			
		||||
			QObject::disconnect(this->mChild, nullptr, this, nullptr);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this->mChild = child;
 | 
			
		||||
		this->flags.setFlag(WrapperManager::NullChild, child == nullptr);
 | 
			
		||||
 | 
			
		||||
		if (child) {
 | 
			
		||||
			QObject::connect(child, &QObject::destroyed, this, &WrapperManager::onChildDestroyed);
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		this->setChild(child);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WrapperManager::unsetChild() {
 | 
			
		||||
	if (!this->mWrapper) {
 | 
			
		||||
		this->setProspectiveChild(nullptr);
 | 
			
		||||
	} else {
 | 
			
		||||
		this->setChild(this->mDefaultChild);
 | 
			
		||||
 | 
			
		||||
		if (!this->mDefaultChild && this->flags.testFlag(WrapperManager::HasMultipleChildren)) {
 | 
			
		||||
			this->printChildCountWarning();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->flags.setFlag(WrapperManager::NullChild, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WrapperManager::onChildDestroyed() {
 | 
			
		||||
	this->mChild = nullptr;
 | 
			
		||||
	this->unsetChild();
 | 
			
		||||
	emit this->childChanged();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void WrapperManager::printChildCountWarning() const {
 | 
			
		||||
	qmlWarning(this->mWrapper) << "Wrapper component cannot have more than one visual child.";
 | 
			
		||||
	qmlWarning(this->mWrapper) << "Remove all additional children, or pick a specific component "
 | 
			
		||||
	                              "to wrap using the child property.";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace qs::widgets
 | 
			
		||||
							
								
								
									
										139
									
								
								src/widgets/wrapper.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								src/widgets/wrapper.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,139 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <qflags.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qpointer.h>
 | 
			
		||||
#include <qqmlintegration.h>
 | 
			
		||||
#include <qqmllist.h>
 | 
			
		||||
#include <qqmlparserstatus.h>
 | 
			
		||||
#include <qquickitem.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qtypes.h>
 | 
			
		||||
 | 
			
		||||
#include "../core/doc.hpp"
 | 
			
		||||
 | 
			
		||||
namespace qs::widgets {
 | 
			
		||||
 | 
			
		||||
///! Helper object for creating components with a single visual child.
 | 
			
		||||
/// WrapperManager determines which child of an Item should be its visual
 | 
			
		||||
/// child, and exposes it for further operations. See @@MarginWrapperManager
 | 
			
		||||
/// for a subclass that implements automatic sizing and margins.
 | 
			
		||||
///
 | 
			
		||||
/// ### Using wrapper types
 | 
			
		||||
/// WrapperManager based types have a single visual child item.
 | 
			
		||||
/// You can specify the child item using the default property, or by
 | 
			
		||||
/// setting the @@child property. You must use the @@child property if
 | 
			
		||||
/// the widget has more than one @@QtQuick.Item based child.
 | 
			
		||||
///
 | 
			
		||||
/// #### Example using the default property
 | 
			
		||||
/// ```qml
 | 
			
		||||
/// WrapperWidget { // a widget that uses WrapperManager
 | 
			
		||||
///   // Putting the item inline uses the default property of WrapperWidget.
 | 
			
		||||
///   @@QtQuick.Text { text: "Hello" }
 | 
			
		||||
///
 | 
			
		||||
///   // Scope does not extend Item, so it can be placed in the
 | 
			
		||||
///   // default property without issue.
 | 
			
		||||
///   @@Quickshell.Scope {}
 | 
			
		||||
/// }
 | 
			
		||||
/// ```
 | 
			
		||||
///
 | 
			
		||||
/// #### Example using the child property
 | 
			
		||||
/// ```qml
 | 
			
		||||
/// WrapperWidget {
 | 
			
		||||
///   @@QtQuick.Text {
 | 
			
		||||
///     id: text
 | 
			
		||||
///     text: "Hello"
 | 
			
		||||
///   }
 | 
			
		||||
///
 | 
			
		||||
///   @@QtQuick.Text {
 | 
			
		||||
///     id: otherText
 | 
			
		||||
///     text: "Other Text"
 | 
			
		||||
///   }
 | 
			
		||||
///
 | 
			
		||||
///   // Both text and otherText extend Item, so one must be specified.
 | 
			
		||||
///   child: text
 | 
			
		||||
/// }
 | 
			
		||||
/// ```
 | 
			
		||||
///
 | 
			
		||||
/// See @@child for more details on how the child property can be used.
 | 
			
		||||
///
 | 
			
		||||
/// ### Implementing wrapper types
 | 
			
		||||
/// In addition to the bundled wrapper types, you can make your own using
 | 
			
		||||
/// WrapperManager. To implement a wrapper, create a WrapperManager inside
 | 
			
		||||
/// your wrapper component 's default property, then alias a new property
 | 
			
		||||
/// to the WrapperManager's @@child property.
 | 
			
		||||
///
 | 
			
		||||
/// #### Example
 | 
			
		||||
/// ```qml
 | 
			
		||||
/// Item { // your wrapper component
 | 
			
		||||
///   WrapperManager { id: wrapperManager }
 | 
			
		||||
///
 | 
			
		||||
///   // Allows consumers of your wrapper component to use the child property.
 | 
			
		||||
///   property alias child: wrapperManager.child
 | 
			
		||||
///
 | 
			
		||||
///   // The rest of your component logic. You can use
 | 
			
		||||
///   // `wrapperManager.child` or `this.child` to refer to the selected child.
 | 
			
		||||
/// }
 | 
			
		||||
/// ```
 | 
			
		||||
///
 | 
			
		||||
/// ### See also
 | 
			
		||||
/// - @@WrapperItem - A @@MarginWrapperManager based component that sizes itself
 | 
			
		||||
///   to its child.
 | 
			
		||||
/// - @@WrapperRectangle - A @@MarginWrapperManager based component that sizes
 | 
			
		||||
///   itself to its child, and provides an option to use its border as an inset.
 | 
			
		||||
class WrapperManager
 | 
			
		||||
    : public QObject
 | 
			
		||||
    , public QQmlParserStatus {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	/// The wrapper component's selected child.
 | 
			
		||||
	///
 | 
			
		||||
	/// Setting this property override's WrapperManager's default selection,
 | 
			
		||||
	/// and resolve ambiguity when more than one visual child is present.
 | 
			
		||||
	/// The property can additionally be defined inline or reference a component
 | 
			
		||||
	/// that is not already a child of the wrapper, in which case it will be
 | 
			
		||||
	/// reparented to the wrapper. Setting child to `null` will select no child,
 | 
			
		||||
	/// and `undefined` will restore the default child.
 | 
			
		||||
	///
 | 
			
		||||
	/// When read, `child` will always return the (potentially null) selected child,
 | 
			
		||||
	/// and not `undefined`.
 | 
			
		||||
	Q_PROPERTY(QQuickItem* child READ child WRITE setProspectiveChild RESET unsetChild NOTIFY childChanged FINAL);
 | 
			
		||||
	// clang-format on
 | 
			
		||||
	QML_ELEMENT;
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit WrapperManager(QObject* parent = nullptr): QObject(parent) {}
 | 
			
		||||
 | 
			
		||||
	void classBegin() override {}
 | 
			
		||||
	void componentComplete() override;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] QQuickItem* child() const;
 | 
			
		||||
	void setChild(QQuickItem* child);
 | 
			
		||||
	void setProspectiveChild(QQuickItem* child);
 | 
			
		||||
	void unsetChild();
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void childChanged();
 | 
			
		||||
	QSDOC_HIDE void initializedChildChanged();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onChildDestroyed();
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
	enum Flag : quint8 {
 | 
			
		||||
		NoFlags = 0x0,
 | 
			
		||||
		NullChild = 0x1,
 | 
			
		||||
		HasMultipleChildren = 0x2,
 | 
			
		||||
	};
 | 
			
		||||
	Q_DECLARE_FLAGS(Flags, Flag);
 | 
			
		||||
 | 
			
		||||
	void printChildCountWarning() const;
 | 
			
		||||
	void updateGeometry();
 | 
			
		||||
 | 
			
		||||
	QQuickItem* mWrapper = nullptr;
 | 
			
		||||
	QPointer<QQuickItem> mDefaultChild;
 | 
			
		||||
	QQuickItem* mChild = nullptr;
 | 
			
		||||
	Flags flags;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace qs::widgets
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue