From 68a43a7d98a9062589249b3684edbcb38026a754 Mon Sep 17 00:00:00 2001 From: mlangkabel Date: Fri, 7 Aug 2026 18:47:43 +0200 Subject: [PATCH] resolve mouse gestures through the action table too --- src/ui/GameWorldView.cpp | 84 ++++++++++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/src/ui/GameWorldView.cpp b/src/ui/GameWorldView.cpp index 222ec00..74d42c3 100644 --- a/src/ui/GameWorldView.cpp +++ b/src/ui/GameWorldView.cpp @@ -74,6 +74,23 @@ 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 // (REQ-LOCK-UI-BLUEPRINT). An empty result means "accept all". std::vector filterUnlockedItems(const std::vector& filter, @@ -1142,33 +1159,47 @@ ControlContext GameWorldView::getControlContext() const void GameWorldView::mousePressEvent(QMouseEvent* event) { const WorldCoordinates coordinates = getCoordinates(); + const ControlContext context = getControlContext(); if (event->button() != Qt::LeftButton) { 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; - // stay in belt builder mode (REQ-BLD-BELT-DRAG). + case ControlAction::CancelBeltLine: + // Drop the in-progress path without placing anything; belt builder + // mode stays active (REQ-BLD-BELT-DRAG). m_buildMode.cancelBeltDrag(); - } - else if (m_buildMode.getMode() != BuildMode::None) - { + break; + case ControlAction::ExitMode: m_buildMode.exitCurrentMode(); + break; + default: + break; } } return; } 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 - // is placed until release (REQ-BLD-BELT-DRAG). + placeBlueprintAtTile(tile); + } + 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_cursorWorldPos = coordinates.widgetToWorld(event->pos()); recomputeBeltDragPath(tile); @@ -1177,27 +1208,22 @@ void GameWorldView::mousePressEvent(QMouseEvent* event) { placeAtTile(tile); } - } - else if (m_buildMode.isBlueprintMode()) - { - placeBlueprintAtTile(tile); - } - else if (m_buildMode.isDeconstructMode()) - { + break; + + case ControlAction::ToggleDeconstruct: // Start a deconstruct box drag; a plain click resolves as a 1x1 box on // release (REQ-BLD-DECONSTRUCT-CLICK, REQ-BLD-DECONSTRUCT-BOX). m_boxSelecting = true; m_boxStartTile = tile; m_boxCurrentTile = tile; - } - else - { - const bool ctrl = (event->modifiers() & Qt::ControlModifier) != 0; + break; + case ControlAction::Select: + case ControlAction::AddToSelection: // 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 // 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 // preserving it for an additive drag. @@ -1205,6 +1231,10 @@ void GameWorldView::mousePressEvent(QMouseEvent* event) m_boxStartTile = tile; m_boxCurrentTile = tile; } + break; + + default: + break; } } @@ -1347,7 +1377,11 @@ void GameWorldView::mouseReleaseEvent(QMouseEvent* event) const std::vector boxIds = 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(); @@ -1409,7 +1443,9 @@ void GameWorldView::mouseReleaseEvent(QMouseEvent* event) 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); } }