place the selection panel beside what it describes
The panel was anchored to the right edge of the view, which is nowhere near whatever the player just clicked. It now stands beside the selection: right of it where it fits, otherwise left, otherwise the roomier side pushed inside the view -- the one case where it covers part of what it describes. Nothing told the panel where the selection was. The selection events carry ids only, and the mode that separates a fresh selection from an expanded one is consumed inside SelectionController before they are built, so the view now publishes the selection's screen bounds itself, immediately before selecting and only when the selection is starting. Freezing that rectangle is what holds the panel still: it does not chase a scrolling view, a ship flying off, or a selection being added to. Only the panel's own size still moves it, and even then it keeps its side and the edge facing the selection. The rectangles come from what the renderer was already computing for the selection outlines, now shared rather than duplicated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
This commit is contained in:
@@ -22,3 +22,55 @@ int getAvailableBottomPx(const QRect& band, const std::vector<QRect>& occupiedRe
|
|||||||
}
|
}
|
||||||
return bottomPx;
|
return bottomPx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PanelSide chooseSide(const QRect& band, const QRect& anchorRect, int widthPx,
|
||||||
|
int marginPx)
|
||||||
|
{
|
||||||
|
// What each side offers: the gap between the anchor and that edge of the band, less
|
||||||
|
// the margin the panel keeps from the anchor.
|
||||||
|
const int roomRightPx = band.right() - anchorRect.right() - marginPx;
|
||||||
|
const int roomLeftPx = anchorRect.left() - band.left() - marginPx;
|
||||||
|
|
||||||
|
if (roomRightPx >= widthPx)
|
||||||
|
{
|
||||||
|
return PanelSide::Right;
|
||||||
|
}
|
||||||
|
if (roomLeftPx >= widthPx)
|
||||||
|
{
|
||||||
|
return PanelSide::Left;
|
||||||
|
}
|
||||||
|
// Neither side can hold it without covering the selection, so it goes where it
|
||||||
|
// covers the least of it.
|
||||||
|
return (roomRightPx >= roomLeftPx) ? PanelSide::Right : PanelSide::Left;
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect placeBesideAnchor(const QRect& band, const QRect& anchorRect, PanelSide side,
|
||||||
|
QSize wantedSize, const std::vector<QRect>& occupiedRects,
|
||||||
|
int marginPx)
|
||||||
|
{
|
||||||
|
const int widthPx = std::min(wantedSize.width(), band.width());
|
||||||
|
|
||||||
|
// Against the anchor on the chosen side, growing away from it: the edge facing the
|
||||||
|
// selection is the one that stays put as the panel's content resizes.
|
||||||
|
int leftPx = (side == PanelSide::Right) ? anchorRect.right() + marginPx + 1
|
||||||
|
: anchorRect.left() - marginPx - widthPx;
|
||||||
|
// A panel that does not fit there is pushed back inside the view rather than hanging
|
||||||
|
// off it, which is what puts it over the selection when neither side had room.
|
||||||
|
leftPx = std::min(leftPx, band.right() - widthPx + 1);
|
||||||
|
leftPx = std::max(leftPx, band.left());
|
||||||
|
|
||||||
|
// Only the widgets its own column meets can shorten it.
|
||||||
|
const int bottomPx = getAvailableBottomPx(band, occupiedRects, leftPx,
|
||||||
|
leftPx + widthPx - 1, marginPx);
|
||||||
|
const int heightPx =
|
||||||
|
std::min(wantedSize.height(), std::max(0, bottomPx - band.top() + 1));
|
||||||
|
|
||||||
|
// Top-aligned with the anchor, then lifted by however much of it hangs below what is
|
||||||
|
// free. Never above the band: a panel taller than the space left is capped instead,
|
||||||
|
// and scrolls.
|
||||||
|
int topPx = std::max(anchorRect.top(), band.top());
|
||||||
|
topPx = std::min(topPx, bottomPx - heightPx + 1);
|
||||||
|
topPx = std::max(topPx, band.top());
|
||||||
|
|
||||||
|
return QRect(leftPx, topPx, widthPx, heightPx);
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <QRect>
|
#include <QRect>
|
||||||
|
#include <QSize>
|
||||||
|
|
||||||
// Geometry for the widgets floating over the game world view (REQ-UI-WORLD-SIZE). Their
|
// Geometry for the widgets floating over the game world view (REQ-UI-WORLD-SIZE). Their
|
||||||
// owner places them in one ordered pass, each into the space the earlier ones left free,
|
// owner places them in one ordered pass, each into the space the earlier ones left free,
|
||||||
@@ -16,3 +17,27 @@
|
|||||||
// REQ-UI-CONTROLS-PANEL). The result is inclusive, as QRect::bottom() is.
|
// REQ-UI-CONTROLS-PANEL). The result is inclusive, as QRect::bottom() is.
|
||||||
int getAvailableBottomPx(const QRect& band, const std::vector<QRect>& occupiedRects,
|
int getAvailableBottomPx(const QRect& band, const std::vector<QRect>& occupiedRects,
|
||||||
int leftPx, int rightPx, int marginPx);
|
int leftPx, int rightPx, int marginPx);
|
||||||
|
|
||||||
|
// Which side of the selection the panel stands on (REQ-UI-SELECTION-PANEL).
|
||||||
|
enum class PanelSide
|
||||||
|
{
|
||||||
|
Right,
|
||||||
|
Left
|
||||||
|
};
|
||||||
|
|
||||||
|
// The side a panel widthPx wide takes beside anchorRect: the right of it where it fits
|
||||||
|
// within band, otherwise the left, and where it fits on neither, whichever side leaves
|
||||||
|
// more room -- the one case in which the panel ends up over the selection
|
||||||
|
// (REQ-UI-SELECTION-PANEL). Decided once when the selection starts and kept for as long
|
||||||
|
// as it lasts, so a card that grows later never flips the panel across the object.
|
||||||
|
PanelSide chooseSide(const QRect& band, const QRect& anchorRect, int widthPx,
|
||||||
|
int marginPx);
|
||||||
|
|
||||||
|
// Where a panel of wantedSize stands beside anchorRect on the given side: separated from
|
||||||
|
// it by marginPx and growing away from it, its top edge on the anchor's top edge, pushed
|
||||||
|
// inside band and above whatever occupies it. The returned height is short of
|
||||||
|
// wantedSize's when there was not enough room, which is the caller's cue to scroll its
|
||||||
|
// content (REQ-UI-SELECTION-PANEL).
|
||||||
|
QRect placeBesideAnchor(const QRect& band, const QRect& anchorRect, PanelSide side,
|
||||||
|
QSize wantedSize, const std::vector<QRect>& occupiedRects,
|
||||||
|
int marginPx);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ SET(HDRS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/BossWaveUpdatedEvent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/BossWaveUpdatedEvent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/SchematicChoicesAvailableEvent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/SchematicChoicesAvailableEvent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/SelectionChangedEvent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/SelectionChangedEvent.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/SelectionAnchorChangedEvent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/GameOverEvent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/GameOverEvent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/GameResetEvent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/GameResetEvent.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/WinEvent.h
|
${CMAKE_CURRENT_SOURCE_DIR}/WinEvent.h
|
||||||
|
|||||||
25
src/lib/eventsystem/event/SelectionAnchorChangedEvent.h
Normal file
25
src/lib/eventsystem/event/SelectionAnchorChangedEvent.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QRect>
|
||||||
|
|
||||||
|
#include "Event.h"
|
||||||
|
|
||||||
|
// Where on the screen the selection that is about to be made sits: the bounds of the one
|
||||||
|
// object selected, or of all of them when the selection starts as a multi-selection
|
||||||
|
// (REQ-UI-SELECTION-PANEL). The rectangle is in the game world view's own widget
|
||||||
|
// coordinates.
|
||||||
|
//
|
||||||
|
// Published only when a selection *starts* -- a plain click or drag, or an additive one
|
||||||
|
// onto an empty selection -- and always immediately before the selection itself. Adding
|
||||||
|
// to a selection publishes nothing, which is what leaves the selection panel where it
|
||||||
|
// is while the selection grows; and because the rectangle is screen space frozen at that
|
||||||
|
// moment, scrolling the view or a selected ship flying off does not move the panel
|
||||||
|
// either.
|
||||||
|
class SelectionAnchorChangedEvent : public Event
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit SelectionAnchorChangedEvent(QRect rectPx)
|
||||||
|
: rectPx(rectPx) {}
|
||||||
|
|
||||||
|
const QRect rectPx;
|
||||||
|
};
|
||||||
@@ -66,3 +66,91 @@ TEST_CASE("A widget filling the column leaves nothing", "[layout]")
|
|||||||
const std::vector<QRect> occupied = { QRect(0, 0, 1000, 600) };
|
const std::vector<QRect> occupied = { QRect(0, 0, 1000, 600) };
|
||||||
REQUIRE(getAvailableBottomPx(makeBand(), occupied, 0, 999, kMarginPx) == -9);
|
REQUIRE(getAvailableBottomPx(makeBand(), occupied, 0, 999, kMarginPx) == -9);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Which side of the selection the panel takes
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE("The panel stands to the right of the selection where it fits", "[layout]")
|
||||||
|
{
|
||||||
|
// REQ-UI-SELECTION-PANEL: right of the anchor is the first choice.
|
||||||
|
REQUIRE(chooseSide(makeBand(), QRect(100, 100, 60, 60), 300, kMarginPx)
|
||||||
|
== PanelSide::Right);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("The panel goes left when the right cannot hold it", "[layout]")
|
||||||
|
{
|
||||||
|
// A selection near the right edge leaves 100 px there, not enough for a 300 px
|
||||||
|
// panel, and the left is wide open.
|
||||||
|
REQUIRE(chooseSide(makeBand(), QRect(880, 100, 20, 60), 300, kMarginPx)
|
||||||
|
== PanelSide::Left);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Fitting on neither side, the panel takes the roomier one", "[layout]")
|
||||||
|
{
|
||||||
|
// A bounding box spanning most of the view: 192 px free on the left, 92 on the
|
||||||
|
// right, and a 300 px panel fits in neither. It covers as little as it can.
|
||||||
|
REQUIRE(chooseSide(makeBand(), QRect(200, 100, 700, 200), 300, kMarginPx)
|
||||||
|
== PanelSide::Left);
|
||||||
|
REQUIRE(chooseSide(makeBand(), QRect(100, 100, 700, 200), 300, kMarginPx)
|
||||||
|
== PanelSide::Right);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Where it then stands
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
TEST_CASE("The panel sits beside the anchor with its top edges aligned", "[layout]")
|
||||||
|
{
|
||||||
|
// REQ-UI-SELECTION-PANEL: separated by the margin, growing away from the selection,
|
||||||
|
// top edge on the anchor's top edge.
|
||||||
|
const QRect placed = placeBesideAnchor(makeBand(), QRect(100, 120, 60, 60),
|
||||||
|
PanelSide::Right, QSize(300, 200), {},
|
||||||
|
kMarginPx);
|
||||||
|
REQUIRE(placed == QRect(168, 120, 300, 200));
|
||||||
|
|
||||||
|
const QRect placedLeft = placeBesideAnchor(makeBand(), QRect(500, 120, 60, 60),
|
||||||
|
PanelSide::Left, QSize(300, 200), {},
|
||||||
|
kMarginPx);
|
||||||
|
REQUIRE(placedLeft == QRect(192, 120, 300, 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A panel that would hang below the view is lifted", "[layout]")
|
||||||
|
{
|
||||||
|
// Top-aligning with a selection low in the view would put the panel's bottom past
|
||||||
|
// the band, so it rises until it fits rather than overrunning it.
|
||||||
|
const QRect placed = placeBesideAnchor(makeBand(), QRect(100, 500, 60, 60),
|
||||||
|
PanelSide::Right, QSize(300, 200), {},
|
||||||
|
kMarginPx);
|
||||||
|
REQUIRE(placed == QRect(168, 400, 300, 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A panel standing over another widget rises above it", "[layout]")
|
||||||
|
{
|
||||||
|
// The controls panel in the bottom-left is in the way of a panel placed to the left
|
||||||
|
// of a selection: it clears the top of it by the margin (REQ-UI-CONTROLS-PANEL).
|
||||||
|
const std::vector<QRect> occupied = { QRect(0, 300, 260, 300) };
|
||||||
|
const QRect placed = placeBesideAnchor(makeBand(), QRect(500, 250, 60, 60),
|
||||||
|
PanelSide::Left, QSize(300, 200), occupied,
|
||||||
|
kMarginPx);
|
||||||
|
REQUIRE(placed == QRect(192, 92, 300, 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A panel taller than the space left is capped", "[layout]")
|
||||||
|
{
|
||||||
|
// Capping is the caller's cue to scroll: it asked for 700 and got what there was.
|
||||||
|
const QRect placed = placeBesideAnchor(makeBand(), QRect(100, 100, 60, 60),
|
||||||
|
PanelSide::Right, QSize(300, 700), {},
|
||||||
|
kMarginPx);
|
||||||
|
REQUIRE(placed == QRect(168, 0, 300, 600));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("A panel that fits on neither side is pushed inside the view", "[layout]")
|
||||||
|
{
|
||||||
|
// The one case where it covers part of the selection (REQ-UI-SELECTION-PANEL): it
|
||||||
|
// stands as far from the anchor as the band allows, not off the edge of it.
|
||||||
|
const QRect placed = placeBesideAnchor(makeBand(), QRect(100, 100, 700, 200),
|
||||||
|
PanelSide::Right, QSize(300, 200), {},
|
||||||
|
kMarginPx);
|
||||||
|
REQUIRE(placed == QRect(700, 100, 300, 200));
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ SET(HDRS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/FloatingPanel.h
|
${CMAKE_CURRENT_SOURCE_DIR}/FloatingPanel.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildButtonBar.h
|
${CMAKE_CURRENT_SOURCE_DIR}/BuildButtonBar.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/SelectionPanel.h
|
${CMAKE_CURRENT_SOURCE_DIR}/SelectionPanel.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/SelectionBounds.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ControlsPanel.h
|
${CMAKE_CURRENT_SOURCE_DIR}/ControlsPanel.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ControlActionText.h
|
${CMAKE_CURRENT_SOURCE_DIR}/ControlActionText.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/BlueprintLibrary.h
|
${CMAKE_CURRENT_SOURCE_DIR}/BlueprintLibrary.h
|
||||||
@@ -48,6 +49,7 @@ SET(SRCS
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/HeaderBar.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/HeaderBar.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildButtonBar.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/BuildButtonBar.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/SelectionPanel.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/SelectionPanel.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/SelectionBounds.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ControlsPanel.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/ControlsPanel.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ControlActionText.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/ControlActionText.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/BlueprintLibrary.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/BlueprintLibrary.cpp
|
||||||
|
|||||||
@@ -47,6 +47,8 @@
|
|||||||
#include "ItemIconCache.h"
|
#include "ItemIconCache.h"
|
||||||
#include "PositionComponent.h"
|
#include "PositionComponent.h"
|
||||||
#include "DebrisSystem.h"
|
#include "DebrisSystem.h"
|
||||||
|
#include "SelectionAnchorChangedEvent.h"
|
||||||
|
#include "SelectionBounds.h"
|
||||||
#include "SelectionChangedEvent.h"
|
#include "SelectionChangedEvent.h"
|
||||||
#include "ShipIdentityComponent.h"
|
#include "ShipIdentityComponent.h"
|
||||||
#include "ShipSystem.h"
|
#include "ShipSystem.h"
|
||||||
@@ -1255,6 +1257,7 @@ bool GameWorldView::selectAtPoint(QPoint tile, QVector2D worldPos, bool additive
|
|||||||
if (!buildingHit.has_value()) { buildingHit = siteAtTile(tile); }
|
if (!buildingHit.has_value()) { buildingHit = siteAtTile(tile); }
|
||||||
if (buildingHit.has_value())
|
if (buildingHit.has_value())
|
||||||
{
|
{
|
||||||
|
publishSelectionAnchor(mode, {*buildingHit}, {}, {});
|
||||||
m_selection.selectBuildings({*buildingHit}, mode);
|
m_selection.selectBuildings({*buildingHit}, mode);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1262,6 +1265,7 @@ bool GameWorldView::selectAtPoint(QPoint tile, QVector2D worldPos, bool additive
|
|||||||
const entt::entity actorHit = entityAtWorldPos(m_sim->getAdmin(), worldPos);
|
const entt::entity actorHit = entityAtWorldPos(m_sim->getAdmin(), worldPos);
|
||||||
if (actorHit != entt::null)
|
if (actorHit != entt::null)
|
||||||
{
|
{
|
||||||
|
publishSelectionAnchor(mode, {}, {actorHit}, {});
|
||||||
m_selection.selectFieldObjects({actorHit}, {}, mode);
|
m_selection.selectFieldObjects({actorHit}, {}, mode);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1269,6 +1273,7 @@ bool GameWorldView::selectAtPoint(QPoint tile, QVector2D worldPos, bool additive
|
|||||||
const entt::entity debrisHit = debrisAtWorldPos(m_sim->getAdmin(), worldPos);
|
const entt::entity debrisHit = debrisAtWorldPos(m_sim->getAdmin(), worldPos);
|
||||||
if (debrisHit != entt::null)
|
if (debrisHit != entt::null)
|
||||||
{
|
{
|
||||||
|
publishSelectionAnchor(mode, {}, {}, {debrisHit});
|
||||||
m_selection.selectFieldObjects({}, {debrisHit}, mode);
|
m_selection.selectFieldObjects({}, {debrisHit}, mode);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1289,6 +1294,7 @@ void GameWorldView::selectInBox(bool additive)
|
|||||||
buildingsInBox(m_sim->getFactoryState(), m_boxStartTile, m_boxCurrentTile);
|
buildingsInBox(m_sim->getFactoryState(), m_boxStartTile, m_boxCurrentTile);
|
||||||
if (!boxIds.empty())
|
if (!boxIds.empty())
|
||||||
{
|
{
|
||||||
|
publishSelectionAnchor(mode, boxIds, {}, {});
|
||||||
m_selection.selectBuildings(boxIds, mode);
|
m_selection.selectBuildings(boxIds, mode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1299,6 +1305,7 @@ void GameWorldView::selectInBox(bool additive)
|
|||||||
debrisInBox(m_sim->getAdmin(), m_boxStartTile, m_boxCurrentTile);
|
debrisInBox(m_sim->getAdmin(), m_boxStartTile, m_boxCurrentTile);
|
||||||
if (!boxActors.empty() || !boxDebris.empty())
|
if (!boxActors.empty() || !boxDebris.empty())
|
||||||
{
|
{
|
||||||
|
publishSelectionAnchor(mode, {}, boxActors, boxDebris);
|
||||||
m_selection.selectFieldObjects(boxActors, boxDebris, mode);
|
m_selection.selectFieldObjects(boxActors, boxDebris, mode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1307,6 +1314,35 @@ void GameWorldView::selectInBox(bool additive)
|
|||||||
if (!additive) { m_selection.clearAll(); }
|
if (!additive) { m_selection.clearAll(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameWorldView::publishSelectionAnchor(SelectionMode mode,
|
||||||
|
const std::vector<BuildingId>& buildings,
|
||||||
|
const std::vector<entt::entity>& actors,
|
||||||
|
const std::vector<entt::entity>& debris)
|
||||||
|
{
|
||||||
|
// An additive gesture onto something already selected is growing that selection, not
|
||||||
|
// starting one, and the panel stays where it was put (REQ-UI-SELECTION-PANEL).
|
||||||
|
const bool startsSelection =
|
||||||
|
(mode == SelectionMode::Replace) || (m_selection.getSelectedBuildings().empty()
|
||||||
|
&& m_selection.getSelectedActors().empty()
|
||||||
|
&& m_selection.getSelectedDebris().empty());
|
||||||
|
if (!startsSelection)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Screen space, frozen here: the panel is placed against where the selection is at
|
||||||
|
// this moment and stays there, however far the view scrolls or the objects move
|
||||||
|
// afterwards.
|
||||||
|
const QRect anchorRect = getSelectionWidgetRect(*m_sim, getCoordinates(),
|
||||||
|
buildings, actors, debris);
|
||||||
|
if (anchorRect.isNull())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
EventManager::getInstance()->sendEventImmediately(
|
||||||
|
std::make_shared<SelectionAnchorChangedEvent>(anchorRect));
|
||||||
|
}
|
||||||
|
|
||||||
void GameWorldView::mouseMoveEvent(QMouseEvent* event)
|
void GameWorldView::mouseMoveEvent(QMouseEvent* event)
|
||||||
{
|
{
|
||||||
const WorldCoordinates coordinates = getCoordinates();
|
const WorldCoordinates coordinates = getCoordinates();
|
||||||
|
|||||||
@@ -226,6 +226,15 @@ private:
|
|||||||
// which is the only case that goes on to start a box drag.
|
// which is the only case that goes on to start a box drag.
|
||||||
bool selectAtPoint(QPoint tile, QVector2D worldPos, bool additive);
|
bool selectAtPoint(QPoint tile, QVector2D worldPos, bool additive);
|
||||||
void selectInBox(bool additive);
|
void selectInBox(bool additive);
|
||||||
|
// Publishes where on the screen the selection about to be made sits, so the
|
||||||
|
// selection panel can be placed beside it (REQ-UI-SELECTION-PANEL). Called with
|
||||||
|
// what is about to be selected, immediately before selecting it, and publishes
|
||||||
|
// nothing unless that selection is starting rather than growing.
|
||||||
|
void publishSelectionAnchor(
|
||||||
|
SelectionMode mode,
|
||||||
|
const std::vector<BuildingId>& buildings,
|
||||||
|
const std::vector<entt::entity>& actors,
|
||||||
|
const std::vector<entt::entity>& debris);
|
||||||
void stepSpeed(int delta);
|
void stepSpeed(int delta);
|
||||||
void placeAtTile(QPoint tile);
|
void placeAtTile(QPoint tile);
|
||||||
|
|
||||||
|
|||||||
115
src/ui/SelectionBounds.cpp
Normal file
115
src/ui/SelectionBounds.cpp
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
#include "SelectionBounds.h"
|
||||||
|
|
||||||
|
#include "Building.h"
|
||||||
|
#include "DebrisComponent.h"
|
||||||
|
#include "EntityAdmin.h"
|
||||||
|
#include "FactoryQueries.h"
|
||||||
|
#include "PositionComponent.h"
|
||||||
|
#include "Simulation.h"
|
||||||
|
#include "StationBodyComponent.h"
|
||||||
|
#include "WorldCoordinates.h"
|
||||||
|
#include "WorldPrimitives.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
// The widget rectangle of a footprint anchored at a tile, both kinds of body being
|
||||||
|
// described the same way.
|
||||||
|
QRectF getFootprintWidgetRect(const WorldCoordinates& coordinates, QPoint anchor,
|
||||||
|
QSize footprint)
|
||||||
|
{
|
||||||
|
const QPointF topLeft = coordinates.tileToWidget(anchor);
|
||||||
|
return QRectF(topLeft.x(), topLeft.y(),
|
||||||
|
footprint.width() * static_cast<qreal>(coordinates.getTilePx()),
|
||||||
|
footprint.height() * static_cast<qreal>(coordinates.getTilePx()));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
|
std::optional<QRectF> getBuildingWidgetRect(const FactoryState& state,
|
||||||
|
const WorldCoordinates& coordinates,
|
||||||
|
BuildingId id)
|
||||||
|
{
|
||||||
|
if (const Building* building = findBuilding(state, id))
|
||||||
|
{
|
||||||
|
return getFootprintWidgetRect(coordinates, building->anchor, building->footprint);
|
||||||
|
}
|
||||||
|
if (const ConstructionSite* site = findSite(state, id))
|
||||||
|
{
|
||||||
|
return getFootprintWidgetRect(coordinates, site->anchor, site->footprint);
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<QRectF> getActorWidgetRect(EntityAdmin& admin,
|
||||||
|
const WorldCoordinates& coordinates,
|
||||||
|
entt::entity actor)
|
||||||
|
{
|
||||||
|
if (!admin.isValid(actor))
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
if (admin.hasAll<StationBodyComponent>(actor))
|
||||||
|
{
|
||||||
|
const StationBodyComponent& body = admin.get<StationBodyComponent>(actor);
|
||||||
|
return getFootprintWidgetRect(coordinates, body.anchor, body.footprint);
|
||||||
|
}
|
||||||
|
if (admin.hasAll<PositionComponent>(actor))
|
||||||
|
{
|
||||||
|
// A ship is a triangle about its center; the square its longest extent fits in
|
||||||
|
// is what the panel is placed beside.
|
||||||
|
const QPointF center =
|
||||||
|
coordinates.worldToWidget(admin.get<PositionComponent>(actor).value);
|
||||||
|
const qreal extent = static_cast<qreal>(getShipForwardExtentPx(coordinates));
|
||||||
|
return QRectF(center.x() - extent, center.y() - extent,
|
||||||
|
2.0 * extent, 2.0 * extent);
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<QRectF> getDebrisWidgetRect(EntityAdmin& admin,
|
||||||
|
const WorldCoordinates& coordinates,
|
||||||
|
entt::entity debris)
|
||||||
|
{
|
||||||
|
if (!admin.isValid(debris) || !admin.hasAll<PositionComponent, DebrisComponent>(debris))
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const QPointF center =
|
||||||
|
coordinates.worldToWidget(admin.get<PositionComponent>(debris).value);
|
||||||
|
const qreal radius = static_cast<qreal>(getDebrisRadiusPx(coordinates));
|
||||||
|
return QRectF(center.x() - radius, center.y() - radius, 2.0 * radius, 2.0 * radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
QRect getSelectionWidgetRect(Simulation& sim, const WorldCoordinates& coordinates,
|
||||||
|
const std::vector<BuildingId>& buildings,
|
||||||
|
const std::vector<entt::entity>& actors,
|
||||||
|
const std::vector<entt::entity>& debris)
|
||||||
|
{
|
||||||
|
QRectF bounds;
|
||||||
|
// A null rect unites to nothing of its own, so the first object found sets the box
|
||||||
|
// and every later one grows it.
|
||||||
|
auto add = [&bounds](const std::optional<QRectF>& rect)
|
||||||
|
{
|
||||||
|
if (rect.has_value())
|
||||||
|
{
|
||||||
|
bounds = bounds.isNull() ? *rect : bounds.united(*rect);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (BuildingId id : buildings)
|
||||||
|
{
|
||||||
|
add(getBuildingWidgetRect(sim.getFactoryState(), coordinates, id));
|
||||||
|
}
|
||||||
|
for (entt::entity actor : actors)
|
||||||
|
{
|
||||||
|
add(getActorWidgetRect(sim.getAdmin(), coordinates, actor));
|
||||||
|
}
|
||||||
|
for (entt::entity piece : debris)
|
||||||
|
{
|
||||||
|
add(getDebrisWidgetRect(sim.getAdmin(), coordinates, piece));
|
||||||
|
}
|
||||||
|
|
||||||
|
return bounds.isNull() ? QRect() : bounds.toAlignedRect();
|
||||||
|
}
|
||||||
50
src/ui/SelectionBounds.h
Normal file
50
src/ui/SelectionBounds.h
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <QRect>
|
||||||
|
|
||||||
|
#include "entt/entity/entity.hpp"
|
||||||
|
|
||||||
|
#include "BuildingId.h"
|
||||||
|
|
||||||
|
class EntityAdmin;
|
||||||
|
class Simulation;
|
||||||
|
class WorldCoordinates;
|
||||||
|
struct FactoryState;
|
||||||
|
|
||||||
|
// Where selectable objects are on the screen. The world renderer draws every selection
|
||||||
|
// outline from these rectangles, and the selection panel is placed beside the one that
|
||||||
|
// covers the whole selection (REQ-UI-SELECTION-PANEL).
|
||||||
|
//
|
||||||
|
// All of them are in the game world view's own widget coordinates, and all of them are
|
||||||
|
// only true for the WorldCoordinates they were asked for: the transform is a value built
|
||||||
|
// per frame or per event, so a rectangle does not survive a scroll or a resize.
|
||||||
|
|
||||||
|
// The footprint of a building or of a construction site (REQ-UI-SELECTION-CARD). Null
|
||||||
|
// when the id names neither, which is what a deconstruction under the caller looks like.
|
||||||
|
std::optional<QRectF> getBuildingWidgetRect(const FactoryState& state,
|
||||||
|
const WorldCoordinates& coordinates,
|
||||||
|
BuildingId id);
|
||||||
|
|
||||||
|
// The body of a ship or of a defence station (REQ-UI-ENTITY-CLICK-SELECT): a station's
|
||||||
|
// footprint, or the square the ship's triangle is drawn in. The admin is taken by
|
||||||
|
// non-const reference only because EntityAdmin::hasAll() is not const; nothing here
|
||||||
|
// writes to it.
|
||||||
|
std::optional<QRectF> getActorWidgetRect(EntityAdmin& admin,
|
||||||
|
const WorldCoordinates& coordinates,
|
||||||
|
entt::entity actor);
|
||||||
|
|
||||||
|
// The circle a piece of debris is drawn as (REQ-UI-DEBRIS-CLICK-SELECT).
|
||||||
|
std::optional<QRectF> getDebrisWidgetRect(EntityAdmin& admin,
|
||||||
|
const WorldCoordinates& coordinates,
|
||||||
|
entt::entity debris);
|
||||||
|
|
||||||
|
// The rectangle covering a whole selection -- one object, or the bounding box of all of
|
||||||
|
// them (REQ-UI-SELECTION-PANEL). Objects that no longer resolve are skipped; the result
|
||||||
|
// is null when none of them does.
|
||||||
|
QRect getSelectionWidgetRect(Simulation& sim, const WorldCoordinates& coordinates,
|
||||||
|
const std::vector<BuildingId>& buildings,
|
||||||
|
const std::vector<entt::entity>& actors,
|
||||||
|
const std::vector<entt::entity>& debris);
|
||||||
@@ -116,6 +116,18 @@ void SelectionPanel::handleEvent(std::shared_ptr<const SelectionChangedEvent> ev
|
|||||||
rebuildContent();
|
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(
|
void SelectionPanel::handleEvent(
|
||||||
std::shared_ptr<const EntitySelectionChangedEvent> event)
|
std::shared_ptr<const EntitySelectionChangedEvent> event)
|
||||||
{
|
{
|
||||||
@@ -226,6 +238,15 @@ void SelectionPanel::placeIn(const QRect& viewRect, const std::vector<QRect>& oc
|
|||||||
|
|
||||||
const QRect band = viewRect.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
|
// 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
|
// 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
|
// from contentsMargins() because the stylesheet box is what sets it, and asking the
|
||||||
@@ -269,17 +290,26 @@ void SelectionPanel::placeIn(const QRect& viewRect, const std::vector<QRect>& oc
|
|||||||
// wants to be; then at that width for the height that follows from it.
|
// wants to be; then at that width for the height that follows from it.
|
||||||
int contentWidthPx = qMin(measureAt(maxWidthPx).width(), maxWidthPx);
|
int contentWidthPx = qMin(measureAt(maxWidthPx).width(), maxWidthPx);
|
||||||
|
|
||||||
// How much height there is depends on where the panel stands: of the widgets
|
// Which side of the selection the panel takes is settled on the first placement
|
||||||
// placed before it, only those whose rectangles meet its own column are in its
|
// after a new anchor and kept for as long as that selection lasts, so a card that
|
||||||
// way (REQ-UI-SELECTION-PANEL). Its column follows from the width just measured,
|
// grows or shrinks never flips the panel across the object it describes
|
||||||
// so this cannot be settled before it -- and where the scroll bar below widens
|
// (REQ-UI-SELECTION-PANEL). This is the first point at which its width is known.
|
||||||
// the panel, the second pass settles it again against the wider column.
|
if (!m_side.has_value())
|
||||||
const int leftPx =
|
{
|
||||||
band.right() - (contentWidthPx + 2 * borderPx) + 1;
|
m_side = chooseSide(band, anchorRect, contentWidthPx + 2 * borderPx,
|
||||||
const int bottomPx = getAvailableBottomPx(band, occupiedRects, leftPx,
|
kMarginPx);
|
||||||
band.right(), kMarginPx);
|
}
|
||||||
const int freeHeightPx = bottomPx - band.top() + 1;
|
|
||||||
const int maxHeightPx = freeHeightPx - 2 * borderPx;
|
// 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)
|
if (maxHeightPx <= 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -313,11 +343,8 @@ void SelectionPanel::placeIn(const QRect& viewRect, const std::vector<QRect>& oc
|
|||||||
const int panelWidthPx = contentWidthPx + 2 * borderPx;
|
const int panelWidthPx = contentWidthPx + 2 * borderPx;
|
||||||
const int panelHeightPx = contentHeightPx + 2 * borderPx;
|
const int panelHeightPx = contentHeightPx + 2 * borderPx;
|
||||||
|
|
||||||
// Right-aligned in the band and centered vertically on the free part of it, so
|
setGeometry(placeBesideAnchor(band, anchorRect, *m_side,
|
||||||
// the panel sits above whatever the widgets placed before it are occupying rather
|
QSize(panelWidthPx, panelHeightPx),
|
||||||
// than over them (REQ-UI-SELECTION-PANEL).
|
occupiedRects, kMarginPx));
|
||||||
setGeometry(band.right() - panelWidthPx + 1,
|
|
||||||
band.top() + (freeHeightPx - panelHeightPx) / 2,
|
|
||||||
panelWidthPx, panelHeightPx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <QRect>
|
#include <QRect>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "DebrisSelectionChangedEvent.h"
|
#include "DebrisSelectionChangedEvent.h"
|
||||||
@@ -10,7 +11,9 @@
|
|||||||
#include "EntitySelectionChangedEvent.h"
|
#include "EntitySelectionChangedEvent.h"
|
||||||
#include "EventHandler.h"
|
#include "EventHandler.h"
|
||||||
#include "FloatingPanel.h"
|
#include "FloatingPanel.h"
|
||||||
|
#include "FloatingPanelPlacement.h"
|
||||||
#include "PlayerCommandsAppliedEvent.h"
|
#include "PlayerCommandsAppliedEvent.h"
|
||||||
|
#include "SelectionAnchorChangedEvent.h"
|
||||||
#include "SelectionChangedEvent.h"
|
#include "SelectionChangedEvent.h"
|
||||||
#include "TickAdvancedEvent.h"
|
#include "TickAdvancedEvent.h"
|
||||||
#include "selection/SelectionContentFactory.h"
|
#include "selection/SelectionContentFactory.h"
|
||||||
@@ -40,6 +43,7 @@ class SelectionPanel : public QWidget,
|
|||||||
PlayerCommandsAppliedEvent,
|
PlayerCommandsAppliedEvent,
|
||||||
EntitySelectionChangedEvent,
|
EntitySelectionChangedEvent,
|
||||||
SelectionChangedEvent,
|
SelectionChangedEvent,
|
||||||
|
SelectionAnchorChangedEvent,
|
||||||
DebrisSelectionChangedEvent,
|
DebrisSelectionChangedEvent,
|
||||||
DebugDrawToggledEvent>
|
DebugDrawToggledEvent>
|
||||||
{
|
{
|
||||||
@@ -53,15 +57,16 @@ public:
|
|||||||
BuildingIconCache* buildingIcons, QWidget* parent = nullptr);
|
BuildingIconCache* buildingIcons, QWidget* parent = nullptr);
|
||||||
~SelectionPanel() override;
|
~SelectionPanel() override;
|
||||||
|
|
||||||
// Sizes the panel to its card and places it in the game world view, right-aligned and
|
// Sizes the panel to its card and places it beside the selection it describes, in
|
||||||
// vertically centered in what the widgets placed before it have left free. Keeping
|
// what the widgets placed before it have left free. Keeping clear of them is entirely
|
||||||
// clear of them is entirely this panel's job; neither of them ever moves for it
|
// this panel's job; neither of them ever moves for it (REQ-UI-SELECTION-PANEL,
|
||||||
// (REQ-UI-SELECTION-PANEL, REQ-UI-BUILD-BAR, REQ-UI-CONTROLS-PANEL).
|
// REQ-UI-BUILD-BAR, REQ-UI-CONTROLS-PANEL).
|
||||||
void placeIn(const QRect& viewRect,
|
void placeIn(const QRect& viewRect,
|
||||||
const std::vector<QRect>& occupiedRects) override;
|
const std::vector<QRect>& occupiedRects) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void handleEvent(std::shared_ptr<const SelectionChangedEvent> event) override;
|
void handleEvent(std::shared_ptr<const SelectionChangedEvent> event) override;
|
||||||
|
void handleEvent(std::shared_ptr<const SelectionAnchorChangedEvent> event) override;
|
||||||
void handleEvent(std::shared_ptr<const EntitySelectionChangedEvent> event) override;
|
void handleEvent(std::shared_ptr<const EntitySelectionChangedEvent> event) override;
|
||||||
void handleEvent(std::shared_ptr<const DebrisSelectionChangedEvent> event) override;
|
void handleEvent(std::shared_ptr<const DebrisSelectionChangedEvent> event) override;
|
||||||
void handleEvent(std::shared_ptr<const TickAdvancedEvent> event) override;
|
void handleEvent(std::shared_ptr<const TickAdvancedEvent> event) override;
|
||||||
@@ -87,6 +92,16 @@ private:
|
|||||||
ContentKey m_contentKey;
|
ContentKey m_contentKey;
|
||||||
SelectionContent* m_content = nullptr;
|
SelectionContent* m_content = nullptr;
|
||||||
|
|
||||||
|
// Where the current selection was on the screen when it started, in the game world
|
||||||
|
// view's coordinates, and which side of it the panel took. Both are frozen for as
|
||||||
|
// long as the selection lasts: the anchor because the panel does not chase a
|
||||||
|
// 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.
|
||||||
|
QRect m_anchorRect;
|
||||||
|
std::optional<PanelSide> m_side;
|
||||||
|
|
||||||
// Scrolls the card once it outgrows the space the panel has (REQ-UI-SELECTION-PANEL).
|
// 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.
|
// The card is a child of m_body, not of the panel itself.
|
||||||
QScrollArea* m_scrollArea;
|
QScrollArea* m_scrollArea;
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
#include "ProductionRules.h"
|
#include "ProductionRules.h"
|
||||||
#include "RepairBehavior.h"
|
#include "RepairBehavior.h"
|
||||||
#include "SalvageScrapBehavior.h"
|
#include "SalvageScrapBehavior.h"
|
||||||
|
#include "SelectionBounds.h"
|
||||||
#include "SensorRangeComponent.h"
|
#include "SensorRangeComponent.h"
|
||||||
#include "ShipIdentityComponent.h"
|
#include "ShipIdentityComponent.h"
|
||||||
#include "Simulation.h"
|
#include "Simulation.h"
|
||||||
@@ -451,30 +452,6 @@ void WorldRenderer::drawBuildings(QPainter& painter, const WorldCoordinates& coo
|
|||||||
drawSelectionHighlights(painter, coordinates, frame);
|
drawSelectionHighlights(painter, coordinates, frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<QRectF> WorldRenderer::footprintWidgetRect(
|
|
||||||
const WorldCoordinates& coordinates, BuildingId id) const
|
|
||||||
{
|
|
||||||
std::optional<QPoint> anchor;
|
|
||||||
std::optional<QSize> footprint;
|
|
||||||
|
|
||||||
if (const Building* b = findBuilding(m_sim.getFactoryState(), id))
|
|
||||||
{
|
|
||||||
anchor = b->anchor;
|
|
||||||
footprint = b->footprint;
|
|
||||||
}
|
|
||||||
else if (const ConstructionSite* s = findSite(m_sim.getFactoryState(), id))
|
|
||||||
{
|
|
||||||
anchor = s->anchor;
|
|
||||||
footprint = s->footprint;
|
|
||||||
}
|
|
||||||
if (!anchor.has_value() || !footprint.has_value()) { return std::nullopt; }
|
|
||||||
|
|
||||||
const QPointF tl = coordinates.tileToWidget(*anchor);
|
|
||||||
return QRectF(tl.x(), tl.y(),
|
|
||||||
footprint->width() * static_cast<qreal>(coordinates.getTilePx()),
|
|
||||||
footprint->height() * static_cast<qreal>(coordinates.getTilePx()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void WorldRenderer::drawSelectionHighlights(QPainter& painter, const WorldCoordinates& coordinates,
|
void WorldRenderer::drawSelectionHighlights(QPainter& painter, const WorldCoordinates& coordinates,
|
||||||
const WorldRenderFrame& frame)
|
const WorldRenderFrame& frame)
|
||||||
{
|
{
|
||||||
@@ -483,7 +460,8 @@ void WorldRenderer::drawSelectionHighlights(QPainter& painter, const WorldCoordi
|
|||||||
|
|
||||||
for (BuildingId selId : frame.selection.getSelectedBuildings())
|
for (BuildingId selId : frame.selection.getSelectedBuildings())
|
||||||
{
|
{
|
||||||
const std::optional<QRectF> rect = footprintWidgetRect(coordinates, selId);
|
const std::optional<QRectF> rect =
|
||||||
|
getBuildingWidgetRect(m_sim.getFactoryState(), coordinates, selId);
|
||||||
if (!rect.has_value()) { continue; }
|
if (!rect.has_value()) { continue; }
|
||||||
// Outline sits 1px outside the footprint (into adjacent tiles).
|
// Outline sits 1px outside the footprint (into adjacent tiles).
|
||||||
painter.drawRect(rect->adjusted(-1, -1, 1, 1));
|
painter.drawRect(rect->adjusted(-1, -1, 1, 1));
|
||||||
|
|||||||
@@ -139,11 +139,6 @@ private:
|
|||||||
BuildingType type, const QRectF& box,
|
BuildingType type, const QRectF& box,
|
||||||
const QColor& fill) const;
|
const QColor& fill) const;
|
||||||
|
|
||||||
// Widget-space rectangle covering a building or construction site's footprint,
|
|
||||||
// or nullopt if the id resolves to neither. Used by the selection highlight.
|
|
||||||
std::optional<QRectF> footprintWidgetRect(const WorldCoordinates& coordinates,
|
|
||||||
BuildingId id) const;
|
|
||||||
|
|
||||||
std::optional<QVector2D> entityPosition(entt::entity entity) const;
|
std::optional<QVector2D> entityPosition(entt::entity entity) const;
|
||||||
|
|
||||||
// Non-const only because EntityAdmin's component accessors are; the renderer
|
// Non-const only because EntityAdmin's component accessors are; the renderer
|
||||||
|
|||||||
Reference in New Issue
Block a user