move pan input into an InputMapper

First slice of pulling key handling out of GameWorldView. The widget no longer
holds A/D key state; the mapper owns it and publishes the resulting direction as
PanDirectionChangedEvent, which the view consumes like any other event.

The event is level-triggered on purpose — the payload is the complete current
direction, PanDirection::None included — so a receiver never reconstructs state
from edges and cannot be left panning by a missing key-up. It is also the one
place in the UI where caching an event payload is right rather than wrong: input
has no other authority to re-read from, so the mapper is the source of truth.
Both points are written down on the event, since they look like violations of
the surrounding conventions otherwise.

Holding the state in one object is what makes releaseAll() possible; restart
uses it, and it is what a focusOutEvent will call to fix the stuck-pan bug in a
follow-up.

Bindings stay hard-coded. Only the ownership moved, so behaviour is unchanged,
including both keys held cancelling out.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
This commit is contained in:
2026-08-05 13:35:23 +02:00
parent b0fffdb00f
commit 1748be57fb
7 changed files with 168 additions and 28 deletions

View File

@@ -31,6 +31,8 @@
#include "EventHandler.h"
#include "ExitBlueprintModeRequestedEvent.h"
#include "ExitBuilderModeRequestedEvent.h"
#include "InputMapper.h"
#include "PanDirectionChangedEvent.h"
#include "DebugDrawToggledEvent.h"
#include "ArtifactCountChangedEvent.h"
#include "BeamFiredEvent.h"
@@ -80,6 +82,7 @@ class GameWorldView : public QOpenGLWidget,
BlueprintPlacementRequestedEvent,
ExitBlueprintModeRequestedEvent,
SpeedChangeRequestedEvent,
PanDirectionChangedEvent,
CommandRequestedEvent>
{
Q_OBJECT
@@ -119,6 +122,7 @@ private:
void handleEvent(std::shared_ptr<const BlueprintPlacementRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const ExitBlueprintModeRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const SpeedChangeRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const PanDirectionChangedEvent> event) override;
void handleEvent(std::shared_ptr<const CommandRequestedEvent> event) override;
// Enqueue a sim command onto the CommandManager (the single mutation path).
@@ -392,13 +396,16 @@ private:
QPoint m_boxStartTile;
QPoint m_boxCurrentTile;
// Held-key state for the A / D pan controls (REQ-UI-SCROLL, REQ-UI-HOTKEYS),
// collapsed into a PanDirection for the camera each frame. Kept here rather
// than on the camera because it is key state, not view state: once controls
// are rebindable this becomes an input mapper publishing the direction, and
// the camera's interface does not change.
bool m_scrollLeft;
bool m_scrollRight;
// Interprets this widget's key events into semantic actions and publishes them
// (REQ-UI-HOTKEYS). Owned here for now because this is the widget that holds
// focus; everything it produces travels by event, so it can move to a
// window-wide owner later without touching its consumers.
InputMapper m_inputMapper;
// Latest pan direction published by the input mapper (REQ-UI-SCROLL), fed to
// the camera each frame. Cached from the event payload rather than re-read:
// input has no other source of truth (see PanDirectionChangedEvent).
PanDirection m_panDirection = PanDirection::None;
bool m_gameOverShown;
bool m_winShown;
bool m_schematicChoiceShown;