resolve keyboard shortcuts through one action table instead of a switch
This commit is contained in:
@@ -72,6 +72,7 @@ void BuildModeController::enterMode(BuildMode mode)
|
||||
std::make_shared<BuilderModeExitedEvent>());
|
||||
break;
|
||||
case BuildMode::Blueprint:
|
||||
m_hoveredGhostIsTransfer = false;
|
||||
EventManager::getInstance()->sendEventImmediately(
|
||||
std::make_shared<BlueprintModeExitedEvent>());
|
||||
break;
|
||||
@@ -249,6 +250,16 @@ void BuildModeController::setBlueprintGhostTile(QPoint tile)
|
||||
m_blueprintGhostTile = tile;
|
||||
}
|
||||
|
||||
bool BuildModeController::isHoveredGhostTransfer() const
|
||||
{
|
||||
return m_hoveredGhostIsTransfer;
|
||||
}
|
||||
|
||||
void BuildModeController::setHoveredGhostTransfer(bool transfer)
|
||||
{
|
||||
m_hoveredGhostIsTransfer = transfer;
|
||||
}
|
||||
|
||||
const std::optional<BuildingId>&
|
||||
BuildModeController::getDeconstructHoverBuildingId() const
|
||||
{
|
||||
|
||||
@@ -95,6 +95,15 @@ public:
|
||||
QPoint getBlueprintGhostTile() const;
|
||||
void setBlueprintGhostTile(QPoint tile);
|
||||
|
||||
// Whether the ghost under the cursor would hand its settings to the building
|
||||
// already there rather than place anything (REQ-UI-BLUEPRINT-TRANSFER). Classifying
|
||||
// it needs the factory state, so the caller resolves it and stores the answer here,
|
||||
// as with setGhostValidity. Kept here rather than recomputed per reader so the
|
||||
// click, the ghost's colour, and the controls panel cannot disagree about what the
|
||||
// cursor is over.
|
||||
bool isHoveredGhostTransfer() const;
|
||||
void setHoveredGhostTransfer(bool transfer);
|
||||
|
||||
// --- deconstruct mode -----------------------------------------------------
|
||||
const std::optional<BuildingId>& getDeconstructHoverBuildingId() const;
|
||||
void setDeconstructHoverBuildingId(std::optional<BuildingId> id);
|
||||
@@ -118,6 +127,7 @@ private:
|
||||
|
||||
Blueprint m_blueprint;
|
||||
QPoint m_blueprintGhostTile;
|
||||
bool m_hoveredGhostIsTransfer = false;
|
||||
|
||||
std::optional<BuildingId> m_deconstructHoverBuildingId;
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ SET(HDRS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/WorldCamera.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/SelectionController.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildModeController.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ControlAction.h
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
@@ -33,6 +34,7 @@ SET(SRCS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/WorldCamera.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/SelectionController.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildModeController.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ControlAction.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
|
||||
263
src/lib/core/ControlAction.cpp
Normal file
263
src/lib/core/ControlAction.cpp
Normal file
@@ -0,0 +1,263 @@
|
||||
#include "ControlAction.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
// A binding that a belt drag can take over. Availability is a property of the action,
|
||||
// but a binding can be claimed by a different action while a gesture is in progress:
|
||||
// right-click cancels the drag instead of leaving builder mode (REQ-BLD-BELT-DRAG), so
|
||||
// ExitMode's right-click drops out and it is left with Q alone.
|
||||
enum class BindingCondition
|
||||
{
|
||||
Always,
|
||||
NotDraggingBelt
|
||||
};
|
||||
|
||||
struct KeyBindingEntry
|
||||
{
|
||||
ControlAction action;
|
||||
int key;
|
||||
// Shown on the badge, and Ctrl additionally participates in matching. Every other
|
||||
// modifier is display only -- see resolveKeyAction.
|
||||
Qt::KeyboardModifiers modifiers;
|
||||
};
|
||||
|
||||
struct MouseBindingEntry
|
||||
{
|
||||
ControlAction action;
|
||||
MouseBinding binding;
|
||||
BindingCondition condition;
|
||||
};
|
||||
|
||||
// Resolution is first-match-wins over these tables, so an entry that must beat another
|
||||
// on the same input is listed above it -- CancelBeltLine before ExitMode on the right
|
||||
// mouse button. Everything else is disjoint by availability.
|
||||
const KeyBindingEntry KEY_BINDINGS[] = {
|
||||
{ControlAction::Move, Qt::Key_A, Qt::NoModifier},
|
||||
{ControlAction::Move, Qt::Key_D, Qt::NoModifier},
|
||||
{ControlAction::GameSpeed, Qt::Key_W, Qt::NoModifier},
|
||||
{ControlAction::GameSpeed, Qt::Key_S, Qt::NoModifier},
|
||||
{ControlAction::TogglePause, Qt::Key_Space, Qt::NoModifier},
|
||||
{ControlAction::CopyTemporary, Qt::Key_C, Qt::NoModifier},
|
||||
{ControlAction::CreateBlueprint, Qt::Key_C, Qt::ControlModifier},
|
||||
{ControlAction::PasteTemporary, Qt::Key_V, Qt::NoModifier},
|
||||
{ControlAction::OpenBlueprints, Qt::Key_V, Qt::ControlModifier},
|
||||
// One binding, two badges: Shift picks the rotation direction and is read by the
|
||||
// handler, the way a build hotkey's digit is (REQ-BLD-ROTATE). Both entries match
|
||||
// the same press, and both resolve to the same action, so listing them twice costs
|
||||
// nothing and is what puts "R" and "Shift+R" on the row.
|
||||
{ControlAction::Rotate, Qt::Key_R, Qt::NoModifier},
|
||||
{ControlAction::Rotate, Qt::Key_R, Qt::ShiftModifier},
|
||||
{ControlAction::EnterDeconstruct, Qt::Key_Q, Qt::NoModifier},
|
||||
{ControlAction::ExitMode, Qt::Key_Q, Qt::NoModifier},
|
||||
{ControlAction::OpenMenu, Qt::Key_Escape, Qt::NoModifier},
|
||||
};
|
||||
|
||||
const MouseBindingEntry MOUSE_BINDINGS[] = {
|
||||
{ControlAction::Select, MouseBinding::LeftClick, BindingCondition::Always},
|
||||
{ControlAction::Place, MouseBinding::LeftClick, BindingCondition::Always},
|
||||
{ControlAction::ApplySettings, MouseBinding::LeftClick, BindingCondition::Always},
|
||||
{ControlAction::ToggleDeconstruct, MouseBinding::LeftClick, BindingCondition::Always},
|
||||
{ControlAction::SelectArea, MouseBinding::LeftDrag, BindingCondition::Always},
|
||||
{ControlAction::PlaceBeltLine, MouseBinding::LeftDrag, BindingCondition::Always},
|
||||
{ControlAction::DeconstructArea, MouseBinding::LeftDrag, BindingCondition::Always},
|
||||
{ControlAction::AddToSelection, MouseBinding::CtrlLeftClick, BindingCondition::Always},
|
||||
{ControlAction::AddAreaToSelection, MouseBinding::CtrlLeftDrag, BindingCondition::Always},
|
||||
{ControlAction::CancelBeltLine, MouseBinding::RightClick, BindingCondition::Always},
|
||||
{ControlAction::ExitMode, MouseBinding::RightClick, BindingCondition::NotDraggingBelt},
|
||||
};
|
||||
|
||||
bool isConditionMet(BindingCondition condition, const ControlContext& context)
|
||||
{
|
||||
if (condition == BindingCondition::NotDraggingBelt) { return !context.draggingBelt; }
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isPlacementMode(const ControlContext& context)
|
||||
{
|
||||
return context.mode == BuildMode::Builder || context.mode == BuildMode::Blueprint;
|
||||
}
|
||||
|
||||
std::vector<ControlAction> filterAvailable(const std::vector<ControlAction>& actions,
|
||||
const ControlContext& context)
|
||||
{
|
||||
std::vector<ControlAction> available;
|
||||
for (ControlAction action : actions)
|
||||
{
|
||||
if (isControlActionAvailable(action, context)) { available.push_back(action); }
|
||||
}
|
||||
return available;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool isControlActionAvailable(ControlAction action, const ControlContext& context)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case ControlAction::None:
|
||||
return false;
|
||||
|
||||
case ControlAction::Move:
|
||||
case ControlAction::GameSpeed:
|
||||
case ControlAction::TogglePause:
|
||||
case ControlAction::OpenBlueprints:
|
||||
case ControlAction::OpenMenu:
|
||||
return true;
|
||||
|
||||
// Does nothing until something has been captured with C, so it is not offered
|
||||
// before then (REQ-UI-BLUEPRINT-TEMP, REQ-UI-CONTROLS-ACCURACY).
|
||||
case ControlAction::PasteTemporary:
|
||||
return context.temporaryBlueprintExists;
|
||||
|
||||
case ControlAction::Select:
|
||||
case ControlAction::SelectArea:
|
||||
case ControlAction::AddToSelection:
|
||||
case ControlAction::AddAreaToSelection:
|
||||
case ControlAction::EnterDeconstruct:
|
||||
return context.mode == BuildMode::None;
|
||||
|
||||
// Both need something a blueprint can be made of; a selection of ships or debris
|
||||
// leaves them inert (REQ-UI-HOTKEYS).
|
||||
case ControlAction::CopyTemporary:
|
||||
case ControlAction::CreateBlueprint:
|
||||
return context.mode == BuildMode::None && context.placeableBuildingSelected;
|
||||
|
||||
// Place and ApplySettings are the same click; which one it is depends on whether
|
||||
// the ghost under the cursor is a transfer target (REQ-UI-BLUEPRINT-TRANSFER).
|
||||
case ControlAction::Place:
|
||||
return isPlacementMode(context) && !context.hoveredGhostIsTransfer;
|
||||
case ControlAction::ApplySettings:
|
||||
return context.mode == BuildMode::Blueprint && context.hoveredGhostIsTransfer;
|
||||
|
||||
// The only building type placed by dragging (REQ-BLD-BELT-DRAG).
|
||||
case ControlAction::PlaceBeltLine:
|
||||
return context.mode == BuildMode::Builder
|
||||
&& context.builderType == BuildingType::Belt;
|
||||
|
||||
case ControlAction::Rotate:
|
||||
return isPlacementMode(context);
|
||||
case ControlAction::CancelBeltLine:
|
||||
return context.draggingBelt;
|
||||
case ControlAction::ExitMode:
|
||||
return context.mode != BuildMode::None;
|
||||
|
||||
case ControlAction::ToggleDeconstruct:
|
||||
case ControlAction::DeconstructArea:
|
||||
return context.mode == BuildMode::Deconstruct;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<ControlBinding> getControlActionBindings(ControlAction action,
|
||||
const ControlContext& context)
|
||||
{
|
||||
std::vector<ControlBinding> bindings;
|
||||
for (const MouseBindingEntry& entry : MOUSE_BINDINGS)
|
||||
{
|
||||
if (entry.action != action) { continue; }
|
||||
if (!isConditionMet(entry.condition, context)) { continue; }
|
||||
ControlBinding binding;
|
||||
binding.isMouse = true;
|
||||
binding.mouse = entry.binding;
|
||||
bindings.push_back(binding);
|
||||
}
|
||||
for (const KeyBindingEntry& entry : KEY_BINDINGS)
|
||||
{
|
||||
if (entry.action != action) { continue; }
|
||||
ControlBinding binding;
|
||||
binding.key = entry.key;
|
||||
binding.modifiers = entry.modifiers;
|
||||
bindings.push_back(binding);
|
||||
}
|
||||
return bindings;
|
||||
}
|
||||
|
||||
ControlContextKind getControlContextKind(const ControlContext& context)
|
||||
{
|
||||
switch (context.mode)
|
||||
{
|
||||
case BuildMode::Builder: return ControlContextKind::Build;
|
||||
case BuildMode::Blueprint: return ControlContextKind::Blueprint;
|
||||
case BuildMode::Deconstruct: return ControlContextKind::Deconstruct;
|
||||
case BuildMode::None: break;
|
||||
}
|
||||
return context.selection == ControlSelection::None ? ControlContextKind::General
|
||||
: ControlContextKind::Selection;
|
||||
}
|
||||
|
||||
std::vector<ControlAction> getContextActions(const ControlContext& context)
|
||||
{
|
||||
// The candidates of each context, in the order REQ-UI-CONTROLS-CONTENT lists them,
|
||||
// then filtered by availability.
|
||||
//
|
||||
// The General and Selection lists differ rather than one filtered list serving
|
||||
// both, because the additive-selection rows are an omission and not an
|
||||
// unavailability: Ctrl+click does work with nothing selected, it just picks the
|
||||
// object like a plain click would. "Add / remove from selection" is a row that
|
||||
// means nothing until there is a selection to add to, so it waits for one
|
||||
// (REQ-UI-CONTROLS-ACCURACY permits omitting an available binding).
|
||||
switch (getControlContextKind(context))
|
||||
{
|
||||
case ControlContextKind::Build:
|
||||
case ControlContextKind::Blueprint:
|
||||
return filterAvailable({ControlAction::Place, ControlAction::ApplySettings,
|
||||
ControlAction::PlaceBeltLine, ControlAction::Rotate,
|
||||
ControlAction::CancelBeltLine, ControlAction::ExitMode},
|
||||
context);
|
||||
case ControlContextKind::Deconstruct:
|
||||
return filterAvailable({ControlAction::ToggleDeconstruct,
|
||||
ControlAction::DeconstructArea, ControlAction::ExitMode},
|
||||
context);
|
||||
case ControlContextKind::Selection:
|
||||
return filterAvailable({ControlAction::Select, ControlAction::SelectArea,
|
||||
ControlAction::AddToSelection,
|
||||
ControlAction::AddAreaToSelection,
|
||||
ControlAction::EnterDeconstruct,
|
||||
ControlAction::CopyTemporary,
|
||||
ControlAction::CreateBlueprint},
|
||||
context);
|
||||
case ControlContextKind::General:
|
||||
break;
|
||||
}
|
||||
return filterAvailable({ControlAction::Select, ControlAction::SelectArea,
|
||||
ControlAction::EnterDeconstruct},
|
||||
context);
|
||||
}
|
||||
|
||||
std::vector<ControlAction> getAlwaysAvailableActions(const ControlContext& context)
|
||||
{
|
||||
return filterAvailable({ControlAction::Move, ControlAction::GameSpeed,
|
||||
ControlAction::TogglePause, ControlAction::PasteTemporary,
|
||||
ControlAction::OpenBlueprints, ControlAction::OpenMenu},
|
||||
context);
|
||||
}
|
||||
|
||||
ControlAction resolveKeyAction(int key, Qt::KeyboardModifiers modifiers,
|
||||
const ControlContext& context)
|
||||
{
|
||||
// Ctrl distinguishes a chord from the bare key (Ctrl+C is not C); every other
|
||||
// modifier is ignored, so Shift+A still pans and Shift+R still rotates. This is
|
||||
// what the key handler has always done, and matching modifiers exactly instead
|
||||
// would silently drop those presses.
|
||||
const bool controlHeld = (modifiers & Qt::ControlModifier) != 0;
|
||||
for (const KeyBindingEntry& entry : KEY_BINDINGS)
|
||||
{
|
||||
if (entry.key != key) { continue; }
|
||||
const bool entryNeedsControl = (entry.modifiers & Qt::ControlModifier) != 0;
|
||||
if (entryNeedsControl != controlHeld) { continue; }
|
||||
if (isControlActionAvailable(entry.action, context)) { return entry.action; }
|
||||
}
|
||||
return ControlAction::None;
|
||||
}
|
||||
|
||||
ControlAction resolveMouseAction(MouseBinding binding, const ControlContext& context)
|
||||
{
|
||||
for (const MouseBindingEntry& entry : MOUSE_BINDINGS)
|
||||
{
|
||||
if (entry.binding != binding) { continue; }
|
||||
if (!isConditionMet(entry.condition, context)) { continue; }
|
||||
if (isControlActionAvailable(entry.action, context)) { return entry.action; }
|
||||
}
|
||||
return ControlAction::None;
|
||||
}
|
||||
164
src/lib/core/ControlAction.h
Normal file
164
src/lib/core/ControlAction.h
Normal file
@@ -0,0 +1,164 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <Qt>
|
||||
|
||||
#include "BuildModeController.h"
|
||||
#include "BuildingType.h"
|
||||
|
||||
// The single statement of what the player can do right now, and which input does it
|
||||
// (REQ-UI-CONTROLS-CONTENT, REQ-UI-CONTROLS-ACCURACY).
|
||||
//
|
||||
// This file declares actions; it never performs them and it never names them. It knows
|
||||
// an action's bindings and the situations in which it does something -- nothing about
|
||||
// the simulation, the widgets, the events an action ends up firing, or the words shown
|
||||
// to the player. Display text lives in ui/ControlActionText.h, which formats the
|
||||
// bindings this hands it, so a badge is derived from the real binding rather than
|
||||
// typed beside it.
|
||||
//
|
||||
// Three readers, all of them consuming this and none of them extending it:
|
||||
//
|
||||
// * ControlsPanel calls getContextActions()/getAlwaysAvailableActions() and draws them.
|
||||
// * InputMapper calls resolveKeyAction() and fires the event the action stands for.
|
||||
// * GameWorldView calls resolveMouseAction() and runs the branch it already ran.
|
||||
//
|
||||
// Two bindings are deliberately absent. Build hotkeys (REQ-UI-HOTKEYS) are advertised
|
||||
// on the build buttons instead of in the panel, and InputMapper::getBuildHotkeyLabel
|
||||
// already derives their badges from the same table the handler switches on, so they
|
||||
// have no drift to fix. F3/F4 are development controls and appear nowhere
|
||||
// (REQ-UI-CONTROLS-ACCURACY).
|
||||
//
|
||||
// When bindings become player-configurable, only the binding tables in the .cpp turn
|
||||
// from hard-coded data into loaded data. The actions, the availability rules, the
|
||||
// panel, and every handler are unaffected.
|
||||
enum class ControlAction
|
||||
{
|
||||
None, // no action is bound to the queried input in the queried context
|
||||
|
||||
// Always available (REQ-UI-CONTROLS-CONTENT).
|
||||
Move,
|
||||
GameSpeed,
|
||||
TogglePause,
|
||||
PasteTemporary,
|
||||
OpenBlueprints,
|
||||
OpenMenu,
|
||||
|
||||
// No build mode active.
|
||||
Select,
|
||||
SelectArea,
|
||||
AddToSelection,
|
||||
AddAreaToSelection,
|
||||
EnterDeconstruct,
|
||||
|
||||
// No build mode active, with something selected.
|
||||
CopyTemporary,
|
||||
CreateBlueprint,
|
||||
|
||||
// Builder and blueprint placement mode.
|
||||
Place,
|
||||
ApplySettings,
|
||||
PlaceBeltLine,
|
||||
Rotate,
|
||||
CancelBeltLine,
|
||||
ExitMode,
|
||||
|
||||
// Deconstruct mode.
|
||||
ToggleDeconstruct,
|
||||
DeconstructArea
|
||||
};
|
||||
|
||||
// The mouse gestures that carry a binding. Each is a whole gesture rather than a raw
|
||||
// event: a drag is one binding, not a press plus a release, because that is the unit
|
||||
// the player and the panel both think in. Which events make up the gesture, and the
|
||||
// state it runs on, stay with the widget that owns them.
|
||||
enum class MouseBinding
|
||||
{
|
||||
LeftClick,
|
||||
LeftDrag,
|
||||
CtrlLeftClick,
|
||||
CtrlLeftDrag,
|
||||
RightClick
|
||||
};
|
||||
|
||||
// Which selection category is held, mirroring REQ-UI-SELECTION-CATEGORIES without
|
||||
// depending on SelectionController.
|
||||
enum class ControlSelection
|
||||
{
|
||||
None,
|
||||
Buildings,
|
||||
FieldObjects
|
||||
};
|
||||
|
||||
// Which card the panel is showing (REQ-UI-CONTROLS-CONTENT). Named rather than
|
||||
// spelled, so the heading text stays a presentation concern.
|
||||
enum class ControlContextKind
|
||||
{
|
||||
General,
|
||||
Selection,
|
||||
Build,
|
||||
Blueprint,
|
||||
Deconstruct
|
||||
};
|
||||
|
||||
// Everything the availability rules are allowed to depend on, as a plain snapshot.
|
||||
//
|
||||
// Taking a snapshot rather than references to the live controllers is what keeps this
|
||||
// testable without a world, and it is what stops an action from reaching into the
|
||||
// simulation: if a rule needs a fact, the fact is named here and the caller supplies it.
|
||||
struct ControlContext
|
||||
{
|
||||
BuildMode mode = BuildMode::None;
|
||||
BuildingType builderType = BuildingType::Belt; // while mode == Builder
|
||||
bool draggingBelt = false;
|
||||
// A single-building blueprint whose ghost is over a configuration-transfer target,
|
||||
// so clicking hands over settings rather than placing (REQ-UI-BLUEPRINT-TRANSFER).
|
||||
bool hoveredGhostIsTransfer = false;
|
||||
ControlSelection selection = ControlSelection::None;
|
||||
int selectionCount = 0;
|
||||
// At least one selected building is player-placeable, the condition under which
|
||||
// C and Ctrl+C do anything (REQ-UI-HOTKEYS).
|
||||
bool placeableBuildingSelected = false;
|
||||
bool temporaryBlueprintExists = false;
|
||||
};
|
||||
|
||||
// One input an action answers to, in structured form so the badge can be rendered from
|
||||
// it. `modifiers` is what the badge shows; matching is looser than equality, see
|
||||
// resolveKeyAction.
|
||||
struct ControlBinding
|
||||
{
|
||||
bool isMouse = false;
|
||||
MouseBinding mouse = MouseBinding::LeftClick;
|
||||
int key = 0; // Qt::Key_*, when !isMouse
|
||||
Qt::KeyboardModifiers modifiers = Qt::NoModifier;
|
||||
};
|
||||
|
||||
// True when triggering the action in this context would do what its label says. The
|
||||
// panel shows exactly the available actions, and the resolvers return only available
|
||||
// ones, which is REQ-UI-CONTROLS-ACCURACY expressed as one function.
|
||||
bool isControlActionAvailable(ControlAction action, const ControlContext& context);
|
||||
|
||||
// The inputs an action answers to, in the order the panel should badge them.
|
||||
// Context-dependent because a binding can be taken over: while a belt drag is in
|
||||
// progress the right mouse button cancels the drag, so ExitMode is left with its key
|
||||
// binding alone (REQ-BLD-BELT-DRAG).
|
||||
std::vector<ControlBinding> getControlActionBindings(ControlAction action,
|
||||
const ControlContext& context);
|
||||
|
||||
// Which card is showing, and the rows it holds -- the context's own, then the block
|
||||
// available everywhere (REQ-UI-CONTROLS-CARD). Both lists are already filtered to the
|
||||
// available actions and ordered as REQ-UI-CONTROLS-CONTENT lists them.
|
||||
ControlContextKind getControlContextKind(const ControlContext& context);
|
||||
std::vector<ControlAction> getContextActions(const ControlContext& context);
|
||||
std::vector<ControlAction> getAlwaysAvailableActions(const ControlContext& context);
|
||||
|
||||
// The action a key press or a mouse gesture triggers here, or None when the input is
|
||||
// unbound in this context. Both return only actions that are available, so a caller can
|
||||
// act on the result without re-checking the situation.
|
||||
//
|
||||
// Rotation direction is not part of the action: R and Shift+R are one Rotate, and the
|
||||
// caller reads the modifier for the direction, exactly as the digit of a build hotkey
|
||||
// is read from the key. An action with a parameter keeps the parameter at the handler.
|
||||
ControlAction resolveKeyAction(int key, Qt::KeyboardModifiers modifiers,
|
||||
const ControlContext& context);
|
||||
ControlAction resolveMouseAction(MouseBinding binding, const ControlContext& context);
|
||||
Reference in New Issue
Block a user