1#include "InputManager.h"
3#include "GamepadMapping.h"
4#include "KeyboardMapping.h"
5#include "Platform/Gamepad.h"
8#include "../ViewContext.h"
9#include "../Serialization/JsonParser.h"
10#include "../Services/Variables.h"
11#include "../Utilities/Parsing.h"
13#include "Foundation/Logging/Logger.h"
24 std::string inputName;
27 if (jsonValue.IsString()) {
28 auto source = Cogs::Core::toKey(jsonValue);
29 auto splits = Cogs::Core::split(source,
":");
31 if (splits.size() < 2) {
32 LOG_ERROR(logger,
"Invalid input source: %.*s", StringViewFormat(source));
36 if (splits.size() == 3) {
37 scale = Cogs::Core::parseFloat(splits[2], 1.0f);
40 device = splits[0].to_string();
41 inputName = splits[1].to_string();
43 else if (jsonValue.IsObject()) {
44 for (
auto & mm : jsonValue.GetObject()) {
45 auto mmKey = Cogs::Core::toKey(mm.name);
47 if (mmKey ==
"device") {
48 device = Cogs::Core::toString(mm.value);
50 else if (mmKey ==
"axis" || mmKey ==
"action") {
51 inputName = Cogs::Core::toString(mm.value);
53 else if (mmKey ==
"scale") {
54 scale = mm.value.GetFloat();
59 if (!device.empty() && !inputName.empty()) {
61 inputManager.addAxisMapping(axisName, device, inputName, scale);
64 inputManager.addActionMapping(axisName, device, inputName);
82 auto & mouse = deviceStates[InputDevices::Mouse];
84 mouse.deviceId = InputDevices::Mouse;
86 mouse.isConnected =
true;
88 mouse.buttonMask = 0x7;
94 {
"MiddleButton", 2 },
103 addAxisMapping(
"MouseX",
"Mouse",
"X", 1.0f);
104 addAxisMapping(
"MouseY",
"Mouse",
"Y", 1.0f);
106 addAxisMapping(
"MouseWheel",
"Mouse",
"Wheel", 1.0f, 1);
108 addActionMapping(
"LeftMouse",
"Mouse",
"LeftButton");
109 addActionMapping(
"MiddleMouse",
"Mouse",
"MiddleButton");
110 addActionMapping(
"RightMouse",
"Mouse",
"RightButton");
112 auto & keyboard = deviceStates[InputDevices::Keyboard];
114 keyboard.id =
"Keyboard";
115 keyboard.deviceId = InputDevices::Keyboard;
116 keyboard.isConnected =
true;
117 keyboard.isValid =
true;
119 keyboard.buttonMask =
static_cast<size_t>(-1);
120 keyboard.axisMask =
static_cast<size_t>(-1);
121 keyboard.actionMap = keyMapping;
122 keyboard.axisMap = keyMapping;
124 for (
int i = 0; i < 4; ++i) {
125 auto & gamepad = deviceStates[InputDevices::Gamepad0 + i];
127 gamepad.id =
"Gamepad" + std::to_string(i);
128 if (i == 0) gamepad.alias =
"Gamepad";
130 gamepad.deviceId = InputDevices::Gamepad0 + i;
132 gamepad.axisMap = gamepadAxisMapping;
133 gamepad.actionMap = gamepadButtonMapping;
135 gamepad.linkedDeadZones[Gamepad::StickRightX] = Gamepad::StickRightY;
136 gamepad.linkedDeadZones[Gamepad::StickRightY] = Gamepad::StickRightX;
138 gamepad.linkedDeadZones[Gamepad::StickLeftX] = Gamepad::StickLeftY;
139 gamepad.linkedDeadZones[Gamepad::StickLeftY] = Gamepad::StickLeftX;
142 readInputConfig(view->getContext()->variables->get(
"input.config",
"Input.config"));
149 addAxisMapping(name, deviceId, axisId.
inputName);
156 addActionMapping(name, deviceId, actionId.
inputName);
159void Cogs::Core::InputManager::readInputConfig(
const StringView & path)
161 auto document = parseJson(view->getContext(), path, JsonParseFlags::NoCachedContent);
163 if (!document.IsObject())
return;
165 for (
auto & m : document.GetObject()) {
166 auto key = toKey(m.name);
168 if (key ==
"axes" || key ==
"actions") {
169 auto axesObj = m.value.GetObject();
171 for (
auto & axesMember : axesObj) {
172 auto axisName = toKey(axesMember.name);
174 if (axesMember.value.IsArray()) {
175 for (
auto & element : axesMember.value.GetArray()) {
176 readInputMapping(*
this, axisName, element, key ==
"axes");
179 readInputMapping(*
this, axisName, axesMember.value, key ==
"axes");
188 inputProviders.push_back(std::move(provider));
193 for (
auto & d : deviceStates) {
194 if (name == d.id || name == d.alias) {
199 for (
size_t i = InputDevices::Custom; i < deviceStates.size(); ++i) {
200 auto & d = deviceStates[i];
206 auto splits = split(name,
":");
208 if (splits.size() >= 2) {
209 pendingDevices.push_back(InputDeviceIdentifier{ splits[0].to_string(), splits[1].to_string() });
216 LOG_ERROR(logger,
"No device with identifier %.*s found.", StringViewFormat(name));
221size_t Cogs::Core::InputManager::getAxisIndex(InputDeviceState * device,
const StringView & inputAxis)
const
223 if (device->axisMap.empty()) {
228 auto found = device->axisMap.find(inputAxis.to_string());
230 if (found == device->axisMap.end()) {
231 LOG_ERROR(logger,
"Axis %.*s does not exist on device %s.", StringViewFormat(inputAxis), device->id.c_str());
235 return found->second;
238void Cogs::Core::InputManager::addAxisMapping(
const StringView & name,
const StringView & deviceName,
const StringView & inputAxis,
float scale,
int flags)
240 auto device = getDevice(deviceName);
244 addAxisMapping(name, device, inputAxis, scale, flags);
247void Cogs::Core::InputManager::addAxisMapping(
const StringView & name, InputDeviceState * device,
const StringView & inputAxis,
float scale,
int flags)
249 auto & axisMapping = axisMappings[name.hash()];
251 if (axisMapping.name.empty()) {
252 axisMapping.name = name.to_string();
255 SourceAxisMapping sourceAxis = {};
256 sourceAxis.scale = scale;
257 sourceAxis.deviceId = device->deviceId;
258 sourceAxis.axisName = inputAxis.to_string();
259 sourceAxis.axisId = getAxisIndex(device, inputAxis);
260 sourceAxis.flags = flags;
262 if (sourceAxis.axisId == ErrorAxis)
return;
264 axisMapping.inputs.push_back(sourceAxis);
267size_t Cogs::Core::InputManager::getButtonIndex(InputDeviceState * device,
const StringView & inputAction)
const
269 if (device->actionMap.empty()) {
273 auto found = device->actionMap.find(inputAction.to_string());
275 if (found == device->actionMap.end()) {
276 LOG_ERROR(logger,
"Action %.*s does not exist on device %s.", StringViewFormat(inputAction), device->id.c_str());
280 return found->second;
283void Cogs::Core::InputManager::addActionMapping(
const StringView & name,
const StringView & deviceName,
const StringView & inputAction,
int placeholder)
285 auto device = getDevice(deviceName);
289 addActionMapping(name, device, inputAction, placeholder);
292void Cogs::Core::InputManager::addActionMapping(
const StringView & name, InputDeviceState * device,
const StringView & inputAction,
int )
294 auto & actionMapping = actionMappings[name.hash()];
296 if (actionMapping.name.empty()) {
297 actionMapping.name = name.to_string();
300 SourceActionMapping sourceAction = {};
301 sourceAction.deviceId = device->deviceId;
302 sourceAction.buttonId = getButtonIndex(device, inputAction);
303 sourceAction.actionName = inputAction.to_string();
305 if (sourceAction.buttonId == ErrorButton)
return;
307 actionMapping.inputs.push_back(sourceAction);
310void Cogs::Core::InputManager::gainedFocus(
double ) {
314void Cogs::Core::InputManager::lostFocus(
double timestamp_ms) {
315 timestamp_ms = checkTimeStamp(timestamp_ms);
316 view->refGestures().reset();
317 view->refMouse().submitReset(timestamp_ms);
318 view->refKeyboard().submitReset(timestamp_ms);
321void Cogs::Core::InputManager::triggerPointerPress(PointerType pointerType, PointerId pointerId, MouseButton button,
const glm::ivec2& position,
double timestamp_ms) {
322 timestamp_ms = checkTimeStamp(timestamp_ms);
323 if (pointerType == PointerType::Mouse) {
324 view->refMouse().submitButtonDown(button, timestamp_ms);
326 view->refGestures().pointerDown(pointerType, pointerId, button, position, timestamp_ms);
328 view->getContext()->engine->setDirty();
331void Cogs::Core::InputManager::triggerPointerRelease(PointerType pointerType, PointerId pointerId, MouseButton button,
const glm::ivec2& position,
double timestamp_ms) {
332 timestamp_ms = checkTimeStamp(timestamp_ms);
333 if (pointerType == PointerType::Mouse) {
334 view->refMouse().submitButtonUp(button, timestamp_ms);
336 view->refGestures().pointerUp(pointerType, pointerId, button, position, timestamp_ms);
338 view->getContext()->engine->setDirty();
341void Cogs::Core::InputManager::triggerPointerMove(PointerType pointerType, PointerId pointerId,
const glm::ivec2& position,
double timestamp_ms) {
342 timestamp_ms = checkTimeStamp(timestamp_ms);
343 if (pointerType == PointerType::Mouse) {
344 view->refMouse().submitMove(position, timestamp_ms);
346 view->refGestures().pointerMove(pointerType, pointerId, position, timestamp_ms);
348 view->getContext()->engine->setDirty();
351void Cogs::Core::InputManager::triggerMouseWheel(int32_t deltaValue,
double timestamp_ms) {
352 timestamp_ms = checkTimeStamp(timestamp_ms);
353 view->refMouse().submitWheel(deltaValue, timestamp_ms);
354 view->refGestures().mouseWheelMove(deltaValue);
356 view->getContext()->engine->setDirty();
359void Cogs::Core::InputManager::triggerKeyDown(Key key,
double timestamp_ms) {
360 timestamp_ms = checkTimeStamp(timestamp_ms);
361 view->refKeyboard().submitKeyDown(key, timestamp_ms);
363 view->getContext()->engine->setDirty();
366void Cogs::Core::InputManager::triggerKeyUp(Key key,
double timestamp_ms) {
367 timestamp_ms = checkTimeStamp(timestamp_ms);
368 view->refKeyboard().submitKeyUp(key, timestamp_ms);
370 view->getContext()->engine->setDirty();
373void Cogs::Core::InputManager::triggerKeyChar(std::string ch,
double timestamp_ms) {
374 timestamp_ms = checkTimeStamp(timestamp_ms);
375 view->refKeyboard().submitChar(std::move(ch), timestamp_ms);
377 view->getContext()->engine->setDirty();
382 return deviceStates[deviceId];
387 auto mapping = axisMappings.find(name.
hash());
389 if (mapping == axisMappings.end()) {
390 LOG_WARNING(logger,
"No such axis: %.*s", StringViewFormat(name));
394 return mapping->second.value;
399 auto mapping = actionMappings.find(name.
hash());
401 if (mapping == actionMappings.end()) {
402 LOG_WARNING(logger,
"No such action: %.*s", StringViewFormat(name));
406 return mapping->second.value;
411 view->refKeyboard().update();
412 view->refMouse().update();
413 view->refGestures().update();
418 for (
auto & d : deviceStates) {
419 if (d.isConnected && !d.actionMap.empty() && d.actionNames.empty()) {
420 d.actionNames.resize(d.highestBit(d.buttonMask) + 1);
422 for (
const auto & a : d.actionMap) {
423 if (a.second < d.actionNames.size()) {
424 d.actionNames[a.second] = a.first;
429 if (d.isConnected && !d.axisMap.empty() && d.axisNames.empty()) {
430 d.axisNames.resize(d.highestBit(d.axisMask) + 1);
432 for (
auto & a : d.axisMap) {
433 if (a.second < d.axisNames.size()) {
434 d.axisNames[a.second] = a.first;
444void Cogs::Core::InputManager::updateConnections()
446 std::vector<InputDeviceIdentifier> found;
448 for (
auto & deviceId : pendingDevices) {
449 for (
auto & provider : inputProviders) {
450 if (provider->canCreate(deviceId)) {
451 auto device = provider->create(deviceId);
454 found.push_back(deviceId);
456 auto & state = *getDevice(deviceId.driver +
":" + deviceId.id);
458 if (!state.isConnected) {
459 state.isConnected =
true;
460 state.device = device;
469 for (
auto & toRemove : found) {
470 pendingDevices.erase(std::remove(pendingDevices.begin(), pendingDevices.end(), toRemove), pendingDevices.end());
474void Cogs::Core::InputManager::updateInputs()
477 for (
auto & d : deviceStates) {
478 d.axisDeltas.fill(0.0f);
479 d.buttonDeltas.fill(
false);
482 InputDeviceState& keyboardDeviceState = getDevice(Core::InputDevices::Keyboard);
483 for (
int i = 0; i < static_cast<int>(Key::Count); ++i) {
484 if (view->refKeyboard().isKeyDown(
static_cast<Key
>(i))) {
485 keyboardDeviceState.setButton(i,
true);
486 keyboardDeviceState.setAxis(i, 1.0f);
489 keyboardDeviceState.setButton(i,
false);
490 keyboardDeviceState.setAxis(i, 0.0f);
494 InputDeviceState& mouseDeviceState = getDevice(Core::InputDevices::Mouse);
495 const Mouse::State& mouseState = view->refMouse().getState();
497 for (
size_t i = 0; i < MouseButton::Count; ++i) {
498 mouseDeviceState.setButton(i, mouseState.buttonDown[i]);
501 mouseDeviceState.setAxis(0,
static_cast<float>(mouseState.position.x));
502 mouseDeviceState.setAxis(1,
static_cast<float>(mouseState.position.y));
503 mouseDeviceState.setAxis(2,
static_cast<float>(mouseState.wheel));
505 view->gamepadHandler->update();
508 for (
auto & d : deviceStates) {
510 d.device->getState(d);
515 for (
const auto & p : inputProviders) {
520void Cogs::Core::InputManager::updateAxes()
522 for (
auto & p : axisMappings) {
523 auto & axisMapping = p.second;
525 for (
auto & input : axisMapping.inputs) {
526 auto & d = deviceStates[input.deviceId];
528 if (input.axisId == NoAxis) {
529 input.axisId = getAxisIndex(&d, input.axisName);
530 if (input.axisId == NoAxis)
continue;
532 if (input.axisId == ErrorAxis)
continue;
534 const size_t axis = input.axisId;
535 const float rawValue = d.axes[axis];
537 if (d.axisDeltas[axis] || (rawValue != 0 && axisMapping.value == 0)) {
538 const float deadZone = d.deadZones[axis];
539 const size_t deadZoneLink = d.linkedDeadZones[axis];
540 const float sensitivity = d.sensitivity[axis];
542 float magnitude = glm::abs(rawValue);
544 if (deadZoneLink != kNoDeadZoneLink) {
545 const glm::vec2 pos(rawValue, d.axes[deadZoneLink]);
546 magnitude = glm::length(pos);
549 if (magnitude > deadZone) {
550 const float axisValue = rawValue * sensitivity * input.scale;
553 axisMapping.value = axisValue;
555 axisMapping.value = axisValue * (magnitude - deadZone) / (1 - deadZone);
558 axisMapping.value = 0.0f;
565void Cogs::Core::InputManager::updateActions()
567 for (
auto & p : actionMappings) {
568 auto & actionMapping = p.second;
570 actionMapping.value =
false;
572 for (
auto & input : actionMapping.inputs) {
573 auto & d = deviceStates[input.deviceId];
575 if (input.buttonId == NoButton) {
576 input.buttonId = getButtonIndex(&d, input.actionName);
577 if (input.buttonId == NoButton)
continue;
579 if (input.buttonId == ErrorButton)
continue;
581 bool actionState = d.buttons[input.buttonId];
585 actionMapping.value |= actionState;
593 double result = std::max(timestamp_ms, prevTimeStamp);
594 if (result == prevTimeStamp) {
595 result = std::nextafter(result, std::numeric_limits<double>::max());
597 prevTimeStamp = result;
Log implementation class.
Provides a weakly referenced view over the contents of a string.
std::string to_string() const
String conversion method.
constexpr size_t hash() const noexcept
Get the hash code of the string.
constexpr Log getLogger(const char(&name)[LEN]) noexcept