stand the expansion button on the ground it buys

The button leaves the header bar for the world, centered on the columns
the next purchase unlocks and panning with them until it leaves the view.
Those columns now take a lighter tint than the locked ground behind them,
so the boundary between the two says how far one purchase reaches.

Nothing in the code reported a scroll: the camera knew it had moved and
told only the hover. A ViewScrolledEvent says it now, which is what a
widget keeping a place in the world rather than on the screen needs. The
panels want none of it -- they are placed against the screen and stay put
as the world moves under them -- so the button is placed after their pass
and never joins the rectangles they step around.

Its face is two lines, "Expand" over the cost. That makes the cost a
display of its own, so it carries the building-block tooltip the way every
other item value does, on hover alone since the click buys the expansion.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
This commit is contained in:
2026-08-18 22:55:06 +02:00
parent 8292bc6134
commit abe8c8f448
16 changed files with 271 additions and 80 deletions

View File

@@ -11,8 +11,10 @@
#include <QDir>
#include <QFile>
#include <QMessageBox>
#include <QPoint>
#include <QResizeEvent>
#include <QVBoxLayout>
#include <QVector2D>
#include "BlueprintLibrary.h"
#include "BlueprintSelectionDialog.h"
@@ -28,6 +30,7 @@
#include "HeaderBar.h"
#include "SelectionPanel.h"
#include "ControlsPanel.h"
#include "ExpandButton.h"
#include "ShipLayoutBlueprintSerializer.h"
#include "ShipLayoutDialog.h"
#include "BuildingIconCache.h"
@@ -74,6 +77,13 @@ MainWindow::MainWindow(Simulation* sim, const std::string& configDir,
m_buildingIcons.get(), m_itemIcons.get(),
this);
// Stands in the world rather than on the screen, on the columns the next expansion
// unlocks (REQ-UI-EXPAND-BUTTON). A sibling of the world view like the panels, which
// is what keeps the builder-mode ghost off the tile beneath it and the click off the
// world (REQ-BLD-GHOST): the view's hover follows underMouse(), false while the
// cursor rests on a sibling.
m_expandButton = new ExpandButton(getItemTooltipContext(), this);
// The blueprints have no widget of their own: they are saved with Ctrl+C and picked
// from a modal dialog (REQ-UI-BLUEPRINT-DIALOG), both driven from this window
// because only it can pause the game and raise the dim overlay. Built after the
@@ -223,6 +233,11 @@ void MainWindow::layoutPanels()
}
}
// Last, and never added to occupiedRects: it marks a place in the world, so the
// panels do not step around it and it does not step around them
// (REQ-UI-EXPAND-BUTTON).
placeExpandButton();
m_layingOut = false;
}
@@ -494,6 +509,48 @@ void MainWindow::handleEvent(std::shared_ptr<const BlueprintSelectionRequestedEv
showBlueprintSelectionDialog();
}
void MainWindow::placeExpandButton()
{
// Null while the button is still being built: refreshing its cost asks for a
// placement pass, and the first refresh happens inside its own constructor.
if (m_expandButton == nullptr)
{
return;
}
// The columns the next purchase unlocks: the ones immediately left of the buildable
// edge (REQ-GW-ASTEROID-EXPAND, REQ-UI-LOCKED-ASTEROID). Their middle, and the
// world's middle vertically, is where the button goes (REQ-UI-EXPAND-BUTTON).
const int asteroidWidth_tiles = m_sim->getCurrentAsteroidWidth_tiles();
const float columns_tiles = static_cast<float>(
m_sim->getConfig().world.expansion.columnsPerExpansion_tiles);
const QVector2D center_tiles(
-static_cast<float>(asteroidWidth_tiles) - columns_tiles / 2.0f,
static_cast<float>(m_sim->getConfig().world.heightTiles) / 2.0f);
// The transform is the view's, so the point it yields is in the view's coordinates;
// the button is a sibling of the view, so it is lifted into this window's.
const QPointF centerInView_px =
m_gameWorldView->getCoordinates().worldToWidget(center_tiles);
const QPoint center_px = m_gameWorldView->geometry().topLeft()
+ QPoint(static_cast<int>(centerInView_px.x()),
static_cast<int>(centerInView_px.y()));
// Neither clamped nor hidden: the button belongs to that ground and leaves the view
// with it, which the window's own edge takes care of (REQ-UI-EXPAND-BUTTON).
const QSize buttonSize = m_expandButton->sizeHint();
m_expandButton->setGeometry(QRect(
center_px - QPoint(buttonSize.width() / 2, buttonSize.height() / 2),
buttonSize));
}
void MainWindow::handleEvent(std::shared_ptr<const ViewScrolledEvent> /*event*/)
{
// The ground moved under the button; nothing else about the layout changed, so this
// is the one widget to re-place (REQ-UI-EXPAND-BUTTON).
placeExpandButton();
}
ItemTooltipContext MainWindow::getItemTooltipContext() const
{
return ItemTooltipContext{ m_sim, &m_sim->getConfig(), m_itemIcons.get(),