diff --git a/framework/uicomponents/qml/Muse/UiComponents/menuview.cpp b/framework/uicomponents/qml/Muse/UiComponents/menuview.cpp
index 0fe4ead2da..46afc705dd 100644
--- a/framework/uicomponents/qml/Muse/UiComponents/menuview.cpp
+++ b/framework/uicomponents/qml/Muse/UiComponents/menuview.cpp
@@ -220,8 +220,8 @@ void MenuView::updateGeometry()
setCascadeAlign(Qt::AlignmentFlag::AlignLeft);
newPopupPos = PopupPosition::Right;
} else {
- // move to the left to an area that doesn't fit
- movePos(m_globalPos.x() - (viewRect.right() - paddedAnchorRect.right()) + padding() * 2, m_globalPos.y());
+ // Place the menu to the left of the cursor instead of pinning it to the screen edge
+ movePos(parentTopLeft.x() - viewRect.width() + padding() * 2, m_globalPos.y());
}
}
diff --git a/framework/uicomponents/qml/Muse/UiComponents/menuview.h b/framework/uicomponents/qml/Muse/UiComponents/menuview.h
index d932cda2da..ea13eedeae 100644
--- a/framework/uicomponents/qml/Muse/UiComponents/menuview.h
+++ b/framework/uicomponents/qml/Muse/UiComponents/menuview.h
@@ -26,6 +26,8 @@
#include "popupview.h"
namespace muse::uicomponents {
+class MenuViewTestAccess;
+
class MenuView : public PopupView
{
Q_OBJECT
@@ -84,6 +86,8 @@ public slots:
void cascadeAlignChanged(Qt::AlignmentFlag cascadeAlign);
private:
+ friend class MenuViewTestAccess;
+
void componentComplete() override;
void updateGeometry() override;
diff --git a/framework/uicomponents/qml/Muse/UiComponents/tests/CMakeLists.txt b/framework/uicomponents/qml/Muse/UiComponents/tests/CMakeLists.txt
index f2fe475ca8..7a25b5f705 100644
--- a/framework/uicomponents/qml/Muse/UiComponents/tests/CMakeLists.txt
+++ b/framework/uicomponents/qml/Muse/UiComponents/tests/CMakeLists.txt
@@ -23,6 +23,7 @@ set(MODULE_TEST muse_uicomponents_qml_tests)
set(MODULE_TEST_SRC
${CMAKE_CURRENT_LIST_DIR}/doubleinputvalidator_tests.cpp
${CMAKE_CURRENT_LIST_DIR}/intinputvalidator_tests.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/menuview_tests.cpp
${CMAKE_CURRENT_LIST_DIR}/sectionproxymodel_tests.cpp
${CMAKE_CURRENT_LIST_DIR}/sortfilterproxymodel_tests.cpp
@@ -31,6 +32,7 @@ set(MODULE_TEST_SRC
set(MODULE_TEST_LINK
Qt::Qml
+ Qt::Quick
muse_uicomponents_qml
)
diff --git a/framework/uicomponents/qml/Muse/UiComponents/tests/menuview_tests.cpp b/framework/uicomponents/qml/Muse/UiComponents/tests/menuview_tests.cpp
new file mode 100644
index 0000000000..dcfaa19b56
--- /dev/null
+++ b/framework/uicomponents/qml/Muse/UiComponents/tests/menuview_tests.cpp
@@ -0,0 +1,143 @@
+/*
+ * SPDX-License-Identifier: GPL-3.0-only
+ * MuseScore-CLA-applies
+ *
+ * MuseScore Studio
+ * Music Composition & Notation
+ *
+ * Copyright (C) 2026 MuseScore Limited and others
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+
+#include "../menuview.h"
+
+using namespace muse::uicomponents;
+
+namespace muse::uicomponents {
+class MenuViewTestAccess
+{
+public:
+ static void updateGeometry(MenuView& view)
+ {
+ view.updateGeometry();
+ }
+};
+}
+
+namespace {
+class MainWindowStub final : public muse::ui::IMainWindow
+{
+public:
+ void init(muse::ui::MainWindowBridge*) override {}
+ void deinit() override {}
+
+ QWindow* qWindow() const override { return nullptr; }
+
+ void requestShowOnBack() override {}
+ void requestShowOnFront() override {}
+
+ bool isFullScreen() const override { return false; }
+ muse::async::Notification isFullScreenChanged() const override { return {}; }
+ void toggleFullScreen() override {}
+
+ QScreen* screen() const override { return QGuiApplication::primaryScreen(); }
+};
+
+class TestMenuView final : public MenuView
+{
+public:
+ using MenuView::MenuView;
+
+ QPointF globalPosition() const { return m_globalPos; }
+
+ void setMainWindowForTest(const std::shared_ptr& window)
+ {
+ mainWindow.set(window);
+ }
+};
+}
+
+TEST(MenuViewTests, OverflowingTopLevelMenuOpensLeftOfCursor)
+{
+ QScreen* screen = QGuiApplication::primaryScreen();
+ ASSERT_NE(screen, nullptr);
+
+ const QRect availableGeometry = screen->availableGeometry();
+ ASSERT_GT(availableGeometry.width(), 400);
+
+ QQuickWindow parentWindow;
+ parentWindow.setGeometry(availableGeometry);
+
+ QQuickItem anchor(parentWindow.contentItem());
+ anchor.setX(availableGeometry.width() - 100);
+ anchor.setY(100);
+ anchor.setSize(QSizeF(1, 1));
+
+ QQuickItem content;
+ TestMenuView menu(&anchor);
+ menu.setMainWindowForTest(std::make_shared());
+ menu.setContentItem(&content);
+ menu.setDesiredWidth(200);
+ menu.setDesiredHeight(100);
+
+ MenuViewTestAccess::updateGeometry(menu);
+
+ const qreal cursorX = anchor.mapToGlobal(QPointF(0, 0)).x();
+ const qreal contentRight = menu.globalPosition().x() + menu.padding() + menu.desiredWidth();
+
+ EXPECT_LT(menu.globalPosition().x(), cursorX);
+ EXPECT_DOUBLE_EQ(contentRight, cursorX);
+ EXPECT_NE(contentRight, availableGeometry.right());
+}
+
+TEST(MenuViewTests, TopLevelMenuUsesScreenBoundaryInsteadOfParentWindow)
+{
+ QScreen* screen = QGuiApplication::primaryScreen();
+ ASSERT_NE(screen, nullptr);
+
+ const QRect availableGeometry = screen->availableGeometry();
+ ASSERT_GT(availableGeometry.width(), 400);
+
+ QQuickWindow parentWindow;
+ parentWindow.setGeometry(availableGeometry.x(), availableGeometry.y(), availableGeometry.width() / 2,
+ availableGeometry.height());
+
+ QQuickItem anchor(parentWindow.contentItem());
+ anchor.setX(parentWindow.width() - 50);
+ anchor.setY(100);
+ anchor.setSize(QSizeF(1, 1));
+
+ QQuickItem content;
+ TestMenuView menu(&anchor);
+ menu.setMainWindowForTest(std::make_shared());
+ menu.setContentItem(&content);
+ menu.setDesiredWidth(200);
+ menu.setDesiredHeight(100);
+
+ MenuViewTestAccess::updateGeometry(menu);
+
+ const qreal contentRight = menu.globalPosition().x() + menu.padding() + menu.desiredWidth();
+
+ EXPECT_GT(contentRight, parentWindow.geometry().right());
+ EXPECT_LE(contentRight, availableGeometry.right());
+}