diff --git a/interface/resources/controllers/keyboardMouse.json b/interface/resources/controllers/keyboardMouse.json index 71450a0c48..7b9a8d733a 100644 --- a/interface/resources/controllers/keyboardMouse.json +++ b/interface/resources/controllers/keyboardMouse.json @@ -1,22 +1,25 @@ { "name": "Keyboard/Mouse to Actions", "channels": [ - { "from": "Keyboard.W", "to": "Actions.LONGITUDINAL_FORWARD" }, - { "from": "Keyboard.S", "to": "Actions.LONGITUDINAL_BACKWARD" }, - { "from": "Keyboard.A", "to": "Actions.YAW_LEFT" }, - { "from": "Keyboard.D", "to": "Actions.YAW_RIGHT" }, { "from": "Keyboard.A", "when": "Keyboard.Shift", "to": "Actions.LATERAL_LEFT" }, { "from": "Keyboard.D", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" }, { "from": "Keyboard.A", "when": "Keyboard.RightMouseClick", "to": "Actions.LATERAL_LEFT" }, { "from": "Keyboard.D", "when": "Keyboard.RightMouseClick", "to": "Actions.LATERAL_RIGHT" }, + + { "from": "Keyboard.W", "to": "Actions.LONGITUDINAL_FORWARD" }, + { "from": "Keyboard.S", "to": "Actions.LONGITUDINAL_BACKWARD" }, + { "from": "Keyboard.A", "to": "Actions.YAW_LEFT" }, + { "from": "Keyboard.D", "to": "Actions.YAW_RIGHT" }, { "from": "Keyboard.C", "to": "Actions.VERTICAL_DOWN" }, { "from": "Keyboard.E", "to": "Actions.VERTICAL_UP" }, + { "from": "Keyboard.Up", "to": "Actions.LONGITUDINAL_FORWARD" }, { "from": "Keyboard.Down", "to": "Actions.LONGITUDINAL_BACKWARD" }, { "from": "Keyboard.Left", "to": "Actions.YAW_LEFT" }, { "from": "Keyboard.Right", "to": "Actions.YAW_RIGHT" }, { "from": "Keyboard.PgDown", "to": "Actions.VERTICAL_DOWN" }, { "from": "Keyboard.PgUp", "to": "Actions.VERTICAL_UP" }, + { "from": "Keyboard.MouseMoveLeft", "when": "Keyboard.RightMouseClick", "to": "Actions.YAW_LEFT" }, { "from": "Keyboard.MouseMoveRight", "when": "Keyboard.RightMouseClick", "to": "Actions.YAW_RIGHT" }, { "from": "Keyboard.MouseMoveUp", "when": "Keyboard.RightMouseClick", "to": "Actions.PITCH_UP" }, diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index fcb90a96cd..4418f94b3a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -629,12 +629,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : auto userInputMapper = DependencyManager::get(); connect(userInputMapper.data(), &UserInputMapper::actionEvent, _controllerScriptingInterface, &ControllerScriptingInterface::actionEvent); connect(userInputMapper.data(), &UserInputMapper::actionEvent, [this](int action, float state) { - if (state) { - switch (controller::Action(action)) { - case controller::Action::TOGGLE_MUTE: - DependencyManager::get()->toggleMute(); - break; - } + if (state && action == toInt(controller::Action::TOGGLE_MUTE)) { + DependencyManager::get()->toggleMute(); } }); diff --git a/libraries/controllers/src/controllers/Conditional.cpp b/libraries/controllers/src/controllers/Conditional.cpp new file mode 100644 index 0000000000..7173c80b76 --- /dev/null +++ b/libraries/controllers/src/controllers/Conditional.cpp @@ -0,0 +1,31 @@ +// +// Created by Bradley Austin Davis 2015/10/20 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "Conditional.h" + +#include + +#include "Endpoint.h" + +namespace controller { + + Conditional::Pointer Conditional::parse(const QJsonValue& json) { + return Conditional::Pointer(); + } + + bool EndpointConditional::satisfied() { + if (!_endpoint) { + return false; + } + auto value = _endpoint->value(); + if (value == 0.0f) { + return false; + } + return true; + } +} diff --git a/libraries/controllers/src/controllers/Conditional.h b/libraries/controllers/src/controllers/Conditional.h new file mode 100644 index 0000000000..d0226d5775 --- /dev/null +++ b/libraries/controllers/src/controllers/Conditional.h @@ -0,0 +1,63 @@ +// +// Created by Bradley Austin Davis 2015/10/20 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once +#ifndef hifi_Controllers_Conditional_h +#define hifi_Controllers_Conditional_h + +#include +#include + +#include + +#include + +#include "Endpoint.h" + +class QJsonValue; + +namespace controller { + /* + * encapsulates a source, destination and filters to apply + */ + class Conditional { + public: + using Pointer = std::shared_ptr; + using List = std::list; + using Factory = hifi::SimpleFactory; + + virtual bool satisfied() = 0; + virtual bool parseParameters(const QJsonValue& parameters) { return true; } + + static Pointer parse(const QJsonValue& json); + static void registerBuilder(const QString& name, Factory::Builder builder); + static Factory& getFactory() { return _factory; } + protected: + static Factory _factory; + }; + + class EndpointConditional : public Conditional { + public: + EndpointConditional(Endpoint::Pointer endpoint) : _endpoint(endpoint) { } + virtual bool satisfied() override; + private: + Endpoint::Pointer _endpoint; + }; + +} + +#define REGISTER_CONDITIONAL_CLASS(classEntry) \ + private: \ + using Registrar = Conditional::Factory::Registrar; \ + static Registrar _registrar; + +#define REGISTER_CONDITIONAL_CLASS_INSTANCE(classEntry, className) \ + classEntry::Registrar classEntry::_registrar(className, Conditional::getFactory()); + + +#endif diff --git a/libraries/controllers/src/controllers/Filter.cpp b/libraries/controllers/src/controllers/Filter.cpp index 94fb87c48e..bc31e9ea44 100644 --- a/libraries/controllers/src/controllers/Filter.cpp +++ b/libraries/controllers/src/controllers/Filter.cpp @@ -18,18 +18,27 @@ using namespace controller; +Filter::Factory Filter::_factory; + +REGISTER_FILTER_CLASS_INSTANCE(InvertFilter, "invert") +REGISTER_FILTER_CLASS_INSTANCE(ConstrainToIntegerFilter, "constrainToInteger") +REGISTER_FILTER_CLASS_INSTANCE(ConstrainToPositiveIntegerFilter, "constrainToPositiveInteger") +REGISTER_FILTER_CLASS_INSTANCE(ScaleFilter, "scale") +REGISTER_FILTER_CLASS_INSTANCE(ClampFilter, "clamp") +REGISTER_FILTER_CLASS_INSTANCE(DeadZoneFilter, "deadZone") +REGISTER_FILTER_CLASS_INSTANCE(PulseFilter, "pulse") + const QString JSON_FILTER_TYPE = QStringLiteral("type"); const QString JSON_FILTER_PARAMS = QStringLiteral("params"); -Filter::Factory Filter::_factory; Filter::Pointer Filter::parse(const QJsonObject& json) { // The filter is an object, now let s check for type and potential arguments Filter::Pointer filter; auto filterType = json[JSON_FILTER_TYPE]; if (filterType.isString()) { - filter.reset(Filter::getFactory().create(filterType.toString().toStdString())); + filter = Filter::getFactory().create(filterType.toString()); if (filter) { // Filter is defined, need to read the parameters and validate auto parameters = json[JSON_FILTER_PARAMS]; @@ -44,7 +53,6 @@ Filter::Pointer Filter::parse(const QJsonObject& json) { return Filter::Pointer(); } -ScaleFilter::FactoryEntryBuilder ScaleFilter::_factoryEntryBuilder; bool ScaleFilter::parseParameters(const QJsonArray& parameters) { if (parameters.size() > 1) { @@ -53,10 +61,6 @@ bool ScaleFilter::parseParameters(const QJsonArray& parameters) { return true; } -InvertFilter::FactoryEntryBuilder InvertFilter::_factoryEntryBuilder; - - -ClampFilter::FactoryEntryBuilder ClampFilter::_factoryEntryBuilder; bool ClampFilter::parseParameters(const QJsonArray& parameters) { if (parameters.size() > 1) { @@ -68,7 +72,6 @@ bool ClampFilter::parseParameters(const QJsonArray& parameters) { return true; } -DeadZoneFilter::FactoryEntryBuilder DeadZoneFilter::_factoryEntryBuilder; float DeadZoneFilter::apply(float value) const { float scale = 1.0f / (1.0f - _min); @@ -85,8 +88,6 @@ bool DeadZoneFilter::parseParameters(const QJsonArray& parameters) { return true; } -PulseFilter::FactoryEntryBuilder PulseFilter::_factoryEntryBuilder; - float PulseFilter::apply(float value) const { float result = 0.0f; @@ -110,7 +111,4 @@ bool PulseFilter::parseParameters(const QJsonArray& parameters) { return true; } -ConstrainToIntegerFilter::FactoryEntryBuilder ConstrainToIntegerFilter::_factoryEntryBuilder; - -ConstrainToPositiveIntegerFilter::FactoryEntryBuilder ConstrainToPositiveIntegerFilter::_factoryEntryBuilder; diff --git a/libraries/controllers/src/controllers/Filter.h b/libraries/controllers/src/controllers/Filter.h index 876f57c97d..1fa9833044 100644 --- a/libraries/controllers/src/controllers/Filter.h +++ b/libraries/controllers/src/controllers/Filter.h @@ -13,9 +13,10 @@ #include #include #include -#include #include +#include + #include #include @@ -23,81 +24,35 @@ class QJsonObject; class QJsonArray; - namespace controller { // Encapsulates part of a filter chain class Filter { public: - virtual float apply(float value) const = 0; - using Pointer = std::shared_ptr; using List = std::list; using Lambda = std::function; + using Factory = hifi::SimpleFactory; + virtual float apply(float value) const = 0; // Factory features virtual bool parseParameters(const QJsonArray& parameters) { return true; } - class Factory { - public: - - class Entry { - public: - virtual Filter* create() const = 0; - virtual const char* getName() const = 0; - - Entry() {}; - virtual ~Entry() {}; - }; - - template class ClassEntry : public Entry { - public: - virtual Filter* create() const { return (Filter*) new T(); } - virtual const char* getName() const { return T::getName(); }; - - ClassEntry() {}; - virtual ~ClassEntry() = default; - - class Builder { - public: - Builder() { - std::shared_ptr classEntry(new ClassEntry()); - Filter::getFactory().registerEntry(classEntry); - } - }; - }; - - using EntryMap = std::map>; - - void registerEntry(std::shared_ptr& entry) { - if (entry) { - _entries.insert(EntryMap::value_type(entry->getName(), entry)); - } - } - - Filter* create(const std::string& name) const { - const auto& entryIt = _entries.find(name); - if (entryIt != _entries.end()) { - return (*entryIt).second->create(); - } - return nullptr; - } - - protected: - EntryMap _entries; - }; - - static Filter::Pointer parse(const QJsonObject& json); + static Pointer parse(const QJsonObject& json); + static void registerBuilder(const QString& name, Factory::Builder builder); static Factory& getFactory() { return _factory; } protected: static Factory _factory; }; } -#define REGISTER_FILTER_CLASS(classEntry, className) \ - static const char* getName() { return className; } \ - using FactoryEntryBuilder = Filter::Factory::ClassEntry::Builder;\ - static FactoryEntryBuilder _factoryEntryBuilder; +#define REGISTER_FILTER_CLASS(classEntry) \ + private: \ + using Registrar = Filter::Factory::Registrar; \ + static Registrar _registrar; + +#define REGISTER_FILTER_CLASS_INSTANCE(classEntry, className) \ + classEntry::Registrar classEntry::_registrar(className, Filter::getFactory()); namespace controller { @@ -123,8 +78,8 @@ namespace controller { }; class ScaleFilter : public Filter { + REGISTER_FILTER_CLASS(ScaleFilter); public: - REGISTER_FILTER_CLASS(ScaleFilter, "scale"); ScaleFilter() {} ScaleFilter(float scale): _scale(scale) {} @@ -138,8 +93,8 @@ namespace controller { }; class InvertFilter : public ScaleFilter { + REGISTER_FILTER_CLASS(InvertFilter); public: - REGISTER_FILTER_CLASS(InvertFilter, "invert"); InvertFilter() : ScaleFilter(-1.0f) {} virtual bool parseParameters(const QJsonArray& parameters) { return true; } @@ -148,8 +103,8 @@ namespace controller { }; class ClampFilter : public Filter { + REGISTER_FILTER_CLASS(ClampFilter); public: - REGISTER_FILTER_CLASS(ClampFilter, "clamp"); ClampFilter(float min = 0.0, float max = 1.0) : _min(min), _max(max) {}; virtual float apply(float value) const override { @@ -162,8 +117,8 @@ namespace controller { }; class DeadZoneFilter : public Filter { + REGISTER_FILTER_CLASS(DeadZoneFilter); public: - REGISTER_FILTER_CLASS(DeadZoneFilter, "deadZone"); DeadZoneFilter(float min = 0.0) : _min(min) {}; virtual float apply(float value) const override; @@ -173,8 +128,8 @@ namespace controller { }; class PulseFilter : public Filter { + REGISTER_FILTER_CLASS(PulseFilter); public: - REGISTER_FILTER_CLASS(PulseFilter, "pulse"); PulseFilter() {} PulseFilter(float interval) : _interval(interval) {} @@ -189,8 +144,8 @@ namespace controller { }; class ConstrainToIntegerFilter : public Filter { + REGISTER_FILTER_CLASS(ConstrainToIntegerFilter); public: - REGISTER_FILTER_CLASS(ConstrainToIntegerFilter, "constrainToInteger"); ConstrainToIntegerFilter() {}; virtual float apply(float value) const override { @@ -200,8 +155,8 @@ namespace controller { }; class ConstrainToPositiveIntegerFilter : public Filter { + REGISTER_FILTER_CLASS(ConstrainToPositiveIntegerFilter); public: - REGISTER_FILTER_CLASS(ConstrainToPositiveIntegerFilter, "constrainToPositiveInteger"); ConstrainToPositiveIntegerFilter() {}; virtual float apply(float value) const override { diff --git a/libraries/controllers/src/controllers/Mapping.h b/libraries/controllers/src/controllers/Mapping.h index ff988bf1b1..99328f310b 100644 --- a/libraries/controllers/src/controllers/Mapping.h +++ b/libraries/controllers/src/controllers/Mapping.h @@ -23,14 +23,12 @@ namespace controller { class Mapping { public: - // Map of source channels to route lists - using Map = std::map; using Pointer = std::shared_ptr; + using List = Route::List; Mapping(const QString& name) : name(name) {} - Map channelMappings; - + List routes; QString name; }; diff --git a/libraries/controllers/src/controllers/Pose.cpp b/libraries/controllers/src/controllers/Pose.cpp index e4674735b0..112e6e01a9 100644 --- a/libraries/controllers/src/controllers/Pose.cpp +++ b/libraries/controllers/src/controllers/Pose.cpp @@ -12,7 +12,7 @@ namespace controller { Pose::Pose(const vec3& translation, const quat& rotation, const vec3& velocity, const quat& angularVelocity) : - translation(translation), rotation(rotation), velocity(velocity), angularVelocity(angularVelocity) { } + translation(translation), rotation(rotation), velocity(velocity), angularVelocity(angularVelocity), valid (true) { } bool Pose::operator==(const Pose& right) const { // invalid poses return false for comparison, even against identical invalid poses, like NaN diff --git a/libraries/controllers/src/controllers/Route.h b/libraries/controllers/src/controllers/Route.h index 8b0e70050f..f290799482 100644 --- a/libraries/controllers/src/controllers/Route.h +++ b/libraries/controllers/src/controllers/Route.h @@ -12,8 +12,7 @@ #include "Endpoint.h" #include "Filter.h" - -class QJsonObject; +#include "Conditional.h" namespace controller { /* @@ -23,6 +22,7 @@ namespace controller { public: Endpoint::Pointer source; Endpoint::Pointer destination; + Conditional::Pointer conditional; Filter::List filters; using Pointer = std::shared_ptr; diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 6c530020a9..6ef64fc784 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -8,6 +8,8 @@ #include "UserInputMapper.h" +#include + #include #include @@ -203,6 +205,9 @@ public: virtual Pose pose() override { return _currentPose; } virtual void apply(const Pose& newValue, const Pose& oldValue, const Pointer& source) override { _currentPose = newValue; + if (!_currentPose.isValid()) { + return; + } if (!(_input == Input::INVALID_INPUT)) { auto userInputMapper = DependencyManager::get(); userInputMapper->setActionState(Action(_input.getChannel()), _currentPose); @@ -253,9 +258,7 @@ void UserInputMapper::registerDevice(InputDevice* device) { } else if (input.device == ACTIONS_DEVICE) { endpoint = std::make_shared(input); } else { - endpoint = std::make_shared([=] { - return proxy->getValue(input, 0); - }); + endpoint = std::make_shared(input); } _inputsByEndpoint[endpoint] = input; _endpointsByInput[input] = endpoint; @@ -265,17 +268,46 @@ void UserInputMapper::registerDevice(InputDevice* device) { auto mapping = loadMapping(device->getDefaultMappingConfig()); if (mapping) { _mappingsByDevice[deviceID] = mapping; - for (const auto& entry : mapping->channelMappings) { - const auto& source = entry.first; - const auto& routes = entry.second; - auto& list = _defaultMapping->channelMappings[source]; - list.insert(list.end(), routes.begin(), routes.end()); - } + auto& defaultRoutes = _defaultMapping->routes; + + // New routes for a device get injected IN FRONT of existing routes. Routes + // are processed in order so this ensures that the standard -> action processing + // takes place after all of the hardware -> standard or hardware -> action processing + // because standard -> action is the first set of routes added. + defaultRoutes.insert(defaultRoutes.begin(), mapping->routes.begin(), mapping->routes.end()); } emit hardwareChanged(); } +// FIXME remove the associated device mappings +void UserInputMapper::removeDevice(int deviceID) { + auto proxyEntry = _registeredDevices.find(deviceID); + if (_registeredDevices.end() == proxyEntry) { + qCWarning(controllers) << "Attempted to remove unknown device " << deviceID; + return; + } + auto proxy = proxyEntry->second; + auto mappingsEntry = _mappingsByDevice.find(deviceID); + if (_mappingsByDevice.end() != mappingsEntry) { + const auto& mapping = mappingsEntry->second; + const auto& deviceRoutes = mapping->routes; + std::set routeSet(deviceRoutes.begin(), deviceRoutes.end()); + + auto& defaultRoutes = _defaultMapping->routes; + std::remove_if(defaultRoutes.begin(), defaultRoutes.end(), [&](Route::Pointer route)->bool { + return routeSet.count(route) != 0; + }); + + _mappingsByDevice.erase(mappingsEntry); + } + + _registeredDevices.erase(proxyEntry); + + emit hardwareChanged(); +} + + DeviceProxy::Pointer UserInputMapper::getDeviceProxy(const Input& input) { auto device = _registeredDevices.find(input.getDevice()); if (device != _registeredDevices.end()) { @@ -347,11 +379,6 @@ Input UserInputMapper::findDeviceInput(const QString& inputName) const { return Input::INVALID_INPUT; } -// FIXME remove the associated device mappings -void UserInputMapper::removeDevice(int device) { - _registeredDevices.erase(device); -} - void fixBisectedAxis(float& full, float& negative, float& positive) { full = full + (negative * -1.0f) + positive; negative = full >= 0.0f ? 0.0f : full * -1.0f; @@ -534,12 +561,6 @@ Input UserInputMapper::makeStandardInput(controller::StandardPoseChannel pose) { return Input(STANDARD_DEVICE, pose, ChannelType::POSE); } -enum Pass { - HARDWARE_PASS, - STANDARD_PASS, - NUM_PASSES -}; - void UserInputMapper::update() { static auto deviceNames = getDeviceNames(); _overrideValues.clear(); @@ -549,71 +570,64 @@ void UserInputMapper::update() { // Now process the current values for each level of the stack for (auto& mapping : _activeMappings) { - for (int pass = HARDWARE_PASS; pass < NUM_PASSES; ++pass) { - for (const auto& mappingEntry : mapping->channelMappings) { - const auto& source = mappingEntry.first; - if (_inputsByEndpoint.count(source)) { - auto sourceInput = _inputsByEndpoint[source]; - if ((sourceInput.device == STANDARD_DEVICE) ^ (pass == STANDARD_PASS)) { - continue; - } - } + for (const auto& route : mapping->routes) { + const auto& source = route->source; + // Endpoints can only be read once (though a given mapping can route them to + // multiple places). Consider... If the default is to wire the A button to JUMP + // and someone else wires it to CONTEXT_MENU, I don't want both to occur when + // I press the button. The exception is if I'm wiring a control back to itself + // in order to adjust my interface, like inverting the Y axis on an analog stick + if (readEndpoints.count(source)) { + continue; + } - // Endpoints can only be read once (though a given mapping can route them to - // multiple places). Consider... If the default is to wire the A button to JUMP - // and someone else wires it to CONTEXT_MENU, I don't want both to occur when - // I press the button. The exception is if I'm wiring a control back to itself - // in order to adjust my interface, like inverting the Y axis on an analog stick - if (readEndpoints.count(source)) { + const auto& destination = route->destination; + // THis could happen if the route destination failed to create + // FIXME: Maybe do not create the route if the destination failed and avoid this case ? + if (!destination) { + continue; + } + + if (writtenEndpoints.count(destination)) { + continue; + } + + if (route->conditional) { + if (!route->conditional->satisfied()) { continue; } + } - // Apply the value to all the routes - const auto& routes = mappingEntry.second; + // Standard controller destinations can only be can only be used once. + if (getStandardDeviceID() == destination->getInput().getDevice()) { + writtenEndpoints.insert(destination); + } - for (const auto& route : routes) { - const auto& destination = route->destination; - // THis could happen if the route destination failed to create - // FIXME: Maybe do not create the route if the destination failed and avoid this case ? - if (!destination) { - continue; - } + // Only consume the input if the route isn't a loopback. + // This allows mappings like `mapping.from(xbox.RY).invert().to(xbox.RY);` + bool loopback = source == destination; + if (!loopback) { + readEndpoints.insert(source); + } - if (writtenEndpoints.count(destination)) { - continue; - } + // Fetch the value, may have been overriden by previous loopback routes + if (source->isPose()) { + Pose value = getPose(source); + // no filters yet for pose + destination->apply(value, Pose(), source); + } else { + // Fetch the value, may have been overriden by previous loopback routes + float value = getValue(source); - // Standard controller destinations can only be can only be used once. - if (getStandardDeviceID() == destination->getInput().getDevice()) { - writtenEndpoints.insert(destination); - } + // Apply each of the filters. + for (const auto& filter : route->filters) { + value = filter->apply(value); + } - // Only consume the input if the route isn't a loopback. - // This allows mappings like `mapping.from(xbox.RY).invert().to(xbox.RY);` - bool loopback = source == destination; - if (!loopback) { - readEndpoints.insert(source); - } - - // Fetch the value, may have been overriden by previous loopback routes - if (source->isPose()) { - Pose value = getPose(source); - // no filters yet for pose - destination->apply(value, Pose(), source); - } else { - float value = getValue(source); - - // Apply each of the filters. - for (const auto& filter : route->filters) { - value = filter->apply(value); - } - - if (loopback) { - _overrideValues[source] = value; - } else { - destination->apply(value, 0, source); - } - } + if (loopback) { + _overrideValues[source] = value; + } else { + destination->apply(value, 0, source); } } } @@ -777,6 +791,7 @@ Mapping::Pointer UserInputMapper::loadMapping(const QString& jsonFile) { const QString JSON_NAME = QStringLiteral("name"); const QString JSON_CHANNELS = QStringLiteral("channels"); const QString JSON_CHANNEL_FROM = QStringLiteral("from"); +const QString JSON_CHANNEL_WHEN = QStringLiteral("when"); const QString JSON_CHANNEL_TO = QStringLiteral("to"); const QString JSON_CHANNEL_FILTERS = QStringLiteral("filters"); @@ -791,6 +806,20 @@ Endpoint::Pointer UserInputMapper::parseEndpoint(const QJsonValue& value) { return Endpoint::Pointer(); } +Conditional::Pointer UserInputMapper::parseConditional(const QJsonValue& value) { + if (value.isString()) { + auto input = findDeviceInput(value.toString()); + auto endpoint = endpointFor(input); + if (!endpoint) { + return Conditional::Pointer(); + } + + return std::make_shared(endpoint); + } + + return Conditional::parse(value); +} + Route::Pointer UserInputMapper::parseRoute(const QJsonValue& value) { if (!value.isObject()) { return Route::Pointer(); @@ -809,11 +838,25 @@ Route::Pointer UserInputMapper::parseRoute(const QJsonValue& value) { return Route::Pointer(); } + if (obj.contains(JSON_CHANNEL_WHEN)) { + auto when = parseConditional(obj[JSON_CHANNEL_WHEN]); + if (!when) { + qWarning() << "Invalid route conditional " << obj[JSON_CHANNEL_TO]; + return Route::Pointer(); + } + result->conditional = when; + } + const auto& filtersValue = obj[JSON_CHANNEL_FILTERS]; + // FIXME support strings for filters with no parameters, both in the array and at the top level... + // i.e. + // { "from": "Standard.DU", "to" : "Actions.LONGITUDINAL_FORWARD", "filters" : "invert" }, + // and + // { "from": "Standard.DU", "to" : "Actions.LONGITUDINAL_FORWARD", "filters" : [ "invert", "constrainToInteger" ] }, if (filtersValue.isArray()) { auto filtersArray = filtersValue.toArray(); for (auto filterValue : filtersArray) { - if (filterValue.isObject()) { + if (!filterValue.isObject()) { qWarning() << "Invalid filter " << filterValue; return Route::Pointer(); } @@ -836,16 +879,14 @@ Mapping::Pointer UserInputMapper::parseMapping(const QJsonValue& json) { auto obj = json.toObject(); auto mapping = std::make_shared("default"); mapping->name = obj[JSON_NAME].toString(); - mapping->channelMappings.clear(); const auto& jsonChannels = obj[JSON_CHANNELS].toArray(); - Mapping::Map map; for (const auto& channelIt : jsonChannels) { Route::Pointer route = parseRoute(channelIt); if (!route) { qWarning() << "Couldn't parse route"; continue; } - mapping->channelMappings[route->source].push_back(route); + mapping->routes.push_back(route); } return mapping; } diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index 7561ba84af..ec1267cd0c 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -150,6 +150,7 @@ namespace controller { Endpoint::Pointer compositeEndpointFor(Endpoint::Pointer first, Endpoint::Pointer second); Mapping::Pointer parseMapping(const QJsonValue& json); Route::Pointer parseRoute(const QJsonValue& value); + Conditional::Pointer parseConditional(const QJsonValue& value); Endpoint::Pointer parseEndpoint(const QJsonValue& value); InputToEndpointMap _endpointsByInput; diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp index fe00bc44c4..14bcbca80e 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp @@ -41,7 +41,7 @@ void RouteBuilderProxy::to(const QScriptValue& destination) { void RouteBuilderProxy::to(const Endpoint::Pointer& destination) { auto sourceEndpoint = _route->source; _route->destination = destination; - _mapping->channelMappings[sourceEndpoint].push_back(_route); + _mapping->routes.push_back(_route); deleteLater(); } diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index f5f8b47a90..ff6c0ce2de 100755 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -183,24 +183,24 @@ void KeyboardMouseDevice::buildDeviceProxy(controller::DeviceProxy::Pointer prox availableInputs.append(Input::NamedPair(makeInput(Qt::Key_PageUp), QKeySequence(Qt::Key_PageUp).toString())); availableInputs.append(Input::NamedPair(makeInput(Qt::Key_PageDown), QKeySequence(Qt::Key_PageDown).toString())); - availableInputs.append(Input::NamedPair(makeInput(Qt::LeftButton), "Left Mouse Click")); - availableInputs.append(Input::NamedPair(makeInput(Qt::MiddleButton), "Middle Mouse Click")); - availableInputs.append(Input::NamedPair(makeInput(Qt::RightButton), "Right Mouse Click")); + availableInputs.append(Input::NamedPair(makeInput(Qt::LeftButton), "LeftMouseClick")); + availableInputs.append(Input::NamedPair(makeInput(Qt::MiddleButton), "MiddleMouseClick")); + availableInputs.append(Input::NamedPair(makeInput(Qt::RightButton), "RightMouseClick")); - availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_X_POS), "Mouse Move Right")); - availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_X_NEG), "Mouse Move Left")); - availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_Y_POS), "Mouse Move Up")); - availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_Y_NEG), "Mouse Move Down")); + availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_X_POS), "MouseMoveRight")); + availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_X_NEG), "MouseMoveLeft")); + availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_Y_POS), "MouseMoveUp")); + availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_Y_NEG), "MouseMoveDown")); - availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_WHEEL_Y_POS), "Mouse Wheel Right")); - availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_WHEEL_Y_NEG), "Mouse Wheel Left")); - availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_WHEEL_X_POS), "Mouse Wheel Up")); - availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_WHEEL_X_NEG), "Mouse Wheel Down")); + availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_WHEEL_Y_POS), "MouseWheelRight")); + availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_WHEEL_Y_NEG), "MouseWheelLeft")); + availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_WHEEL_X_POS), "MouseWheelUp")); + availableInputs.append(Input::NamedPair(makeInput(MOUSE_AXIS_WHEEL_X_NEG), "MouseWheelDown")); - availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_X_POS), "Touchpad Right")); - availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_X_NEG), "Touchpad Left")); - availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_Y_POS), "Touchpad Up")); - availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_Y_NEG), "Touchpad Down")); + availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_X_POS), "TouchpadRight")); + availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_X_NEG), "TouchpadLeft")); + availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_Y_POS), "TouchpadUp")); + availableInputs.append(Input::NamedPair(makeInput(TOUCH_AXIS_Y_NEG), "TouchpadDown")); return availableInputs; }; diff --git a/libraries/shared/src/shared/Factory.h b/libraries/shared/src/shared/Factory.h new file mode 100644 index 0000000000..6f1da6644b --- /dev/null +++ b/libraries/shared/src/shared/Factory.h @@ -0,0 +1,51 @@ +// +// Created by Bradley Austin Davis 2015/10/09 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once +#ifndef hifi_Shared_Factory_h +#define hifi_Shared_Factory_h + +#include +#include +#include + +namespace hifi { + + template + class SimpleFactory { + public: + using Pointer = std::shared_ptr; + using Builder = std::function; + using BuilderMap = std::map; + + void registerBuilder(const Key& name, Builder builder) { + // FIXME don't allow name collisions + _builders[name] = builder; + } + + Pointer create(const Key& name) const { + const auto& entryIt = _builders.find(name); + if (entryIt != _builders.end()) { + return (*entryIt).second(); + } + return Pointer(); + } + + template + class Registrar { + public: + Registrar(const Key& name, SimpleFactory& factory) { + factory.registerBuilder(name, [] { return std::make_shared(); }); + } + }; + protected: + BuilderMap _builders; + }; +} + +#endif