exposed input key bindings to js to enable key remapping

This commit is contained in:
Sam Gondelman 2015-06-04 16:29:58 -07:00
parent a12fd5c3d0
commit 984d449bf9
8 changed files with 289 additions and 16 deletions

View file

@ -148,6 +148,7 @@ public:
static glm::quat getOrientationForPath() { return getInstance()->_myAvatar->getOrientation(); }
static glm::vec3 getPositionForAudio() { return getInstance()->_myAvatar->getHead()->getPosition(); }
static glm::quat getOrientationForAudio() { return getInstance()->_myAvatar->getHead()->getFinalOrientationInWorldFrame(); }
static UserInputMapper* getUserInputMapper() { return &getInstance()->_userInputMapper; }
static void initPlugins();
static void shutdownPlugins();

View file

@ -159,9 +159,25 @@ void KeyboardMouseDevice::registerToUserInputMapper(UserInputMapper& mapper) {
// Grab the current free device ID
_deviceID = mapper.getFreeDeviceID();
auto proxy = UserInputMapper::DeviceProxy::Pointer(new UserInputMapper::DeviceProxy());
auto proxy = UserInputMapper::DeviceProxy::Pointer(new UserInputMapper::DeviceProxy("Keyboard"));
proxy->getButton = [this] (const UserInputMapper::Input& input, int timestamp) -> bool { return this->getButton(input._channel); };
proxy->getAxis = [this] (const UserInputMapper::Input& input, int timestamp) -> float { return this->getAxis(input._channel); };
proxy->getAvailabeInputs = [this] () -> QVector<UserInputMapper::InputPair> {
QVector<UserInputMapper::InputPair> availableInputs;
for (int i = 0; i <= 9; i++) {
availableInputs.append(UserInputMapper::InputPair(makeInput(Qt::Key(48 + i)), QKeySequence(Qt::Key(48 + i)).toString()));
}
for (int i = 0; i < 26; i++) {
availableInputs.append(UserInputMapper::InputPair(makeInput(Qt::Key(65 + i)), QKeySequence(Qt::Key(65 + i)).toString()));
}
availableInputs.append(UserInputMapper::InputPair(makeInput(Qt::Key_Space), QKeySequence(Qt::Key_Space).toString()));
return availableInputs;
};
proxy->resetDeviceBindings = [this, &_mapper = mapper, &device = _deviceID] () -> bool {
_mapper.removeAllInputChannelsForDevice(device);
this->assignDefaultInputMapping(_mapper);
return true;
};
mapper.registerDevice(_deviceID, proxy);
}

View file

