diff --git a/src/gui/AetherialAudioStrip.cpp b/src/gui/AetherialAudioStrip.cpp index e9558071c..19975a926 100644 --- a/src/gui/AetherialAudioStrip.cpp +++ b/src/gui/AetherialAudioStrip.cpp @@ -5,6 +5,7 @@ #include "AetherDspWidget.h" #include "FramelessMoveHelper.h" +#include "FramelessResizer.h" #include "StripChainWidget.h" #include "StripRxChainWidget.h" #include "ClientEqApplet.h" @@ -54,13 +55,14 @@ #include #include #include -#include #include "core/ThemeManager.h" namespace AetherSDR { namespace { -constexpr int kResizeMargin = 6; +// Edge-to-edge title-bar height. Reserved from the frameless resize edge so a +// title-bar grab starts a move, not a top-edge resize (#4266). +constexpr int kTitleBarHeight = 18; } AetherialAudioStrip::AetherialAudioStrip(AudioEngine* engine, QWidget* parent) @@ -94,9 +96,11 @@ AetherialAudioStrip::AetherialAudioStrip(AudioEngine* engine, QWidget* parent) } resize(1140, initialHeight); - // Track mouse without buttons pressed so the resize cursor updates - // while hovering the bare margin around the embedded grid. - setMouseTracking(true); + // Listen at the native-window boundary so edge presses still reach the + // resize handler when the child-heavy strip content covers every margin. + // Reserve the edge-to-edge title-bar strip for moves so a title-bar grab + // isn't stolen by the top-edge resize zone (#4266). + FramelessResizer::install(this, 6, kTitleBarHeight); // Outer layout has zero margins so the title bar can run edge-to-edge // across the whole window (matching the applet ContainerTitleBar). @@ -114,7 +118,7 @@ AetherialAudioStrip::AetherialAudioStrip(AudioEngine* engine, QWidget* parent) // it shares with the docked applet panels. { m_titleBar = new QWidget(this); - m_titleBar->setFixedHeight(18); + m_titleBar->setFixedHeight(kTitleBarHeight); m_titleBar->setAttribute(Qt::WA_StyledBackground, true); AetherSDR::ThemeManager::instance().applyStyleSheet(m_titleBar, "QWidget { background: qlineargradient(x1:0,y1:0,x2:0,y2:1," "stop:0 #5a7494, stop:0.5 #384e68, stop:1 {{color.background.1}}); " @@ -845,72 +849,6 @@ void AetherialAudioStrip::hideEvent(QHideEvent* ev) QWidget::hideEvent(ev); } -Qt::Edges AetherialAudioStrip::edgesAt(const QPoint& pos) const -{ - if (!(windowFlags() & Qt::FramelessWindowHint)) - return {}; - if (isMaximized() || isFullScreen()) - return {}; - - Qt::Edges edges; - if (pos.x() <= kResizeMargin) - edges |= Qt::LeftEdge; - else if (pos.x() >= width() - kResizeMargin) - edges |= Qt::RightEdge; - if (pos.y() <= kResizeMargin) - edges |= Qt::TopEdge; - else if (pos.y() >= height() - kResizeMargin) - edges |= Qt::BottomEdge; - return edges; -} - -void AetherialAudioStrip::updateResizeCursor(const QPoint& pos) -{ - const Qt::Edges edges = edgesAt(pos); - Qt::CursorShape shape = Qt::ArrowCursor; - // Two diagonals → SizeFDiag (↘↖); the other two → SizeBDiag (↙↗). - if ((edges & (Qt::LeftEdge | Qt::TopEdge)) == (Qt::LeftEdge | Qt::TopEdge) - || (edges & (Qt::RightEdge | Qt::BottomEdge)) == (Qt::RightEdge | Qt::BottomEdge)) { - shape = Qt::SizeFDiagCursor; - } else if ((edges & (Qt::RightEdge | Qt::TopEdge)) == (Qt::RightEdge | Qt::TopEdge) - || (edges & (Qt::LeftEdge | Qt::BottomEdge)) == (Qt::LeftEdge | Qt::BottomEdge)) { - shape = Qt::SizeBDiagCursor; - } else if (edges & (Qt::LeftEdge | Qt::RightEdge)) { - shape = Qt::SizeHorCursor; - } else if (edges & (Qt::TopEdge | Qt::BottomEdge)) { - shape = Qt::SizeVerCursor; - } - setCursor(shape); -} - -void AetherialAudioStrip::mouseMoveEvent(QMouseEvent* ev) -{ - if (!(ev->buttons() & Qt::LeftButton)) - updateResizeCursor(ev->pos()); - QWidget::mouseMoveEvent(ev); -} - -void AetherialAudioStrip::mousePressEvent(QMouseEvent* ev) -{ - if (ev->button() == Qt::LeftButton) { - const Qt::Edges edges = edgesAt(ev->pos()); - if (edges) { - if (auto* h = windowHandle()) { - h->startSystemResize(edges); - ev->accept(); - return; - } - } - } - QWidget::mousePressEvent(ev); -} - -void AetherialAudioStrip::leaveEvent(QEvent* ev) -{ - setCursor(Qt::ArrowCursor); - QWidget::leaveEvent(ev); -} - bool AetherialAudioStrip::eventFilter(QObject* obj, QEvent* ev) { if (obj == m_titleBar && ev->type() == QEvent::MouseMove) { diff --git a/src/gui/AetherialAudioStrip.h b/src/gui/AetherialAudioStrip.h index 0eb0f42e4..2f5369cc1 100644 --- a/src/gui/AetherialAudioStrip.h +++ b/src/gui/AetherialAudioStrip.h @@ -137,19 +137,11 @@ class AetherialAudioStrip : public QWidget { void resizeEvent(QResizeEvent* ev) override; void showEvent(QShowEvent* ev) override; void hideEvent(QHideEvent* ev) override; - // Frameless 8-axis resize: 4 edges + 4 corners. Mouse hover on - // the bare margin around the embedded grid updates the cursor; - // press starts a compositor-managed resize via startSystemResize. - void mouseMoveEvent(QMouseEvent* ev) override; - void mousePressEvent(QMouseEvent* ev) override; - void leaveEvent(QEvent* ev) override; bool eventFilter(QObject* obj, QEvent* ev) override; private: void saveGeometryToSettings(); void restoreGeometryFromSettings(); - Qt::Edges edgesAt(const QPoint& pos) const; - void updateResizeCursor(const QPoint& pos); // Master bypass — snapshot all enabled TX stages, then disable // them. Restores the snapshot on uncheck. Mirrors the docked diff --git a/src/gui/FramelessResizer.cpp b/src/gui/FramelessResizer.cpp index f9cb07fec..a63947cb3 100644 --- a/src/gui/FramelessResizer.cpp +++ b/src/gui/FramelessResizer.cpp @@ -8,13 +8,14 @@ namespace AetherSDR { -void FramelessResizer::install(QWidget* window, int margin) +void FramelessResizer::install(QWidget* window, int margin, int topMoveReserve) { - new FramelessResizer(window, margin); + new FramelessResizer(window, margin, topMoveReserve); } -FramelessResizer::FramelessResizer(QWidget* window, int margin) - : QObject(window), m_window(window), m_margin(margin) +FramelessResizer::FramelessResizer(QWidget* window, int margin, int topMoveReserve) + : QObject(window), m_window(window), m_margin(margin), + m_topMoveReserve(topMoveReserve) { if (window->windowHandle()) { // Native window already exists — install directly on it. @@ -39,6 +40,20 @@ FramelessResizer::~FramelessResizer() Qt::Edges FramelessResizer::edgesAt(const QPoint& p) const { + // A maximized or fullscreen window must not resize from an edge drag — the + // compositor owns its geometry. Previously each adopter guarded this in its + // own edgesAt(); centralizing it here closes the gap for every adopter + // (#4266). + if (m_window->isMaximized() || m_window->isFullScreen()) { + return {}; + } + // Reserve an edge-to-edge top move handle (e.g. a full-width title bar) for + // the window's own move handling rather than the top-edge resize zone, so a + // title-bar grab isn't consumed as a resize (#4266). Resize still works + // everywhere below the reserved strip. + if (m_topMoveReserve > 0 && p.y() < m_topMoveReserve) { + return {}; + } const QRect r = m_window->rect(); Qt::Edges edges; if (p.x() <= m_margin) edges |= Qt::LeftEdge; diff --git a/src/gui/FramelessResizer.h b/src/gui/FramelessResizer.h index b2d543fc0..097560482 100644 --- a/src/gui/FramelessResizer.h +++ b/src/gui/FramelessResizer.h @@ -28,20 +28,26 @@ namespace AetherSDR { class FramelessResizer : public QObject { Q_OBJECT public: - static void install(QWidget* window, int margin = 6); + // `topMoveReserve`: height (px) of an edge-to-edge move handle (e.g. a title + // bar) at the top of the window. The top strip is reserved for the window's + // own move handling instead of the top-edge resize zone, so a title-bar grab + // isn't stolen by the resizer (#4266). 0 (default) keeps the full top edge + // resizable — the behavior for adopters that inset their title bar. + static void install(QWidget* window, int margin = 6, int topMoveReserve = 0); ~FramelessResizer() override; protected: bool eventFilter(QObject* obj, QEvent* ev) override; private: - explicit FramelessResizer(QWidget* window, int margin); + explicit FramelessResizer(QWidget* window, int margin, int topMoveReserve); Qt::Edges edgesAt(const QPoint& windowPos) const; void enterEdgeZone(Qt::Edges edges); void leaveEdgeZone(); QWidget* m_window{nullptr}; int m_margin{6}; + int m_topMoveReserve{0}; bool m_cursorOverridden{false}; Qt::Edges m_lastEdges{}; };