aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRichard Moe Gustavsen <[email protected]>2024-04-23 15:53:10 +0200
committerRichard Moe Gustavsen <[email protected]>2024-05-28 09:24:19 +0200
commit34ec2dec17bb0b4b32b6d9c1b0c23f3afd6b78f4 (patch)
tree7dc1c4012e93004844441a94770f68c9e3f27e42
parent2874586cc848397ab649b49b2d39daf4c86d95fc (diff)
Menu: add insets to the menus, to accommodate for drop shadows
Several of the styles offers a Menu with a drop shadow. And the shadow is drawn on the outside of the Menu. This works fine when the menu is shown as an item in the scene, since Quick allows controls to draw out-of-bounds. But when we now place the Menus inside native windows, this will no longer be the case, as the windows will be resized to fit the Menus, shadows excluded. To solve this, this patch will make the Menus bigger, without touching the size of the background, so that they include the drop shadows. This is easily done by pushing the background items a bit in, using insets. The next issue is that when the application, or the MenuBar, requests a Menu to open at at specific position, we want the top left corner of the menu frame to be placed at this position. But since the Menu background is now shifted into the Menu, we need to teach QQuickPopup and and QQuickMenu to take insets into account when positioning a popup/menu. Taking the insets into account like this should be fine, since they're documented to be used for this exact purpose, of adding drop shadow effects. Change-Id: I2e5f0bcf14100d92dc4cd3c2cb7630601c0c1320 Reviewed-by: Mitch Curtis <[email protected]>
-rw-r--r--src/quickcontrols/macos/Menu.qml17
-rw-r--r--src/quickcontrols/windows/Menu.qml17
-rw-r--r--src/quicktemplates/qquickmenu.cpp29
-rw-r--r--src/quicktemplates/qquickpopupitem.cpp12
-rw-r--r--src/quicktemplates/qquickpopupitem_p_p.h2
-rw-r--r--src/quicktemplates/qquickpopuppositioner.cpp33
-rw-r--r--tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp40
7 files changed, 126 insertions, 24 deletions
diff --git a/src/quickcontrols/macos/Menu.qml b/src/quickcontrols/macos/Menu.qml
index 0634a56860..5004a44c42 100644
--- a/src/quickcontrols/macos/Menu.qml
+++ b/src/quickcontrols/macos/Menu.qml
@@ -15,10 +15,19 @@ T.Menu {
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
contentHeight + topPadding + bottomPadding)
- leftPadding: 5
- rightPadding: 5
- topPadding: 5
- bottomPadding: 5
+ // The insets are found by examining the MultiEffect.itemRect, which
+ // contains the drop shadow offsets. QQuickPopup will subract these insets when
+ // it opens up the menu so that the top left corner of the background ends up at
+ // the requested popup position.
+ // Note: the insets are hard-coded to avoid a binding loop to implicit size.
+ leftInset: 32
+ topInset: 32
+ rightInset: 32
+ bottomInset: 32
+ leftPadding: leftInset + 5
+ rightPadding: rightInset + 5
+ topPadding: topInset + 5
+ bottomPadding: bottomInset + 5
margins: 0
overlap: 4
diff --git a/src/quickcontrols/windows/Menu.qml b/src/quickcontrols/windows/Menu.qml
index a0041a9439..0b110d7744 100644
--- a/src/quickcontrols/windows/Menu.qml
+++ b/src/quickcontrols/windows/Menu.qml
@@ -15,10 +15,19 @@ T.Menu {
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
contentHeight + topPadding + bottomPadding)
- leftPadding: 5
- rightPadding: 5
- topPadding: 5
- bottomPadding: 5
+ // The insets are found by examining the MultiEffect.itemRect, which
+ // contains the drop shadow offsets. QQuickPopup will subract these insets when
+ // it opens up the menu so that the top left corner of the background ends up at
+ // the requested popup position.
+ // Note: the insets are hard-coded to avoid a binding loop to implicit size.
+ leftInset: 32
+ topInset: 32
+ rightInset: 32
+ bottomInset: 32
+ leftPadding: leftInset + 5
+ rightPadding: rightInset + 5
+ topPadding: topInset + 5
+ bottomPadding: bottomInset + 5
margins: 0
overlap: 4
diff --git a/src/quicktemplates/qquickmenu.cpp b/src/quicktemplates/qquickmenu.cpp
index ac538b5a81..f216c4985f 100644
--- a/src/quicktemplates/qquickmenu.cpp
+++ b/src/quicktemplates/qquickmenu.cpp
@@ -866,18 +866,29 @@ QQuickPopupPositioner *QQuickMenuPrivate::getPositioner()
void QQuickMenuPositioner::reposition()
{
QQuickMenu *menu = static_cast<QQuickMenu *>(popup());
- QQuickMenuPrivate *p = QQuickMenuPrivate::get(menu);
- if (p->parentMenu) {
- if (p->cascade) {
- if (p->popupItem->isMirrored())
- menu->setPosition(QPointF(-menu->width() - p->parentMenu->leftPadding() + menu->overlap(), -menu->topPadding()));
- else if (p->parentItem)
- menu->setPosition(QPointF(p->parentItem->width() + p->parentMenu->rightPadding() - menu->overlap(), -menu->topPadding()));
+ QQuickMenuPrivate *menu_d = QQuickMenuPrivate::get(menu);
+
+ if (QQuickMenu *parentMenu = menu_d->parentMenu) {
+ if (menu_d->cascade) {
+ // Align the menu to the frame of the parent menu, minus overlap. The position
+ // should be in the coordinate system of the parentItem.
+ if (menu_d->popupItem->isMirrored()) {
+ menu->setPosition({-menu->width()
+ - parentMenu->leftPadding() + parentMenu->leftInset()
+ + menu->overlap(),
+ menu->topInset() - menu->topPadding()});
+ } else if (menu_d->parentItem) {
+ menu->setPosition({menu_d->parentItem->width()
+ + parentMenu->rightPadding() - parentMenu->rightInset()
+ - menu->overlap(),
+ menu->topInset() - menu->topPadding()});
+ }
} else {
- menu->setPosition(QPointF(p->parentMenu->x() + (p->parentMenu->width() - menu->width()) / 2,
- p->parentMenu->y() + (p->parentMenu->height() - menu->height()) / 2));
+ menu->setPosition(QPointF(parentMenu->x() + (parentMenu->width() - menu->width()) / 2,
+ parentMenu->y() + (parentMenu->height() - menu->height()) / 2));
}
}
+
QQuickPopupPositioner::reposition();
}
diff --git a/src/quicktemplates/qquickpopupitem.cpp b/src/quicktemplates/qquickpopupitem.cpp
index 574ea4589a..e60e4c5d77 100644
--- a/src/quicktemplates/qquickpopupitem.cpp
+++ b/src/quicktemplates/qquickpopupitem.cpp
@@ -146,6 +146,18 @@ QPalette QQuickPopupItemPrivate::parentPalette(const QPalette &fallbackPalette)
return QQuickPopupPrivate::get(popup)->parentPalette(fallbackPalette);
}
+bool QQuickPopupItem::contains(const QPointF &point) const
+{
+ Q_D(const QQuickPopupItem);
+ // A popup will often contain a drop shadow. And when determining if a point
+ // is inside the popup, we want to exclude that shadow from the test, and only
+ // consider the background rect.
+ const QRectF backgroundRect = boundingRect().adjusted(
+ d->popup->leftInset(), d->popup->topInset(),
+ -d->popup->rightInset(), -d->popup->bottomInset());
+ return backgroundRect.contains(point);
+}
+
void QQuickPopupItem::updatePolish()
{
Q_D(QQuickPopupItem);
diff --git a/src/quicktemplates/qquickpopupitem_p_p.h b/src/quicktemplates/qquickpopupitem_p_p.h
index c0ef0f4d85..cb0d2709cd 100644
--- a/src/quicktemplates/qquickpopupitem_p_p.h
+++ b/src/quicktemplates/qquickpopupitem_p_p.h
@@ -30,6 +30,8 @@ class Q_QUICKTEMPLATES2_EXPORT QQuickPopupItem : public QQuickPage
public:
explicit QQuickPopupItem(QQuickPopup *popup);
+ bool contains(const QPointF &point) const override;
+
protected:
void updatePolish() override;
diff --git a/src/quicktemplates/qquickpopuppositioner.cpp b/src/quicktemplates/qquickpopuppositioner.cpp
index 8c247290b3..c211da0ff0 100644
--- a/src/quicktemplates/qquickpopuppositioner.cpp
+++ b/src/quicktemplates/qquickpopuppositioner.cpp
@@ -73,16 +73,30 @@ void QQuickPopupPositioner::setParentItem(QQuickItem *parent)
void QQuickPopupPositioner::reposition()
{
- if (auto p = QQuickPopupPrivate::get(popup()); p->usePopupWindow()) {
+ auto p = QQuickPopupPrivate::get(popup());
+ QQuickPopupItem *popupItem = static_cast<QQuickPopupItem *>(m_popup->popupItem());
+
+ if (p->usePopupWindow()) {
+ // If the popup has insets, it means that it could have a drop shadow. At least
+ // that's the use case insets are documented to solve. And then we align the
+ // window so that the corner of the background, rather than the drop shadow,
+ // ends up at the requested position. We only do that for positive insets, since
+ // negative insets means that the background is already at the correct position.
+ // Note that this might move the popup out of the window again, but only the shadow.
+ QPoint pos(p->x, p->y);
+ if (popupItem->leftInset() > 0)
+ pos.rx() -= popupItem->leftInset();
+ if (popupItem->topInset() > 0)
+ pos.ry() -= popupItem->topInset();
+
if (!p->popupWindow || !p->parentItem) {
- p->effectiveX = p->x;
- p->effectiveY = p->y;
+ p->effectiveX = pos.x();
+ p->effectiveY = pos.y();
return;
}
const QQuickItem *centerInParent = p->anchors ? p->getAnchors()->centerIn() : nullptr;
const QQuickOverlay *centerInOverlay = qobject_cast<const QQuickOverlay *>(centerInParent);
- QPoint pos(p->x, p->y);
if (centerInParent == p->parentItem || centerInOverlay) {
pos = centerInOverlay ? QPoint(qRound(centerInOverlay->width() / 2.0), qRound(centerInOverlay->height() / 2.0))
@@ -97,8 +111,6 @@ void QQuickPopupPositioner::reposition()
return;
}
- QQuickItem *popupItem = m_popup->popupItem();
-
if (!popupItem->isVisible())
return;
@@ -116,7 +128,6 @@ void QQuickPopupPositioner::reposition()
bool widthAdjusted = false;
bool heightAdjusted = false;
- QQuickPopupPrivate *p = QQuickPopupPrivate::get(m_popup);
const QQuickItem *centerInParent = p->anchors ? p->getAnchors()->centerIn() : nullptr;
const QQuickOverlay *centerInOverlay = qobject_cast<const QQuickOverlay*>(centerInParent);
@@ -255,6 +266,14 @@ void QQuickPopupPositioner::reposition()
m_positioning = true;
+ // Ensure that the corner of the background item is placed at the
+ // designated location, even if this means that visual effects like
+ // drop shadows end up outside the window.
+ if (popupItem->leftInset() > 0)
+ rect.translate(-popupItem->leftInset(), 0);
+ if (popupItem->topInset() > 0)
+ rect.translate(0, -popupItem->topInset());
+
popupItem->setPosition(rect.topLeft());
// If the popup was assigned a parent, rect will be in scene coordinates,
diff --git a/tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp b/tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp
index 9cd806f05e..17960ccde5 100644
--- a/tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp
+++ b/tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp
@@ -65,6 +65,7 @@ private slots:
void applicationWindow();
void menubarAsHeader_data();
void menubarAsHeader();
+ void menuPosition();
void changeDelegate_data();
void changeDelegate();
void invalidDelegate_data();
@@ -1468,6 +1469,45 @@ void tst_qquickmenubar::menubarAsHeader()
}
}
+void tst_qquickmenubar::menuPosition()
+{
+ // A Menu.qml will typically have a background with a drop-shadow. And to make
+ // room for this shadow, the Menu itself is made bigger by using Control.insets.
+ // This will make room for both the background and its shadow.
+ // To make sure that the corner of the background (rather than the shadow) ends up
+ // at the requested menu position, the effective position of the menu will be
+ // shifted a bit up and left. This test will therefore check that the corner of the
+ // background ends up that the requested position.
+ QCoreApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, true);
+ // Use in-scene popups for this test, since we have no guarantee where a window
+ // manager might end up placing a menu.
+ QCoreApplication::setAttribute(Qt::AA_DontUsePopupWindows, true);
+ QQmlApplicationEngine engine;
+ engine.load(testFileUrl("menus.qml"));
+
+ QScopedPointer<QQuickApplicationWindow> window(qobject_cast<QQuickApplicationWindow *>(engine.rootObjects().value(0)));
+ QVERIFY(window);
+ QQuickMenuBar *menuBar = window->property("menuBar").value<QQuickMenuBar *>();
+ QVERIFY(menuBar);
+
+ QPointF requestedPos{50, 50};
+
+ QQuickMenu *editMenu = menuBar->menuAt(1);
+ QVERIFY(editMenu);
+ editMenu->setX(requestedPos.x());
+ editMenu->setY(requestedPos.y());
+ editMenu->setVisible(true);
+ QTRY_VERIFY(editMenu->isOpened());
+ QCOMPARE(editMenu->x(), requestedPos.x());
+ QCOMPARE(editMenu->y(), requestedPos.y());
+
+ QQuickItem *background = editMenu->background();
+ QVERIFY(background);
+
+ QPointF bgPos = background->mapToItem(editMenu->parentItem(), {0, 0});
+ QCOMPARE(bgPos, requestedPos);
+}
+
void tst_qquickmenubar::changeDelegate_data()
{
QTest::addColumn<bool>("native");