resolve mouse gestures through the action table too

The panel has to say what the left button does right now -- "Place" or "Apply
settings", "Select" or "Toggle deconstruct" -- and that answer lived in the
if-chain at the top of mousePressEvent, where the panel could not reach it.
So the chain becomes a switch on the resolved action, and the panel will read
the same resolver. Which branch runs is now decided in one place; what each
branch does is untouched, drag state machines and all.

Right-click gets the clearest win: cancel-the-drag versus leave-the-mode was
two nested conditions inspecting belt state, and is now the two actions the
table already distinguishes for the panel's sake.

Ctrl variants fall back to the plain gesture wherever nothing claims them,
which is what keeps Ctrl+click placing a building in builder mode and Ctrl+drag
deconstructing an area -- the modifier means something only where an action
says it does, rather than every handler re-deciding whether to ignore it.

Behaviour is unchanged. Full suite passes; the app runs clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
This commit is contained in:
2026-08-07 17:24:25 +02:00
parent 77bbd58d02
commit 3e79b09fec

View File

@@ -74,6 +74,23 @@
namespace namespace
{ {
// The action a left-button gesture triggers, with the Ctrl variant falling back to the
// plain one wherever nothing is bound to it. That fallback is what keeps Ctrl+click
// placing a building in builder mode and Ctrl+drag deconstructing an area: the modifier
// only means something where an action claims it (REQ-UI-CONTROLS-CONTENT).
ControlAction resolveLeftGesture(bool controlHeld, bool isDrag,
const ControlContext& context)
{
if (controlHeld)
{
const ControlAction action = resolveMouseAction(
isDrag ? MouseBinding::CtrlLeftDrag : MouseBinding::CtrlLeftClick, context);
if (action != ControlAction::None) { return action; }
}
return resolveMouseAction(isDrag ? MouseBinding::LeftDrag : MouseBinding::LeftClick,
context);
}
// Keep only the filter entries whose item type is currently unlocked // Keep only the filter entries whose item type is currently unlocked
// (REQ-LOCK-UI-BLUEPRINT). An empty result means "accept all". // (REQ-LOCK-UI-BLUEPRINT). An empty result means "accept all".
std::vector<ItemType> filterUnlockedItems(const std::vector<ItemType>& filter, std::vector<ItemType> filterUnlockedItems(const std::vector<ItemType>& filter,
@@ -1142,33 +1159,47 @@ ControlContext GameWorldView::getControlContext() const
void GameWorldView::mousePressEvent(QMouseEvent* event) void GameWorldView::mousePressEvent(QMouseEvent* event)
{ {
const WorldCoordinates coordinates = getCoordinates(); const WorldCoordinates coordinates = getCoordinates();
const ControlContext context = getControlContext();
if (event->button() != Qt::LeftButton) if (event->button() != Qt::LeftButton)
{ {
if (event->button() == Qt::RightButton) if (event->button() == Qt::RightButton)
{ {
if (m_buildMode.isBuilderMode() && m_buildMode.isDraggingBelt()) switch (resolveMouseAction(MouseBinding::RightClick, context))
{ {
// Cancel the in-progress belt drag without placing anything; case ControlAction::CancelBeltLine:
// stay in belt builder mode (REQ-BLD-BELT-DRAG). // Drop the in-progress path without placing anything; belt builder
// mode stays active (REQ-BLD-BELT-DRAG).
m_buildMode.cancelBeltDrag(); m_buildMode.cancelBeltDrag();
} break;
else if (m_buildMode.getMode() != BuildMode::None) case ControlAction::ExitMode:
{
m_buildMode.exitCurrentMode(); m_buildMode.exitCurrentMode();
break;
default:
break;
} }
} }
return; return;
} }
const QPoint tile = coordinates.widgetToTile(event->pos()); const QPoint tile = coordinates.widgetToTile(event->pos());
const bool controlHeld = (event->modifiers() & Qt::ControlModifier) != 0;
if (m_buildMode.isBuilderMode()) // A press begins the click gesture; whether it turns out to be a drag is settled on
// release, where the drag binding is resolved instead.
switch (resolveLeftGesture(controlHeld, /*isDrag*/ false, context))
{ {
if (m_buildMode.getBuilderType() == BuildingType::Belt) case ControlAction::Place:
case ControlAction::ApplySettings:
if (m_buildMode.isBlueprintMode())
{ {
// Deferred placement: start the drag and show the path ghost; nothing placeBlueprintAtTile(tile);
// is placed until release (REQ-BLD-BELT-DRAG). }
else if (m_buildMode.getBuilderType() == BuildingType::Belt)
{
// Belts place by dragging, so the press only anchors the path and shows its
// ghost; nothing is placed until release, and a plain click is the one-tile
// case (REQ-BLD-BELT-DRAG).
m_buildMode.beginBeltDrag(tile); m_buildMode.beginBeltDrag(tile);
m_cursorWorldPos = coordinates.widgetToWorld(event->pos()); m_cursorWorldPos = coordinates.widgetToWorld(event->pos());
recomputeBeltDragPath(tile); recomputeBeltDragPath(tile);
@@ -1177,27 +1208,22 @@ void GameWorldView::mousePressEvent(QMouseEvent* event)
{ {
placeAtTile(tile); placeAtTile(tile);
} }
} break;
else if (m_buildMode.isBlueprintMode())
{ case ControlAction::ToggleDeconstruct:
placeBlueprintAtTile(tile);
}
else if (m_buildMode.isDeconstructMode())
{
// Start a deconstruct box drag; a plain click resolves as a 1x1 box on // Start a deconstruct box drag; a plain click resolves as a 1x1 box on
// release (REQ-BLD-DECONSTRUCT-CLICK, REQ-BLD-DECONSTRUCT-BOX). // release (REQ-BLD-DECONSTRUCT-CLICK, REQ-BLD-DECONSTRUCT-BOX).
m_boxSelecting = true; m_boxSelecting = true;
m_boxStartTile = tile; m_boxStartTile = tile;
m_boxCurrentTile = tile; m_boxCurrentTile = tile;
} break;
else
{
const bool ctrl = (event->modifiers() & Qt::ControlModifier) != 0;
case ControlAction::Select:
case ControlAction::AddToSelection:
// Only a click that hit nothing starts a box drag. Starting one on a hit // Only a click that hit nothing starts a box drag. Starting one on a hit
// would re-resolve the same object as a 1x1 box on release and undo the // would re-resolve the same object as a 1x1 box on release and undo the
// click: a Ctrl+click would toggle the building off, then straight back on. // click: a Ctrl+click would toggle the building off, then straight back on.
if (!selectAtPoint(tile, coordinates.widgetToWorld(event->pos()), ctrl)) if (!selectAtPoint(tile, coordinates.widgetToWorld(event->pos()), controlHeld))
{ {
// selectAtPoint has already cleared the selection unless Ctrl is // selectAtPoint has already cleared the selection unless Ctrl is
// preserving it for an additive drag. // preserving it for an additive drag.
@@ -1205,6 +1231,10 @@ void GameWorldView::mousePressEvent(QMouseEvent* event)
m_boxStartTile = tile; m_boxStartTile = tile;
m_boxCurrentTile = tile; m_boxCurrentTile = tile;
} }
break;
default:
break;
} }
} }
@@ -1347,7 +1377,11 @@ void GameWorldView::mouseReleaseEvent(QMouseEvent* event)
const std::vector<BuildingId> boxIds = const std::vector<BuildingId> boxIds =
buildingsInBox(m_sim->getFactoryState(), m_boxStartTile, m_boxCurrentTile); buildingsInBox(m_sim->getFactoryState(), m_boxStartTile, m_boxCurrentTile);
if (m_buildMode.isDeconstructMode()) const bool controlHeld = (event->modifiers() & Qt::ControlModifier) != 0;
const ControlAction dragAction =
resolveLeftGesture(controlHeld, /*isDrag*/ true, getControlContext());
if (dragAction == ControlAction::DeconstructArea)
{ {
const FactoryState& factory = m_sim->getFactoryState(); const FactoryState& factory = m_sim->getFactoryState();
@@ -1409,7 +1443,9 @@ void GameWorldView::mouseReleaseEvent(QMouseEvent* event)
return; return;
} }
selectInBox((event->modifiers() & Qt::ControlModifier) != 0); // A Ctrl box adds and never deselects, where a plain one replaces
// (REQ-UI-MULTI-SELECT).
selectInBox(dragAction == ControlAction::AddAreaToSelection);
} }
} }