@ -28,6 +28,90 @@ ControllerScriptingInterface::ControllerScriptingInterface() :
{
}
static int actionMetaTypeId = qRegisterMetaType<UserInputMapper::Action>();
static int inputChannelMetaTypeId = qRegisterMetaType<UserInputMapper::InputChannel>();
static int inputMetaTypeId = qRegisterMetaType<UserInputMapper::Input>();
static int inputPairMetaTypeId = qRegisterMetaType<UserInputMapper::InputPair>();
QScriptValue inputToScriptValue(QScriptEngine* engine, const UserInputMapper::Input& input);
void inputFromScriptValue(const QScriptValue& object, UserInputMapper::Input& input);
QScriptValue inputChannelToScriptValue(QScriptEngine* engine, const UserInputMapper::InputChannel& inputChannel);
void inputChannelFromScriptValue(const QScriptValue& object, UserInputMapper::InputChannel& inputChannel);
QScriptValue actionToScriptValue(QScriptEngine* engine, const UserInputMapper::Action& action);
void actionFromScriptValue(const QScriptValue& object, UserInputMapper::Action& action);
QScriptValue inputPairToScriptValue(QScriptEngine* engine, const UserInputMapper::InputPair& inputPair);
void inputPairFromScriptValue(const QScriptValue& object, UserInputMapper::InputPair& inputPair);
QScriptValue inputToScriptValue(QScriptEngine* engine, const UserInputMapper::Input& input) {
QScriptValue obj = engine->newObject();
obj.setProperty("device", input.getDevice());
obj.setProperty("channel", input.getChannel());
obj.setProperty("type", input._type);
obj.setProperty("id", input.getID());
return obj;
}
void inputFromScriptValue(const QScriptValue& object, UserInputMapper::Input& input) {
input._device = object.property("device").toUInt16();
input._channel = object.property("channel").toUInt16();
input._type = object.property("type").toUInt16();
input._id = object.property("id").toInt32();
}
QScriptValue inputChannelToScriptValue(QScriptEngine* engine, const UserInputMapper::InputChannel& inputChannel) {
QScriptValue obj = engine->newObject();
obj.setProperty("input", inputToScriptValue(engine, inputChannel._input));
obj.setProperty("modifier", inputToScriptValue(engine, inputChannel._modifier));
obj.setProperty("action", inputChannel._action);
obj.setProperty("scale", inputChannel._scale);
return obj;
}
void inputChannelFromScriptValue(const QScriptValue& object, UserInputMapper::InputChannel& inputChannel) {
inputFromScriptValue(object.property("input"), inputChannel._input);
inputFromScriptValue(object.property("modifier"), inputChannel._modifier);
inputChannel._action = UserInputMapper::Action(object.property("action").toVariant().toInt());
inputChannel._scale = object.property("scale").toVariant().toFloat();
}
QScriptValue actionToScriptValue(QScriptEngine* engine, const UserInputMapper::Action& action) {
QScriptValue obj = engine->newObject();
QVector<UserInputMapper::InputChannel> inputChannels = Application::getUserInputMapper()->getInputChannelsForAction(action);
QScriptValue _inputChannels = engine->newArray(inputChannels.size());
for (int i = 0; i < inputChannels.size(); i++) {
_inputChannels.setProperty(i, inputChannelToScriptValue(engine, inputChannels[i]));
}
obj.setProperty("action", (int) action);
obj.setProperty("actionName", Application::getUserInputMapper()->getActionName(action));
obj.setProperty("inputChannels", _inputChannels);
return obj;
}
void actionFromScriptValue(const QScriptValue& object, UserInputMapper::Action& action) {
action = UserInputMapper::Action(object.property("action").toVariant().toInt());
}
QScriptValue inputPairToScriptValue(QScriptEngine* engine, const UserInputMapper::InputPair& inputPair) {
QScriptValue obj = engine->newObject();
obj.setProperty("input", inputToScriptValue(engine, inputPair.first));
obj.setProperty("inputName", inputPair.second);
return obj;
}
void inputPairFromScriptValue(const QScriptValue& object, UserInputMapper::InputPair& inputPair) {
inputFromScriptValue(object.property("input"), inputPair.first);
inputPair.second = QString(object.property("inputName").toVariant().toString());
}
void ControllerScriptingInterface::registerControllerTypes(QScriptEngine* engine) {
qScriptRegisterSequenceMetaType<QVector<UserInputMapper::Action> >(engine);
qScriptRegisterSequenceMetaType<QVector<UserInputMapper::InputChannel> >(engine);
qScriptRegisterSequenceMetaType<QVector<UserInputMapper::InputPair> >(engine);
qScriptRegisterMetaType(engine, actionToScriptValue, actionFromScriptValue);
qScriptRegisterMetaType(engine, inputChannelToScriptValue, inputChannelFromScriptValue);
qScriptRegisterMetaType(engine, inputToScriptValue, inputFromScriptValue);
qScriptRegisterMetaType(engine, inputPairToScriptValue, inputPairFromScriptValue);
}
void ControllerScriptingInterface::handleMetaEvent(HFMetaEvent* event) {
if (event->type() == HFActionEvent::startType()) {
@ -337,6 +421,37 @@ void ControllerScriptingInterface::updateInputControllers() {
}
}
QVector<UserInputMapper::Action> ControllerScriptingInterface::getAllActions() {
return Application::getUserInputMapper()->getAllActions();
}
QVector<UserInputMapper::InputChannel> ControllerScriptingInterface::getInputChannelsForAction(UserInputMapper::Action action) {
return Application::getUserInputMapper()->getInputChannelsForAction(action);
}
QString ControllerScriptingInterface::getDeviceName(unsigned int device) {
return Application::getUserInputMapper()->getDeviceName((unsigned short) device);
}
QVector<UserInputMapper::InputChannel> ControllerScriptingInterface::getAllInputsForDevice(unsigned int device) {
return Application::getUserInputMapper()->getAllInputsForDevice(device);
}
bool ControllerScriptingInterface::addInputChannel(UserInputMapper::InputChannel inputChannel) {
return Application::getUserInputMapper()->addInputChannel(inputChannel._action, inputChannel._input, inputChannel._modifier, inputChannel._scale);
}
bool ControllerScriptingInterface::removeInputChannel(UserInputMapper::InputChannel inputChannel) {
return Application::getUserInputMapper()->removeInputChannel(inputChannel);
}
QVector<UserInputMapper::InputPair> ControllerScriptingInterface::getAvailableInputs(unsigned int device) {
return Application::getUserInputMapper()->getAvailableInputs((unsigned short) device);
}
void ControllerScriptingInterface::resetAllDeviceBindings() {
Application::getUserInputMapper()->resetAllDeviceBindings();
}
InputController::InputController(int deviceTrackerId, int subTrackerId, QObject* parent) :
AbstractInputController(),
@ -373,4 +488,4 @@ const unsigned int INPUTCONTROLLER_KEY_DEVICE_MASK = 16;
InputController::Key InputController::getKey() const {
return (((_deviceTrackerId & INPUTCONTROLLER_KEY_DEVICE_MASK) << INPUTCONTROLLER_KEY_DEVICE_OFFSET) | _subTrackerId);
}
}

