forked from quickshell/quickshell
		
	core/popupanchor: add item-relative anchor rect support
This commit is contained in:
		
							parent
							
								
									adcef7fc30
								
							
						
					
					
						commit
						6d42d26c79
					
				
					 6 changed files with 124 additions and 47 deletions
				
			
		| 
						 | 
				
			
			@ -4,6 +4,7 @@
 | 
			
		|||
#include <qcontainerfwd.h>
 | 
			
		||||
#include <qlogging.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qquickitem.h>
 | 
			
		||||
#include <qsize.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qwindow.h>
 | 
			
		||||
| 
						 | 
				
			
			@ -25,14 +26,11 @@ bool PopupAnchor::isDirty() const {
 | 
			
		|||
void PopupAnchor::markClean() { this->lastState = this->state; }
 | 
			
		||||
void PopupAnchor::markDirty() { this->lastState.reset(); }
 | 
			
		||||
 | 
			
		||||
QObject* PopupAnchor::window() const { return this->mWindow; }
 | 
			
		||||
ProxyWindowBase* PopupAnchor::proxyWindow() const { return this->mProxyWindow; }
 | 
			
		||||
 | 
			
		||||
QWindow* PopupAnchor::backingWindow() const {
 | 
			
		||||
	return this->mProxyWindow ? this->mProxyWindow->backingWindow() : nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PopupAnchor::setWindow(QObject* window) {
 | 
			
		||||
void PopupAnchor::setWindowInternal(QObject* window) {
 | 
			
		||||
	if (window == this->mWindow) return;
 | 
			
		||||
 | 
			
		||||
	if (this->mWindow) {
 | 
			
		||||
| 
						 | 
				
			
			@ -78,6 +76,27 @@ setnull:
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PopupAnchor::setWindow(QObject* window) {
 | 
			
		||||
	this->setItem(nullptr);
 | 
			
		||||
	this->setWindowInternal(window);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PopupAnchor::setItem(QQuickItem* item) {
 | 
			
		||||
	if (item == this->mItem) return;
 | 
			
		||||
 | 
			
		||||
	if (this->mItem) {
 | 
			
		||||
		QObject::disconnect(this->mItem, nullptr, this, nullptr);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->mItem = item;
 | 
			
		||||
	this->onItemWindowChanged();
 | 
			
		||||
 | 
			
		||||
	if (item) {
 | 
			
		||||
		QObject::connect(item, &QObject::destroyed, this, &PopupAnchor::onItemDestroyed);
 | 
			
		||||
		QObject::connect(item, &QQuickItem::windowChanged, this, &PopupAnchor::onItemWindowChanged);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PopupAnchor::onWindowDestroyed() {
 | 
			
		||||
	this->mWindow = nullptr;
 | 
			
		||||
	this->mProxyWindow = nullptr;
 | 
			
		||||
| 
						 | 
				
			
			@ -85,18 +104,41 @@ void PopupAnchor::onWindowDestroyed() {
 | 
			
		|||
	emit this->backingWindowVisibilityChanged();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Box PopupAnchor::rect() const { return this->state.rect; }
 | 
			
		||||
 | 
			
		||||
void PopupAnchor::setRect(Box rect) {
 | 
			
		||||
	if (rect == this->state.rect) return;
 | 
			
		||||
	if (rect.w <= 0) rect.w = 1;
 | 
			
		||||
	if (rect.h <= 0) rect.h = 1;
 | 
			
		||||
 | 
			
		||||
	this->state.rect = rect;
 | 
			
		||||
	emit this->rectChanged();
 | 
			
		||||
void PopupAnchor::onItemDestroyed() {
 | 
			
		||||
	this->mItem = nullptr;
 | 
			
		||||
	emit this->itemChanged();
 | 
			
		||||
	this->setWindowInternal(nullptr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Edges::Flags PopupAnchor::edges() const { return this->state.edges; }
 | 
			
		||||
void PopupAnchor::onItemWindowChanged() {
 | 
			
		||||
	if (auto* window = qobject_cast<ProxiedWindow*>(this->mItem->window())) {
 | 
			
		||||
		this->setWindowInternal(window->proxy());
 | 
			
		||||
	} else {
 | 
			
		||||
		this->setWindowInternal(nullptr);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PopupAnchor::setRect(Box rect) {
 | 
			
		||||
	if (rect.w <= 0) rect.w = 1;
 | 
			
		||||
	if (rect.h <= 0) rect.h = 1;
 | 
			
		||||
	if (rect == this->mUserRect) return;
 | 
			
		||||
 | 
			
		||||
	this->mUserRect = rect;
 | 
			
		||||
	emit this->rectChanged();
 | 
			
		||||
 | 
			
		||||
	this->setWindowRect(rect);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PopupAnchor::setWindowRect(Box rect) {
 | 
			
		||||
	if (rect.w <= 0) rect.w = 1;
 | 
			
		||||
	if (rect.h <= 0) rect.h = 1;
 | 
			
		||||
	if (rect == this->state.rect) return;
 | 
			
		||||
 | 
			
		||||
	this->state.rect = rect;
 | 
			
		||||
	emit this->windowRectChanged();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PopupAnchor::resetRect() { this->mUserRect = Box(); }
 | 
			
		||||
 | 
			
		||||
void PopupAnchor::setEdges(Edges::Flags edges) {
 | 
			
		||||
	if (edges == this->state.edges) return;
 | 
			
		||||
| 
						 | 
				
			
			@ -110,8 +152,6 @@ void PopupAnchor::setEdges(Edges::Flags edges) {
 | 
			
		|||
	emit this->edgesChanged();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Edges::Flags PopupAnchor::gravity() const { return this->state.gravity; }
 | 
			
		||||
 | 
			
		||||
void PopupAnchor::setGravity(Edges::Flags gravity) {
 | 
			
		||||
	if (gravity == this->state.gravity) return;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -124,8 +164,6 @@ void PopupAnchor::setGravity(Edges::Flags gravity) {
 | 
			
		|||
	emit this->gravityChanged();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PopupAdjustment::Flags PopupAnchor::adjustment() const { return this->state.adjustment; }
 | 
			
		||||
 | 
			
		||||
void PopupAnchor::setAdjustment(PopupAdjustment::Flags adjustment) {
 | 
			
		||||
	if (adjustment == this->state.adjustment) return;
 | 
			
		||||
	this->state.adjustment = adjustment;
 | 
			
		||||
| 
						 | 
				
			
			@ -137,6 +175,19 @@ void PopupAnchor::updatePlacement(const QPoint& anchorpoint, const QSize& size)
 | 
			
		|||
	this->state.size = size;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PopupAnchor::updateAnchor() {
 | 
			
		||||
	if (this->mItem && this->mProxyWindow) {
 | 
			
		||||
		auto rect = this->mProxyWindow->contentItem()->mapRectFromItem(
 | 
			
		||||
		    this->mItem,
 | 
			
		||||
		    this->mUserRect.isEmpty() ? this->mItem->boundingRect() : this->mUserRect.qrect()
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		this->setWindowRect(rect);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	emit this->anchoring();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static PopupPositioner* POSITIONER = nullptr; // NOLINT
 | 
			
		||||
 | 
			
		||||
void PopupPositioner::reposition(PopupAnchor* anchor, QWindow* window, bool onlyIfDirty) {
 | 
			
		||||
| 
						 | 
				
			
			@ -148,7 +199,7 @@ void PopupPositioner::reposition(PopupAnchor* anchor, QWindow* window, bool only
 | 
			
		|||
	auto parentGeometry = parentWindow->geometry();
 | 
			
		||||
	auto windowGeometry = window->geometry();
 | 
			
		||||
 | 
			
		||||
	emit anchor->anchoring();
 | 
			
		||||
	anchor->updateAnchor();
 | 
			
		||||
	anchor->updatePlacement(parentGeometry.topLeft(), windowGeometry.size());
 | 
			
		||||
 | 
			
		||||
	if (onlyIfDirty && !anchor->isDirty()) return;
 | 
			
		||||
| 
						 | 
				
			
			@ -156,7 +207,7 @@ void PopupPositioner::reposition(PopupAnchor* anchor, QWindow* window, bool only
 | 
			
		|||
 | 
			
		||||
	auto adjustment = anchor->adjustment();
 | 
			
		||||
	auto screenGeometry = parentWindow->screen()->geometry();
 | 
			
		||||
	auto anchorRectGeometry = anchor->rect().qrect().translated(parentGeometry.topLeft());
 | 
			
		||||
	auto anchorRectGeometry = anchor->windowRect().qrect().translated(parentGeometry.topLeft());
 | 
			
		||||
 | 
			
		||||
	auto anchorEdges = anchor->edges();
 | 
			
		||||
	auto anchorGravity = anchor->gravity();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,7 @@
 | 
			
		|||
#include <qobject.h>
 | 
			
		||||
#include <qpoint.h>
 | 
			
		||||
#include <qqmlintegration.h>
 | 
			
		||||
#include <qquickitem.h>
 | 
			
		||||
#include <qsize.h>
 | 
			
		||||
#include <qtclasshelpermacros.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
| 
						 | 
				
			
			@ -72,25 +73,29 @@ struct PopupAnchorState {
 | 
			
		|||
class PopupAnchor: public QObject {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	/// The window to anchor / attach the popup to.
 | 
			
		||||
	/// The window to anchor / attach the popup to. Setting this property unsets @@item.
 | 
			
		||||
	Q_PROPERTY(QObject* window READ window WRITE setWindow NOTIFY windowChanged);
 | 
			
		||||
	/// The anchorpoints the popup will attach to. Which anchors will be used is
 | 
			
		||||
	/// determined by the @@edges, @@gravity, and @@adjustment.
 | 
			
		||||
	/// The item to anchor / attach the popup to. Setting this property unsets @@window.
 | 
			
		||||
	///
 | 
			
		||||
	/// The popup's position relative to its parent window is only calculated when it is
 | 
			
		||||
	/// initially shown (directly before @@anchoring(s) is emitted), meaning its anchor
 | 
			
		||||
	/// rectangle will be set relative to the item's position in the window at that time.
 | 
			
		||||
	/// @@updateAnchor() can be called to update the anchor rectangle if the item's position
 | 
			
		||||
	/// has changed.
 | 
			
		||||
	///
 | 
			
		||||
	/// > [!NOTE] If a more flexible way to position a popup relative to an item is needed,
 | 
			
		||||
	/// > set @@window to the item's parent window, and handle the @@anchoring signal to
 | 
			
		||||
	/// > position the popup relative to the window's contentItem.
 | 
			
		||||
	Q_PROPERTY(QQuickItem* item READ item WRITE setItem NOTIFY itemChanged);
 | 
			
		||||
	/// The anchorpoints the popup will attach to, relative to @@item or @@window.
 | 
			
		||||
	/// Which anchors will be used is determined by the @@edges, @@gravity, and @@adjustment.
 | 
			
		||||
	///
 | 
			
		||||
	/// If you leave @@edges, @@gravity and @@adjustment at their default values,
 | 
			
		||||
	/// setting more than `x` and `y` does not matter. The anchor rect cannot
 | 
			
		||||
	/// be smaller than 1x1 pixels.
 | 
			
		||||
	///
 | 
			
		||||
	/// > [!INFO] To position a popup relative to an item inside a window,
 | 
			
		||||
	/// > you can use [coordinate mapping functions] (note the warning below).
 | 
			
		||||
	///
 | 
			
		||||
	/// > [!WARNING] Using [coordinate mapping functions] in a binding to
 | 
			
		||||
	/// > this property will position the anchor incorrectly.
 | 
			
		||||
	/// > If you want to use them, do so in @@anchoring(s), or use
 | 
			
		||||
	/// > @@TransformWatcher if you need real-time updates to mapped coordinates.
 | 
			
		||||
	///
 | 
			
		||||
	/// [coordinate mapping functions]: https://doc.qt.io/qt-6/qml-qtquick-item.html#mapFromItem-method
 | 
			
		||||
	Q_PROPERTY(Box rect READ rect WRITE setRect NOTIFY rectChanged);
 | 
			
		||||
	Q_PROPERTY(Box rect READ rect WRITE setRect RESET resetRect NOTIFY rectChanged);
 | 
			
		||||
	/// The point on the anchor rectangle the popup should anchor to.
 | 
			
		||||
	/// Opposing edges suchs as `Edges.Left | Edges.Right` are not allowed.
 | 
			
		||||
	///
 | 
			
		||||
| 
						 | 
				
			
			@ -113,25 +118,40 @@ class PopupAnchor: public QObject {
 | 
			
		|||
public:
 | 
			
		||||
	explicit PopupAnchor(QObject* parent): QObject(parent) {}
 | 
			
		||||
 | 
			
		||||
	/// Update the popup's anchor rect relative to its parent window.
 | 
			
		||||
	///
 | 
			
		||||
	/// If anchored to an item, popups anchors will not automatically follow
 | 
			
		||||
	/// the item if its position changes. This function can be called to
 | 
			
		||||
	/// recalculate the anchors.
 | 
			
		||||
	Q_INVOKABLE void updateAnchor();
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] bool isDirty() const;
 | 
			
		||||
	void markClean();
 | 
			
		||||
	void markDirty();
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] QObject* window() const;
 | 
			
		||||
	[[nodiscard]] ProxyWindowBase* proxyWindow() const;
 | 
			
		||||
	[[nodiscard]] QObject* window() const { return this->mWindow; }
 | 
			
		||||
	[[nodiscard]] ProxyWindowBase* proxyWindow() const { return this->mProxyWindow; }
 | 
			
		||||
	[[nodiscard]] QWindow* backingWindow() const;
 | 
			
		||||
	void setWindowInternal(QObject* window);
 | 
			
		||||
	void setWindow(QObject* window);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] Box rect() const;
 | 
			
		||||
	void setRect(Box rect);
 | 
			
		||||
	[[nodiscard]] QQuickItem* item() const { return this->mItem; }
 | 
			
		||||
	void setItem(QQuickItem* item);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] Edges::Flags edges() const;
 | 
			
		||||
	[[nodiscard]] Box windowRect() const { return this->state.rect; }
 | 
			
		||||
	void setWindowRect(Box rect);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] Box rect() const { return this->mUserRect; }
 | 
			
		||||
	void setRect(Box rect);
 | 
			
		||||
	void resetRect();
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] Edges::Flags edges() const { return this->state.edges; }
 | 
			
		||||
	void setEdges(Edges::Flags edges);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] Edges::Flags gravity() const;
 | 
			
		||||
	[[nodiscard]] Edges::Flags gravity() const { return this->state.gravity; }
 | 
			
		||||
	void setGravity(Edges::Flags gravity);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] PopupAdjustment::Flags adjustment() const;
 | 
			
		||||
	[[nodiscard]] PopupAdjustment::Flags adjustment() const { return this->state.adjustment; }
 | 
			
		||||
	void setAdjustment(PopupAdjustment::Flags adjustment);
 | 
			
		||||
 | 
			
		||||
	void updatePlacement(const QPoint& anchorpoint, const QSize& size);
 | 
			
		||||
| 
						 | 
				
			
			@ -144,7 +164,9 @@ signals:
 | 
			
		|||
	void anchoring();
 | 
			
		||||
 | 
			
		||||
	void windowChanged();
 | 
			
		||||
	void itemChanged();
 | 
			
		||||
	QSDOC_HIDE void backingWindowVisibilityChanged();
 | 
			
		||||
	QSDOC_HIDE void windowRectChanged();
 | 
			
		||||
	void rectChanged();
 | 
			
		||||
	void edgesChanged();
 | 
			
		||||
	void gravityChanged();
 | 
			
		||||
| 
						 | 
				
			
			@ -152,11 +174,15 @@ signals:
 | 
			
		|||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onWindowDestroyed();
 | 
			
		||||
	void onItemDestroyed();
 | 
			
		||||
	void onItemWindowChanged();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	QObject* mWindow = nullptr;
 | 
			
		||||
	QQuickItem* mItem = nullptr;
 | 
			
		||||
	ProxyWindowBase* mProxyWindow = nullptr;
 | 
			
		||||
	PopupAnchorState state;
 | 
			
		||||
	Box mUserRect;
 | 
			
		||||
	std::optional<PopupAnchorState> lastState;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,6 +44,7 @@ public:
 | 
			
		|||
	qint32 h = 0;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] QRect qrect() const;
 | 
			
		||||
	[[nodiscard]] bool isEmpty() const { return this->w == 0 && this->h == 0; }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
QDebug operator<<(QDebug debug, const Box& box);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,6 @@
 | 
			
		|||
#include <private/qwayland-xdg-shell.h>
 | 
			
		||||
#include <private/qwaylandwindow_p.h>
 | 
			
		||||
#include <private/wayland-xdg-shell-client-protocol.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qvariant.h>
 | 
			
		||||
#include <qwindow.h>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -21,7 +20,7 @@ void WaylandPopupPositioner::reposition(PopupAnchor* anchor, QWindow* window, bo
 | 
			
		|||
	auto* waylandWindow = dynamic_cast<QWaylandWindow*>(window->handle());
 | 
			
		||||
	auto* popupRole = waylandWindow ? waylandWindow->surfaceRole<::xdg_popup>() : nullptr;
 | 
			
		||||
 | 
			
		||||
	emit anchor->anchoring();
 | 
			
		||||
	anchor->updateAnchor();
 | 
			
		||||
 | 
			
		||||
	// If a popup becomes invisble after creation ensure the _q properties will
 | 
			
		||||
	// be set and not ignored because the rest is the same.
 | 
			
		||||
| 
						 | 
				
			
			@ -44,7 +43,7 @@ void WaylandPopupPositioner::reposition(PopupAnchor* anchor, QWindow* window, bo
 | 
			
		|||
 | 
			
		||||
		positioner.set_constraint_adjustment(anchor->adjustment().toInt());
 | 
			
		||||
 | 
			
		||||
		auto anchorRect = anchor->rect();
 | 
			
		||||
		auto anchorRect = anchor->windowRect();
 | 
			
		||||
 | 
			
		||||
		if (auto* p = window->transientParent()) {
 | 
			
		||||
			anchorRect.x = QHighDpi::toNativePixels(anchorRect.x, p);
 | 
			
		||||
| 
						 | 
				
			
			@ -104,8 +103,8 @@ void WaylandPopupPositioner::reposition(PopupAnchor* anchor, QWindow* window, bo
 | 
			
		|||
bool WaylandPopupPositioner::shouldRepositionOnMove() const { return true; }
 | 
			
		||||
 | 
			
		||||
void WaylandPopupPositioner::setFlags(PopupAnchor* anchor, QWindow* window) {
 | 
			
		||||
	emit anchor->anchoring();
 | 
			
		||||
	auto anchorRect = anchor->rect();
 | 
			
		||||
	anchor->updateAnchor();
 | 
			
		||||
	auto anchorRect = anchor->windowRect();
 | 
			
		||||
 | 
			
		||||
	if (auto* p = window->transientParent()) {
 | 
			
		||||
		anchorRect.x = QHighDpi::toNativePixels(anchorRect.x, p);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,7 @@ ProxyPopupWindow::ProxyPopupWindow(QObject* parent): ProxyWindowBase(parent) {
 | 
			
		|||
	this->mVisible = false;
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	QObject::connect(&this->mAnchor, &PopupAnchor::windowChanged, this, &ProxyPopupWindow::parentWindowChanged);
 | 
			
		||||
	QObject::connect(&this->mAnchor, &PopupAnchor::rectChanged, this, &ProxyPopupWindow::reposition);
 | 
			
		||||
	QObject::connect(&this->mAnchor, &PopupAnchor::windowRectChanged, this, &ProxyPopupWindow::reposition);
 | 
			
		||||
	QObject::connect(&this->mAnchor, &PopupAnchor::edgesChanged, this, &ProxyPopupWindow::reposition);
 | 
			
		||||
	QObject::connect(&this->mAnchor, &PopupAnchor::gravityChanged, this, &ProxyPopupWindow::reposition);
 | 
			
		||||
	QObject::connect(&this->mAnchor, &PopupAnchor::adjustmentChanged, this, &ProxyPopupWindow::reposition);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -57,8 +57,8 @@ class ProxyPopupWindow: public ProxyWindowBase {
 | 
			
		|||
	///
 | 
			
		||||
	/// The Y position of the popup relative to the parent window.
 | 
			
		||||
	Q_PROPERTY(qint32 relativeY READ relativeY WRITE setRelativeY NOTIFY relativeYChanged);
 | 
			
		||||
	/// The popup's anchor / positioner relative to another window. The popup will not be
 | 
			
		||||
	/// shown until it has a valid anchor relative to a window and @@visible is true.
 | 
			
		||||
	/// The popup's anchor / positioner relative to another item or window. The popup will
 | 
			
		||||
	/// not be shown until it has a valid anchor relative to a window and @@visible is true.
 | 
			
		||||
	///
 | 
			
		||||
	/// You can set properties of the anchor like so:
 | 
			
		||||
	/// ```qml
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue