Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -270,18 +270,41 @@ MenuView {

root.subMenuLoader.opened.connect(function(itemId) {
root.closePolicies = PopupView.NoAutoClose

// Notify C++ about submenu geometry
if (root.subMenuLoader.menu && root.subMenuLoader.menu.contentItem) {
Qt.callLater(function() {
var subMenuContent = root.subMenuLoader.menu.contentItem
var intermediateItem = view
var subMenuTopLeftInWindow = subMenuContent.mapToItem(intermediateItem, 0, 0)
var subMenuTopLeftInContent = content.mapFromItem(intermediateItem, subMenuTopLeftInWindow.x, subMenuTopLeftInWindow.y)

var subMenuGeometry = Qt.rect(
subMenuTopLeftInContent.x,
subMenuTopLeftInContent.y,
subMenuContent.width,
subMenuContent.height
)
root.setSubMenuGeometry(subMenuGeometry)
})
}
})

root.subMenuLoader.closed.connect(function(force) {
root.closePolicies = PopupView.CloseOnPressOutsideParent

// Notify C++ that submenu closed
root.clearSubMenuGeometry()

if (force) {
root.close(true)
root.openNextMenu()
}
})
}



StyledListView {
id: view

Expand Down Expand Up @@ -359,6 +382,7 @@ MenuView {

menuAnchorItem: root.anchorItem
parentWindow: root.window
parentMenu: root

navigation.panel: content.navigationPanel
navigation.row: model.index
Expand All @@ -372,6 +396,9 @@ MenuView {

subMenuShowed: root.subMenuLoader.isMenuOpened && root.subMenuLoader.parent === item

// Pass Amazon triangle state to menu item
amazonTriangleActive: root.amazonTriangleActive

onOpenSubMenuRequested: function(byHover) {
if (!hasSubMenu) {
if (byHover) {
Expand All @@ -388,6 +415,12 @@ MenuView {
}
}

// Clear the Amazon triangle when opening a different submenu
// This allows the new submenu to open even if triangle was active
if (root.subMenuLoader.parent !== item) {
root.clearSubMenuGeometry()
}

root.subMenuLoader.parent = item
root.subMenuLoader.open(subMenuItems)
}
Expand Down Expand Up @@ -417,5 +450,39 @@ MenuView {
}
}
}

// Amazon-triangle visualization (debug)
Canvas {
id: amazonTriangleCanvas
anchors.fill: parent
z: 500 // Below mouseArea (1000) but above view
visible: root.amazonTriangleActive

onPaint: {
const ctx = getContext("2d")
ctx.clearRect(0, 0, width, height)

if (!root.amazonTriangleActive) {
return
}

// Draw semi-transparent triangle
ctx.fillStyle = "rgba(255, 0, 0, 0.5)" // Red for debugging
ctx.beginPath()
ctx.moveTo(root.triangleP1.x, root.triangleP1.y)
ctx.lineTo(root.triangleP2.x, root.triangleP2.y)
ctx.lineTo(root.triangleP3.x, root.triangleP3.y)
ctx.closePath()
ctx.fill()
}

Connections {
target: root
function onTriangleP1Changed() { amazonTriangleCanvas.requestPaint() }
function onTriangleP2Changed() { amazonTriangleCanvas.requestPaint() }
function onTriangleP3Changed() { amazonTriangleCanvas.requestPaint() }
function onAmazonTriangleActiveChanged() { amazonTriangleCanvas.requestPaint() }
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ ListItemBlank {
property var menuAnchorItem: null

property var parentWindow: null
property var parentMenu: null

// Amazon triangle state - passed from parent menu
property bool amazonTriangleActive: false

enum IconAndCheckMarkMode {
None,
Expand Down Expand Up @@ -111,6 +115,56 @@ ListItemBlank {
// so that it takes precedence.
mouseArea.z: 1000

// Shared function to check if we should open submenu
function shouldOpenSubmenu() {
if (!mouseArea.containsMouse) {
return false
}

// If triangle is active, check if mouse is inside it
if (root.amazonTriangleActive && root.parentMenu) {
// Map mouse position from item to menu content coordinates
var mousePosInMenu = root.mapToItem(root.parentMenu.contentItem, mouseArea.mouseX, mouseArea.mouseY)
if (root.parentMenu.isMouseInsideTriangle(mousePosInMenu)) {
// Mouse is inside triangle - don't open submenu
return false
}
}

return true
}

// Handle hover events - check Amazon triangle before opening
Connections {
target: mouseArea

// Handle hover state changes
function onContainsMouseChanged() {
if (root.shouldOpenSubmenu()) {
root.openSubMenuRequested(true)
}
}

// Handle mouse movement (needed for smooth hover detection)
function onPositionChanged(mouse) {
if (root.shouldOpenSubmenu()) {
root.openSubMenuRequested(true)
}
}
}

// When triangle disappears, open submenu if hovering this item
onAmazonTriangleActiveChanged: {
if (!amazonTriangleActive) {
// Triangle disappeared - check if we should open submenu
Qt.callLater(function() {
if (root.shouldOpenSubmenu()) {
root.openSubMenuRequested(true)
}
})
}
}

QtObject {
id: itemPrv

Expand Down Expand Up @@ -234,12 +288,6 @@ ListItemBlank {
}
}

onHovered: function(isHovered, mouseX, mouseY) {
if (isHovered) {
root.openSubMenuRequested(true)
}
}

onClicked: {
if (root.hasSubMenu) {
root.openSubMenuRequested(false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,22 @@ bool PopupViewCloseController::eventFilter(QObject* watched, QEvent* event)
if (!m_canClosed) {
event->ignore();
}
} else if (QEvent::HoverMove == event->type() || QEvent::HoverEnter == event->type()) {
// Track mouse position for amazon triangle
QWindow* window = popupWindow();
if (window) {
QPointF globalPos = static_cast<QMouseEvent*>(event)->globalPosition();
QPointF localPos = window->mapFromGlobal(globalPos);

// Notify about mouse movement (this updates MenuView's internal state)
if (m_mouseMoveCallback) {
m_mouseMoveCallback(localPos);
}

// NOTE: We no longer filter hover events when in Amazon triangle.
// Instead, we let the events through and block submenu opening logic at QML level.
// This allows proper hover state tracking while still preventing accidental submenu changes.
}
}

return QObject::eventFilter(watched, event);
Expand Down Expand Up @@ -190,6 +206,16 @@ bool PopupViewCloseController::isMouseWithinBoundaries(const QPointF& mousePos)
}
}

//! NOTE Check amazon triangle - if mouse is in the triangle, it's considered within boundaries
//! This prevents the menu from closing when the mouse is moving toward a submenu
// if (m_amazonTriangleActive) {
// // Convert global mousePos to local coordinates relative to the parent item
// QPointF localPos = window->mapFromGlobal(mousePos);
// if (isPointInTriangle(localPos)) {
// return true;
// }
// }

// Hack for https://github.com/musescore/MuseScore/issues/29656
if (interactiveProvider()->isSelectColorOpened()) {
return true;
Expand All @@ -216,3 +242,50 @@ QWindow* PopupViewCloseController::popupWindow() const
{
return m_popupWindow;
}

void PopupViewCloseController::setAmazonTriangle(const QPointF& p1, const QPointF& p2, const QPointF& p3, bool active)
{
m_triangleP1 = p1;
m_triangleP2 = p2;
m_triangleP3 = p3;
if (p1.x() > p2.x()) {
m_amazonTriangleActive = false;
} else {
m_amazonTriangleActive = active;
}
}

void PopupViewCloseController::setMouseMoveCallback(std::function<void(const QPointF&)> callback)
{
m_mouseMoveCallback = callback;
}

bool PopupViewCloseController::isPointInTriangle(const QPointF& point) const
{
if (!m_amazonTriangleActive) {
return false;
}

// Helper lambda to calculate cross product sign
auto sign = [](const QPointF& p1, const QPointF& p2, const QPointF& p3) -> qreal {
return (p1.x() - p3.x()) * (p2.y() - p3.y()) - (p2.x() - p3.x()) * (p1.y() - p3.y());
};

qreal d1 = sign(point, m_triangleP1, m_triangleP2);
qreal d2 = sign(point, m_triangleP2, m_triangleP3);
qreal d3 = sign(point, m_triangleP3, m_triangleP1);

bool hasNeg = (d1 < 0) || (d2 < 0) || (d3 < 0);
bool hasPos = (d1 > 0) || (d2 > 0) || (d3 > 0);

// LOGD("Checking (%f, %f). Triangle [(%f, %f) - (%f, %f) - (%f, %f)] -> %s",
// point.x(), point.y(),
// m_triangleP1.x(), m_triangleP1.y(),
// m_triangleP2.x(), m_triangleP2.y(),
// m_triangleP3.x(), m_triangleP3.y(),
// !(hasNeg && hasPos) ? "true" : "false"
// );

// Point is inside if all cross products have the same sign
return !(hasNeg && hasPos);
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#include <QObject>
#include <QQuickItem>
#include <functional>

#include "async/notification.h"
#include "async/asyncable.h"
Expand Down Expand Up @@ -57,6 +58,10 @@ class PopupViewCloseController : public QObject, public muse::Injectable, public
void setIsCloseOnPressOutsideParent(bool arg);
void setCanClosed(bool arg);

// Amazon triangle support
void setAmazonTriangle(const QPointF& p1, const QPointF& p2, const QPointF& p3, bool active);
void setMouseMoveCallback(std::function<void(const QPointF&)> callback);

async::Notification closeNotification() const;

private slots:
Expand All @@ -69,6 +74,7 @@ private slots:
virtual void doUpdateEventFilters();

bool isMouseWithinBoundaries(const QPointF& mousePos) const;
bool isPointInTriangle(const QPointF& point) const;

void notifyAboutClose();

Expand All @@ -83,6 +89,13 @@ private slots:
bool m_isCloseOnPressOutsideParent = false;
bool m_canClosed = true;

// Amazon triangle state
QPointF m_triangleP1;
QPointF m_triangleP2;
QPointF m_triangleP3;
bool m_amazonTriangleActive = false;
std::function<void(const QPointF&)> m_mouseMoveCallback;

async::Notification m_closeNotification;
};
}
Loading
Loading