allow to move the selection panel via mouse drag
This commit is contained in:
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <QPoint>
|
||||
#include <QRect>
|
||||
#include <QWidget>
|
||||
|
||||
@@ -25,6 +26,7 @@ class BuildingIconCache;
|
||||
class ItemIconCache;
|
||||
class SelectionContent;
|
||||
class Simulation;
|
||||
class QMouseEvent;
|
||||
class QScrollArea;
|
||||
class QVBoxLayout;
|
||||
|
||||
@@ -64,6 +66,14 @@ public:
|
||||
void placeIn(const QRect& viewRect,
|
||||
const std::vector<QRect>& occupiedRects) override;
|
||||
|
||||
protected:
|
||||
// The panel is moved by dragging its card header (REQ-UI-SELECTION-PANEL-DRAG). Every
|
||||
// other mouse event over the panel is swallowed here so that none of them reaches the
|
||||
// game world beneath (REQ-UI-SELECTION-PANEL).
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
void mouseReleaseEvent(QMouseEvent* event) override;
|
||||
|
||||
private:
|
||||
void handleEvent(std::shared_ptr<const SelectionChangedEvent> event) override;
|
||||
void handleEvent(std::shared_ptr<const SelectionAnchorChangedEvent> event) override;
|
||||
@@ -98,10 +108,23 @@ private:
|
||||
// scrolling view or a moving ship, the side because a card that grows must not flip
|
||||
// the panel across the object (REQ-UI-SELECTION-PANEL). The side is resolved on the
|
||||
// first placement after a new anchor, being the first point at which the panel's
|
||||
// width is known.
|
||||
// width is known. Dragging the panel supersedes the pair for the rest of the
|
||||
// selection (REQ-UI-SELECTION-PANEL-DRAG).
|
||||
QRect m_anchorRect;
|
||||
std::optional<PanelSide> m_side;
|
||||
|
||||
// Where the player dragged the panel, in the game world view's coordinates, and the
|
||||
// cursor's offset within the panel while a drag is running
|
||||
// (REQ-UI-SELECTION-PANEL-DRAG). The desired position is kept exactly as dropped:
|
||||
// resolving it against the view's edges and the widgets the panel steps around never
|
||||
// writes back to it, so the panel returns to it once the room is there again. It
|
||||
// lasts as long as the selection it was set in.
|
||||
std::optional<QPoint> m_desiredTopLeftPx;
|
||||
std::optional<QPoint> m_dragGrabOffsetPx;
|
||||
// Origin of the view the panel was last placed in, which is what the two coordinate
|
||||
// systems differ by -- the panel is a sibling of the view, not a child of it.
|
||||
QPoint m_viewOriginPx;
|
||||
|
||||
// Scrolls the card once it outgrows the space the panel has (REQ-UI-SELECTION-PANEL).
|
||||
// The card is a child of m_body, not of the panel itself.
|
||||
QScrollArea* m_scrollArea;
|
||||
|
||||
@@ -50,26 +50,30 @@ SelectionContent::SelectionContent(const SelectionContext& context,
|
||||
|
||||
// Header: identity symbol, name, and the right slot pushed to the far edge
|
||||
// (REQ-UI-SELECTION-CARD).
|
||||
QWidget* header = new QWidget(this);
|
||||
QHBoxLayout* headerLayout = new QHBoxLayout(header);
|
||||
m_header = new QWidget(this);
|
||||
// The header doubles as the panel's drag handle (REQ-UI-SELECTION-PANEL-DRAG); the
|
||||
// cursor says so, as the controls panel's heading does for the click that collapses
|
||||
// it. The gesture itself belongs to SelectionPanel, which owns the placement.
|
||||
m_header->setCursor(Qt::SizeAllCursor);
|
||||
QHBoxLayout* headerLayout = new QHBoxLayout(m_header);
|
||||
headerLayout->setContentsMargins(0, 0, 0, 0);
|
||||
headerLayout->setSpacing(kHeaderSpacingPx);
|
||||
|
||||
m_symbolLabel = new QLabel(header);
|
||||
m_symbolLabel = new QLabel(m_header);
|
||||
m_symbolLabel->hide();
|
||||
|
||||
m_nameLabel = new QLabel(header);
|
||||
m_nameLabel = new QLabel(m_header);
|
||||
QFont nameFont = m_nameLabel->font();
|
||||
nameFont.setBold(true);
|
||||
m_nameLabel->setFont(nameFont);
|
||||
|
||||
m_statusPill = new StatusPill(header);
|
||||
m_statusPill = new StatusPill(m_header);
|
||||
|
||||
headerLayout->addWidget(m_symbolLabel);
|
||||
headerLayout->addWidget(m_nameLabel);
|
||||
headerLayout->addStretch(1);
|
||||
headerLayout->addWidget(m_statusPill);
|
||||
cardLayout->addWidget(header);
|
||||
cardLayout->addWidget(m_header);
|
||||
|
||||
m_configurationGroup = new QWidget(this);
|
||||
QVBoxLayout* configurationLayout = new QVBoxLayout(m_configurationGroup);
|
||||
|
||||
@@ -45,6 +45,11 @@ public:
|
||||
// SelectionPanel owns that decision.
|
||||
void refresh();
|
||||
|
||||
// The card's header (REQ-UI-SELECTION-CARD), which is also the handle the panel is
|
||||
// dragged by (REQ-UI-SELECTION-PANEL-DRAG). The panel hit-tests against it; the card
|
||||
// itself does nothing with the gesture.
|
||||
QWidget* getHeaderWidget() const { return m_header; }
|
||||
|
||||
protected:
|
||||
// constructionSiteId is set only while the card shows a construction site, in which
|
||||
// case this base builds and drives the construction section in place of whatever the
|
||||
@@ -91,6 +96,7 @@ private:
|
||||
// Set while this card shows a construction site rather than a finished object.
|
||||
std::optional<BuildingId> m_siteId;
|
||||
|
||||
QWidget* m_header;
|
||||
QLabel* m_symbolLabel;
|
||||
QLabel* m_nameLabel;
|
||||
StatusPill* m_statusPill;
|
||||
|
||||
Reference in New Issue
Block a user