diff --git a/src/ui/ModalLayer.cpp b/src/ui/ModalLayer.cpp index 59bffce..1c0feea 100644 --- a/src/ui/ModalLayer.cpp +++ b/src/ui/ModalLayer.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -133,6 +134,47 @@ void ModalLayer::paintEvent(QPaintEvent* /*event*/) painter.fillRect(rect(), m_dimColor); } +void ModalLayer::mousePressEvent(QMouseEvent* event) +{ + m_pressedOutside = event->button() == Qt::LeftButton + && isOutsideOpenModal(event->pos()); + + // Taken whether or not it dismisses anything: the window behind a modal receives no + // input, and closing the modal does not turn this click into one for what lies under + // it (REQ-UI-DIALOG-DISMISS). + event->accept(); +} + +void ModalLayer::mouseReleaseEvent(QMouseEvent* event) +{ + const bool pressedOutside = m_pressedOutside; + m_pressedOutside = false; + event->accept(); + + if (event->button() != Qt::LeftButton || !pressedOutside + || !isOutsideOpenModal(event->pos()) || m_stack.empty()) + { + return; + } + + // A drag that began inside the modal never gets here -- the widget it began on keeps + // the release -- so no gesture ends by discarding the modal it was made in. + ModalDialog* openModal = m_stack.back().content; + if (openModal->isDismissible()) + { + openModal->requestDismiss(); + } +} + +bool ModalLayer::isOutsideOpenModal(const QPoint& position) const +{ + if (m_stack.empty()) + { + return true; + } + return !m_stack.back().host->geometry().contains(position); +} + void ModalLayer::place(QScrollArea& host, ModalDialog& content, const QRect& anchorRect) const { diff --git a/src/ui/ModalLayer.h b/src/ui/ModalLayer.h index f9c7e85..c9dd4b0 100644 --- a/src/ui/ModalLayer.h +++ b/src/ui/ModalLayer.h @@ -3,10 +3,12 @@ #include #include +#include #include #include class ModalDialog; +class QMouseEvent; class QPaintEvent; class QScrollArea; @@ -52,6 +54,8 @@ public: protected: void paintEvent(QPaintEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; private: // A modal and the scroll area it is shown in. The host is what the layer places and @@ -69,7 +73,16 @@ private: void updateVisibility(); + // Whether a point in this layer's coordinates lies beyond the open modal. A click on + // an inert part of the modal -- a label, the space between two controls -- arrives + // here too, propagated by the widget that ignored it, so where the click landed is + // what decides, never that the layer received it. + bool isOutsideOpenModal(const QPoint& position) const; + QColor m_dimColor; + // Set by a left press that landed outside the open modal, so that only a press and a + // release both outside dismiss it (REQ-UI-DIALOG-DISMISS). + bool m_pressedOutside = false; std::vector m_stack; // bottom-most first; back() is the open one int m_holdCount = 0; };