summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorDavid Edmundson <[email protected]>2023-05-25 22:25:46 +0100
committerDavid Edmundson <[email protected]>2025-10-14 00:02:02 +0100
commit4eed22e67ed98ec2565be84a4838955af0faf8b0 (patch)
tree406c45e7a0d046ba7cf67e52553f426a3a803fe2 /src
parent6bb3171e312d1caf3c29c00bcd2cc366b98a5bd1 (diff)
widgets: Pass popup semantic information to Wayland
On Wayland applications do not know their own window positions, this means that popups cannot stay on screen by themselves. When a window tries to go offscreen the way the window is adjusted is context-specific: - The first menu will be kept on screen by moving the x position until it all fits. - A submenu is too close to the screen edge, the menu will open on the opposite side of the parent menu to avoid obscuring it. - A combo box drop downs will vertically flip to the other side of the originating combobox. The Wayland API requires the application to provide semantic hints about the area in which the popup should be placed, along with hints about in which direction we should try to place the popup and how to handle being constrained. At a QPA level we don't know the location of individual widgets so this needs forwarding explicitly. Rather than exposing all possible positioning information, it is inferred from the window type at a QPA level, this required extending windowType to have more explicit values. Ideally this needs to be application-facing API as there are third-party comboboxes, but for now it is private with Qt's default controls opting into it. Task-number: QTBUG-99618 Task-number: QTBUG-124810 Fixes: QTBUG-135883 Change-Id: I49a2f18d1bfe1b755f259627722e076d58c13e8f Reviewed-by: David Redondo <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/gui/kernel/qplatformwindow_p.h12
-rw-r--r--src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp193
-rw-r--r--src/plugins/platforms/wayland/qwaylandwindow.cpp24
-rw-r--r--src/plugins/platforms/wayland/qwaylandwindow_p.h10
-rw-r--r--src/widgets/kernel/qtooltip.cpp13
-rw-r--r--src/widgets/util/qcompleter.cpp15
-rw-r--r--src/widgets/widgets/qcombobox.cpp14
-rw-r--r--src/widgets/widgets/qmenu.cpp19
8 files changed, 212 insertions, 88 deletions
diff --git a/src/gui/kernel/qplatformwindow_p.h b/src/gui/kernel/qplatformwindow_p.h
index c446ac760c0..24c0fd7c431 100644
--- a/src/gui/kernel/qplatformwindow_p.h
+++ b/src/gui/kernel/qplatformwindow_p.h
@@ -125,6 +125,14 @@ struct Q_GUI_EXPORT QWaylandWindow : public QObject
public:
QT_DECLARE_NATIVE_INTERFACE(QWaylandWindow, 1, QWindow)
+ enum WindowType {
+ Default,
+ ToolTip,
+ ComboBox,
+ Menu,
+ SubMenu,
+ };
+
virtual wl_surface *surface() const = 0;
virtual void setCustomMargins(const QMargins &margins) = 0;
virtual void requestXdgActivationToken(uint serial) = 0;
@@ -136,6 +144,10 @@ public:
return role ? *role : nullptr;
}
virtual void setSessionRestoreId(const QString &role) = 0;
+
+ virtual void setExtendedWindowType(WindowType windowType) = 0;
+ virtual void setParentControlGeometry(const QRect &parentAnchor) = 0;
+
Q_SIGNALS:
void surfaceCreated();
void surfaceDestroyed();
diff --git a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp
index 8386b65bdce..17422bd606d 100644
--- a/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp
+++ b/src/plugins/platforms/wayland/plugins/shellintegration/xdg-shell/qwaylandxdgshell.cpp
@@ -697,108 +697,135 @@ void QWaylandXdgSurface::setWindowPosition(const QPoint &position)
window()->updateExposure();
}
+static QtWayland::xdg_positioner::gravity gravityFromEdge(Qt::Edges edges)
+{
+ switch (edges) {
+ case Qt::Edges():
+ return QtWayland::xdg_positioner::gravity_none;
+ case Qt::TopEdge:
+ return QtWayland::xdg_positioner::gravity_top;
+ case Qt::TopEdge | Qt::RightEdge:
+ return QtWayland::xdg_positioner::gravity_top_right;
+ case Qt::RightEdge:
+ return QtWayland::xdg_positioner::gravity_right;
+ case Qt::BottomEdge | Qt::RightEdge:
+ return QtWayland::xdg_positioner::gravity_bottom_right;
+ case Qt::BottomEdge:
+ return QtWayland::xdg_positioner::gravity_bottom;
+ case Qt::BottomEdge | Qt::LeftEdge:
+ return QtWayland::xdg_positioner::gravity_bottom_left;
+ case Qt::LeftEdge:
+ return QtWayland::xdg_positioner::gravity_left;
+ case Qt::TopEdge | Qt::LeftEdge:
+ return QtWayland::xdg_positioner::gravity_top_left;
+ }
+ qCWarning(lcQpaWayland) << "Cannot map positioner gravity " << edges;
+ return QtWayland::xdg_positioner::gravity_none;
+}
+
+static QtWayland::xdg_positioner::anchor anchorFromEdge(Qt::Edges edges)
+{
+ switch (edges) {
+ case Qt::Edges():
+ return QtWayland::xdg_positioner::anchor_none;
+ case Qt::TopEdge:
+ return QtWayland::xdg_positioner::anchor_top;
+ case Qt::TopEdge | Qt::RightEdge:
+ return QtWayland::xdg_positioner::anchor_top_right;
+ case Qt::RightEdge:
+ return QtWayland::xdg_positioner::anchor_right;
+ case Qt::BottomEdge | Qt::RightEdge:
+ return QtWayland::xdg_positioner::anchor_bottom_right;
+ case Qt::BottomEdge:
+ return QtWayland::xdg_positioner::anchor_bottom;
+ case Qt::BottomEdge | Qt::LeftEdge:
+ return QtWayland::xdg_positioner::anchor_bottom_left;
+ case Qt::LeftEdge:
+ return QtWayland::xdg_positioner::anchor_left;
+ case Qt::TopEdge | Qt::LeftEdge:
+ return QtWayland::xdg_positioner::anchor_top_left;
+ }
+ qCWarning(lcQpaWayland) << "Cannot map positioner anchor" << edges;
+ return QtWayland::xdg_positioner::anchor_none;
+}
+
std::unique_ptr<QWaylandXdgSurface::Positioner> QWaylandXdgSurface::createPositioner(QWaylandWindow *parent)
{
std::unique_ptr<Positioner> positioner(new Positioner(m_shell));
- // set_popup expects a position relative to the parent
- QRect windowGeometry = m_window->windowContentGeometry();
- QMargins windowMargins = m_window->windowContentMargins() - m_window->clientSideMargins();
- QMargins parentMargins = parent->windowContentMargins() - parent->clientSideMargins();
- // These property overrides may be removed when public API becomes available
- QRect placementAnchor = m_window->window()->property("_q_waylandPopupAnchorRect").toRect();
- if (!placementAnchor.isValid()) {
- placementAnchor = QRect(m_window->geometry().topLeft() - parent->geometry().topLeft(), QSize(1,1));
- }
- placementAnchor.translate(windowMargins.left(), windowMargins.top());
- placementAnchor.translate(-parentMargins.left(), -parentMargins.top());
+ // Default case, map the guessed global position to a relative position
+ QRect placementAnchor = QRect(m_window->geometry().topLeft() - parent->geometry().topLeft(), QSize(1,1));
+ Qt::Edges anchor = Qt::TopEdge | Qt::RightEdge;
+ Qt::Edges gravity = Qt::BottomEdge | Qt::RightEdge;
+ uint32_t constraintAdjustment = QtWayland::xdg_positioner::constraint_adjustment_slide_x | QtWayland::xdg_positioner::constraint_adjustment_slide_y;
- uint32_t anchor = QtWayland::xdg_positioner::anchor_top_left;
+ // Override from window type
+ if (m_window->parentControlGeometry().isValid())
+ placementAnchor = m_window->parentControlGeometry();
+
+ switch (m_window->extendedWindowType()) {
+ case QNativeInterface::Private::QWaylandWindow::Menu:
+ case QNativeInterface::Private::QWaylandWindow::WindowType::ComboBox:
+ anchor = Qt::BottomEdge | Qt::LeftEdge;
+ gravity = Qt::BottomEdge | Qt::RightEdge;
+ constraintAdjustment = QtWayland::xdg_positioner::constraint_adjustment_slide_x |
+ QtWayland::xdg_positioner::constraint_adjustment_flip_y | QtWayland::xdg_positioner::constraint_adjustment_slide_y;
+ break;
+ case QNativeInterface::Private::QWaylandWindow::SubMenu:
+ anchor = Qt::TopEdge | Qt::RightEdge;
+ gravity = Qt::BottomEdge | Qt::RightEdge;
+ constraintAdjustment = QtWayland::xdg_positioner::constraint_adjustment_flip_x |
+ QtWayland::xdg_positioner::constraint_adjustment_slide_y;
+ break;
+ case QNativeInterface::Private::QWaylandWindow::ToolTip:
+ anchor = Qt::BottomEdge | Qt::RightEdge;
+ gravity = Qt::BottomEdge | Qt::RightEdge;
+ constraintAdjustment = QtWayland::xdg_positioner::constraint_adjustment_flip_x | QtWayland::xdg_positioner::constraint_adjustment_slide_x |
+ QtWayland::xdg_positioner::constraint_adjustment_flip_y | QtWayland::xdg_positioner::constraint_adjustment_slide_y;
+ break;
+ default:
+ break;
+ }
+
+ if (qApp->layoutDirection() == Qt::RightToLeft) {
+ if (anchor & (Qt::RightEdge | Qt::LeftEdge))
+ anchor ^= (Qt::RightEdge | Qt::LeftEdge);
+ if (gravity & (Qt::RightEdge | Qt::LeftEdge))
+ gravity ^= (Qt::RightEdge | Qt::LeftEdge);
+ }
+
+ // Override with properties fauxAPI
+ const QVariant placementAnchorVariant = m_window->window()->property("_q_waylandPopupAnchorRect");
+ if (placementAnchorVariant.isValid())
+ placementAnchor = placementAnchorVariant.toRect();
const QVariant anchorVariant = m_window->window()->property("_q_waylandPopupAnchor");
- if (anchorVariant.isValid()) {
- switch (anchorVariant.value<Qt::Edges>()) {
- case Qt::Edges():
- anchor = QtWayland::xdg_positioner::anchor_none;
- break;
- case Qt::TopEdge:
- anchor = QtWayland::xdg_positioner::anchor_top;
- break;
- case Qt::TopEdge | Qt::RightEdge:
- anchor = QtWayland::xdg_positioner::anchor_top_right;
- break;
- case Qt::RightEdge:
- anchor = QtWayland::xdg_positioner::anchor_right;
- break;
- case Qt::BottomEdge | Qt::RightEdge:
- anchor = QtWayland::xdg_positioner::anchor_bottom_right;
- break;
- case Qt::BottomEdge:
- anchor = QtWayland::xdg_positioner::anchor_bottom;
- break;
- case Qt::BottomEdge | Qt::LeftEdge:
- anchor = QtWayland::xdg_positioner::anchor_bottom_left;
- break;
- case Qt::LeftEdge:
- anchor = QtWayland::xdg_positioner::anchor_left;
- break;
- case Qt::TopEdge | Qt::LeftEdge:
- anchor = QtWayland::xdg_positioner::anchor_top_left;
- break;
- }
- }
-
- uint32_t gravity = QtWayland::xdg_positioner::gravity_bottom_right;
+ if (anchorVariant.isValid())
+ anchor = anchorVariant.value<Qt::Edges>();
const QVariant popupGravityVariant = m_window->window()->property("_q_waylandPopupGravity");
- if (popupGravityVariant.isValid()) {
- switch (popupGravityVariant.value<Qt::Edges>()) {
- case Qt::Edges():
- gravity = QtWayland::xdg_positioner::gravity_none;
- break;
- case Qt::TopEdge:
- gravity = QtWayland::xdg_positioner::gravity_top;
- break;
- case Qt::TopEdge | Qt::RightEdge:
- gravity = QtWayland::xdg_positioner::gravity_top_right;
- break;
- case Qt::RightEdge:
- gravity = QtWayland::xdg_positioner::gravity_right;
- break;
- case Qt::BottomEdge | Qt::RightEdge:
- gravity = QtWayland::xdg_positioner::gravity_bottom_right;
- break;
- case Qt::BottomEdge:
- gravity = QtWayland::xdg_positioner::gravity_bottom;
- break;
- case Qt::BottomEdge | Qt::LeftEdge:
- gravity = QtWayland::xdg_positioner::gravity_bottom_left;
- break;
- case Qt::LeftEdge:
- gravity = QtWayland::xdg_positioner::gravity_left;
- break;
- case Qt::TopEdge | Qt::LeftEdge:
- gravity = QtWayland::xdg_positioner::gravity_top_left;
- break;
- }
- }
-
- uint32_t constraintAdjustment = QtWayland::xdg_positioner::constraint_adjustment_slide_x | QtWayland::xdg_positioner::constraint_adjustment_slide_y;
+ if (popupGravityVariant.isValid())
+ gravity = popupGravityVariant.value<Qt::Edges>();
const QVariant constraintAdjustmentVariant = m_window->window()->property("_q_waylandPopupConstraintAdjustment");
- if (constraintAdjustmentVariant.isValid()) {
+ if (constraintAdjustmentVariant.isValid())
constraintAdjustment = constraintAdjustmentVariant.toUInt();
- }
+
+ // set_popup expects a position relative to the parent
+ QRect windowGeometry = m_window->windowContentGeometry();
+ QMargins windowMargins = m_window->windowContentMargins() - m_window->clientSideMargins();
+ QMargins parentMargins = parent->windowContentMargins() - parent->clientSideMargins();
+ placementAnchor.translate(windowMargins.left(), windowMargins.top());
+ placementAnchor.translate(-parentMargins.left(), -parentMargins.top());
positioner->set_anchor_rect(placementAnchor.x(),
placementAnchor.y(),
placementAnchor.width(),
placementAnchor.height());
- positioner->set_anchor(anchor);
- positioner->set_gravity(gravity);
+ positioner->set_anchor(anchorFromEdge(anchor));
+ positioner->set_gravity(gravityFromEdge(gravity));
positioner->set_size(windowGeometry.width(), windowGeometry.height());
positioner->set_constraint_adjustment(constraintAdjustment);
return positioner;
}
-
void QWaylandXdgSurface::setIcon(const QIcon &icon)
{
if (!m_shell->m_topLevelIconManager || !m_toplevel)
diff --git a/src/plugins/platforms/wayland/qwaylandwindow.cpp b/src/plugins/platforms/wayland/qwaylandwindow.cpp
index 7c300843518..be527b08f4d 100644
--- a/src/plugins/platforms/wayland/qwaylandwindow.cpp
+++ b/src/plugins/platforms/wayland/qwaylandwindow.cpp
@@ -481,8 +481,9 @@ void QWaylandWindow::setGeometry(const QRect &r)
if (mShellSurface && !mInResizeFromApplyConfigure) {
const QRect frameGeometry = r.marginsAdded(clientSideMargins()).marginsRemoved(windowContentMargins());
- if (qt_window_private(window())->positionAutomatic)
+ if (qt_window_private(window())->positionAutomatic || m_popupInfo.parentControlGeometry.isValid())
mShellSurface->setWindowSize(frameGeometry.size());
+
else
mShellSurface->setWindowGeometry(frameGeometry);
}
@@ -1945,6 +1946,27 @@ QString QWaylandWindow::sessionRestoreId() const
return mSessionRestoreId;
}
+void QWaylandWindow::setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::WindowType windowType) {
+ m_popupInfo.extendedWindowType = windowType;
+}
+
+QNativeInterface::Private::QWaylandWindow::WindowType QWaylandWindow::extendedWindowType() const
+{
+ return m_popupInfo.extendedWindowType;
+}
+
+void QWaylandWindow::setParentControlGeometry(const QRect &parentControlGeometry) {
+ m_popupInfo.parentControlGeometry = parentControlGeometry;
+ if (mExposed) {
+ mShellSurface->setWindowPosition(window()->position());
+ }
+}
+
+QRect QWaylandWindow::parentControlGeometry() const
+{
+ return m_popupInfo.parentControlGeometry;
+}
+
}
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/wayland/qwaylandwindow_p.h b/src/plugins/platforms/wayland/qwaylandwindow_p.h
index 854724daf82..d6b24d0569f 100644
--- a/src/plugins/platforms/wayland/qwaylandwindow_p.h
+++ b/src/plugins/platforms/wayland/qwaylandwindow_p.h
@@ -255,6 +255,11 @@ public:
void setSessionRestoreId(const QString &role) override;
QString sessionRestoreId() const;
+ void setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::WindowType) override;
+ QNativeInterface::Private::QWaylandWindow::WindowType extendedWindowType() const;
+ void setParentControlGeometry(const QRect &parentAnchor) override;
+ QRect parentControlGeometry() const;
+
public Q_SLOTS:
void applyConfigure();
@@ -397,6 +402,11 @@ private:
void handleFrameCallback(struct ::wl_callback* callback);
const QPlatformWindow *lastParent = nullptr;
+ struct {
+ QRect parentControlGeometry;
+ QNativeInterface::Private::QWaylandWindow::WindowType extendedWindowType = QNativeInterface::Private::QWaylandWindow::Default;
+ } m_popupInfo;
+
static QWaylandWindow *mMouseGrab;
static QWaylandWindow *mTopPopup;
diff --git a/src/widgets/kernel/qtooltip.cpp b/src/widgets/kernel/qtooltip.cpp
index 9e6aaf4b95f..d989feb7f91 100644
--- a/src/widgets/kernel/qtooltip.cpp
+++ b/src/widgets/kernel/qtooltip.cpp
@@ -20,6 +20,8 @@
#if QT_CONFIG(style_stylesheet)
#include <private/qstylesheetstyle_p.h>
#endif
+#include <qpa/qplatformwindow.h>
+#include <qpa/qplatformwindow_p.h>
#include <qlabel.h>
#include <QtWidgets/private/qlabel_p.h>
@@ -386,6 +388,17 @@ void QTipLabel::placeTip(const QPoint &pos, QWidget *w)
p += offset;
+#if QT_CONFIG(wayland)
+ create();
+ if (auto waylandWindow = dynamic_cast<QNativeInterface::Private::QWaylandWindow*>(windowHandle()->handle())) {
+ // based on the existing code below, by default position at 'p' stored at the bottom right of our rect
+ // then flip to the other arbitrary 4x24 space if constrained
+ const QRect controlGeometry(QRect(p.x() - 4, p.y() - 24, 4, 24));
+ waylandWindow->setParentControlGeometry(controlGeometry);
+ waylandWindow->setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::ToolTip);
+ }
+#endif
+
QRect screenRect = screen->geometry();
if (p.x() + this->width() > screenRect.x() + screenRect.width())
p.rx() -= 4 + this->width();
diff --git a/src/widgets/util/qcompleter.cpp b/src/widgets/util/qcompleter.cpp
index 220f600ea41..735b574d293 100644
--- a/src/widgets/util/qcompleter.cpp
+++ b/src/widgets/util/qcompleter.cpp
@@ -122,6 +122,8 @@
#include "QtGui/qevent.h"
#include <private/qapplication_p.h>
#include <private/qwidget_p.h>
+#include <qpa/qplatformwindow.h>
+#include <qpa/qplatformwindow_p.h>
#if QT_CONFIG(lineedit)
#include "QtWidgets/qlineedit.h"
#endif
@@ -925,10 +927,15 @@ void QCompleterPrivate::showPopup(const QRect& rect)
popup->setGeometry(pos.x(), pos.y(), w, h);
if (!popup->isVisible()) {
- // Make sure popup has a transient parent set, Wayland needs it. QTBUG-130474
- popup->winId(); // force creation of windowHandle
- popup->windowHandle()->setTransientParent(widget->window()->windowHandle());
-
+#if QT_CONFIG(wayland)
+ popup->createWinId();
+ if (auto waylandWindow = dynamic_cast<QNativeInterface::Private::QWaylandWindow*>(popup->windowHandle()->handle())) {
+ popup->windowHandle()->setTransientParent(widget->window()->windowHandle());
+ const QRect controlGeometry = QRect(widget->mapTo(widget->topLevelWidget(), QPoint(0,0)), widget->size());
+ waylandWindow->setParentControlGeometry(controlGeometry);
+ waylandWindow->setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::ComboBox);
+ }
+#endif
popup->show();
}
}
diff --git a/src/widgets/widgets/qcombobox.cpp b/src/widgets/widgets/qcombobox.cpp
index 6f25b8bde67..2f51b83a49d 100644
--- a/src/widgets/widgets/qcombobox.cpp
+++ b/src/widgets/widgets/qcombobox.cpp
@@ -7,6 +7,9 @@
#include <qstylepainter.h>
#include <qpa/qplatformtheme.h>
#include <qpa/qplatformmenu.h>
+#include <qpa/qplatformwindow.h>
+#include <qpa/qplatformwindow_p.h>
+
#include <qlineedit.h>
#include <qapplication.h>
#include <qlistview.h>
@@ -2868,6 +2871,17 @@ void QComboBox::showPopup()
container->hide();
}
}
+
+#if QT_CONFIG(wayland)
+ if (auto waylandWindow = dynamic_cast<QNativeInterface::Private::QWaylandWindow*>(container->windowHandle()->handle())) {
+ const QRect popup(style->subControlRect(QStyle::CC_ComboBox, &opt,
+ QStyle::SC_ComboBoxListBoxPopup, this));
+ const QRect controlGeometry = QRect(mapTo(window(), popup.topLeft()), popup.size());
+ waylandWindow->setParentControlGeometry(controlGeometry);
+ waylandWindow->setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::ComboBox);
+ }
+#endif
+
container->show();
if (!neededHorizontalScrollBar && needHorizontalScrollBar()) {
listRect.adjust(0, 0, 0, sb->height());
diff --git a/src/widgets/widgets/qmenu.cpp b/src/widgets/widgets/qmenu.cpp
index 5cda8f33f4c..7d4228709be 100644
--- a/src/widgets/widgets/qmenu.cpp
+++ b/src/widgets/widgets/qmenu.cpp
@@ -40,6 +40,8 @@
#include <private/qaction_p.h>
#include <private/qguiapplication_p.h>
#include <qpa/qplatformtheme.h>
+#include <qpa/qplatformwindow.h>
+#include <qpa/qplatformwindow_p.h>
#include <private/qstyle_p.h>
QT_BEGIN_NAMESPACE
@@ -2535,6 +2537,23 @@ void QMenuPrivate::popup(const QPoint &p, QAction *atAction, PositionFunction po
}
popupScreen = QGuiApplication::screenAt(pos);
q->setGeometry(QRect(pos, size));
+
+#if QT_CONFIG(wayland)
+ q->create();
+ if (auto waylandWindow = dynamic_cast<QNativeInterface::Private::QWaylandWindow*>(q->windowHandle()->handle())) {
+ if (causedButton) {
+ waylandWindow->setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::Menu);
+ waylandWindow->setParentControlGeometry(causedButton->geometry());
+ } else if (caused) {
+ waylandWindow->setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::SubMenu);
+ waylandWindow->setParentControlGeometry(caused->d_func()->actionRect(caused->d_func()->currentAction));
+ } else if (auto menubar = qobject_cast<QMenuBar*>(causedPopup.widget)) {
+ waylandWindow->setExtendedWindowType(QNativeInterface::Private::QWaylandWindow::Menu);
+ waylandWindow->setParentControlGeometry(menubar->actionGeometry(causedPopup.action));
+ }
+ }
+#endif
+
#if QT_CONFIG(effects)
int hGuess = q->isRightToLeft() ? QEffects::LeftScroll : QEffects::RightScroll;
int vGuess = QEffects::DownScroll;