forked from quickshell/quickshell
		
	core/popupanchor: rework popup anchoring and add PopupAnchor
This commit is contained in:
		
							parent
							
								
									14910b1b60
								
							
						
					
					
						commit
						ebfa8ec448
					
				
					 14 changed files with 770 additions and 108 deletions
				
			
		| 
						 | 
				
			
			@ -33,6 +33,8 @@ qt_add_library(quickshell-core STATIC
 | 
			
		|||
	platformmenu.cpp
 | 
			
		||||
	qsmenu.cpp
 | 
			
		||||
	retainable.cpp
 | 
			
		||||
	popupanchor.cpp
 | 
			
		||||
	types.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
set_source_files_properties(main.cpp PROPERTIES COMPILE_DEFINITIONS GIT_REVISION="${GIT_REVISION}")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,5 +24,7 @@ headers = [
 | 
			
		|||
	"objectrepeater.hpp",
 | 
			
		||||
	"qsmenu.hpp",
 | 
			
		||||
	"retainable.hpp",
 | 
			
		||||
	"popupanchor.hpp",
 | 
			
		||||
	"types.hpp",
 | 
			
		||||
]
 | 
			
		||||
-----
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										275
									
								
								src/core/popupanchor.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								src/core/popupanchor.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,275 @@
 | 
			
		|||
#include "popupanchor.hpp"
 | 
			
		||||
 | 
			
		||||
#include <qlogging.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qwindow.h>
 | 
			
		||||
 | 
			
		||||
#include "proxywindow.hpp"
 | 
			
		||||
#include "types.hpp"
 | 
			
		||||
#include "windowinterface.hpp"
 | 
			
		||||
 | 
			
		||||
bool PopupAnchorState::operator==(const PopupAnchorState& other) const {
 | 
			
		||||
	return this->rect == other.rect && this->edges == other.edges && this->gravity == other.gravity
 | 
			
		||||
	    && this->adjustment == other.adjustment && this->anchorpoint == other.anchorpoint;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PopupAnchor::isDirty() const {
 | 
			
		||||
	return !this->lastState.has_value() || this->state != this->lastState.value();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
	if (window == this->mWindow) return;
 | 
			
		||||
 | 
			
		||||
	if (this->mWindow) {
 | 
			
		||||
		QObject::disconnect(this->mWindow, nullptr, this, nullptr);
 | 
			
		||||
		QObject::disconnect(this->mProxyWindow, nullptr, this, nullptr);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (window) {
 | 
			
		||||
		if (auto* proxy = qobject_cast<ProxyWindowBase*>(window)) {
 | 
			
		||||
			this->mProxyWindow = proxy;
 | 
			
		||||
		} else if (auto* interface = qobject_cast<WindowInterface*>(window)) {
 | 
			
		||||
			this->mProxyWindow = interface->proxyWindow();
 | 
			
		||||
		} else {
 | 
			
		||||
			qWarning() << "Tried to set popup anchor window to" << window
 | 
			
		||||
			           << "which is not a quickshell window.";
 | 
			
		||||
			goto setnull;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this->mWindow = window;
 | 
			
		||||
 | 
			
		||||
		QObject::connect(this->mWindow, &QObject::destroyed, this, &PopupAnchor::onWindowDestroyed);
 | 
			
		||||
 | 
			
		||||
		QObject::connect(
 | 
			
		||||
		    this->mProxyWindow,
 | 
			
		||||
		    &ProxyWindowBase::backerVisibilityChanged,
 | 
			
		||||
		    this,
 | 
			
		||||
		    &PopupAnchor::backingWindowVisibilityChanged
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		emit this->windowChanged();
 | 
			
		||||
		emit this->backingWindowVisibilityChanged();
 | 
			
		||||
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
setnull:
 | 
			
		||||
	if (this->mWindow) {
 | 
			
		||||
		this->mWindow = nullptr;
 | 
			
		||||
		this->mProxyWindow = nullptr;
 | 
			
		||||
 | 
			
		||||
		emit this->windowChanged();
 | 
			
		||||
		emit this->backingWindowVisibilityChanged();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PopupAnchor::onWindowDestroyed() {
 | 
			
		||||
	this->mWindow = nullptr;
 | 
			
		||||
	this->mProxyWindow = nullptr;
 | 
			
		||||
	emit this->windowChanged();
 | 
			
		||||
	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();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Edges::Flags PopupAnchor::edges() const { return this->state.edges; }
 | 
			
		||||
 | 
			
		||||
void PopupAnchor::setEdges(Edges::Flags edges) {
 | 
			
		||||
	if (edges == this->state.edges) return;
 | 
			
		||||
 | 
			
		||||
	if (Edges::isOpposing(edges)) {
 | 
			
		||||
		qWarning() << "Cannot set opposing edges for anchor edges. Tried to set" << edges;
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->state.edges = 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;
 | 
			
		||||
 | 
			
		||||
	if (Edges::isOpposing(gravity)) {
 | 
			
		||||
		qWarning() << "Cannot set opposing edges for anchor gravity. Tried to set" << gravity;
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->state.gravity = 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;
 | 
			
		||||
	emit this->adjustmentChanged();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PopupAnchor::updateAnchorpoint(const QPoint& anchorpoint) {
 | 
			
		||||
	this->state.anchorpoint = anchorpoint;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static PopupPositioner* POSITIONER = nullptr; // NOLINT
 | 
			
		||||
 | 
			
		||||
void PopupPositioner::reposition(PopupAnchor* anchor, QWindow* window, bool onlyIfDirty) {
 | 
			
		||||
	auto* parentWindow = window->transientParent();
 | 
			
		||||
	if (!parentWindow) {
 | 
			
		||||
		qFatal() << "Cannot reposition popup that does not have a transient parent.";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	auto adjustment = anchor->adjustment();
 | 
			
		||||
	auto screenGeometry = parentWindow->screen()->geometry();
 | 
			
		||||
	auto parentGeometry = parentWindow->geometry();
 | 
			
		||||
	auto windowGeometry = window->geometry();
 | 
			
		||||
	auto anchorRectGeometry = anchor->rect().qrect().translated(parentGeometry.topLeft());
 | 
			
		||||
 | 
			
		||||
	auto anchorEdges = anchor->edges();
 | 
			
		||||
	auto anchorGravity = anchor->gravity();
 | 
			
		||||
 | 
			
		||||
	auto width = windowGeometry.width();
 | 
			
		||||
	auto height = windowGeometry.height();
 | 
			
		||||
 | 
			
		||||
	auto anchorX = anchorEdges.testFlag(Edges::Left)  ? anchorRectGeometry.left()
 | 
			
		||||
	             : anchorEdges.testFlag(Edges::Right) ? anchorRectGeometry.right()
 | 
			
		||||
	                                                  : anchorRectGeometry.center().x();
 | 
			
		||||
 | 
			
		||||
	auto anchorY = anchorEdges.testFlag(Edges::Top)    ? anchorRectGeometry.top()
 | 
			
		||||
	             : anchorEdges.testFlag(Edges::Bottom) ? anchorRectGeometry.bottom()
 | 
			
		||||
	                                                   : anchorRectGeometry.center().y();
 | 
			
		||||
 | 
			
		||||
	anchor->updateAnchorpoint({anchorX, anchorY});
 | 
			
		||||
	if (onlyIfDirty && !anchor->isDirty()) return;
 | 
			
		||||
	anchor->markClean();
 | 
			
		||||
 | 
			
		||||
	auto calcEffectiveX = [&]() {
 | 
			
		||||
		return anchorGravity.testFlag(Edges::Left)  ? anchorX - windowGeometry.width() + 1
 | 
			
		||||
		     : anchorGravity.testFlag(Edges::Right) ? anchorX
 | 
			
		||||
		                                            : anchorX - windowGeometry.width() / 2;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	auto calcEffectiveY = [&]() {
 | 
			
		||||
		return anchorGravity.testFlag(Edges::Top)    ? anchorY - windowGeometry.height() + 1
 | 
			
		||||
		     : anchorGravity.testFlag(Edges::Bottom) ? anchorY
 | 
			
		||||
		                                             : anchorY - windowGeometry.height() / 2;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	auto effectiveX = calcEffectiveX();
 | 
			
		||||
	auto effectiveY = calcEffectiveY();
 | 
			
		||||
 | 
			
		||||
	if (adjustment.testFlag(PopupAdjustment::FlipX)) {
 | 
			
		||||
		if (anchorGravity.testFlag(Edges::Left)) {
 | 
			
		||||
			if (effectiveX < screenGeometry.left()) {
 | 
			
		||||
				anchorGravity = anchorGravity ^ Edges::Left | Edges::Right;
 | 
			
		||||
				anchorX = anchorRectGeometry.right();
 | 
			
		||||
				effectiveX = calcEffectiveX();
 | 
			
		||||
			}
 | 
			
		||||
		} else if (anchorGravity.testFlag(Edges::Right)) {
 | 
			
		||||
			if (effectiveX + windowGeometry.width() > screenGeometry.right()) {
 | 
			
		||||
				anchorGravity = anchorGravity ^ Edges::Right | Edges::Left;
 | 
			
		||||
				anchorX = anchorRectGeometry.left();
 | 
			
		||||
				effectiveX = calcEffectiveX();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (adjustment.testFlag(PopupAdjustment::FlipY)) {
 | 
			
		||||
		if (anchorGravity.testFlag(Edges::Top)) {
 | 
			
		||||
			if (effectiveY < screenGeometry.top()) {
 | 
			
		||||
				anchorGravity = anchorGravity ^ Edges::Top | Edges::Bottom;
 | 
			
		||||
				effectiveY = calcEffectiveY();
 | 
			
		||||
			}
 | 
			
		||||
		} else if (anchorGravity.testFlag(Edges::Bottom)) {
 | 
			
		||||
			if (effectiveY + windowGeometry.height() > screenGeometry.bottom()) {
 | 
			
		||||
				anchorGravity = anchorGravity ^ Edges::Bottom | Edges::Top;
 | 
			
		||||
				effectiveY = calcEffectiveY();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Slide order is important for the case where the window is too large to fit on screen.
 | 
			
		||||
	if (adjustment.testFlag(PopupAdjustment::SlideX)) {
 | 
			
		||||
		if (effectiveX + windowGeometry.width() > screenGeometry.right()) {
 | 
			
		||||
			effectiveX = screenGeometry.right() - windowGeometry.width() + 1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (effectiveX < screenGeometry.left()) {
 | 
			
		||||
			effectiveX = screenGeometry.left();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (adjustment.testFlag(PopupAdjustment::SlideY)) {
 | 
			
		||||
		if (effectiveY + windowGeometry.height() > screenGeometry.bottom()) {
 | 
			
		||||
			effectiveY = screenGeometry.bottom() - windowGeometry.height() + 1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (effectiveY < screenGeometry.top()) {
 | 
			
		||||
			effectiveY = screenGeometry.top();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (adjustment.testFlag(PopupAdjustment::ResizeX)) {
 | 
			
		||||
		if (effectiveX < screenGeometry.left()) {
 | 
			
		||||
			auto diff = screenGeometry.left() - effectiveX;
 | 
			
		||||
			effectiveX = screenGeometry.left();
 | 
			
		||||
			width -= diff;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		auto effectiveX2 = effectiveX + windowGeometry.width();
 | 
			
		||||
		if (effectiveX2 > screenGeometry.right()) {
 | 
			
		||||
			width -= effectiveX2 - screenGeometry.right() - 1;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (adjustment.testFlag(PopupAdjustment::ResizeY)) {
 | 
			
		||||
		if (effectiveY < screenGeometry.top()) {
 | 
			
		||||
			auto diff = screenGeometry.top() - effectiveY;
 | 
			
		||||
			effectiveY = screenGeometry.top();
 | 
			
		||||
			height -= diff;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		auto effectiveY2 = effectiveY + windowGeometry.height();
 | 
			
		||||
		if (effectiveY2 > screenGeometry.bottom()) {
 | 
			
		||||
			height -= effectiveY2 - screenGeometry.bottom() - 1;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	window->setGeometry({effectiveX, effectiveY, width, height});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool PopupPositioner::shouldRepositionOnMove() const { return true; }
 | 
			
		||||
 | 
			
		||||
PopupPositioner* PopupPositioner::instance() {
 | 
			
		||||
	if (POSITIONER == nullptr) {
 | 
			
		||||
		POSITIONER = new PopupPositioner();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return POSITIONER;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PopupPositioner::setInstance(PopupPositioner* instance) {
 | 
			
		||||
	delete POSITIONER;
 | 
			
		||||
	POSITIONER = instance;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										157
									
								
								src/core/popupanchor.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								src/core/popupanchor.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,157 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <optional>
 | 
			
		||||
 | 
			
		||||
#include <QtQmlIntegration/qqmlintegration.h>
 | 
			
		||||
#include <qflags.h>
 | 
			
		||||
#include <qnamespace.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qpoint.h>
 | 
			
		||||
#include <qqmlintegration.h>
 | 
			
		||||
#include <qtclasshelpermacros.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qwindow.h>
 | 
			
		||||
 | 
			
		||||
#include "doc.hpp"
 | 
			
		||||
#include "proxywindow.hpp"
 | 
			
		||||
#include "types.hpp"
 | 
			
		||||
 | 
			
		||||
///! Adjustment strategy for popups that do not fit on screen.
 | 
			
		||||
/// Adjustment strategy for popups. See @@PopupAnchor.adjustment.
 | 
			
		||||
///
 | 
			
		||||
/// Adjustment flags can be combined with the `|` operator.
 | 
			
		||||
///
 | 
			
		||||
/// `Flip` will be applied first, then `Slide`, then `Resize`.
 | 
			
		||||
namespace PopupAdjustment { // NOLINT
 | 
			
		||||
Q_NAMESPACE;
 | 
			
		||||
QML_ELEMENT;
 | 
			
		||||
 | 
			
		||||
enum Enum {
 | 
			
		||||
	None = 0,
 | 
			
		||||
	/// If the X axis is constrained, the popup will slide along the X axis until it fits onscreen.
 | 
			
		||||
	SlideX = 1,
 | 
			
		||||
	/// If the Y axis is constrained, the popup will slide along the Y axis until it fits onscreen.
 | 
			
		||||
	SlideY = 2,
 | 
			
		||||
	/// Alias for `SlideX | SlideY`.
 | 
			
		||||
	Slide = SlideX | SlideY,
 | 
			
		||||
	/// If the X axis is constrained, the popup will invert its horizontal gravity if any.
 | 
			
		||||
	FlipX = 4,
 | 
			
		||||
	/// If the Y axis is constrained, the popup will invert its vertical gravity if any.
 | 
			
		||||
	FlipY = 8,
 | 
			
		||||
	/// Alias for `FlipX | FlipY`.
 | 
			
		||||
	Flip = FlipX | FlipY,
 | 
			
		||||
	/// If the X axis is constrained, the width of the popup will be reduced to fit on screen.
 | 
			
		||||
	ResizeX = 16,
 | 
			
		||||
	/// If the Y axis is constrained, the height of the popup will be reduced to fit on screen.
 | 
			
		||||
	ResizeY = 32,
 | 
			
		||||
	/// Alias for `ResizeX | ResizeY`
 | 
			
		||||
	Resize = ResizeX | ResizeY,
 | 
			
		||||
	/// Alias for `Flip | Slide | Resize`.
 | 
			
		||||
	All = Slide | Flip | Resize,
 | 
			
		||||
};
 | 
			
		||||
Q_ENUM_NS(Enum);
 | 
			
		||||
Q_DECLARE_FLAGS(Flags, Enum);
 | 
			
		||||
 | 
			
		||||
} // namespace PopupAdjustment
 | 
			
		||||
 | 
			
		||||
Q_DECLARE_OPERATORS_FOR_FLAGS(PopupAdjustment::Flags);
 | 
			
		||||
 | 
			
		||||
struct PopupAnchorState {
 | 
			
		||||
	bool operator==(const PopupAnchorState& other) const;
 | 
			
		||||
 | 
			
		||||
	Box rect = {0, 0, 1, 1};
 | 
			
		||||
	Edges::Flags edges = Edges::Top | Edges::Left;
 | 
			
		||||
	Edges::Flags gravity = Edges::Bottom | Edges::Right;
 | 
			
		||||
	PopupAdjustment::Flags adjustment = PopupAdjustment::Slide;
 | 
			
		||||
	QPoint anchorpoint;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
///! Anchorpoint or positioner for popup windows.
 | 
			
		||||
class PopupAnchor: public QObject {
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	/// The window to anchor / attach the popup to.
 | 
			
		||||
	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.
 | 
			
		||||
	///
 | 
			
		||||
	/// If you leave @@edges, @@gravity and @@adjustment at their default values,
 | 
			
		||||
	/// setting more than `x` and `y` does not matter.
 | 
			
		||||
	///
 | 
			
		||||
	/// > [!INFO] The anchor rect cannot be smaller than 1x1 pixels.
 | 
			
		||||
	Q_PROPERTY(Box rect READ rect WRITE setRect NOTIFY rectChanged);
 | 
			
		||||
	/// The point on the anchor rectangle the popup should anchor to.
 | 
			
		||||
	/// Opposing edges suchs as `Edges.Left | Edges.Right` are not allowed.
 | 
			
		||||
	///
 | 
			
		||||
	/// Defaults to `Edges.Top | Edges.Left`.
 | 
			
		||||
	Q_PROPERTY(Edges::Flags edges READ edges WRITE setEdges NOTIFY edgesChanged);
 | 
			
		||||
	/// The direction the popup should expand towards, relative to the anchorpoint.
 | 
			
		||||
	/// Opposing edges suchs as `Edges.Left | Edges.Right` are not allowed.
 | 
			
		||||
	///
 | 
			
		||||
	/// Defaults to `Edges.Bottom | Edges.Right`.
 | 
			
		||||
	Q_PROPERTY(Edges::Flags gravity READ gravity WRITE setGravity NOTIFY gravityChanged);
 | 
			
		||||
	/// The strategy used to adjust the popup's position if it would otherwise not fit on screen,
 | 
			
		||||
	/// based on the anchor @@rect, preferred @@edges, and @@gravity.
 | 
			
		||||
	///
 | 
			
		||||
	/// See the documentation for @@PopupAdjustment for details.
 | 
			
		||||
	Q_PROPERTY(PopupAdjustment::Flags adjustment READ adjustment WRITE setAdjustment NOTIFY adjustmentChanged);
 | 
			
		||||
	// clang-format on
 | 
			
		||||
	QML_ELEMENT;
 | 
			
		||||
	QML_UNCREATABLE("");
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit PopupAnchor(QObject* parent): QObject(parent) {}
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] bool isDirty() const;
 | 
			
		||||
	void markClean();
 | 
			
		||||
	void markDirty();
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] QObject* window() const;
 | 
			
		||||
	[[nodiscard]] ProxyWindowBase* proxyWindow() const;
 | 
			
		||||
	[[nodiscard]] QWindow* backingWindow() const;
 | 
			
		||||
	void setWindow(QObject* window);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] Box rect() const;
 | 
			
		||||
	void setRect(Box rect);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] Edges::Flags edges() const;
 | 
			
		||||
	void setEdges(Edges::Flags edges);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] Edges::Flags gravity() const;
 | 
			
		||||
	void setGravity(Edges::Flags gravity);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] PopupAdjustment::Flags adjustment() const;
 | 
			
		||||
	void setAdjustment(PopupAdjustment::Flags adjustment);
 | 
			
		||||
 | 
			
		||||
	void updateAnchorpoint(const QPoint& anchorpoint);
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void windowChanged();
 | 
			
		||||
	QSDOC_HIDE void backingWindowVisibilityChanged();
 | 
			
		||||
	void rectChanged();
 | 
			
		||||
	void edgesChanged();
 | 
			
		||||
	void gravityChanged();
 | 
			
		||||
	void adjustmentChanged();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onWindowDestroyed();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	QObject* mWindow = nullptr;
 | 
			
		||||
	ProxyWindowBase* mProxyWindow = nullptr;
 | 
			
		||||
	PopupAnchorState state;
 | 
			
		||||
	std::optional<PopupAnchorState> lastState;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class PopupPositioner {
 | 
			
		||||
public:
 | 
			
		||||
	explicit PopupPositioner() = default;
 | 
			
		||||
	virtual ~PopupPositioner() = default;
 | 
			
		||||
	Q_DISABLE_COPY_MOVE(PopupPositioner);
 | 
			
		||||
 | 
			
		||||
	virtual void reposition(PopupAnchor* anchor, QWindow* window, bool onlyIfDirty = true);
 | 
			
		||||
	[[nodiscard]] virtual bool shouldRepositionOnMove() const;
 | 
			
		||||
 | 
			
		||||
	static PopupPositioner* instance();
 | 
			
		||||
	static void setInstance(PopupPositioner* instance);
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -4,86 +4,66 @@
 | 
			
		|||
#include <qnamespace.h>
 | 
			
		||||
#include <qobject.h>
 | 
			
		||||
#include <qquickwindow.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
#include <qtypes.h>
 | 
			
		||||
#include <qwindow.h>
 | 
			
		||||
 | 
			
		||||
#include "popupanchor.hpp"
 | 
			
		||||
#include "proxywindow.hpp"
 | 
			
		||||
#include "qmlscreen.hpp"
 | 
			
		||||
#include "windowinterface.hpp"
 | 
			
		||||
 | 
			
		||||
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::edgesChanged, this, &ProxyPopupWindow::reposition);
 | 
			
		||||
	QObject::connect(&this->mAnchor, &PopupAnchor::gravityChanged, this, &ProxyPopupWindow::reposition);
 | 
			
		||||
	QObject::connect(&this->mAnchor, &PopupAnchor::adjustmentChanged, this, &ProxyPopupWindow::reposition);
 | 
			
		||||
	QObject::connect(&this->mAnchor, &PopupAnchor::backingWindowVisibilityChanged, this, &ProxyPopupWindow::onParentUpdated);
 | 
			
		||||
	// clang-format on
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProxyPopupWindow::completeWindow() {
 | 
			
		||||
	this->ProxyWindowBase::completeWindow();
 | 
			
		||||
	QObject::connect(
 | 
			
		||||
	    this->window,
 | 
			
		||||
	    &QWindow::visibleChanged,
 | 
			
		||||
	    this,
 | 
			
		||||
	    &ProxyPopupWindow::onVisibleChanged
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	this->window->setFlag(Qt::ToolTip);
 | 
			
		||||
	this->updateTransientParent();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProxyPopupWindow::postCompleteWindow() { this->ProxyWindowBase::setVisible(this->mVisible); }
 | 
			
		||||
 | 
			
		||||
bool ProxyPopupWindow::deleteOnInvisible() const {
 | 
			
		||||
	// Currently crashes in normal mode, do not have the time to debug it now.
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
qint32 ProxyPopupWindow::x() const {
 | 
			
		||||
	// QTBUG-121550
 | 
			
		||||
	auto basepos = this->mParentProxyWindow == nullptr ? 0 : this->mParentProxyWindow->x();
 | 
			
		||||
	return basepos + this->mRelativeX;
 | 
			
		||||
}
 | 
			
		||||
void ProxyPopupWindow::postCompleteWindow() { this->updateTransientParent(); }
 | 
			
		||||
 | 
			
		||||
void ProxyPopupWindow::setParentWindow(QObject* parent) {
 | 
			
		||||
	if (parent == this->mParentWindow) return;
 | 
			
		||||
 | 
			
		||||
	if (this->mParentWindow != nullptr) {
 | 
			
		||||
		QObject::disconnect(this->mParentWindow, nullptr, this, nullptr);
 | 
			
		||||
		QObject::disconnect(this->mParentProxyWindow, nullptr, this, nullptr);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (parent == nullptr) {
 | 
			
		||||
		this->mParentWindow = nullptr;
 | 
			
		||||
		this->mParentProxyWindow = nullptr;
 | 
			
		||||
	} else {
 | 
			
		||||
		if (auto* proxy = qobject_cast<ProxyWindowBase*>(parent)) {
 | 
			
		||||
			this->mParentProxyWindow = proxy;
 | 
			
		||||
		} else if (auto* interface = qobject_cast<WindowInterface*>(parent)) {
 | 
			
		||||
			this->mParentProxyWindow = interface->proxyWindow();
 | 
			
		||||
		} else {
 | 
			
		||||
			qWarning() << "Tried to set popup parent window to something that is not a quickshell window:"
 | 
			
		||||
			           << parent;
 | 
			
		||||
			this->mParentWindow = nullptr;
 | 
			
		||||
			this->mParentProxyWindow = nullptr;
 | 
			
		||||
			this->updateTransientParent();
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this->mParentWindow = parent;
 | 
			
		||||
 | 
			
		||||
		// clang-format off
 | 
			
		||||
		QObject::connect(this->mParentWindow, &QObject::destroyed, this, &ProxyPopupWindow::onParentDestroyed);
 | 
			
		||||
 | 
			
		||||
		QObject::connect(this->mParentProxyWindow, &ProxyWindowBase::xChanged, this, &ProxyPopupWindow::updateX);
 | 
			
		||||
		QObject::connect(this->mParentProxyWindow, &ProxyWindowBase::yChanged, this, &ProxyPopupWindow::updateY);
 | 
			
		||||
		QObject::connect(this->mParentProxyWindow, &ProxyWindowBase::backerVisibilityChanged, this, &ProxyPopupWindow::onParentUpdated);
 | 
			
		||||
		// clang-format on
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->updateTransientParent();
 | 
			
		||||
	qWarning() << "PopupWindow.parentWindow is deprecated. Use PopupWindow.anchor.window.";
 | 
			
		||||
	this->mAnchor.setWindow(parent);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QObject* ProxyPopupWindow::parentWindow() const { return this->mParentWindow; }
 | 
			
		||||
QObject* ProxyPopupWindow::parentWindow() const {
 | 
			
		||||
	qWarning() << "PopupWindow.parentWindow is deprecated. Use PopupWindow.anchor.window.";
 | 
			
		||||
	return this->mAnchor.window();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProxyPopupWindow::updateTransientParent() {
 | 
			
		||||
	this->updateX();
 | 
			
		||||
	this->updateY();
 | 
			
		||||
	auto* bw = this->mAnchor.backingWindow();
 | 
			
		||||
 | 
			
		||||
	if (this->window != nullptr) {
 | 
			
		||||
		this->window->setTransientParent(
 | 
			
		||||
		    this->mParentProxyWindow == nullptr ? nullptr : this->mParentProxyWindow->backingWindow()
 | 
			
		||||
		);
 | 
			
		||||
	if (this->window != nullptr && bw != this->window->transientParent()) {
 | 
			
		||||
		if (this->window->transientParent()) {
 | 
			
		||||
			QObject::disconnect(this->window->transientParent(), nullptr, this, nullptr);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (bw && PopupPositioner::instance()->shouldRepositionOnMove()) {
 | 
			
		||||
			QObject::connect(bw, &QWindow::xChanged, this, &ProxyPopupWindow::reposition);
 | 
			
		||||
			QObject::connect(bw, &QWindow::yChanged, this, &ProxyPopupWindow::reposition);
 | 
			
		||||
			QObject::connect(bw, &QWindow::widthChanged, this, &ProxyPopupWindow::reposition);
 | 
			
		||||
			QObject::connect(bw, &QWindow::heightChanged, this, &ProxyPopupWindow::reposition);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		this->window->setTransientParent(bw);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->updateVisible();
 | 
			
		||||
| 
						 | 
				
			
			@ -91,13 +71,6 @@ void ProxyPopupWindow::updateTransientParent() {
 | 
			
		|||
 | 
			
		||||
void ProxyPopupWindow::onParentUpdated() { this->updateTransientParent(); }
 | 
			
		||||
 | 
			
		||||
void ProxyPopupWindow::onParentDestroyed() {
 | 
			
		||||
	this->mParentWindow = nullptr;
 | 
			
		||||
	this->mParentProxyWindow = nullptr;
 | 
			
		||||
	this->updateVisible();
 | 
			
		||||
	emit this->parentWindowChanged();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProxyPopupWindow::setScreen(QuickshellScreenInfo* /*unused*/) {
 | 
			
		||||
	qWarning() << "Cannot set screen of popup window, as that is controlled by the parent window";
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -109,53 +82,55 @@ void ProxyPopupWindow::setVisible(bool visible) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
void ProxyPopupWindow::updateVisible() {
 | 
			
		||||
	auto target = this->wantsVisible && this->mParentWindow != nullptr
 | 
			
		||||
	           && this->mParentProxyWindow->isVisibleDirect();
 | 
			
		||||
	auto target = this->wantsVisible && this->mAnchor.window() != nullptr
 | 
			
		||||
	           && this->mAnchor.proxyWindow()->isVisibleDirect();
 | 
			
		||||
 | 
			
		||||
	if (target && this->window != nullptr && !this->window->isVisible()) {
 | 
			
		||||
		this->updateX(); // QTBUG-121550
 | 
			
		||||
		PopupPositioner::instance()->reposition(&this->mAnchor, this->window);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this->ProxyWindowBase::setVisible(target);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProxyPopupWindow::setRelativeX(qint32 x) {
 | 
			
		||||
	if (x == this->mRelativeX) return;
 | 
			
		||||
	this->mRelativeX = x;
 | 
			
		||||
	this->updateX();
 | 
			
		||||
void ProxyPopupWindow::onVisibleChanged() {
 | 
			
		||||
	// If the window was made invisible without its parent becoming invisible
 | 
			
		||||
	// the compositor probably destroyed it. Without this the window won't ever
 | 
			
		||||
	// be able to become visible again.
 | 
			
		||||
	if (this->window->transientParent() && this->window->transientParent()->isVisible()) {
 | 
			
		||||
		this->wantsVisible = this->window->isVisible();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
qint32 ProxyPopupWindow::relativeX() const { return this->mRelativeX; }
 | 
			
		||||
void ProxyPopupWindow::setRelativeX(qint32 x) {
 | 
			
		||||
	qWarning() << "PopupWindow.relativeX is deprecated. Use PopupWindow.anchor.rect.x.";
 | 
			
		||||
	auto rect = this->mAnchor.rect();
 | 
			
		||||
	if (x == rect.x) return;
 | 
			
		||||
	rect.x = x;
 | 
			
		||||
	this->mAnchor.setRect(rect);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
qint32 ProxyPopupWindow::relativeX() const {
 | 
			
		||||
	qWarning() << "PopupWindow.relativeX is deprecated. Use PopupWindow.anchor.rect.x.";
 | 
			
		||||
	return this->mAnchor.rect().x;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProxyPopupWindow::setRelativeY(qint32 y) {
 | 
			
		||||
	if (y == this->mRelativeY) return;
 | 
			
		||||
	this->mRelativeY = y;
 | 
			
		||||
	this->updateY();
 | 
			
		||||
	qWarning() << "PopupWindow.relativeY is deprecated. Use PopupWindow.anchor.rect.y.";
 | 
			
		||||
	auto rect = this->mAnchor.rect();
 | 
			
		||||
	if (y == rect.y) return;
 | 
			
		||||
	rect.y = y;
 | 
			
		||||
	this->mAnchor.setRect(rect);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
qint32 ProxyPopupWindow::relativeY() const { return this->mRelativeY; }
 | 
			
		||||
 | 
			
		||||
void ProxyPopupWindow::updateX() {
 | 
			
		||||
	if (this->mParentWindow == nullptr || this->window == nullptr) return;
 | 
			
		||||
 | 
			
		||||
	auto target = this->x() - 1; // QTBUG-121550
 | 
			
		||||
 | 
			
		||||
	auto reshow = this->isVisibleDirect() && (this->window->x() != target && this->x() != target);
 | 
			
		||||
	if (reshow) this->setVisibleDirect(false);
 | 
			
		||||
	if (this->window != nullptr) this->window->setX(target);
 | 
			
		||||
	if (reshow && this->wantsVisible) this->setVisibleDirect(true);
 | 
			
		||||
qint32 ProxyPopupWindow::relativeY() const {
 | 
			
		||||
	qWarning() << "PopupWindow.relativeY is deprecated. Use PopupWindow.anchor.rect.y.";
 | 
			
		||||
	return this->mAnchor.rect().y;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ProxyPopupWindow::updateY() {
 | 
			
		||||
	if (this->mParentWindow == nullptr || this->window == nullptr) return;
 | 
			
		||||
PopupAnchor* ProxyPopupWindow::anchor() { return &this->mAnchor; }
 | 
			
		||||
 | 
			
		||||
	auto target = this->mParentProxyWindow->y() + this->relativeY();
 | 
			
		||||
 | 
			
		||||
	auto reshow = this->isVisibleDirect() && this->window->y() != target;
 | 
			
		||||
	if (reshow) {
 | 
			
		||||
		this->setVisibleDirect(false);
 | 
			
		||||
		this->updateX(); // QTBUG-121550
 | 
			
		||||
void ProxyPopupWindow::reposition() {
 | 
			
		||||
	if (this->window != nullptr) {
 | 
			
		||||
		PopupPositioner::instance()->reposition(&this->mAnchor, this->window);
 | 
			
		||||
	}
 | 
			
		||||
	if (this->window != nullptr) this->window->setY(target);
 | 
			
		||||
	if (reshow && this->wantsVisible) this->setVisibleDirect(true);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,7 @@
 | 
			
		|||
#include <qtypes.h>
 | 
			
		||||
 | 
			
		||||
#include "doc.hpp"
 | 
			
		||||
#include "popupanchor.hpp"
 | 
			
		||||
#include "proxywindow.hpp"
 | 
			
		||||
#include "qmlscreen.hpp"
 | 
			
		||||
#include "windowinterface.hpp"
 | 
			
		||||
| 
						 | 
				
			
			@ -42,15 +43,37 @@ class ProxyPopupWindow: public ProxyWindowBase {
 | 
			
		|||
	QSDOC_BASECLASS(WindowInterface);
 | 
			
		||||
	Q_OBJECT;
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	/// > [!ERROR] Deprecated in favor of `anchor.window`.
 | 
			
		||||
	///
 | 
			
		||||
	/// The parent window of this popup.
 | 
			
		||||
	///
 | 
			
		||||
	/// Changing this property reparents the popup.
 | 
			
		||||
	Q_PROPERTY(QObject* parentWindow READ parentWindow WRITE setParentWindow NOTIFY parentWindowChanged);
 | 
			
		||||
	/// > [!ERROR] Deprecated in favor of `anchor.rect.x`.
 | 
			
		||||
	///
 | 
			
		||||
	/// The X position of the popup relative to the parent window.
 | 
			
		||||
	Q_PROPERTY(qint32 relativeX READ relativeX WRITE setRelativeX NOTIFY relativeXChanged);
 | 
			
		||||
	/// > [!ERROR] Deprecated in favor of `anchor.rect.y`.
 | 
			
		||||
	///
 | 
			
		||||
	/// 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.
 | 
			
		||||
	///
 | 
			
		||||
	/// You can set properties of the anchor like so:
 | 
			
		||||
	/// ```qml
 | 
			
		||||
	/// PopupWindow {
 | 
			
		||||
	///   anchor.window: parentwindow
 | 
			
		||||
	///   // or
 | 
			
		||||
	///   anchor {
 | 
			
		||||
	///     window: parentwindow
 | 
			
		||||
	///   }
 | 
			
		||||
	/// }
 | 
			
		||||
	/// ```
 | 
			
		||||
	Q_PROPERTY(PopupAnchor* anchor READ anchor CONSTANT);
 | 
			
		||||
	/// If the window is shown or hidden. Defaults to false.
 | 
			
		||||
	///
 | 
			
		||||
	/// The popup will not be shown until @@anchor is valid, regardless of this property.
 | 
			
		||||
	QSDOC_PROPERTY_OVERRIDE(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged);
 | 
			
		||||
	/// The screen that the window currently occupies.
 | 
			
		||||
	///
 | 
			
		||||
| 
						 | 
				
			
			@ -64,13 +87,10 @@ public:
 | 
			
		|||
 | 
			
		||||
	void completeWindow() override;
 | 
			
		||||
	void postCompleteWindow() override;
 | 
			
		||||
	[[nodiscard]] bool deleteOnInvisible() const override;
 | 
			
		||||
 | 
			
		||||
	void setScreen(QuickshellScreenInfo* screen) override;
 | 
			
		||||
	void setVisible(bool visible) override;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] qint32 x() const override;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] QObject* parentWindow() const;
 | 
			
		||||
	void setParentWindow(QObject* parent);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -80,25 +100,23 @@ public:
 | 
			
		|||
	[[nodiscard]] qint32 relativeY() const;
 | 
			
		||||
	void setRelativeY(qint32 y);
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] PopupAnchor* anchor();
 | 
			
		||||
 | 
			
		||||
signals:
 | 
			
		||||
	void parentWindowChanged();
 | 
			
		||||
	void relativeXChanged();
 | 
			
		||||
	void relativeYChanged();
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	void onVisibleChanged();
 | 
			
		||||
	void onParentUpdated();
 | 
			
		||||
	void onParentDestroyed();
 | 
			
		||||
	void updateX();
 | 
			
		||||
	void updateY();
 | 
			
		||||
	void reposition();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	QQuickWindow* parentBackingWindow();
 | 
			
		||||
	void updateTransientParent();
 | 
			
		||||
	void updateVisible();
 | 
			
		||||
 | 
			
		||||
	QObject* mParentWindow = nullptr;
 | 
			
		||||
	ProxyWindowBase* mParentProxyWindow = nullptr;
 | 
			
		||||
	qint32 mRelativeX = 0;
 | 
			
		||||
	qint32 mRelativeY = 0;
 | 
			
		||||
	PopupAnchor mAnchor {this};
 | 
			
		||||
	bool wantsVisible = false;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										23
									
								
								src/core/types.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/core/types.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,23 @@
 | 
			
		|||
#include "types.hpp"
 | 
			
		||||
 | 
			
		||||
#include <qdebug.h>
 | 
			
		||||
#include <qnamespace.h>
 | 
			
		||||
#include <qrect.h>
 | 
			
		||||
 | 
			
		||||
QRect Box::qrect() const { return {this->x, this->y, this->w, this->h}; }
 | 
			
		||||
 | 
			
		||||
bool Box::operator==(const Box& other) const {
 | 
			
		||||
	return this->x == other.x && this->y == other.y && this->w == other.w && this->h == other.h;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QDebug operator<<(QDebug debug, const Box& box) {
 | 
			
		||||
	auto saver = QDebugStateSaver(debug);
 | 
			
		||||
	debug.nospace() << "Box(" << box.x << ',' << box.y << ' ' << box.w << 'x' << box.h << ')';
 | 
			
		||||
	return debug;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Qt::Edges Edges::toQt(Edges::Flags edges) { return Qt::Edges(edges.toInt()); }
 | 
			
		||||
 | 
			
		||||
bool Edges::isOpposing(Edges::Flags edges) {
 | 
			
		||||
	return edges.testFlags(Edges::Top | Edges::Bottom) || edges.testFlags(Edges::Left | Edges::Right);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								src/core/types.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/core/types.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,54 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <qdebug.h>
 | 
			
		||||
#include <qnamespace.h>
 | 
			
		||||
#include <qqmlintegration.h>
 | 
			
		||||
#include <qtmetamacros.h>
 | 
			
		||||
 | 
			
		||||
class Box {
 | 
			
		||||
	Q_GADGET;
 | 
			
		||||
	Q_PROPERTY(qint32 x MEMBER x);
 | 
			
		||||
	Q_PROPERTY(qint32 y MEMBER y);
 | 
			
		||||
	Q_PROPERTY(qint32 w MEMBER w);
 | 
			
		||||
	Q_PROPERTY(qint32 h MEMBER h);
 | 
			
		||||
	Q_PROPERTY(qint32 width MEMBER w);
 | 
			
		||||
	Q_PROPERTY(qint32 height MEMBER h);
 | 
			
		||||
	QML_VALUE_TYPE(box);
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
	explicit Box() = default;
 | 
			
		||||
	Box(qint32 x, qint32 y, qint32 w, qint32 h): x(x), y(y), w(w), h(h) {}
 | 
			
		||||
	bool operator==(const Box& other) const;
 | 
			
		||||
 | 
			
		||||
	qint32 x = 0;
 | 
			
		||||
	qint32 y = 0;
 | 
			
		||||
	qint32 w = 0;
 | 
			
		||||
	qint32 h = 0;
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] QRect qrect() const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
QDebug operator<<(QDebug debug, const Box& box);
 | 
			
		||||
 | 
			
		||||
///! Top Left Right Bottom flags.
 | 
			
		||||
/// Edge flags can be combined with the `|` operator.
 | 
			
		||||
namespace Edges { // NOLINT
 | 
			
		||||
Q_NAMESPACE;
 | 
			
		||||
QML_NAMED_ELEMENT(Edges);
 | 
			
		||||
 | 
			
		||||
enum Enum {
 | 
			
		||||
	None = 0,
 | 
			
		||||
	Top = Qt::TopEdge,
 | 
			
		||||
	Left = Qt::LeftEdge,
 | 
			
		||||
	Right = Qt::RightEdge,
 | 
			
		||||
	Bottom = Qt::BottomEdge,
 | 
			
		||||
};
 | 
			
		||||
Q_ENUM_NS(Enum);
 | 
			
		||||
Q_DECLARE_FLAGS(Flags, Enum);
 | 
			
		||||
 | 
			
		||||
Qt::Edges toQt(Flags edges);
 | 
			
		||||
bool isOpposing(Flags edges);
 | 
			
		||||
 | 
			
		||||
}; // namespace Edges
 | 
			
		||||
 | 
			
		||||
Q_DECLARE_OPERATORS_FOR_FLAGS(Edges::Flags);
 | 
			
		||||
| 
						 | 
				
			
			@ -52,6 +52,8 @@ endfunction()
 | 
			
		|||
 | 
			
		||||
qt_add_library(quickshell-wayland STATIC
 | 
			
		||||
	platformmenu.cpp
 | 
			
		||||
	popupanchor.cpp
 | 
			
		||||
	xdgshell.cpp
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# required to make sure the constructor is linked
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,12 +4,14 @@
 | 
			
		|||
#include <qtenvironmentvariables.h>
 | 
			
		||||
 | 
			
		||||
#include "../core/plugin.hpp"
 | 
			
		||||
#include "platformmenu.hpp"
 | 
			
		||||
 | 
			
		||||
#ifdef QS_WAYLAND_WLR_LAYERSHELL
 | 
			
		||||
#include "wlr_layershell.hpp"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
void installPlatformMenuHook();
 | 
			
		||||
void installPopupPositioner();
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
class WaylandPlugin: public QuickshellPlugin {
 | 
			
		||||
| 
						 | 
				
			
			@ -27,7 +29,10 @@ class WaylandPlugin: public QuickshellPlugin {
 | 
			
		|||
		return isWayland;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void init() override { installPlatformMenuHook(); }
 | 
			
		||||
	void init() override {
 | 
			
		||||
		installPlatformMenuHook();
 | 
			
		||||
		installPopupPositioner();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void registerTypes() override {
 | 
			
		||||
#ifdef QS_WAYLAND_WLR_LAYERSHELL
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										99
									
								
								src/wayland/popupanchor.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								src/wayland/popupanchor.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,99 @@
 | 
			
		|||
#include "popupanchor.hpp"
 | 
			
		||||
 | 
			
		||||
#include <private/qwayland-xdg-shell.h>
 | 
			
		||||
#include <private/qwaylandwindow_p.h>
 | 
			
		||||
#include <private/wayland-xdg-shell-client-protocol.h>
 | 
			
		||||
#include <qvariant.h>
 | 
			
		||||
#include <qwindow.h>
 | 
			
		||||
 | 
			
		||||
#include "../core/popupanchor.hpp"
 | 
			
		||||
#include "../core/types.hpp"
 | 
			
		||||
#include "xdgshell.hpp"
 | 
			
		||||
 | 
			
		||||
using QtWaylandClient::QWaylandWindow;
 | 
			
		||||
using XdgPositioner = QtWayland::xdg_positioner;
 | 
			
		||||
using qs::wayland::xdg_shell::XdgWmBase;
 | 
			
		||||
 | 
			
		||||
void WaylandPopupPositioner::reposition(PopupAnchor* anchor, QWindow* window, bool onlyIfDirty) {
 | 
			
		||||
	if (onlyIfDirty && !anchor->isDirty()) return;
 | 
			
		||||
 | 
			
		||||
	auto* waylandWindow = dynamic_cast<QWaylandWindow*>(window->handle());
 | 
			
		||||
	auto* popupRole = waylandWindow ? waylandWindow->surfaceRole<::xdg_popup>() : nullptr;
 | 
			
		||||
 | 
			
		||||
	anchor->markClean();
 | 
			
		||||
 | 
			
		||||
	if (popupRole) {
 | 
			
		||||
		auto* xdgWmBase = XdgWmBase::instance();
 | 
			
		||||
 | 
			
		||||
		if (xdgWmBase->QtWayland::xdg_wm_base::version() < XDG_POPUP_REPOSITION_SINCE_VERSION) {
 | 
			
		||||
			window->setVisible(false);
 | 
			
		||||
			WaylandPopupPositioner::setFlags(anchor, window);
 | 
			
		||||
			window->setVisible(true);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		auto positioner = XdgPositioner(xdgWmBase->create_positioner());
 | 
			
		||||
 | 
			
		||||
		positioner.set_constraint_adjustment(anchor->adjustment().toInt());
 | 
			
		||||
 | 
			
		||||
		auto anchorRect = anchor->rect();
 | 
			
		||||
		positioner.set_anchor_rect(anchorRect.x, anchorRect.y, anchorRect.w, anchorRect.h);
 | 
			
		||||
 | 
			
		||||
		XdgPositioner::anchor anchorFlag = XdgPositioner::anchor_none;
 | 
			
		||||
		switch (anchor->edges()) {
 | 
			
		||||
		case Edges::Top: anchorFlag = XdgPositioner::anchor_top; break;
 | 
			
		||||
		case Edges::Top | Edges::Right: anchorFlag = XdgPositioner::anchor_top_right; break;
 | 
			
		||||
		case Edges::Right: anchorFlag = XdgPositioner::anchor_right; break;
 | 
			
		||||
		case Edges::Bottom | Edges::Right: anchorFlag = XdgPositioner::anchor_bottom_right; break;
 | 
			
		||||
		case Edges::Bottom: anchorFlag = XdgPositioner::anchor_bottom; break;
 | 
			
		||||
		case Edges::Bottom | Edges::Left: anchorFlag = XdgPositioner::anchor_bottom_left; break;
 | 
			
		||||
		case Edges::Left: anchorFlag = XdgPositioner::anchor_left; break;
 | 
			
		||||
		case Edges::Top | Edges::Left: anchorFlag = XdgPositioner::anchor_top_left; break;
 | 
			
		||||
		default: break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		positioner.set_anchor(anchorFlag);
 | 
			
		||||
 | 
			
		||||
		XdgPositioner::gravity gravity = XdgPositioner::gravity_none;
 | 
			
		||||
		switch (anchor->gravity()) {
 | 
			
		||||
		case Edges::Top: gravity = XdgPositioner::gravity_top; break;
 | 
			
		||||
		case Edges::Top | Edges::Right: gravity = XdgPositioner::gravity_top_right; break;
 | 
			
		||||
		case Edges::Right: gravity = XdgPositioner::gravity_right; break;
 | 
			
		||||
		case Edges::Bottom | Edges::Right: gravity = XdgPositioner::gravity_bottom_right; break;
 | 
			
		||||
		case Edges::Bottom: gravity = XdgPositioner::gravity_bottom; break;
 | 
			
		||||
		case Edges::Bottom | Edges::Left: gravity = XdgPositioner::gravity_bottom_left; break;
 | 
			
		||||
		case Edges::Left: gravity = XdgPositioner::gravity_left; break;
 | 
			
		||||
		case Edges::Top | Edges::Left: gravity = XdgPositioner::gravity_top_left; break;
 | 
			
		||||
		default: break;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		positioner.set_gravity(gravity);
 | 
			
		||||
		auto geometry = waylandWindow->geometry();
 | 
			
		||||
		positioner.set_size(geometry.width(), geometry.height());
 | 
			
		||||
 | 
			
		||||
		// Note: this needs to be set for the initial position as well but no compositor
 | 
			
		||||
		// supports it enough to test
 | 
			
		||||
		positioner.set_reactive();
 | 
			
		||||
 | 
			
		||||
		xdg_popup_reposition(popupRole, positioner.object(), 0);
 | 
			
		||||
 | 
			
		||||
		positioner.destroy();
 | 
			
		||||
	} else {
 | 
			
		||||
		WaylandPopupPositioner::setFlags(anchor, window);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Should be false but nobody supports set_reactive.
 | 
			
		||||
// This just tries its best when something like a bar gets resized.
 | 
			
		||||
bool WaylandPopupPositioner::shouldRepositionOnMove() const { return true; }
 | 
			
		||||
 | 
			
		||||
void WaylandPopupPositioner::setFlags(PopupAnchor* anchor, QWindow* window) {
 | 
			
		||||
	// clang-format off
 | 
			
		||||
	window->setProperty("_q_waylandPopupConstraintAdjustment", anchor->adjustment().toInt());
 | 
			
		||||
	window->setProperty("_q_waylandPopupAnchorRect", anchor->rect().qrect());
 | 
			
		||||
	window->setProperty("_q_waylandPopupAnchor", QVariant::fromValue(Edges::toQt(anchor->edges())));
 | 
			
		||||
	window->setProperty("_q_waylandPopupGravity", QVariant::fromValue(Edges::toQt(anchor->gravity())));
 | 
			
		||||
	// clang-format on
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void installPopupPositioner() { PopupPositioner::setInstance(new WaylandPopupPositioner()); }
 | 
			
		||||
							
								
								
									
										16
									
								
								src/wayland/popupanchor.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/wayland/popupanchor.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <qwindow.h>
 | 
			
		||||
 | 
			
		||||
#include "../core/popupanchor.hpp"
 | 
			
		||||
 | 
			
		||||
class WaylandPopupPositioner: public PopupPositioner {
 | 
			
		||||
public:
 | 
			
		||||
	void reposition(PopupAnchor* anchor, QWindow* window, bool onlyIfDirty = true) override;
 | 
			
		||||
	[[nodiscard]] bool shouldRepositionOnMove() const override;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	static void setFlags(PopupAnchor* anchor, QWindow* window);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void installPopupPositioner();
 | 
			
		||||
							
								
								
									
										14
									
								
								src/wayland/xdgshell.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/wayland/xdgshell.cpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
#include "xdgshell.hpp"
 | 
			
		||||
 | 
			
		||||
#include <qwaylandclientextension.h>
 | 
			
		||||
 | 
			
		||||
namespace qs::wayland::xdg_shell {
 | 
			
		||||
 | 
			
		||||
XdgWmBase::XdgWmBase(): QWaylandClientExtensionTemplate(6) { this->initialize(); }
 | 
			
		||||
 | 
			
		||||
XdgWmBase* XdgWmBase::instance() {
 | 
			
		||||
	static auto* instance = new XdgWmBase(); // NOLINT
 | 
			
		||||
	return instance;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace qs::wayland::xdg_shell
 | 
			
		||||
							
								
								
									
										20
									
								
								src/wayland/xdgshell.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/wayland/xdgshell.hpp
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,20 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <private/qwayland-xdg-shell.h>
 | 
			
		||||
#include <qwaylandclientextension.h>
 | 
			
		||||
 | 
			
		||||
namespace qs::wayland::xdg_shell {
 | 
			
		||||
 | 
			
		||||
// Hack that binds xdg_wm_base twice as QtWaylandXdgShell headers are not exported anywhere.
 | 
			
		||||
 | 
			
		||||
class XdgWmBase
 | 
			
		||||
    : public QWaylandClientExtensionTemplate<XdgWmBase>
 | 
			
		||||
    , public QtWayland::xdg_wm_base {
 | 
			
		||||
public:
 | 
			
		||||
	static XdgWmBase* instance();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	explicit XdgWmBase();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace qs::wayland::xdg_shell
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue