place the selection panel beside what it describes
This commit is contained in:
@@ -5,6 +5,9 @@
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "BuildingIconCache.h"
|
||||
#include "EventManager.h"
|
||||
#include "FloatingLayoutInvalidatedEvent.h"
|
||||
#include "FloatingPanelPlacement.h"
|
||||
#include "ItemIconCache.h"
|
||||
#include "Simulation.h"
|
||||
#include "VisualsConfig.h"
|
||||
@@ -13,8 +16,8 @@
|
||||
namespace
|
||||
{
|
||||
|
||||
// Distance kept between the panel and the edges of the band it is anchored to
|
||||
// (REQ-UI-SELECTION-PANEL).
|
||||
// Distance kept between the panel and the edges of the game world view, and between it
|
||||
// and the widgets it steps around (REQ-UI-SELECTION-PANEL).
|
||||
const int kMarginPx = 8;
|
||||
|
||||
// Upper bound on the card width. The panel is content-sized, but several of the cards'
|
||||
@@ -94,10 +97,10 @@ SelectionPanel::~SelectionPanel()
|
||||
unregisterForEvents();
|
||||
}
|
||||
|
||||
void SelectionPanel::anchorTo(const QRect& bandRect)
|
||||
void SelectionPanel::invalidateLayout()
|
||||
{
|
||||
m_bandRect = bandRect;
|
||||
updateVisibility();
|
||||
EventManager::getInstance()->sendEventImmediately(
|
||||
std::make_shared<FloatingLayoutInvalidatedEvent>());
|
||||
}
|
||||
|
||||
void SelectionPanel::handleEvent(std::shared_ptr<const SelectionChangedEvent> event)
|
||||
@@ -113,6 +116,18 @@ void SelectionPanel::handleEvent(std::shared_ptr<const SelectionChangedEvent> ev
|
||||
rebuildContent();
|
||||
}
|
||||
|
||||
void SelectionPanel::handleEvent(
|
||||
std::shared_ptr<const SelectionAnchorChangedEvent> event)
|
||||
{
|
||||
// A new selection is starting. Both the anchor and the side are settled against it
|
||||
// and then left alone for as long as it lasts (REQ-UI-SELECTION-PANEL); the side is
|
||||
// only reset here, being resolved on the next placement once the card's width is
|
||||
// known. The rect arrives in the world view's coordinates and is translated when the
|
||||
// panel is placed, the two widgets being siblings in the same parent.
|
||||
m_anchorRect = event->rectPx;
|
||||
m_side.reset();
|
||||
}
|
||||
|
||||
void SelectionPanel::handleEvent(
|
||||
std::shared_ptr<const EntitySelectionChangedEvent> event)
|
||||
{
|
||||
@@ -178,7 +193,7 @@ void SelectionPanel::refreshContent()
|
||||
}
|
||||
|
||||
m_content->refresh();
|
||||
updateVisibility();
|
||||
invalidateLayout();
|
||||
}
|
||||
|
||||
void SelectionPanel::rebuildContent()
|
||||
@@ -208,36 +223,37 @@ void SelectionPanel::rebuildContent()
|
||||
m_content->show();
|
||||
m_content->refresh();
|
||||
}
|
||||
updateVisibility();
|
||||
invalidateLayout();
|
||||
}
|
||||
|
||||
void SelectionPanel::updateVisibility()
|
||||
void SelectionPanel::placeIn(const QRect& viewRect, const std::vector<QRect>& occupiedRects)
|
||||
{
|
||||
// Nothing selected in either category means no panel at all rather than an empty one
|
||||
// (REQ-UI-EMPTY-SELECTION), leaving the whole game world view visible. Before the
|
||||
// owner has anchored the panel there is nowhere to put it either, so it stays hidden
|
||||
// until then.
|
||||
const bool shouldShow = (m_content != nullptr) && !m_bandRect.isNull();
|
||||
setVisible(shouldShow);
|
||||
if (shouldShow)
|
||||
// (REQ-UI-EMPTY-SELECTION), leaving the whole game world view visible.
|
||||
setVisible(m_content != nullptr);
|
||||
if (m_content == nullptr || viewRect.isNull())
|
||||
{
|
||||
refit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void SelectionPanel::refit()
|
||||
{
|
||||
const QRect band =
|
||||
m_bandRect.adjusted(kMarginPx, kMarginPx, -kMarginPx, -kMarginPx);
|
||||
const QRect band = viewRect.adjusted(kMarginPx, kMarginPx, -kMarginPx, -kMarginPx);
|
||||
|
||||
// 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
|
||||
// standing beside a point just outside that corner -- in practice unreachable, every
|
||||
// non-empty selection following a click or a drag that publishes one.
|
||||
const QRect anchorRect =
|
||||
m_anchorRect.isNull() ? QRect(band.right() + kMarginPx + 1, band.top(), 1, 1)
|
||||
: m_anchorRect.translated(viewRect.topLeft());
|
||||
|
||||
// The panel's border is drawn around the scroll area rather than around the card, so
|
||||
// it is added to whatever the card asks for. Spelled out here instead of read back
|
||||
// from contentsMargins() because the stylesheet box is what sets it, and asking the
|
||||
// style for it before the first show is unreliable.
|
||||
const int borderPx = 1;
|
||||
const int maxWidthPx = qMin(kMaxContentWidthPx, band.width() - 2 * borderPx);
|
||||
const int maxHeightPx = band.height() - 2 * borderPx;
|
||||
if (maxWidthPx <= 0 || maxHeightPx <= 0)
|
||||
const int borderPx = 1;
|
||||
const int maxWidthPx = qMin(kMaxContentWidthPx, band.width() - 2 * borderPx);
|
||||
if (maxWidthPx <= 0 || band.height() <= 2 * borderPx)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -273,9 +289,35 @@ void SelectionPanel::refit()
|
||||
// First at the cap, the most room the card can ever get, to learn how wide it
|
||||
// wants to be; then at that width for the height that follows from it.
|
||||
int contentWidthPx = qMin(measureAt(maxWidthPx).width(), maxWidthPx);
|
||||
|
||||
// Which side of the selection the panel takes is settled on the first placement
|
||||
// after a new anchor and kept for as long as that selection lasts, so a card that
|
||||
// grows or shrinks never flips the panel across the object it describes
|
||||
// (REQ-UI-SELECTION-PANEL). This is the first point at which its width is known.
|
||||
if (!m_side.has_value())
|
||||
{
|
||||
m_side = chooseSide(band, anchorRect, contentWidthPx + 2 * borderPx,
|
||||
kMarginPx);
|
||||
}
|
||||
|
||||
// How much height there is depends on where the panel ends up standing: of the
|
||||
// widgets placed before it, only those whose rectangles meet its own column are
|
||||
// in its way. That column follows from the width just measured, so this cannot be
|
||||
// settled before it -- and where the scroll bar below widens the panel, the second
|
||||
// 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;
|
||||
if (maxHeightPx <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int contentHeightPx = measureAt(contentWidthPx).height();
|
||||
|
||||
// A card taller than the band is capped there and scrolls
|
||||
// A card taller than the space left is capped there and scrolls
|
||||
// (REQ-UI-SELECTION-PANEL). The bar is laid out beside the card, so the panel
|
||||
// widens by its width to leave the card the width its height was measured for --
|
||||
// and where the cap does not allow that, the card is measured again at what is
|
||||
@@ -301,11 +343,8 @@ void SelectionPanel::refit()
|
||||
const int panelWidthPx = contentWidthPx + 2 * borderPx;
|
||||
const int panelHeightPx = contentHeightPx + 2 * borderPx;
|
||||
|
||||
// Right-aligned in the band and centered on it vertically. The band excludes the
|
||||
// build button bar's strip, so centering here never puts the panel over the bar
|
||||
// (REQ-UI-SELECTION-PANEL).
|
||||
setGeometry(band.right() - panelWidthPx + 1,
|
||||
band.top() + (band.height() - panelHeightPx) / 2,
|
||||
panelWidthPx, panelHeightPx);
|
||||
setGeometry(placeBesideAnchor(band, anchorRect, *m_side,
|
||||
QSize(panelWidthPx, panelHeightPx),
|
||||
occupiedRects, kMarginPx));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user