Files
dota_factory/src/ui/InputMapper.cpp

226 lines
8.5 KiB
C++

#include "InputMapper.h"
#include <memory>
#include <optional>
#include <QKeyEvent>
#include "BlueprintSaveRequestedEvent.h"
#include "BlueprintSelectionRequestedEvent.h"
#include "BuildHotkeyPressedEvent.h"
#include "BuildingType.h"
#include "ControlAction.h"
#include "DebugDrawToggleRequestedEvent.h"
#include "EscapeMenuRequestedEvent.h"
#include "EventManager.h"
#include "GhostRotationRequestedEvent.h"
#include "ModeCancelRequestedEvent.h"
#include "PanDirectionChangedEvent.h"
#include "PauseToggleRequestedEvent.h"
#include "SpeedStepRequestedEvent.h"
#include "TemporaryBlueprintCaptureRequestedEvent.h"
#include "TemporaryBlueprintPlaceRequestedEvent.h"
#include "TracePrintRequestedEvent.h"
namespace
{
// The building type a number-key build hotkey selects, or nullopt for the digits
// that are unbound (REQ-UI-HOTKEYS). Plain digits pick the transport buildings,
// Shift+digit the production ones.
std::optional<BuildingType> buildHotkeyType(int digit, bool shiftHeld)
{
if (!shiftHeld)
{
switch (digit)
{
case 1: return BuildingType::Belt;
case 2: return BuildingType::Splitter;
case 3: return BuildingType::TunnelEntry; // unified tunnel mode (REQ-BLD-TUNNEL-MODE); 4 unused
}
return std::nullopt;
}
switch (digit)
{
case 1: return BuildingType::Miner;
case 2: return BuildingType::Smelter;
case 3: return BuildingType::Assembler;
case 4: return BuildingType::Shipyard;
case 5: return BuildingType::SalvageBay;
case 6: return BuildingType::ReprocessingPlant;
}
return std::nullopt;
}
} // namespace
QString InputMapper::getBuildHotkeyLabel(BuildingType type)
{
// Searched out of the binding table above rather than spelled out a second time,
// so a badge can never claim a key the handler does not act on. The shift glyph
// is written as a code point because the sources are not guaranteed to be read
// as UTF-8 by every compiler this builds with. A plain stroke arrow rather than
// U+21E7 UPWARDS WHITE ARROW: the standard UI fonts do not all carry the outlined
// shift glyph, and the substitute they fall back to is unreadable at badge size.
const QChar shiftGlyph(0x2191); // U+2191 UPWARDS ARROW
for (int digit = 1; digit <= 9; ++digit)
{
if (buildHotkeyType(digit, false) == type)
{
return QString::number(digit);
}
if (buildHotkeyType(digit, true) == type)
{
return shiftGlyph + QString::number(digit);
}
}
return QString();
}
bool InputMapper::handleKeyPress(QKeyEvent* event, const ControlContext& context)
{
// Auto-repeat says nothing new about which keys are down, and a held action is
// already held.
if (event->isAutoRepeat()) { return false; }
// Number-key build-mode hotkeys (REQ-UI-HOTKEYS). nativeVirtualKey gives the
// physical digit independent of keyboard layout and Shift (with Shift held, key()
// for the number row can arrive as Key_Exclam etc.). VK_1..VK_9 = 0x31..0x39.
// Not part of the ControlAction table: these are advertised on the build buttons
// rather than in the controls panel, and getBuildHotkeyLabel already reads the
// same binding table this does (REQ-UI-CONTROLS-ACCURACY).
const quint32 virtualKey = event->nativeVirtualKey();
if (virtualKey >= 0x31 && virtualKey <= 0x39)
{
const std::optional<BuildingType> type = buildHotkeyType(
static_cast<int>(virtualKey - 0x30),
(event->modifiers() & Qt::ShiftModifier) != 0);
if (type.has_value())
{
EventManager::getInstance()->sendEventImmediately(
std::make_shared<BuildHotkeyPressedEvent>(*type));
return true;
}
}
// Development controls, deliberately outside the table so they are never offered
// to the player (REQ-UI-CONTROLS-ACCURACY).
switch (event->key())
{
case Qt::Key_F3:
EventManager::getInstance()->sendEventImmediately(
std::make_shared<DebugDrawToggleRequestedEvent>());
return true;
case Qt::Key_F4:
EventManager::getInstance()->addEvent(
std::make_shared<TracePrintRequestedEvent>());
return true;
default:
break;
}
// Everything else: the key names an action, the action names an event. Which key
// is bound to what, and whether it does anything in this situation, are both the
// table's business -- this switch only knows what each action means
// (REQ-UI-HOTKEYS, REQ-UI-CONTROLS-ACCURACY).
switch (resolveKeyAction(event->key(), event->modifiers(), context))
{
case ControlAction::Move:
// A parameter of the action rather than an action of its own: the table binds
// both keys to Move and the direction is read off the key here, as the rotation
// direction and the build hotkey's digit are.
if (event->key() == Qt::Key_A) { m_panLeftHeld = true; }
else { m_panRightHeld = true; }
updatePanDirection();
return true;
case ControlAction::GameSpeed:
EventManager::getInstance()->sendEventImmediately(
std::make_shared<SpeedStepRequestedEvent>(event->key() == Qt::Key_W ? +1 : -1));
return true;
case ControlAction::TogglePause:
EventManager::getInstance()->sendEventImmediately(
std::make_shared<PauseToggleRequestedEvent>());
return true;
case ControlAction::Rotate:
// Shift reverses the rotation direction (REQ-BLD-ROTATE).
EventManager::getInstance()->sendEventImmediately(
std::make_shared<GhostRotationRequestedEvent>(
(event->modifiers() & Qt::ShiftModifier) != 0));
return true;
case ControlAction::EnterDeconstruct:
case ControlAction::ExitMode:
EventManager::getInstance()->sendEventImmediately(
std::make_shared<ModeCancelRequestedEvent>());
return true;
case ControlAction::CopyTemporary:
// The BlueprintLibrary owns the selection and blueprint-capture logic; it drives
// placement mode from there (REQ-UI-BLUEPRINT-TEMP).
EventManager::getInstance()->sendEventImmediately(
std::make_shared<TemporaryBlueprintCaptureRequestedEvent>());
return true;
case ControlAction::PasteTemporary:
EventManager::getInstance()->sendEventImmediately(
std::make_shared<TemporaryBlueprintPlaceRequestedEvent>());
return true;
case ControlAction::CreateBlueprint:
// Decided by MainWindow, the only widget that can pause the game and dim the
// window for a modal.
EventManager::getInstance()->sendEventImmediately(
std::make_shared<BlueprintSaveRequestedEvent>());
return true;
case ControlAction::OpenBlueprints:
EventManager::getInstance()->sendEventImmediately(
std::make_shared<BlueprintSelectionRequestedEvent>());
return true;
case ControlAction::OpenMenu:
EventManager::getInstance()->sendEventImmediately(
std::make_shared<EscapeMenuRequestedEvent>());
return true;
default:
// Either nothing is bound to the key here, or what is bound is a mouse gesture
// the view handles. Unconsumed, so ordinary Qt shortcuts keep working.
return false;
}
}
bool InputMapper::handleKeyRelease(QKeyEvent* event, const ControlContext& context)
{
if (event->isAutoRepeat()) { return false; }
// Only held actions have a release worth acting on. Resolved through the table
// rather than matched against A and D directly, so the keys stay rebindable in one
// place rather than two.
if (resolveKeyAction(event->key(), event->modifiers(), context) != ControlAction::Move)
{
return false;
}
if (event->key() == Qt::Key_A) { m_panLeftHeld = false; }
else { m_panRightHeld = false; }
updatePanDirection();
return true;
}
void InputMapper::releaseAll()
{
m_panLeftHeld = false;
m_panRightHeld = false;
updatePanDirection();
}
void InputMapper::updatePanDirection()
{
// Holding both keys cancels out rather than favouring one.
PanDirection direction = PanDirection::None;
if (m_panLeftHeld != m_panRightHeld)
{
direction = m_panLeftHeld ? PanDirection::Left : PanDirection::Right;
}
if (direction == m_panDirection) { return; }
m_panDirection = direction;
EventManager::getInstance()->sendEventImmediately(
std::make_shared<PanDirectionChangedEvent>(direction));
}