diff options
| author | Richard Moe Gustavsen <[email protected]> | 2024-04-23 15:53:10 +0200 |
|---|---|---|
| committer | Richard Moe Gustavsen <[email protected]> | 2024-05-28 09:24:19 +0200 |
| commit | 34ec2dec17bb0b4b32b6d9c1b0c23f3afd6b78f4 (patch) | |
| tree | 7dc1c4012e93004844441a94770f68c9e3f27e42 | |
| parent | 2874586cc848397ab649b49b2d39daf4c86d95fc (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.qml | 17 | ||||
| -rw-r--r-- | src/quickcontrols/windows/Menu.qml | 17 | ||||
| -rw-r--r-- | src/quicktemplates/qquickmenu.cpp | 29 | ||||
| -rw-r--r-- | src/quicktemplates/qquickpopupitem.cpp | 12 | ||||
| -rw-r--r-- | src/quicktemplates/qquickpopupitem_p_p.h | 2 | ||||
| -rw-r--r-- | src/quicktemplates/qquickpopuppositioner.cpp | 33 | ||||
| -rw-r--r-- | tests/auto/quickcontrols/qquickmenubar/tst_qquickmenubar.cpp | 40 |
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"); |