View file

@ -14,10 +14,11 @@
#include <QtCore/QObject>
#include "ui/UserInputMapper.h"
#include <AbstractControllerScriptingInterface.h>
class PalmData;
class InputController : public AbstractInputController {
Q_OBJECT
@ -54,6 +55,9 @@ class ControllerScriptingInterface : public AbstractControllerScriptingInterface
public:
ControllerScriptingInterface();
virtual void registerControllerTypes(QScriptEngine* engine);
void emitKeyPressEvent(QKeyEvent* event) { emit keyPressEvent(KeyEvent(*event)); }
void emitKeyReleaseEvent(QKeyEvent* event) { emit keyReleaseEvent(KeyEvent(*event)); }
@ -79,6 +83,14 @@ public:
void updateInputControllers();
public slots:
Q_INVOKABLE virtual QVector<UserInputMapper::Action> getAllActions();
Q_INVOKABLE virtual QVector<UserInputMapper::InputChannel> getInputChannelsForAction(UserInputMapper::Action action);
Q_INVOKABLE virtual QString getDeviceName(unsigned int device);
Q_INVOKABLE virtual QVector<UserInputMapper::InputChannel> getAllInputsForDevice(unsigned int device);
Q_INVOKABLE virtual bool addInputChannel(UserInputMapper::InputChannel inputChannel);
Q_INVOKABLE virtual bool removeInputChannel(UserInputMapper::InputChannel inputChannel);
Q_INVOKABLE virtual QVector<UserInputMapper::InputPair> getAvailableInputs(unsigned int device);
Q_INVOKABLE virtual void resetAllDeviceBindings();
virtual bool isPrimaryButtonPressed() const;
virtual glm::vec2 getPrimaryJoystickPosition() const;

View file

@ -13,7 +13,15 @@
// UserInputMapper Class
// Default contruct allocate the poutput size with the current hardcoded action channels
UserInputMapper::UserInputMapper() {
assignDefaulActionScales();
createActionNames();
}
bool UserInputMapper::registerDevice(uint16 deviceID, const DeviceProxy::Pointer& proxy){
proxy->_name += " (" + QString::number(deviceID) + ")";
_registeredDevices[deviceID] = proxy;
return true;
}
@ -27,6 +35,12 @@ UserInputMapper::DeviceProxy::Pointer UserInputMapper::getDeviceProxy(const Inpu
}
}
void UserInputMapper::resetAllDeviceBindings() {
for (auto device : _registeredDevices) {
device.second->resetDeviceBindings();
}
}
bool UserInputMapper::addInputChannel(Action action, const Input& input, float scale) {
return addInputChannel(action, input, Input(), scale);
}
@ -37,7 +51,7 @@ bool UserInputMapper::addInputChannel(Action action, const Input& input, const I
qDebug() << "UserInputMapper::addInputChannel: The input comes from a device #" << input.getDevice() << "is unknown. no inputChannel mapped.";
return false;
}
auto inputChannel = InputChannel(input, modifier, action, scale);
// Insert or replace the input to modifiers
@ -61,6 +75,37 @@ int UserInputMapper::addInputChannels(const InputChannels& channels) {
return nbAdded;
}
bool UserInputMapper::removeInputChannel(InputChannel inputChannel) {
// Remove from Input to Modifiers map
if (inputChannel.hasModifier()) {
_inputToModifiersMap.erase(inputChannel._input.getID());
}
// Remove from Action to Inputs map
std::pair<ActionToInputsMap::iterator, ActionToInputsMap::iterator> ret;
ret = _actionToInputsMap.equal_range(inputChannel._action);
for (ActionToInputsMap::iterator it=ret.first; it!=ret.second; ++it) {
if (it->second == inputChannel) {
_actionToInputsMap.erase(it);
return true;
}
}
return false;
}
void UserInputMapper::removeAllInputChannels() {
_inputToModifiersMap.clear();
_actionToInputsMap.clear();
}
void UserInputMapper::removeAllInputChannelsForDevice(uint16 device) {
QVector<InputChannel> channels = getAllInputsForDevice(device);
for (auto& channel : channels) {
removeInputChannel(channel);
}
}
int UserInputMapper::getInputChannels(InputChannels& channels) const {
for (auto& channel : _actionToInputsMap) {
channels.push_back(channel.second);
@ -69,6 +114,20 @@ int UserInputMapper::getInputChannels(InputChannels& channels) const {
return _actionToInputsMap.size();
}
QVector<UserInputMapper::InputChannel> UserInputMapper::getAllInputsForDevice(uint16 device) {
InputChannels allChannels;
getInputChannels(allChannels);
QVector<InputChannel> channels;
for (InputChannel inputChannel : allChannels) {
if (inputChannel._input._device == device) {
channels.push_back(inputChannel);
}
}
return channels;
}
void UserInputMapper::update(float deltaTime) {
// Reset the axis state for next loop
@ -130,6 +189,24 @@ void UserInputMapper::update(float deltaTime) {
}
}
QVector<UserInputMapper::Action> UserInputMapper::getAllActions() {
QVector<Action> actions;
for (auto i = 0; i < NUM_ACTIONS; i++) {
actions.append(Action(i));
}
return actions;
}
QVector<UserInputMapper::InputChannel> UserInputMapper::getInputChannelsForAction(UserInputMapper::Action action) {
QVector<InputChannel> inputChannels;
std::pair <ActionToInputsMap::iterator, ActionToInputsMap::iterator> ret;
ret = _actionToInputsMap.equal_range(action);
for (ActionToInputsMap::iterator it=ret.first; it!=ret.second; ++it) {
inputChannels.append(it->second);
}
return inputChannels;
}
void UserInputMapper::assignDefaulActionScales() {
_actionScales[LONGITUDINAL_BACKWARD] = 1.0f; // 1m per unit
_actionScales[LONGITUDINAL_FORWARD] = 1.0f; // 1m per unit
@ -144,3 +221,20 @@ void UserInputMapper::assignDefaulActionScales() {
_actionScales[BOOM_IN] = 1.0f; // 1m per unit
_actionScales[BOOM_OUT] = 1.0f; // 1m per unit
}
// This is only necessary as long as the actions are hardcoded
// Eventually you can just add the string when you add the action
void UserInputMapper::createActionNames() {
_actionNames[LONGITUDINAL_BACKWARD] = "LONGITUDINAL_BACKWARD";
_actionNames[LONGITUDINAL_FORWARD] = "LONGITUDINAL_FORWARD";
_actionNames[LATERAL_LEFT] = "LATERAL_LEFT";
_actionNames[LATERAL_RIGHT] = "LATERAL_RIGHT";
_actionNames[VERTICAL_DOWN] = "VERTICAL_DOWN";
_actionNames[VERTICAL_UP] = "VERTICAL_UP";
_actionNames[YAW_LEFT] = "YAW_LEFT";
_actionNames[YAW_RIGHT] = "YAW_RIGHT";
_actionNames[PITCH_DOWN] = "PITCH_DOWN";
_actionNames[PITCH_UP] = "PITCH_UP";
_actionNames[BOOM_IN] = "BOOM_IN";
_actionNames[BOOM_OUT] = "BOOM_OUT";
}

View file

@ -21,6 +21,7 @@
class UserInputMapper : public QObject {
Q_OBJECT
Q_ENUMS(Action)
public:
typedef unsigned short uint16;
typedef unsigned int uint32;
@ -64,6 +65,7 @@ public:
explicit Input(uint16 device, uint16 channel, ChannelType type) : _device(device), _channel(channel), _type(uint16(type)) {}
Input(const Input& src) : _id(src._id) {}
Input& operator = (const Input& src) { _id = src._id; return (*this); }
bool operator ==(const Input& right) const { return _id == right._id; }
bool operator < (const Input& src) const { return _id < src._id; }
};
@ -83,22 +85,32 @@ public:
typedef std::function<bool (const Input& input, int timestamp)> ButtonGetter;
typedef std::function<float (const Input& input, int timestamp)> AxisGetter;
typedef std::function<JointValue (const Input& input, int timestamp)> JointGetter;
typedef QPair<Input, QString> InputPair;
typedef std::function<QVector<InputPair> ()> AvailableInputGetter;
typedef std::function<bool ()> ResetBindings;
typedef QVector<InputPair> AvailableInput;
class DeviceProxy {
public:
DeviceProxy() {}
ButtonGetter getButton = [] (const Input& input, int timestamp) -> bool { return false; };
AxisGetter getAxis = [] (const Input& input, int timestamp) -> bool { return 0.0f; };
JointGetter getJoint = [] (const Input& input, int timestamp) -> JointValue { return JointValue(); };
typedef std::shared_ptr<DeviceProxy> Pointer;
DeviceProxy(QString name) { _name = name; }
QString _name;
ButtonGetter getButton = [] (const Input& input, int timestamp) -> bool { return false; };
AxisGetter getAxis = [] (const Input& input, int timestamp) -> bool { return 0.0f; };
JointGetter getJoint = [] (const Input& input, int timestamp) -> JointValue { return JointValue(); };
AvailableInputGetter getAvailabeInputs = [] () -> AvailableInput { return QVector<InputPair>(); };
ResetBindings resetDeviceBindings = [] () -> bool { return true; };
typedef std::shared_ptr<DeviceProxy> Pointer;
};
// GetFreeDeviceID should be called before registering a device to use an ID not used by a different device.
uint16 getFreeDeviceID() { return _nextFreeDeviceID++; }
bool registerDevice(uint16 deviceID, const DeviceProxy::Pointer& device);
DeviceProxy::Pointer getDeviceProxy(const Input& input);
QString getDeviceName(uint16 deviceID) { return _registeredDevices[deviceID]->_name; }
QVector<InputPair> getAvailableInputs(uint16 deviceID) { return _registeredDevices[deviceID]->getAvailabeInputs(); }
void resetAllDeviceBindings();
// Actions are the output channels of the Mapper, that's what the InputChannel map to
// For now the Actions are hardcoded, this is bad, but we will fix that in the near future
@ -123,8 +135,14 @@ public:
NUM_ACTIONS,
};
std::vector<QString> _actionNames = std::vector<QString>(NUM_ACTIONS);
void createActionNames();
QVector<Action> getAllActions();
QString getActionName(Action action) { return UserInputMapper::_actionNames[(int) action]; }
float getActionState(Action action) const { return _actionStates[action]; }
// QVector<InputChannel>
void assignDefaulActionScales();
// Add input channel to the mapper and check that all the used channels are registered.
@ -146,21 +164,27 @@ public:
_input(input), _modifier(modifier), _action(action), _scale(scale) {}
InputChannel(const InputChannel& src) : InputChannel(src._input, src._modifier, src._action, src._scale) {}
InputChannel& operator = (const InputChannel& src) { _input = src._input; _modifier = src._modifier; _action = src._action; _scale = src._scale; return (*this); }
bool operator ==(const InputChannel& right) const { return _input == right._input && _modifier == right._modifier && _action == right._action && _scale == right._scale; }
bool hasModifier() { return _modifier.isValid(); }
};
typedef std::vector< InputChannel > InputChannels;
// Add a bunch of input channels, return the true number of channels that successfully were added
int addInputChannels(const InputChannels& channels);
// Remove the first found instance of the input channel from the input mapper, true if found
bool removeInputChannel(InputChannel channel);
void removeAllInputChannels();
void removeAllInputChannelsForDevice(uint16 device);
//Grab all the input channels currently in use, return the number
int getInputChannels(InputChannels& channels) const;
QVector<InputChannel> getAllInputsForDevice(uint16 device);
QVector<InputChannel> getInputChannelsForAction(UserInputMapper::Action action);
std::multimap<Action, InputChannel> getActionToInputsMap() { return _actionToInputsMap; }
// Update means go grab all the device input channels and update the output channel values
void update(float deltaTime);
// Default contruct allocate the poutput size with the current hardcoded action channels
UserInputMapper() { assignDefaulActionScales(); }
UserInputMapper();
protected:
typedef std::map<int, DeviceProxy::Pointer> DevicesMap;
@ -177,4 +201,12 @@ protected:
std::vector<float> _actionScales = std::vector<float>(NUM_ACTIONS, 1.0f);
};
Q_DECLARE_METATYPE(UserInputMapper::InputPair)
Q_DECLARE_METATYPE(QVector<UserInputMapper::InputPair>)
Q_DECLARE_METATYPE(UserInputMapper::Input)
Q_DECLARE_METATYPE(UserInputMapper::InputChannel)
Q_DECLARE_METATYPE(QVector<UserInputMapper::InputChannel>)
Q_DECLARE_METATYPE(UserInputMapper::Action)
Q_DECLARE_METATYPE(QVector<UserInputMapper::Action>)
#endif // hifi_UserInputMapper_h

View file

@ -52,6 +52,8 @@ class AbstractControllerScriptingInterface : public QObject {
Q_OBJECT
public slots:
virtual void registerControllerTypes(QScriptEngine* engine) = 0;
virtual bool isPrimaryButtonPressed() const = 0;
virtual glm::vec2 getPrimaryJoystickPosition() const = 0;

View file

@ -317,6 +317,7 @@ void ScriptEngine::init() {
registerAnimationTypes(this);
registerAvatarTypes(this);
registerAudioMetaTypes(this);
_controllerScriptingInterface->registerControllerTypes(this);
qScriptRegisterMetaType(this, EntityItemPropertiesToScriptValue, EntityItemPropertiesFromScriptValueHonorReadOnly);
qScriptRegisterMetaType(this, EntityItemIDtoScriptValue, EntityItemIDfromScriptValue);