move the remaining hotkeys into the InputMapper

Space, W, S, R, Q and F3 all acted on GameWorldView state, so each needed a
request event before it could move. One event per action rather than a shared
action enum, matching how the existing request events are named and keeping
subscribers from waking on actions they do not care about.

The split is deliberate about where knowledge lives. Each event says only what
the player asked for, never how to satisfy it:

- PauseToggleRequestedEvent carries no speed; which speed to restore is
  remembered by the receiver.
- SpeedStepRequestedEvent is a relative notch, because the ladder of speeds
  belongs to the receiver (unlike SpeedChangeRequestedEvent, which the speed
  buttons send with an absolute multiplier).
- ModeCancelRequestedEvent names no mode; which of builder, blueprint or
  deconstruct is active is state only the receiver has.
- DebugDrawToggleRequestedEvent is the request to flip the flag, where the
  existing DebugDrawToggledEvent is the announcement that it was flipped, so
  the flag keeps a single owner.

GameWorldView::keyPressEvent is now nothing but a forward to the mapper. Shift
release stays behind on purpose: it is the modifier of a mouse gesture
(REQ-BLD-COPY-CONFIG), not a keyboard action, and modelling it as one would
misrepresent it.

Bindings are still hard-coded, and behaviour is unchanged.

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 14:02:35 +02:00
parent ed84e44428
commit 71c07531f0
9 changed files with 160 additions and 38 deletions

View File

