allow to move the selection panel via mouse drag

This commit is contained in:
2026-08-09 22:09:41 +02:00
parent 37a7a7499a
commit 37378e3c1b
8 changed files with 274 additions and 38 deletions

View File

@@ -1,5 +1,6 @@
#include "SelectionPanel.h"
#include <QMouseEvent>
#include <QScrollArea>
#include <QScrollBar>
#include <QVBoxLayout>
@@ -103,6 +104,56 @@ void SelectionPanel::invalidateLayout()
std::make_shared<FloatingLayoutInvalidatedEvent>());
}
void SelectionPanel::mousePressEvent(QMouseEvent* event)
{
// Only the card's header moves the panel (REQ-UI-SELECTION-PANEL-DRAG). Presses on
// the header's labels arrive here by ordinary propagation, none of them being a
// widget that accepts mouse events, and carry a position in this panel's coordinates.
if (event->button() == Qt::LeftButton && m_content != nullptr)
{
QWidget* header = m_content->getHeaderWidget();
QRect headerRect(header->mapTo(this, QPoint(0, 0)), header->size());
// A card long enough to scroll can have its header scrolled out of sight; what is
// hidden is not a handle, and the pixels it would claim show other parts of the
// card.
QWidget* viewport = m_scrollArea->viewport();
headerRect &= QRect(viewport->mapTo(this, QPoint(0, 0)), viewport->size());
if (headerRect.contains(event->pos()))
{
m_dragGrabOffsetPx = event->pos();
}
}
// Accepted whether or not it started a drag: no mouse event over the panel reaches
// the game world (REQ-UI-SELECTION-PANEL).
event->accept();
}
void SelectionPanel::mouseMoveEvent(QMouseEvent* event)
{
if (m_dragGrabOffsetPx.has_value() && parentWidget() != nullptr)
{
// Read from the cursor's position on the screen rather than from the panel's own
// coordinates, which move under the cursor as the drag places the panel again.
const QPoint topLeftPx =
parentWidget()->mapFromGlobal(event->globalPos()) - *m_dragGrabOffsetPx;
m_desiredTopLeftPx = topLeftPx - m_viewOriginPx;
// Where the panel actually lands follows from the desired position by the
// ordinary rules, the widgets it steps around included (REQ-UI-SELECTION-PANEL-DRAG).
invalidateLayout();
}
event->accept();
}
void SelectionPanel::mouseReleaseEvent(QMouseEvent* event)
{
// Qt's implicit grab has kept the drag alive while the cursor was outside the panel
// or outside the view; it ends here, wherever the button was let go
// (REQ-UI-SELECTION-PANEL-DRAG). The desired position stays as dropped.
m_dragGrabOffsetPx.reset();
event->accept();
}
void SelectionPanel::handleEvent(std::shared_ptr<const SelectionChangedEvent> event)
{
m_request.buildings = event->ids;
@@ -126,6 +177,13 @@ void SelectionPanel::handleEvent(
// panel is placed, the two widgets being siblings in the same parent.
m_anchorRect = event->rectPx;
m_side.reset();
// A position the player dragged the panel to belongs to the selection it was set in.
// A new selection places the panel anew against its own anchor
// (REQ-UI-SELECTION-PANEL-DRAG); this event is published only when one starts, and
// not when an existing selection is added to or reduced, which is exactly the scope
// the dragged position keeps.
m_desiredTopLeftPx.reset();
m_dragGrabOffsetPx.reset();
}
void SelectionPanel::handleEvent(
@@ -238,6 +296,11 @@ void SelectionPanel::placeIn(const QRect& viewRect, const std::vector<QRect>& oc
const QRect band = viewRect.adjusted(kMarginPx, kMarginPx, -kMarginPx, -kMarginPx);
// What the view's coordinates and this panel's differ by, kept for the drag gesture,
// which learns of the cursor in the latter and stores its result in the former
// (REQ-UI-SELECTION-PANEL-DRAG).
m_viewOriginPx = viewRect.topLeft();
// The anchor is published in the world view's coordinates and this panel is placed in
// its parent's; the two widgets are siblings, so the view's own origin is the whole
// difference. Without an anchor the panel falls back to the top-right corner, by
@@ -278,6 +341,22 @@ void SelectionPanel::placeIn(const QRect& viewRect, const std::vector<QRect>& oc
return m_body->sizeHint();
};
// Where a panel of that size stands: beside the selection, or at the position the
// player dragged the panel to, which replaces both the anchor and the side for the
// rest of the selection (REQ-UI-SELECTION-PANEL-DRAG). Either way the result is
// resolved against the view's edges and the widgets placed before this one, so a
// dragged panel steps around them exactly as an anchored one does.
auto solve = [&](QSize wantedSize) -> QRect
{
if (m_desiredTopLeftPx.has_value())
{
return placeAtDesiredTopLeft(band, *m_desiredTopLeftPx + viewRect.topLeft(),
wantedSize, occupiedRects, kMarginPx);
}
return placeBesideAnchor(band, anchorRect, *m_side, wantedSize, occupiedRects,
kMarginPx);
};
// Run twice. Parts of a card report an unstyled size until the style has actually
// reached them, which for a freshly built card happens during the first round of
// measuring; the second round then measures a card that is fully laid out and
@@ -307,9 +386,8 @@ void SelectionPanel::placeIn(const QRect& viewRect, const std::vector<QRect>& oc
// pass settles it again against the wider column. Asking for the whole band's
// height is what makes the answer the most the panel could have there.
const int maxHeightPx =
placeBesideAnchor(band, anchorRect, *m_side,
QSize(contentWidthPx + 2 * borderPx, band.height()),
occupiedRects, kMarginPx).height() - 2 * borderPx;
solve(QSize(contentWidthPx + 2 * borderPx, band.height())).height()
- 2 * borderPx;
if (maxHeightPx <= 0)
{
return;
@@ -343,8 +421,6 @@ void SelectionPanel::placeIn(const QRect& viewRect, const std::vector<QRect>& oc
const int panelWidthPx = contentWidthPx + 2 * borderPx;
const int panelHeightPx = contentHeightPx + 2 * borderPx;
setGeometry(placeBesideAnchor(band, anchorRect, *m_side,
QSize(panelWidthPx, panelHeightPx),
occupiedRects, kMarginPx));
setGeometry(solve(QSize(panelWidthPx, panelHeightPx)));
}
}