Skip to content
Merged
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
82 changes: 10 additions & 72 deletions src/gui/AetherialAudioStrip.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

#include "AetherDspWidget.h"
#include "FramelessMoveHelper.h"
#include "FramelessResizer.h"
#include "StripChainWidget.h"
#include "StripRxChainWidget.h"
#include "ClientEqApplet.h"
Expand Down Expand Up @@ -54,13 +55,14 @@
#include <QStackedWidget>
#include <QStandardPaths>
#include <QVBoxLayout>
#include <QWindow>
#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)
Expand Down Expand Up @@ -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).
Expand All @@ -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}}); "
Expand Down Expand Up @@ -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) {
Expand Down
8 changes: 0 additions & 8 deletions src/gui/AetherialAudioStrip.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 19 additions & 4 deletions src/gui/FramelessResizer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
Expand Down
10 changes: 8 additions & 2 deletions src/gui/FramelessResizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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{};
};
Expand Down
Loading