@@ -17,6 +17,11 @@ SET(HDRS
${CMAKE_CURRENT_SOURCE_DIR}/BlueprintModeExitedEvent.h ${CMAKE_CURRENT_SOURCE_DIR}/BlueprintModeExitedEvent.h
${CMAKE_CURRENT_SOURCE_DIR}/EscapeMenuRequestedEvent.h ${CMAKE_CURRENT_SOURCE_DIR}/EscapeMenuRequestedEvent.h
${CMAKE_CURRENT_SOURCE_DIR}/PanDirectionChangedEvent.h ${CMAKE_CURRENT_SOURCE_DIR}/PanDirectionChangedEvent.h
${CMAKE_CURRENT_SOURCE_DIR}/PauseToggleRequestedEvent.h
${CMAKE_CURRENT_SOURCE_DIR}/SpeedStepRequestedEvent.h
${CMAKE_CURRENT_SOURCE_DIR}/GhostRotationRequestedEvent.h
${CMAKE_CURRENT_SOURCE_DIR}/ModeCancelRequestedEvent.h
${CMAKE_CURRENT_SOURCE_DIR}/DebugDrawToggleRequestedEvent.h
${CMAKE_CURRENT_SOURCE_DIR}/DeconstructModeChangedEvent.h ${CMAKE_CURRENT_SOURCE_DIR}/DeconstructModeChangedEvent.h
${CMAKE_CURRENT_SOURCE_DIR}/BuildingTypeSelectedEvent.h ${CMAKE_CURRENT_SOURCE_DIR}/BuildingTypeSelectedEvent.h
${CMAKE_CURRENT_SOURCE_DIR}/BuildHotkeyPressedEvent.h ${CMAKE_CURRENT_SOURCE_DIR}/BuildHotkeyPressedEvent.h

View File

@@ -0,0 +1,12 @@
#pragma once
#include "Event.h"
// The player asked to toggle the debug overlays (REQ-UI-HOTKEYS). The request to
// flip the flag; DebugDrawToggledEvent is the announcement that it was flipped and
// what it now is. Splitting the two keeps the flag itself in one owner.
class DebugDrawToggleRequestedEvent : public Event
{
public:
DebugDrawToggleRequestedEvent() = default;
};

View File

@@ -0,0 +1,13 @@
#pragma once
#include "Event.h"
// The player asked to rotate the placement ghost (REQ-BLD-ROTATE, REQ-UI-HOTKEYS).
// Sent whether or not a builder or blueprint mode is actually active; deciding
// there is nothing to rotate is the receiver's job.
class GhostRotationRequestedEvent : public Event
{
public:
explicit GhostRotationRequestedEvent(bool clockwise) : clockwise(clockwise) {}
const bool clockwise;
};

View File

@@ -0,0 +1,13 @@
#pragma once
#include "Event.h"
// The player pressed the one "get me out of the current mode" key (REQ-UI-HOTKEYS).
// Intentionally says only that, not which mode to leave: which of builder,
// blueprint placement, or deconstruct is active — and that the key falls through to
// entering deconstruct mode when none of them is — is state only the receiver has.
class ModeCancelRequestedEvent : public Event
{
public:
ModeCancelRequestedEvent() = default;
};

View File

@@ -0,0 +1,12 @@
#pragma once
#include "Event.h"
// The player asked to pause or unpause (REQ-UI-HOTKEYS). Carries no speed: which
// speed to restore on unpause is the receiver's business, since it is the one that
// remembers what was running before the pause.
class PauseToggleRequestedEvent : public Event
{
public:
PauseToggleRequestedEvent() = default;
};

View File

@@ -0,0 +1,14 @@
#pragma once
#include "Event.h"
// The player asked to step the game speed one notch (REQ-UI-HOTKEYS): +1 faster,
// -1 slower. A relative step rather than a target speed, because the ladder of
// available speeds belongs to the receiver — contrast SpeedChangeRequestedEvent,
// which names an absolute multiplier and is what the speed buttons send.
class SpeedStepRequestedEvent : public Event
{
public:
explicit SpeedStepRequestedEvent(int delta) : delta(delta) {}
const int delta;
};

View File

@@ -2311,46 +2311,12 @@ void GameWorldView::keyPressEvent(QKeyEvent* event)
return; return;
} }
// Keys the input mapper owns are turned into actions and published from there // Keys are turned into actions and published by the input mapper
// (REQ-UI-HOTKEYS); this widget reacts to those as an ordinary subscriber. // (REQ-UI-HOTKEYS); this widget reacts to those as an ordinary subscriber, so
// nothing is handled here directly.
if (m_inputMapper.handleKeyPress(event)) { return; } if (m_inputMapper.handleKeyPress(event)) { return; }
switch (event->key())
{
case Qt::Key_Space:
if (m_gameSpeedMultiplier > 0.0)
{
m_prevNonZeroSpeed = m_gameSpeedMultiplier;
setGameSpeed(0.0);
}
else
{
setGameSpeed(m_prevNonZeroSpeed);
}
break;
case Qt::Key_W:
stepSpeed(+1);
break;
case Qt::Key_S:
stepSpeed(-1);
break;
case Qt::Key_R:
rotateGhost(event->modifiers() & Qt::ShiftModifier);
break;
case Qt::Key_Q:
if (m_builderType.has_value()) { exitBuilderMode(); }
else if (m_blueprintMode.has_value()) { exitBlueprintMode(); }
else { toggleDeconstructMode(); }
break;
case Qt::Key_F3:
m_debugDraw = !m_debugDraw;
EventManager::getInstance()->sendEventImmediately(
std::make_shared<DebugDrawToggledEvent>(m_debugDraw));
break;
default:
QOpenGLWidget::keyPressEvent(event); QOpenGLWidget::keyPressEvent(event);
break;
}
} }
void GameWorldView::keyReleaseEvent(QKeyEvent* event) void GameWorldView::keyReleaseEvent(QKeyEvent* event)
@@ -3113,6 +3079,45 @@ void GameWorldView::handleEvent(std::shared_ptr<const PanDirectionChangedEvent>
m_panDirection = event->direction; m_panDirection = event->direction;
} }
void GameWorldView::handleEvent(std::shared_ptr<const PauseToggleRequestedEvent> /*event*/)
{
if (m_gameSpeedMultiplier > 0.0)
{
m_prevNonZeroSpeed = m_gameSpeedMultiplier;
setGameSpeed(0.0);
}
else
{
setGameSpeed(m_prevNonZeroSpeed);
}
}
void GameWorldView::handleEvent(std::shared_ptr<const SpeedStepRequestedEvent> event)
{
stepSpeed(event->delta);
}
void GameWorldView::handleEvent(std::shared_ptr<const GhostRotationRequestedEvent> event)
{
rotateGhost(event->clockwise);
}
void GameWorldView::handleEvent(std::shared_ptr<const ModeCancelRequestedEvent> /*event*/)
{
// One key backs out of whichever mode is active, and enters deconstruct mode
// when none is (REQ-UI-HOTKEYS).
if (m_builderType.has_value()) { exitBuilderMode(); }
else if (m_blueprintMode.has_value()) { exitBlueprintMode(); }
else { toggleDeconstructMode(); }
}
void GameWorldView::handleEvent(std::shared_ptr<const DebugDrawToggleRequestedEvent> /*event*/)
{
m_debugDraw = !m_debugDraw;
EventManager::getInstance()->sendEventImmediately(
std::make_shared<DebugDrawToggledEvent>(m_debugDraw));
}
void GameWorldView::handleEvent(std::shared_ptr<const CommandRequestedEvent> event) void GameWorldView::handleEvent(std::shared_ptr<const CommandRequestedEvent> event)
{ {
// Other widgets (MainWindow, SelectedBuildingPanel) request commands via this // Other widgets (MainWindow, SelectedBuildingPanel) request commands via this

View File

@@ -31,8 +31,13 @@
#include "EventHandler.h" #include "EventHandler.h"
#include "ExitBlueprintModeRequestedEvent.h" #include "ExitBlueprintModeRequestedEvent.h"
#include "ExitBuilderModeRequestedEvent.h" #include "ExitBuilderModeRequestedEvent.h"
#include "DebugDrawToggleRequestedEvent.h"
#include "GhostRotationRequestedEvent.h"
#include "InputMapper.h" #include "InputMapper.h"
#include "ModeCancelRequestedEvent.h"
#include "PanDirectionChangedEvent.h" #include "PanDirectionChangedEvent.h"
#include "PauseToggleRequestedEvent.h"
#include "SpeedStepRequestedEvent.h"
#include "DebugDrawToggledEvent.h" #include "DebugDrawToggledEvent.h"
#include "ArtifactCountChangedEvent.h" #include "ArtifactCountChangedEvent.h"
#include "BeamFiredEvent.h" #include "BeamFiredEvent.h"
@@ -83,6 +88,11 @@ class GameWorldView : public QOpenGLWidget,
ExitBlueprintModeRequestedEvent, ExitBlueprintModeRequestedEvent,
SpeedChangeRequestedEvent, SpeedChangeRequestedEvent,
PanDirectionChangedEvent, PanDirectionChangedEvent,
PauseToggleRequestedEvent,
SpeedStepRequestedEvent,
GhostRotationRequestedEvent,
ModeCancelRequestedEvent,
DebugDrawToggleRequestedEvent,
CommandRequestedEvent> CommandRequestedEvent>
{ {
Q_OBJECT Q_OBJECT
@@ -105,6 +115,8 @@ public:
protected: protected:
void initializeGL() override; void initializeGL() override;
void paintGL() override; void paintGL() override;
// Only forwards to the input mapper; every key this widget acts on reaches it
// as a published action instead (REQ-UI-HOTKEYS).
void keyPressEvent(QKeyEvent* event) override; void keyPressEvent(QKeyEvent* event) override;
void keyReleaseEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override;
// Key-up never arrives for a key that was still held when focus moved away — // Key-up never arrives for a key that was still held when focus moved away —
@@ -127,6 +139,11 @@ private:
void handleEvent(std::shared_ptr<const ExitBlueprintModeRequestedEvent> 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 SpeedChangeRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const PanDirectionChangedEvent> event) override; void handleEvent(std::shared_ptr<const PanDirectionChangedEvent> event) override;
void handleEvent(std::shared_ptr<const PauseToggleRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const SpeedStepRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const GhostRotationRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const ModeCancelRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const DebugDrawToggleRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const CommandRequestedEvent> event) override; void handleEvent(std::shared_ptr<const CommandRequestedEvent> event) override;
// Enqueue a sim command onto the CommandManager (the single mutation path). // Enqueue a sim command onto the CommandManager (the single mutation path).

View File

@@ -7,9 +7,14 @@
#include "BuildHotkeyPressedEvent.h" #include "BuildHotkeyPressedEvent.h"
#include "BuildingType.h" #include "BuildingType.h"
#include "DebugDrawToggleRequestedEvent.h"
#include "EscapeMenuRequestedEvent.h" #include "EscapeMenuRequestedEvent.h"
#include "EventManager.h" #include "EventManager.h"
#include "GhostRotationRequestedEvent.h"
#include "ModeCancelRequestedEvent.h"
#include "PanDirectionChangedEvent.h" #include "PanDirectionChangedEvent.h"
#include "PauseToggleRequestedEvent.h"
#include "SpeedStepRequestedEvent.h"
#include "TemporaryBlueprintRequestedEvent.h" #include "TemporaryBlueprintRequestedEvent.h"
#include "TracePrintRequestedEvent.h" #include "TracePrintRequestedEvent.h"
@@ -79,6 +84,28 @@ bool InputMapper::handleKeyPress(QKeyEvent* event)
m_panRightHeld = true; m_panRightHeld = true;
updatePanDirection(); updatePanDirection();
return true; return true;
case Qt::Key_Space:
EventManager::getInstance()->sendEventImmediately(
std::make_shared<PauseToggleRequestedEvent>());
return true;
case Qt::Key_W:
EventManager::getInstance()->sendEventImmediately(
std::make_shared<SpeedStepRequestedEvent>(+1));
return true;
case Qt::Key_S:
EventManager::getInstance()->sendEventImmediately(
std::make_shared<SpeedStepRequestedEvent>(-1));
return true;
case Qt::Key_R:
// Shift reverses the rotation direction (REQ-BLD-ROTATE).
EventManager::getInstance()->sendEventImmediately(
std::make_shared<GhostRotationRequestedEvent>(
(event->modifiers() & Qt::ShiftModifier) != 0));
return true;
case Qt::Key_Q:
EventManager::getInstance()->sendEventImmediately(
std::make_shared<ModeCancelRequestedEvent>());
return true;
case Qt::Key_T: case Qt::Key_T:
// Request a temporary blueprint from the current selection (REQ-UI-BLUEPRINT-TEMP). // Request a temporary blueprint from the current selection (REQ-UI-BLUEPRINT-TEMP).
// The BlueprintPanel owns the selection and blueprint-capture logic; it decides // The BlueprintPanel owns the selection and blueprint-capture logic; it decides
@@ -86,6 +113,10 @@ bool InputMapper::handleKeyPress(QKeyEvent* event)
EventManager::getInstance()->sendEventImmediately( EventManager::getInstance()->sendEventImmediately(
std::make_shared<TemporaryBlueprintRequestedEvent>()); std::make_shared<TemporaryBlueprintRequestedEvent>());
return true; return true;
case Qt::Key_F3:
EventManager::getInstance()->sendEventImmediately(
std::make_shared<DebugDrawToggleRequestedEvent>());
return true;
case Qt::Key_Escape: case Qt::Key_Escape:
EventManager::getInstance()->sendEventImmediately( EventManager::getInstance()->sendEventImmediately(
std::make_shared<EscapeMenuRequestedEvent>()); std::make_shared<EscapeMenuRequestedEvent>());