remove the Shift copy-building-settings gesture

This commit is contained in:
2026-08-06 19:09:11 +02:00
parent fd6a7c5815
commit cd31af2611
12 changed files with 26 additions and 231 deletions

View File

@@ -269,20 +269,6 @@ void GameWorldView::onFrame()
pruneDespawnedDebris();
pruneDespawnedActors();
// Expire copy/paste flashes. Lifetime is wall-clock (the frame delta), so the
// flash plays for a fixed real duration regardless of game speed, including
// while the game is paused (REQ-BLD-COPY-CONFIG-FEEDBACK).
if (!m_copyConfigFlashes.empty())
{
std::vector<CopyConfigFlash> live;
for (CopyConfigFlash flash : m_copyConfigFlashes)
{
flash.remainingMs -= elapsed;
if (flash.remainingMs > 0) { live.push_back(flash); }
}
m_copyConfigFlashes = std::move(live);
}
// Apply held scroll
{
const bool viewMoved =
@@ -414,9 +400,8 @@ void GameWorldView::paintGL()
WorldRenderFrame GameWorldView::makeRenderFrame() const
{
return WorldRenderFrame{m_selection, m_buildMode, m_activeBeams, m_copiedConfig,
m_copyConfigFlashes, m_boxSelecting, m_boxStartTile,
m_boxCurrentTile, m_debugDraw};
return WorldRenderFrame{m_selection, m_buildMode, m_activeBeams, m_boxSelecting,
m_boxStartTile, m_boxCurrentTile, m_debugDraw};
}
// ---------------------------------------------------------------------------
@@ -1026,10 +1011,6 @@ void GameWorldView::keyReleaseEvent(QKeyEvent* event)
return;
}
if (m_inputMapper.handleKeyRelease(event)) { return; }
// Releasing Shift discards the copied building settings (REQ-BLD-COPY-CONFIG).
// Stays here rather than moving to the input mapper: Shift is the modifier of a
// mouse gesture, not a keyboard action of its own.
if (event->key() == Qt::Key_Shift) { m_copiedConfig.reset(); }
QOpenGLWidget::keyReleaseEvent(event);
}
@@ -1061,15 +1042,6 @@ void GameWorldView::mousePressEvent(QMouseEvent* event)
{
m_buildMode.exitCurrentMode();
}
else if (event->modifiers() & Qt::ShiftModifier)
{
// Shift + right-click copies a building's settings, but only in the
// default selection mode (REQ-BLD-COPY-CONFIG).
const QPoint tile = coordinates.widgetToTile(event->pos());
std::optional<BuildingId> id = buildingAtTile(tile);
if (!id.has_value()) { id = siteAtTile(tile); }
if (id.has_value()) { copyConfigFrom(*id); }
}
}
return;
}
@@ -1105,20 +1077,6 @@ void GameWorldView::mousePressEvent(QMouseEvent* event)
}
else
{
// Shift + left-click applies the copied settings to a same-type building
// (REQ-BLD-COPY-CONFIG). Consumes the click so it does not change the
// selection. Only active in the default selection mode.
if ((event->modifiers() & Qt::ShiftModifier) && m_copiedConfig.has_value())
{
std::optional<BuildingId> id = buildingAtTile(tile);
if (!id.has_value()) { id = siteAtTile(tile); }
if (id.has_value())
{
pasteConfigTo(*id);
return;
}
}
const bool ctrl = (event->modifiers() & Qt::ControlModifier) != 0;
// Only a click that hit nothing starts a box drag. Starting one on a hit
@@ -1375,85 +1333,6 @@ void GameWorldView::rotateGhost(bool clockwise)
}
}
void GameWorldView::copyConfigFrom(BuildingId id)
{
const std::optional<BuildingConfig> config = readBuildingConfig(*m_sim, id);
if (!config.has_value()) { return; }
// Only cache when there is something to copy: a selected recipe / schematic
// (Miner, Assembler, Shipyard) or any Splitter (empty filters = accept-all).
// Building types with no settings (Smelter, Reprocessing Plant, Salvage Bay,
// belts / tunnels, HQ) leave any existing cache untouched (REQ-BLD-COPY-CONFIG).
if (!config->recipeId.has_value() && !config->isSplitter) { return; }
m_copiedConfig = config;
m_copyConfigFlashes.push_back({ id, kCopyFlashDurationMs });
}
void GameWorldView::pasteConfigTo(BuildingId id)
{
if (!m_copiedConfig.has_value()) { return; }
const std::optional<BuildingConfig> target = readBuildingConfig(*m_sim, id);
if (!target.has_value() || target->type != m_copiedConfig->type) { return; }
// The paste applies below; flash the target to confirm (REQ-BLD-COPY-CONFIG-FEEDBACK).
m_copyConfigFlashes.push_back({ id, kCopyFlashDurationMs });
const BuildingConfig& source = *m_copiedConfig;
// The cached settings were valid on a same-type source building, so they are
// valid and available on the target: unlock state is global, so a selected
// recipe / schematic (REQ-LOCK-UI-RECIPE, REQ-LOCK-UI-SCHEMATIC) and splitter
// filter item types (REQ-LOCK-UI-SPLITTER) remain unlocked. Applying reuses the
// same configuration commands as the selected building panel, inheriting their
// buffer-clearing and mid-cycle-cancel semantics (REQ-MAT-INPUT-BUFFER,
// REQ-MAT-OUTPUT-BUFFER, REQ-BLD-SHIPYARD).
if (source.isSplitter)
{
// Operational splitters are configured by tile; sites by BuildingId
// (mirrors SelectedBuildingPanel::onSplitterFilterChanged).
if (const Building* building = findBuilding(m_sim->getFactoryState(), id))
{
std::shared_ptr<SetSplitterFiltersCommand> command =
std::make_shared<SetSplitterFiltersCommand>();
command->tile = building->anchor;
command->filterA = source.splitterFilterA;
command->filterB = source.splitterFilterB;
enqueueCommand(command);
}
else
{
std::shared_ptr<SetSiteSplitterFiltersCommand> command =
std::make_shared<SetSiteSplitterFiltersCommand>();
command->id = id;
command->filterA = source.splitterFilterA;
command->filterB = source.splitterFilterB;
enqueueCommand(command);
}
return;
}
if (source.recipeId.has_value())
{
std::shared_ptr<SetRecipeCommand> command = std::make_shared<SetRecipeCommand>();
command->id = id;
command->recipeId = *source.recipeId;
enqueueCommand(command);
}
// For a shipyard the schematic (above) must be applied before its module
// layout, so both commands drain in order at the next tick boundary.
if (source.type == BuildingType::Shipyard && source.shipLayout.has_value())
{
std::shared_ptr<SetShipLayoutCommand> command =
std::make_shared<SetShipLayoutCommand>();
command->id = id;
command->layout = *source.shipLayout;
enqueueCommand(command);
}
}
double GameWorldView::getGameSpeed() const
{
return m_gameSpeedMultiplier;
@@ -1484,8 +1363,6 @@ void GameWorldView::resetForNewGame()
m_activeBeams.clear();
m_schematicChoiceShown = false;
m_selection.clearAll();
m_copiedConfig = std::nullopt;
m_copyConfigFlashes.clear();
m_boxSelecting = false;
m_camera.reset();
// Drops any key still held across the restart, which also republishes the pan

View File

@@ -20,7 +20,6 @@
#include "Blueprint.h"
#include "BuildModeController.h"
#include "BuildingConfig.h"
#include "BlueprintModeExitedEvent.h"
#include "BlueprintPlacementRequestedEvent.h"
#include "BuilderModeExitedEvent.h"
@@ -217,12 +216,6 @@ private:
// Enqueues placements and rotate-in-place commands for the resolved path.
void applyBeltDragPath();
// Copy-settings gesture (REQ-BLD-COPY-CONFIG): Shift+right-click copies a
// building's configuration into m_copiedConfig; Shift+left-click applies it to
// another building of the same type via the existing configuration commands.
void copyConfigFrom(BuildingId id);
void pasteConfigTo(BuildingId id);
// Turns the ghost and refreshes everything that depends on its facing: placement
// validity, the tunnel completion match, and an in-progress belt drag's path.
// The mode transitions themselves live on m_buildMode.
@@ -271,17 +264,6 @@ private:
// and to resolve the tunnel ghost sub-tile (REQ-BLD-TUNNEL-MODE).
QVector2D m_cursorWorldPos;
// Temporary cache for the copy-settings gesture (REQ-BLD-COPY-CONFIG); held
// only while Shift is down and cleared on Shift release.
std::optional<BuildingConfig> m_copiedConfig;
// Brief outline flash shown on a building when settings are copied from it or
// pasted onto it (REQ-BLD-COPY-CONFIG-FEEDBACK). remainingMs counts down in
// wall-clock time so the flash plays at a fixed length regardless of game speed
// (and while paused).
std::vector<CopyConfigFlash> m_copyConfigFlashes;
static constexpr qint64 kCopyFlashDurationMs = 300;
bool m_debugDraw;
// Owns the selection across all three categories and the rules for moving

View File

@@ -48,7 +48,6 @@ struct OverlayVisuals
QColor selectionRect;
QColor tileHighlight;
QColor selectedOutline;
QColor copyConfig;
QColor lockedAsteroid;
QColor modalDim;
QColor tunnelPreview; // tunnel connection preview highlight (REQ-BLD-TUNNEL-MODE)

View File

@@ -224,7 +224,6 @@ VisualsConfig VisualsLoader::load(const std::string& path)
cfg.overlays.selectionRect = parseColor(requireString(ov, "selection_rect", "overlays"), "overlays.selection_rect");
cfg.overlays.tileHighlight = parseColor(requireString(ov, "tile_highlight", "overlays"), "overlays.tile_highlight");
cfg.overlays.selectedOutline = parseColor(requireString(ov, "selected_outline", "overlays"), "overlays.selected_outline");
cfg.overlays.copyConfig = parseColor(requireString(ov, "copy_config", "overlays"), "overlays.copy_config");
cfg.overlays.lockedAsteroid = parseColor(requireString(ov, "locked_asteroid", "overlays"), "overlays.locked_asteroid");
cfg.overlays.modalDim = parseColor(requireString(ov, "modal_dim", "overlays"), "overlays.modal_dim");
cfg.overlays.tunnelPreview = parseColor(requireString(ov, "tunnel_preview", "overlays"), "overlays.tunnel_preview");

View File

@@ -129,7 +129,6 @@ void WorldRenderer::render(QPainter& painter, const WorldCoordinates& coordinate
// into the port and stay visible while crossing directly between two touching
// buildings (REQ-MAT-OUTPUT-EMERGE, REQ-MAT-INPUT-INTAKE, REQ-MAT-DIRECT-COUPLE).
drawPortItems(painter, coordinates, frame);
drawCopyConfigFeedback(painter, coordinates, frame);
drawStations(painter, coordinates, frame);
drawBeltItems(painter, coordinates, frame);
drawDebris(painter, coordinates, frame);
@@ -505,44 +504,6 @@ void WorldRenderer::drawSelectionHighlights(QPainter& painter, const WorldCoordi
}
}
void WorldRenderer::drawCopyConfigFeedback(QPainter& painter, const WorldCoordinates& coordinates,
const WorldRenderFrame& frame)
{
const QColor color = m_visuals.overlays.copyConfig;
// Eligible-target tint: while a configuration is cached, every same-type
// building and site (the source included) is a valid paste target and is
// washed in the copy-settings color (REQ-BLD-COPY-CONFIG-FEEDBACK).
if (frame.copiedConfig.has_value())
{
painter.setPen(Qt::NoPen);
painter.setBrush(color);
const BuildingType type = frame.copiedConfig->type;
for (const Building& b : getAllBuildings(m_sim.getFactoryState()))
{
if (b.type != type) { continue; }
const std::optional<QRectF> rect = footprintWidgetRect(coordinates, b.id);
if (rect.has_value()) { painter.drawRect(*rect); }
}
for (const ConstructionSite& s : getAllSites(m_sim.getFactoryState()))
{
if (s.type != type) { continue; }
const std::optional<QRectF> rect = footprintWidgetRect(coordinates, s.id);
if (rect.has_value()) { painter.drawRect(*rect); }
}
}
// Copy / paste flashes: a brief outline in the same color, drawn like the
// selection outline (REQ-BLD-COPY-CONFIG-FEEDBACK).
painter.setPen(QPen(color, 2));
painter.setBrush(Qt::NoBrush);
for (const CopyConfigFlash& flash : frame.copyConfigFlashes)
{
const std::optional<QRectF> rect = footprintWidgetRect(coordinates, flash.id);
if (rect.has_value()) { painter.drawRect(rect->adjusted(-1, -1, 1, 1)); }
}
}
void WorldRenderer::drawPortItems(QPainter& painter, const WorldCoordinates& coordinates,
const WorldRenderFrame& /*frame*/)
{

View File

@@ -14,7 +14,6 @@
#include "BeamFiredEvent.h"
#include "BuildModeController.h"
#include "BuildingConfig.h"
#include "BuildingId.h"
#include "BuildingType.h"
#include "Rotation.h"
@@ -36,31 +35,19 @@ struct ActiveBeam
QVector2D targetOffset;
};
// Brief outline flash shown on a building when settings are copied from it or
// pasted onto it (REQ-BLD-COPY-CONFIG-FEEDBACK). remainingMs counts down in
// wall-clock time so the flash plays at a fixed length regardless of game speed
// (and while paused).
struct CopyConfigFlash
{
BuildingId id;
qint64 remainingMs;
};
// Everything the renderer draws that the simulation does not know about: what the
// player has selected, which build mode is active, and the other transient bits of
// interaction state the view owns. Assembled fresh each frame and passed by
// reference, so the renderer holds no copy that could go stale.
struct WorldRenderFrame
{
const SelectionController& selection;
const BuildModeController& buildMode;
const std::vector<ActiveBeam>& beams;
const std::optional<BuildingConfig>& copiedConfig;
const std::vector<CopyConfigFlash>& copyConfigFlashes;
bool isBoxSelecting;
QPoint boxStartTile;
QPoint boxCurrentTile;
bool isDebugDrawEnabled;
const SelectionController& selection;
const BuildModeController& buildMode;
const std::vector<ActiveBeam>& beams;
bool isBoxSelecting;
QPoint boxStartTile;
QPoint boxCurrentTile;
bool isDebugDrawEnabled;
};
// Draws the game world: terrain, buildings, items, ships, effects and the build
@@ -94,8 +81,6 @@ private:
const WorldRenderFrame& frame);
void drawSelectionHighlights(QPainter& painter, const WorldCoordinates& coordinates,
const WorldRenderFrame& frame);
void drawCopyConfigFeedback(QPainter& painter, const WorldCoordinates& coordinates,
const WorldRenderFrame& frame);
void drawPortItems(QPainter& painter, const WorldCoordinates& coordinates,
const WorldRenderFrame& frame);
void drawStations(QPainter& painter, const WorldCoordinates& coordinates,
@@ -145,8 +130,7 @@ private:
const QColor& fill) const;
// Widget-space rectangle covering a building or construction site's footprint,
// or nullopt if the id resolves to neither. Shared by the selection highlight
// and the copy-settings feedback (REQ-BLD-COPY-CONFIG-FEEDBACK).
// or nullopt if the id resolves to neither. Used by the selection highlight.
std::optional<QRectF> footprintWidgetRect(const WorldCoordinates& coordinates,
BuildingId id) const;