As it stands, on Wayland, widgets without parents can't be moved with the move
command. See this limitation here and another discussion here.
To move a top level widget we could use an "interactive session" provided by the platform that supports it, through the QWindow.startSystemMove
call.
The problem is by depending on something other than Qt to handle the move, on Wayland, the Qt MouseEvents do not work properly, after the system move.
I added a code snippet below to illustrate. After the startSystemMove
, the MouseRelease event is not fired. Hover events also don't work on the child widgets until the parent widget receives a right-click or 3rd mouse button click.
I am trying to make hover events on child widgets work, after startSystemMove
is done.
I've tried simulating QMouseRelease event through the application right after a startSystemMove is done, but this doesn't work as well. Is there something I'm missing?
import sys
from PySide6 import QtWidgets, QtCore, QtGui
import PySide6.QtWidgets
Qt = QtCore.Qt
QMouseEvent = QtGui.QMouseEvent
QWidget = QtWidgets.QWidget
QEvent = QtCore.QEvent
CursorShape = Qt.CursorShape
QApplication = QtWidgets.QApplication
WidgetAttributes = Qt.WidgetAttribute
WindowTypes = Qt.WindowType
QPoint = QtCore.QPoint
QVBoxLayout = QtWidgets.QVBoxLayout
QObject = QtCore.QObject
Signal = QtCore.Signal
Slot = QtCore.Slot
QFrame = QtWidgets.QFrame
QByteArray = QtCore.QByteArray
class QMovableWidget(QWidget):
def __init__(self, p, f,):
# type: (QWidget | None, WindowTypes,) -> None
super().__init__(p, f)
self.setAttribute(Qt.WidgetAttribute.WA_MouseTracking, True)
self.setAttribute(Qt.WidgetAttribute.WA_NativeWindow, True)
self.___systemMove = False
def event(self, ev):
# type: (QEvent | QMouseEvent) -> bool
if (
ev.type() == QEvent.Type.MouseButtonPress
and ev.button() == Qt.MouseButton.LeftButton
):
print(self.window().windowHandle().startSystemMove())
self.___systemMove = True
print("Start System Move")
if ev.type() == QEvent.Type.WindowActivate:
if self.___systemMove:
print("System Move Done")
self.___systemMove = False
self.updateGeometry()
return super().event(ev)
class OuterWidget(QMovableWidget):
def __init__(self, p: QWidget | None, f: WindowTypes,) -> None:
super().__init__(p, f,)
self.setAttribute(WidgetAttributes.WA_Hover, True)
self.setCursor(CursorShape.ArrowCursor)
def event(self, ev: QEvent | QMouseEvent) -> bool:
if ev.type() == QEvent.Type.HoverEnter:
print("OuterWidget hover enter")
elif (ev.type() == QEvent.Type.HoverLeave):
print("OuterWidget hover leave")
return super().event(ev)
def mousePressEvent(self, event: QMouseEvent) -> None:
print("OuterWidget", "ms press")
return super().mousePressEvent(event)
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
print("OuterWidget", "ms release")
return super().mouseReleaseEvent(event)
# def mouseMoveEvent(self, event: QMouseEvent) -> None:
# global count
# print("OuterWidget", "ms move", count, event)
# count += 1
# return super().mouseMoveEvent(event)
def nativeEvent(self, eventType: QByteArray | bytes, message: int) -> object:
print("OuterWidget", "Native Event", eventType, message)
return super().nativeEvent(eventType, message)
class InnerWidget(QFrame):
def __init__(self, parent: QWidget | None = ..., f: WindowTypes = ...) -> None:
super().__init__(parent, f)
self.setAttribute(WidgetAttributes.WA_MouseTracking, True)
self.setAttribute(WidgetAttributes.WA_Hover, True)
self.setCursor(CursorShape.PointingHandCursor)
def event(self, e: QEvent) -> bool:
if (e.type() == QEvent.Type.HoverEnter):
print("InnerWidget hover enter")
elif (e.type() == QEvent.Type.HoverLeave):
print("InnerWidget hover leave")
return super().event(e)
def nativeEvent(self, eventType: QByteArray | bytes, message: int) -> object:
print("InnerWidget", "Native Event", eventType, message)
return super().nativeEvent(eventType, message)
def mousePressEvent(self, event: QMouseEvent) -> None:
print("InnerWidget", "ms press")
return super().mousePressEvent(event)
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
print("InnerWidget", "ms release")
return super().mouseReleaseEvent(event)
# def mouseMoveEvent(self, event: QMouseEvent) -> None:
# global count
# print("InnerWidget", "ms move", count, event)
# event.accept()
# count += 1
# return super().mouseMoveEvent(event)
count = 1
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setStyle("Fusion")
window = OuterWidget(
None, WindowTypes.Window | WindowTypes.FramelessWindowHint
)
window.resize(300, 300)
l = QVBoxLayout()
window.setLayout(l)
w = InnerWidget(window, WindowTypes.Widget)
w.setStyleSheet("background-color: red")
l.addWidget(w)
window.show()
sys.exit(app.exec())