From 14f511350d5c3b88408cd48f710188ab2842a8a7 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Sat, 10 Oct 2015 22:34:11 -0700 Subject: [PATCH] Working on refactoring the xbox hardware access and wiring up test code --- .../controllers/src/controllers/Endpoint.cpp | 80 +- .../controllers/src/controllers/Endpoint.h | 31 +- .../controllers/src/controllers/Filter.cpp | 2 +- .../controllers/src/controllers/Filter.h | 2 +- .../controllers/src/controllers/Logging.cpp | 11 + .../controllers/src/controllers/Logging.h | 16 + .../controllers/src/controllers/Mapping.cpp | 60 +- .../controllers/src/controllers/Mapping.h | 9 +- .../NewControllerScriptingInterface.cpp | 386 ++++++++-- .../NewControllerScriptingInterface.h | 65 +- .../controllers/src/controllers/Route.cpp | 3 +- libraries/controllers/src/controllers/Route.h | 2 +- .../controllers/impl/MappingBuilderProxy.cpp | 33 +- .../controllers/impl/MappingBuilderProxy.h | 20 +- .../controllers/impl/RouteBuilderProxy.cpp | 76 +- .../src/controllers/impl/RouteBuilderProxy.h | 24 +- .../src/input-plugins/InputPlugin.cpp | 4 +- .../src/input-plugins/Joystick.cpp | 176 ++--- .../src/input-plugins/Joystick.h | 19 +- .../src/input-plugins/KeyboardMouseDevice.cpp | 7 +- .../src/input-plugins/KeyboardMouseDevice.h | 7 +- .../src/input-plugins/StandardController.cpp | 157 ++-- .../src/input-plugins/StandardController.h | 39 +- .../src/input-plugins/StandardControls.h | 55 ++ .../src/input-plugins/UserInputMapper.cpp | 703 +++++++++--------- .../src/input-plugins/UserInputMapper.h | 20 +- tests/controllers/qml/Xbox.qml | 99 +++ tests/controllers/qml/content.qml | 234 +++--- .../controllers/qml/controls/AnalogButton.qml | 45 ++ .../controllers/qml/controls/AnalogStick.qml | 50 ++ .../qml/controls/ScrollingGraph.qml | 104 +++ .../controllers/qml/controls/ToggleButton.qml | 43 ++ tests/controllers/qml/xbox/DPad.qml | 43 ++ .../controllers/qml/xbox/LeftAnalogStick.qml | 21 + .../controllers/qml/xbox/RightAnalogStick.qml | 21 + tests/controllers/qml/xbox/XboxButtons.qml | 46 ++ .../qml/xbox/xbox360-controller-md.png | Bin 0 -> 29588 bytes tests/controllers/src/main.cpp | 274 +++---- 38 files changed, 1815 insertions(+), 1172 deletions(-) create mode 100644 libraries/controllers/src/controllers/Logging.cpp create mode 100644 libraries/controllers/src/controllers/Logging.h create mode 100644 libraries/input-plugins/src/input-plugins/StandardControls.h create mode 100644 tests/controllers/qml/Xbox.qml create mode 100644 tests/controllers/qml/controls/AnalogButton.qml create mode 100644 tests/controllers/qml/controls/AnalogStick.qml create mode 100644 tests/controllers/qml/controls/ScrollingGraph.qml create mode 100644 tests/controllers/qml/controls/ToggleButton.qml create mode 100644 tests/controllers/qml/xbox/DPad.qml create mode 100644 tests/controllers/qml/xbox/LeftAnalogStick.qml create mode 100644 tests/controllers/qml/xbox/RightAnalogStick.qml create mode 100644 tests/controllers/qml/xbox/XboxButtons.qml create mode 100644 tests/controllers/qml/xbox/xbox360-controller-md.png diff --git a/libraries/controllers/src/controllers/Endpoint.cpp b/libraries/controllers/src/controllers/Endpoint.cpp index dddacc5ae5..3f1d12b9de 100644 --- a/libraries/controllers/src/controllers/Endpoint.cpp +++ b/libraries/controllers/src/controllers/Endpoint.cpp @@ -8,84 +8,6 @@ #include "Endpoint.h" -#include -#include +namespace controller { -namespace Controllers { - - // Ex: xbox.RY, xbox.A .... - class HardwareEndpoint : public Endpoint { - public: - virtual float value() override { - // ... - } - - virtual void apply(float newValue, float oldValue, const Endpoint& source) override { - // Default does nothing, but in theory this could be something like vibration - // mapping.from(xbox.X).to(xbox.Vibrate) - } - }; - - // Ex: Standard.RY, Action.Yaw - class VirtualEndpoint : public Endpoint { - public: - virtual void apply(float newValue) { - if (newValue != _lastValue) { - _lastValue = newValue; - } - } - - virtual float value() { - return _lastValue; - } - - float _lastValue; - }; - - float currentTime() { - return 0; - } - /* - * A function which provides input - */ - class FunctionEndpoint : public Endpoint { - public: - - virtual float value() override { - float now = currentTime(); - float delta = now - _lastCalled; - float result = _inputFunction.call(_object, QScriptValue(delta)).toNumber(); - _lastCalled = now; - return result; - } - - virtual void apply(float newValue, float oldValue, const Endpoint& source) override { - if (newValue != oldValue) { - //_outputFunction.call(newValue, oldValue, source); - } - } - - float _lastValue{ NAN }; - float _lastCalled{ 0 }; - QScriptValue _outputFunction; - QScriptValue _inputFunction; - QScriptValue _object; - }; - - - - // FIXME how do we handle dynamic changes in connected hardware? - const Endpoint::List& Endpoint::getHardwareEndpoints() { - static Endpoint::List ACTIVE_HARDWARE_ENDPOINTS; - static std::once_flag once; - std::call_once(once, [&] { - auto userInputMapper = DependencyManager::get(); - // TODO populate ACTIVE_HARDWARE with all the connected devices - // For each connected device - // for each input channel - // build a HardwareEndpoint instance around the input channel and add it to the list - }); - - return ACTIVE_HARDWARE_ENDPOINTS; - } } diff --git a/libraries/controllers/src/controllers/Endpoint.h b/libraries/controllers/src/controllers/Endpoint.h index 48cdf015fa..bea33517f5 100644 --- a/libraries/controllers/src/controllers/Endpoint.h +++ b/libraries/controllers/src/controllers/Endpoint.h @@ -12,26 +12,45 @@ #include #include +#include + +#include class QScriptValue; -namespace Controllers { +namespace controller { /* * Encapsulates a particular input / output, * i.e. Hydra.Button0, Standard.X, Action.Yaw */ class Endpoint { public: - virtual float value() { return 0; } // = 0; - virtual void apply(float newValue, float oldValue, const Endpoint& source) {} // = 0; - using Pointer = std::shared_ptr; using List = std::list; + using Pair = std::pair; + using ReadLambda = std::function; + using WriteLambda = std::function; - static const List& getHardwareEndpoints(); - static Pointer getEndpoint(const QScriptValue& value); + Endpoint(const UserInputMapper::Input& id) : _id(id) {} + virtual float value() = 0; + virtual void apply(float newValue, float oldValue, const Pointer& source) = 0; + const UserInputMapper::Input& getId() { return _id; } + protected: + UserInputMapper::Input _id; }; + class LambdaEndpoint : public Endpoint { + public: + LambdaEndpoint(ReadLambda readLambda, WriteLambda writeLambda = [](float) {}) + : Endpoint(UserInputMapper::Input::INVALID_INPUT), _readLambda(readLambda), _writeLambda(writeLambda) { } + + virtual float value() override { return _readLambda(); } + virtual void apply(float newValue, float oldValue, const Pointer& source) override { _writeLambda(newValue); } + + private: + ReadLambda _readLambda; + WriteLambda _writeLambda; + }; } #endif diff --git a/libraries/controllers/src/controllers/Filter.cpp b/libraries/controllers/src/controllers/Filter.cpp index e0c6adfcac..17715eceff 100644 --- a/libraries/controllers/src/controllers/Filter.cpp +++ b/libraries/controllers/src/controllers/Filter.cpp @@ -11,7 +11,7 @@ #include #include -namespace Controllers { +namespace controller { Filter::Pointer Filter::parse(const QJsonObject& json) { // FIXME parse the json object and determine the instance type to create diff --git a/libraries/controllers/src/controllers/Filter.h b/libraries/controllers/src/controllers/Filter.h index de58dc3647..f3978e2c0a 100644 --- a/libraries/controllers/src/controllers/Filter.h +++ b/libraries/controllers/src/controllers/Filter.h @@ -19,7 +19,7 @@ class QJsonObject; -namespace Controllers { +namespace controller { // Encapsulates part of a filter chain class Filter { diff --git a/libraries/controllers/src/controllers/Logging.cpp b/libraries/controllers/src/controllers/Logging.cpp new file mode 100644 index 0000000000..ae6b523a45 --- /dev/null +++ b/libraries/controllers/src/controllers/Logging.cpp @@ -0,0 +1,11 @@ +// +// Created by Bradley Austin Davis 2015/10/11 +// 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 "Logging.h" + +Q_LOGGING_CATEGORY(controllers, "hifi.controllers") diff --git a/libraries/controllers/src/controllers/Logging.h b/libraries/controllers/src/controllers/Logging.h new file mode 100644 index 0000000000..d74ddae59f --- /dev/null +++ b/libraries/controllers/src/controllers/Logging.h @@ -0,0 +1,16 @@ +// +// Created by Bradley Austin Davis 2015/10/11 +// 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 +// + +#ifndef hifi_Controllers_Logging_h +#define hifi_Controllers_Logging_h + +#include + +Q_DECLARE_LOGGING_CATEGORY(controllers) + +#endif diff --git a/libraries/controllers/src/controllers/Mapping.cpp b/libraries/controllers/src/controllers/Mapping.cpp index dd1ec14d1e..0063a1d24a 100644 --- a/libraries/controllers/src/controllers/Mapping.cpp +++ b/libraries/controllers/src/controllers/Mapping.cpp @@ -1,63 +1,5 @@ #include "Mapping.h" -namespace Controllers { +namespace controller { } -// class MappingsStack { -// std::list _stack; -// ValueMap _lastValues; -// -// void update() { -// EndpointList hardwareInputs = getHardwareEndpoints(); -// ValueMap currentValues; -// -// for (auto input : hardwareInputs) { -// currentValues[input] = input->value(); -// } -// -// // Now process the current values for each level of the stack -// for (auto& mapping : _stack) { -// update(mapping, currentValues); -// } -// -// _lastValues = currentValues; -// } -// -// void update(Mapping& mapping, ValueMap& values) { -// ValueMap updates; -// EndpointList consumedEndpoints; -// for (const auto& entry : values) { -// Endpoint* endpoint = entry.first; -// if (!mapping._channelMappings.count(endpoint)) { -// continue; -// } -// -// const Mapping::List& routes = mapping._channelMappings[endpoint]; -// consumedEndpoints.push_back(endpoint); -// for (const auto& route : routes) { -// float lastValue = 0; -// if (mapping._lastValues.count(endpoint)) { -// lastValue = mapping._lastValues[endpoint]; -// } -// float value = entry.second; -// for (const auto& filter : route._filters) { -// value = filter->apply(value, lastValue); -// } -// updates[route._destination] = value; -// } -// } -// -// // Update the last seen values -// mapping._lastValues = values; -// -// // Remove all the consumed inputs -// for (auto endpoint : consumedEndpoints) { -// values.erase(endpoint); -// } -// -// // Add all the updates (may restore some of the consumed data if a passthrough was created (i.e. source == dest) -// for (const auto& entry : updates) { -// values[entry.first] = entry.second; -// } -// } -// }; diff --git a/libraries/controllers/src/controllers/Mapping.h b/libraries/controllers/src/controllers/Mapping.h index 4154701478..5b54a1745b 100644 --- a/libraries/controllers/src/controllers/Mapping.h +++ b/libraries/controllers/src/controllers/Mapping.h @@ -11,6 +11,7 @@ #define hifi_Controllers_Mapping_h #include +#include #include @@ -18,19 +19,15 @@ #include "Filter.h" #include "Route.h" -namespace Controllers { - - using ValueMap = std::map; +namespace controller { class Mapping { public: // Map of source channels to route lists using Map = std::map; using Pointer = std::shared_ptr; - using List = std::list; Map _channelMappings; - ValueMap _lastValues; void parse(const QString& json); QString serialize(); @@ -38,4 +35,4 @@ namespace Controllers { } -#endif \ No newline at end of file +#endif diff --git a/libraries/controllers/src/controllers/NewControllerScriptingInterface.cpp b/libraries/controllers/src/controllers/NewControllerScriptingInterface.cpp index bc915ba1a6..f5d6276b91 100644 --- a/libraries/controllers/src/controllers/NewControllerScriptingInterface.cpp +++ b/libraries/controllers/src/controllers/NewControllerScriptingInterface.cpp @@ -1,100 +1,330 @@ -#include "NewControllerScriptingInterface.h" +// +// 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 +// +#include "NewControllerScriptingInterface.h" #include #include +#include + #include #include #include +#include +#include +#include #include "impl/MappingBuilderProxy.h" +#include "Logging.h" -namespace Controllers { - void NewControllerScriptingInterface::update() { - auto userInputMapper = DependencyManager::get(); - static float last = secTimestampNow(); - float now = secTimestampNow(); - userInputMapper->update(now - last); - last = now; +static const uint16_t ACTIONS_DEVICE = UserInputMapper::Input::INVALID_DEVICE - (uint16_t)1; + +namespace controller { + + + class VirtualEndpoint : public Endpoint { + public: + VirtualEndpoint(const UserInputMapper::Input& id = UserInputMapper::Input(-1)) + : Endpoint(id) { + } + + virtual float value() override { return _currentValue; } + virtual void apply(float newValue, float oldValue, const Pointer& source) override { _currentValue = newValue; } + + private: + float _currentValue{ 0.0f }; + }; + + + class JSEndpoint : public Endpoint { + public: + JSEndpoint(const QJSValue& callable) + : Endpoint(UserInputMapper::Input(-1)), _callable(callable) {} + + virtual float value() { + float result = (float)_callable.call().toNumber();; + return result; + } + + virtual void apply(float newValue, float oldValue, const Pointer& source) { + _callable.call(QJSValueList({ QJSValue(newValue) })); + } + + private: + QJSValue _callable; + }; + + class CompositeEndpoint : public Endpoint, Endpoint::Pair { + public: + CompositeEndpoint(Endpoint::Pointer first, Endpoint::Pointer second) + : Endpoint(UserInputMapper::Input(-1)), Pair(first, second) { } + + virtual float value() { + float result = first->value() * -1.0 + second->value(); + return result; + } + + virtual void apply(float newValue, float oldValue, const Pointer& source) { + // Composites are read only + } + + private: + Endpoint::Pointer _first; + Endpoint::Pointer _second; + }; + + QString sanatizeName(const QString& name) { + QString cleanName{ name }; + cleanName.remove(QRegularExpression{ "[\\(\\)\\.\\s]" }); + return cleanName; } - QObject* NewControllerScriptingInterface::newMapping() { - qDebug() << "Creating new Mapping proxy"; - return new MappingBuilderProxy(std::make_shared()); + QVariantMap createDeviceMap(const UserInputMapper::DeviceProxy* device) { + auto userInputMapper = DependencyManager::get(); + QVariantMap deviceMap; + for (const auto& inputMapping : device->getAvailabeInputs()) { + const auto& input = inputMapping.first; + const auto inputName = sanatizeName(inputMapping.second); + qCDebug(controllers) << "\tInput " << input.getChannel() << (int)input.getType() + << QString::number(input.getID(), 16) << ": " << inputName; + deviceMap.insert(inputName, input.getID()); + } + return deviceMap; + } + + NewControllerScriptingInterface::NewControllerScriptingInterface() { + auto userInputMapper = DependencyManager::get(); + auto devices = userInputMapper->getDevices(); + for (const auto& deviceMapping : devices) { + auto device = deviceMapping.second.get(); + auto deviceName = sanatizeName(device->getName()); + qCDebug(controllers) << "Device" << deviceMapping.first << ":" << deviceName; + // Expose the IDs to JS + _hardware.insert(deviceName, createDeviceMap(device)); + + // Create the endpoints + for (const auto& inputMapping : device->getAvailabeInputs()) { + const auto& input = inputMapping.first; + // Ignore aliases + if (_endpoints.count(input)) { + continue; + } + _endpoints[input] = std::make_shared([=] { + auto deviceProxy = userInputMapper->getDeviceProxy(input); + if (!deviceProxy) { + return 0.0f; + } + return deviceProxy->getValue(input, 0); + }); + } + } + + qCDebug(controllers) << "Setting up standard controller abstraction"; + auto standardDevice = userInputMapper->getStandardDevice(); + // Expose the IDs to JS + _standard = createDeviceMap(standardDevice.get()); + // Create the endpoints + for (const auto& inputMapping : standardDevice->getAvailabeInputs()) { + const auto& standardInput = inputMapping.first; + // Ignore aliases + if (_endpoints.count(standardInput)) { + continue; + } + _endpoints[standardInput] = std::make_shared(standardInput); + } + + auto actionNames = userInputMapper->getActionNames(); + int actionNumber = 0; + qCDebug(controllers) << "Setting up standard actions"; + for (const auto& actionName : actionNames) { + UserInputMapper::Input actionInput(ACTIONS_DEVICE, actionNumber++, UserInputMapper::ChannelType::AXIS); + qCDebug(controllers) << "\tAction: " << actionName << " " << QString::number(actionInput.getID(), 16); + // Expose the IDs to JS + _actions.insert(sanatizeName(actionName), actionInput.getID()); + + // Create the endpoints + // FIXME action endpoints need to accumulate values, and have them cleared at each frame + _endpoints[actionInput] = std::make_shared(); + } + } + + QObject* NewControllerScriptingInterface::newMapping(const QString& mappingName) { + if (_mappingsByName.count(mappingName)) { + qCWarning(controllers) << "Refusing to recreate mapping named " << mappingName; + } + qDebug() << "Creating new Mapping " << mappingName; + Mapping::Pointer mapping = std::make_shared(); + _mappingsByName[mappingName] = mapping; + return new MappingBuilderProxy(*this, mapping); + } + + void NewControllerScriptingInterface::enableMapping(const QString& mappingName, bool enable) { + auto iterator = _mappingsByName.find(mappingName); + if (_mappingsByName.end() == iterator) { + qCWarning(controllers) << "Request to enable / disable unknown mapping " << mappingName; + return; + } + + auto mapping = iterator->second; + if (enable) { + _activeMappings.push_front(mapping); + } else { + auto activeIterator = std::find(_activeMappings.begin(), _activeMappings.end(), mapping); + if (_activeMappings.end() == activeIterator) { + qCWarning(controllers) << "Attempted to disable inactive mapping " << mappingName; + return; + } + _activeMappings.erase(activeIterator); + } } float NewControllerScriptingInterface::getValue(const int& source) { - //UserInputMapper::Input input; input._id = source; - //auto userInputMapper = DependencyManager::get(); - //auto deviceProxy = userInputMapper->getDeviceProxy(input); - //return deviceProxy->getButton(input, 0) ? 1.0 : 0.0; + // return (sin(secTimestampNow()) + 1.0f) / 2.0f; + UserInputMapper::Input input(source); + auto iterator = _endpoints.find(input); + if (_endpoints.end() == iterator) { + return 0.0; + } - return (sin(secTimestampNow()) + 1.0f) / 2.0f; + const auto& endpoint = iterator->second; + return getValue(endpoint); + } + + float NewControllerScriptingInterface::getValue(const Endpoint::Pointer& endpoint) { + auto valuesIterator = _overrideValues.find(endpoint); + if (_overrideValues.end() != valuesIterator) { + return valuesIterator->second; + } + + return endpoint->value(); + } + + + void NewControllerScriptingInterface::update() { + static float last = secTimestampNow(); + float now = secTimestampNow(); + float delta = now - last; + last = now; + + foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { + inputPlugin->pluginUpdate(delta, false); + } + + auto userInputMapper = DependencyManager::get(); + userInputMapper->update(delta); + + _overrideValues.clear(); + EndpointSet readEndpoints; + EndpointSet writtenEndpoints; + // Now process the current values for each level of the stack + for (auto& mapping : _activeMappings) { + for (const auto& mappingEntry : mapping->_channelMappings) { + const auto& source = mappingEntry.first; + + // 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; + } + + // Apply the value to all the routes + const auto& routes = mappingEntry.second; + + for (const auto& route : routes) { + const auto& destination = route->_destination; + + if (writtenEndpoints.count(destination)) { + continue; + } + + // Standard controller destinations can only be can only be used once. + if (userInputMapper->getStandardDeviceID() == destination->getId().getDevice()) { + writtenEndpoints.insert(destination); + } + + // 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 + float value = getValue(source); + + // Apply each of the filters. + const auto& filters = route->_filters; + for (const auto& filter : route->_filters) { + value = filter->apply(value); + } + + if (loopback) { + _overrideValues[source] = value; + } else { + destination->apply(value, 0, source); + } + } + } + } + } + + + + Endpoint::Pointer NewControllerScriptingInterface::endpointFor(const QJSValue& endpoint) { + if (endpoint.isNumber()) { + return endpointFor(UserInputMapper::Input(endpoint.toInt())); + } + + if (endpoint.isCallable()) { + auto result = std::make_shared(endpoint); + return result; + } + + qWarning() << "Unsupported input type " << endpoint.toString(); + return Endpoint::Pointer(); + } + + Endpoint::Pointer NewControllerScriptingInterface::endpointFor(const QScriptValue& endpoint) { + if (endpoint.isNumber()) { + return endpointFor(UserInputMapper::Input(endpoint.toInt32())); + } + + qWarning() << "Unsupported input type " << endpoint.toString(); + return Endpoint::Pointer(); + } + + Endpoint::Pointer NewControllerScriptingInterface::endpointFor(const UserInputMapper::Input& inputId) { + auto iterator = _endpoints.find(inputId); + if (_endpoints.end() == iterator) { + qWarning() << "Unknown input: " << QString::number(inputId.getID(), 16); + return Endpoint::Pointer(); + } + return iterator->second; + } + + Endpoint::Pointer NewControllerScriptingInterface::compositeEndpointFor(Endpoint::Pointer first, Endpoint::Pointer second) { + EndpointPair pair(first, second); + Endpoint::Pointer result; + auto iterator = _compositeEndpoints.find(pair); + if (_compositeEndpoints.end() == iterator) { + result = std::make_shared(first, second); + _compositeEndpoints[pair] = result; + } else { + result = iterator->second; + } + return result; } } // namespace controllers - - - -// class MappingsStack { -// std::list _stack; -// ValueMap _lastValues; -// -// void update() { -// EndpointList hardwareInputs = getHardwareEndpoints(); -// ValueMap currentValues; -// -// for (auto input : hardwareInputs) { -// currentValues[input] = input->value(); -// } -// -// // Now process the current values for each level of the stack -// for (auto& mapping : _stack) { -// update(mapping, currentValues); -// } -// -// _lastValues = currentValues; -// } -// -// void update(Mapping& mapping, ValueMap& values) { -// ValueMap updates; -// EndpointList consumedEndpoints; -// for (const auto& entry : values) { -// Endpoint* endpoint = entry.first; -// if (!mapping._channelMappings.count(endpoint)) { -// continue; -// } -// -// const Mapping::List& routes = mapping._channelMappings[endpoint]; -// consumedEndpoints.push_back(endpoint); -// for (const auto& route : routes) { -// float lastValue = 0; -// if (mapping._lastValues.count(endpoint)) { -// lastValue = mapping._lastValues[endpoint]; -// } -// float value = entry.second; -// for (const auto& filter : route._filters) { -// value = filter->apply(value, lastValue); -// } -// updates[route._destination] = value; -// } -// } -// -// // Update the last seen values -// mapping._lastValues = values; -// -// // Remove all the consumed inputs -// for (auto endpoint : consumedEndpoints) { -// values.erase(endpoint); -// } -// -// // Add all the updates (may restore some of the consumed data if a passthrough was created (i.e. source == dest) -// for (const auto& entry : updates) { -// values[entry.first] = entry.second; -// } -// } -// }; //var mapping = Controller.newMapping(); //mapping.map(hydra.LeftButton0, actions.ContextMenu); //mapping.map(hydra.LeftButton0).to(xbox.RT); @@ -134,5 +364,3 @@ namespace Controllers { // mappingSnap.from(hydra.LX).to(function(newValue, oldValue) { // timeSinceLastYaw += deltaTime - -#include "NewControllerScriptingInterface.moc" diff --git a/libraries/controllers/src/controllers/NewControllerScriptingInterface.h b/libraries/controllers/src/controllers/NewControllerScriptingInterface.h index c4c42a2245..6bf0bda40d 100644 --- a/libraries/controllers/src/controllers/NewControllerScriptingInterface.h +++ b/libraries/controllers/src/controllers/NewControllerScriptingInterface.h @@ -10,21 +10,78 @@ #ifndef hifi_Controllers_NewControllerScriptingInterface_h #define hifi_Controllers_NewControllerScriptingInterface_h +#include +#include +#include +#include + #include #include +#include +#include + +#include + #include "Mapping.h" class QScriptValue; -namespace Controllers { +namespace controller { class NewControllerScriptingInterface : public QObject { Q_OBJECT - + Q_PROPERTY(QVariantMap Hardware READ getHardware CONSTANT FINAL) + Q_PROPERTY(QVariantMap Actions READ getActions CONSTANT FINAL) + Q_PROPERTY(QVariantMap Standard READ getStandard CONSTANT FINAL) + public: - Q_INVOKABLE void update(); - Q_INVOKABLE QObject* newMapping(); + NewControllerScriptingInterface(); Q_INVOKABLE float getValue(const int& source); + + Q_INVOKABLE void update(); + Q_INVOKABLE QObject* newMapping(const QString& mappingName); + Q_INVOKABLE void enableMapping(const QString& mappingName, bool enable = true); + Q_INVOKABLE void disableMapping(const QString& mappingName) { + enableMapping(mappingName, false); + } + + + const QVariantMap& getHardware() { return _hardware; } + const QVariantMap& getActions() { return _actions; } + const QVariantMap& getStandard() { return _standard; } + + private: + + // FIXME move to unordered set / map + using MappingMap = std::map; + using MappingStack = std::list; + using InputToEndpointMap = std::map; + using EndpointSet = std::unordered_set; + using ValueMap = std::map; + using EndpointPair = std::pair; + using EndpointPairMap = std::map; + + void update(Mapping::Pointer& mapping, EndpointSet& consumed); + float getValue(const Endpoint::Pointer& endpoint); + Endpoint::Pointer endpointFor(const QJSValue& endpoint); + Endpoint::Pointer endpointFor(const QScriptValue& endpoint); + Endpoint::Pointer endpointFor(const UserInputMapper::Input& endpoint); + Endpoint::Pointer compositeEndpointFor(Endpoint::Pointer first, Endpoint::Pointer second); + + friend class MappingBuilderProxy; + friend class RouteBuilderProxy; + private: + uint16_t _nextFunctionId; + InputToEndpointMap _endpoints; + EndpointPairMap _compositeEndpoints; + + ValueMap _overrideValues; + MappingMap _mappingsByName; + MappingStack _activeMappings; + + QVariantMap _hardware; + QVariantMap _actions; + QVariantMap _standard; }; } diff --git a/libraries/controllers/src/controllers/Route.cpp b/libraries/controllers/src/controllers/Route.cpp index 29fb9b04eb..b9116d2813 100644 --- a/libraries/controllers/src/controllers/Route.cpp +++ b/libraries/controllers/src/controllers/Route.cpp @@ -12,10 +12,11 @@ #include "Endpoint.h" #include "Filter.h" +#include "Logging.h" class QJsonObject; -namespace Controllers { +namespace controller { /* * encapsulates a source, destination and filters to apply diff --git a/libraries/controllers/src/controllers/Route.h b/libraries/controllers/src/controllers/Route.h index 9459369d18..01770a87d1 100644 --- a/libraries/controllers/src/controllers/Route.h +++ b/libraries/controllers/src/controllers/Route.h @@ -15,7 +15,7 @@ class QJsonObject; -namespace Controllers { +namespace controller { /* * encapsulates a source, destination and filters to apply diff --git a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.cpp b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.cpp index e16fa511db..4e2c6a4d8c 100644 --- a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.cpp +++ b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.cpp @@ -12,22 +12,33 @@ #include #include "RouteBuilderProxy.h" +#include "../NewControllerScriptingInterface.h" +#include "../Logging.h" -namespace Controllers { +namespace controller { -QObject* MappingBuilderProxy::from(const QString& source) { - qDebug() << "Creating new Route builder proxy from " << source; +QObject* MappingBuilderProxy::from(const QJSValue& source) { + qCDebug(controllers) << "Creating new Route builder proxy from " << source.toString(); + auto sourceEndpoint = _parent.endpointFor(source); + return from(sourceEndpoint); +} + +QObject* MappingBuilderProxy::from(const QScriptValue& source) { + qCDebug(controllers) << "Creating new Route builder proxy from " << source.toString(); + auto sourceEndpoint = _parent.endpointFor(source); + return from(sourceEndpoint); +} + +QObject* MappingBuilderProxy::from(const Endpoint::Pointer& source) { auto route = Route::Pointer(new Route()); - route->_source = endpointFor(source); - return new RouteBuilderProxy(this, route); + route->_source = source; + return new RouteBuilderProxy(_parent, _mapping, route); } -Endpoint::Pointer MappingBuilderProxy::endpointFor(const QString& endpoint) { - static QHash ENDPOINTS; - if (!ENDPOINTS.contains(endpoint)) { - ENDPOINTS[endpoint] = std::make_shared(); - } - return ENDPOINTS[endpoint]; +QObject* MappingBuilderProxy::join(const QJSValue& source1, const QJSValue& source2) { + auto source1Endpoint = _parent.endpointFor(source1); + auto source2Endpoint = _parent.endpointFor(source2); + return from(_parent.compositeEndpointFor(source1Endpoint, source2Endpoint)); } } diff --git a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h index 6dac38b21e..b5e02bbfdf 100644 --- a/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/MappingBuilderProxy.h @@ -13,20 +13,30 @@ #include #include "../Mapping.h" +#include "../Endpoint.h" -namespace Controllers { +class QJSValue; +class QScriptValue; + +namespace controller { + +class NewControllerScriptingInterface; class MappingBuilderProxy : public QObject { Q_OBJECT public: - MappingBuilderProxy(Mapping::Pointer mapping) - : _mapping(mapping) { } + MappingBuilderProxy(NewControllerScriptingInterface& parent, Mapping::Pointer mapping) + : _parent(parent), _mapping(mapping) { } - Q_INVOKABLE QObject* from(const QString& fromEndpoint); + Q_INVOKABLE QObject* from(const QJSValue& source); + Q_INVOKABLE QObject* from(const QScriptValue& source); + Q_INVOKABLE QObject* join(const QJSValue& source1, const QJSValue& source2); protected: + QObject* from(const Endpoint::Pointer& source); + friend class RouteBuilderProxy; - Endpoint::Pointer endpointFor(const QString& endpoint); + NewControllerScriptingInterface& _parent; Mapping::Pointer _mapping; }; diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp index d1659573e4..e6b67e9ca6 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp @@ -12,17 +12,47 @@ #include #include "MappingBuilderProxy.h" +#include "../NewControllerScriptingInterface.h" +#include "../Logging.h" -namespace Controllers { +namespace controller { -void RouteBuilderProxy::to(const QString& destination) { - qDebug() << "Completed route: " << destination; +void RouteBuilderProxy::to(const QJSValue& destination) { + qCDebug(controllers) << "Completing route " << destination.toString(); + auto destinationEndpoint = _parent.endpointFor(destination); + return to(destinationEndpoint); +} + +void RouteBuilderProxy::to(const QScriptValue& destination) { + qCDebug(controllers) << "Completing route " << destination.toString(); + auto destinationEndpoint = _parent.endpointFor(destination); + return to(destinationEndpoint); +} + +void RouteBuilderProxy::to(const Endpoint::Pointer& destination) { auto sourceEndpoint = _route->_source; - auto& mapping = _parent->_mapping; - mapping->_channelMappings[sourceEndpoint].push_back(_route); + _route->_destination = destination; + _mapping->_channelMappings[sourceEndpoint].push_back(_route); deleteLater(); } +QObject* RouteBuilderProxy::filter(const QJSValue& expression) { + if (expression.isCallable()) { + addFilter([=](float value) { + QJSValue originalExpression = expression; + QJSValueList params({ QJSValue(value) }); + auto result = originalExpression.call(params); + return (float)(result.toNumber()); + }); + } + return this; +} + +QObject* RouteBuilderProxy::filter(const QScriptValue& expression) { + return this; +} + + QObject* RouteBuilderProxy::clamp(float min, float max) { addFilter([=](float value) { return glm::clamp(value, min, max); @@ -45,7 +75,7 @@ QObject* RouteBuilderProxy::deadZone(float min) { assert(min < 1.0f); float scale = 1.0f / (1.0f - min); addFilter([=](float value) { - if (value < min) { + if (abs(value) < min) { return 0.0f; } return (value - min) * scale; @@ -68,6 +98,40 @@ QObject* RouteBuilderProxy::constrainToPositiveInteger() { return this; } + +class PulseFilter : public Filter { +public: + PulseFilter(float interval) : _interval(interval) {} + + virtual float apply(float value) const override { + float result = 0.0; + + if (0.0 != value) { + float now = secTimestampNow(); + float delta = now - _lastEmitTime; + if (delta >= _interval) { + _lastEmitTime = now; + result = value; + } + } + + return result; + } + +private: + mutable float _lastEmitTime{ -std::numeric_limits::max() }; + const float _interval; +}; + + +QObject* RouteBuilderProxy::pulse(float interval) { + Filter::Pointer filter = std::make_shared(interval); + addFilter(filter); + return this; +} + + + void RouteBuilderProxy::addFilter(Filter::Lambda lambda) { Filter::Pointer filterPointer = std::make_shared < LambdaFilter > (lambda); addFilter(filterPointer); diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h index 516712b969..63cd106edb 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.h @@ -12,19 +12,28 @@ #include #include "../Filter.h" #include "../Route.h" +#include "../Mapping.h" -namespace Controllers { +class QJSValue; +class QScriptValue; -class MappingBuilderProxy; +namespace controller { + +class NewControllerScriptingInterface; class RouteBuilderProxy : public QObject { Q_OBJECT public: - RouteBuilderProxy(MappingBuilderProxy* parent, Route::Pointer route) - : _parent(parent), _route(route) { } + RouteBuilderProxy(NewControllerScriptingInterface& parent, Mapping::Pointer mapping, Route::Pointer route) + : _parent(parent), _mapping(mapping), _route(route) { } - Q_INVOKABLE void to(const QString& destination); + Q_INVOKABLE void to(const QJSValue& destination); + Q_INVOKABLE void to(const QScriptValue& destination); + + Q_INVOKABLE QObject* filter(const QJSValue& expression); + Q_INVOKABLE QObject* filter(const QScriptValue& expression); Q_INVOKABLE QObject* clamp(float min, float max); + Q_INVOKABLE QObject* pulse(float interval); Q_INVOKABLE QObject* scale(float multiplier); Q_INVOKABLE QObject* invert(); Q_INVOKABLE QObject* deadZone(float min); @@ -32,10 +41,13 @@ class RouteBuilderProxy : public QObject { Q_INVOKABLE QObject* constrainToPositiveInteger(); private: + void to(const Endpoint::Pointer& destination); void addFilter(Filter::Lambda lambda); void addFilter(Filter::Pointer filter); + Mapping::Pointer _mapping; Route::Pointer _route; - MappingBuilderProxy* _parent; + + NewControllerScriptingInterface& _parent; }; } diff --git a/libraries/input-plugins/src/input-plugins/InputPlugin.cpp b/libraries/input-plugins/src/input-plugins/InputPlugin.cpp index 227bd12e1b..b52dd3f658 100644 --- a/libraries/input-plugins/src/input-plugins/InputPlugin.cpp +++ b/libraries/input-plugins/src/input-plugins/InputPlugin.cpp @@ -22,8 +22,8 @@ InputPluginList getInputPlugins() { InputPlugin* PLUGIN_POOL[] = { new KeyboardMouseDevice(), new SDL2Manager(), - new SixenseManager(), - new ViveControllerManager(), + //new SixenseManager(), + //new ViveControllerManager(), nullptr }; diff --git a/libraries/input-plugins/src/input-plugins/Joystick.cpp b/libraries/input-plugins/src/input-plugins/Joystick.cpp index d0e2705e98..5c6f43c604 100644 --- a/libraries/input-plugins/src/input-plugins/Joystick.cpp +++ b/libraries/input-plugins/src/input-plugins/Joystick.cpp @@ -15,6 +15,8 @@ #include "Joystick.h" +#include "StandardControls.h" + const float CONTROLLER_THRESHOLD = 0.3f; #ifdef HAVE_SDL2 @@ -55,39 +57,14 @@ void Joystick::focusOutEvent() { }; #ifdef HAVE_SDL2 + void Joystick::handleAxisEvent(const SDL_ControllerAxisEvent& event) { SDL_GameControllerAxis axis = (SDL_GameControllerAxis) event.axis; - - switch (axis) { - case SDL_CONTROLLER_AXIS_LEFTX: - _axisStateMap[makeInput(LEFT_AXIS_X_POS).getChannel()] = (event.value > 0.0f) ? event.value / MAX_AXIS : 0.0f; - _axisStateMap[makeInput(LEFT_AXIS_X_NEG).getChannel()] = (event.value < 0.0f) ? -event.value / MAX_AXIS : 0.0f; - break; - case SDL_CONTROLLER_AXIS_LEFTY: - _axisStateMap[makeInput(LEFT_AXIS_Y_POS).getChannel()] = (event.value > 0.0f) ? event.value / MAX_AXIS : 0.0f; - _axisStateMap[makeInput(LEFT_AXIS_Y_NEG).getChannel()] = (event.value < 0.0f) ? -event.value / MAX_AXIS : 0.0f; - break; - case SDL_CONTROLLER_AXIS_RIGHTX: - _axisStateMap[makeInput(RIGHT_AXIS_X_POS).getChannel()] = (event.value > 0.0f) ? event.value / MAX_AXIS : 0.0f; - _axisStateMap[makeInput(RIGHT_AXIS_X_NEG).getChannel()] = (event.value < 0.0f) ? -event.value / MAX_AXIS : 0.0f; - break; - case SDL_CONTROLLER_AXIS_RIGHTY: - _axisStateMap[makeInput(RIGHT_AXIS_Y_POS).getChannel()] = (event.value > 0.0f) ? event.value / MAX_AXIS : 0.0f; - _axisStateMap[makeInput(RIGHT_AXIS_Y_NEG).getChannel()] = (event.value < 0.0f) ? -event.value / MAX_AXIS : 0.0f; - break; - case SDL_CONTROLLER_AXIS_TRIGGERRIGHT: - _axisStateMap[makeInput(RIGHT_SHOULDER).getChannel()] = event.value / MAX_AXIS; - break; - case SDL_CONTROLLER_AXIS_TRIGGERLEFT: - _axisStateMap[makeInput(LEFT_SHOULDER).getChannel()] = event.value / MAX_AXIS; - break; - default: - break; - } + _axisStateMap[makeInput((Controllers::StandardAxisChannel)axis).getChannel()] = (float)event.value / MAX_AXIS; } void Joystick::handleButtonEvent(const SDL_ControllerButtonEvent& event) { - auto input = makeInput((SDL_GameControllerButton) event.button); + auto input = makeInput((Controllers::StandardButtonChannel)event.button); bool newValue = event.state == SDL_PRESSED; if (newValue) { _buttonPressedMap.insert(input.getChannel()); @@ -95,6 +72,7 @@ void Joystick::handleButtonEvent(const SDL_ControllerButtonEvent& event) { _buttonPressedMap.erase(input.getChannel()); } } + #endif @@ -107,32 +85,57 @@ void Joystick::registerToUserInputMapper(UserInputMapper& mapper) { proxy->getAxis = [this] (const UserInputMapper::Input& input, int timestamp) -> float { return this->getAxis(input.getChannel()); }; proxy->getAvailabeInputs = [this] () -> QVector { QVector availableInputs; -#ifdef HAVE_SDL2 - availableInputs.append(UserInputMapper::InputPair(makeInput(SDL_CONTROLLER_BUTTON_A), "Bottom Button")); - availableInputs.append(UserInputMapper::InputPair(makeInput(SDL_CONTROLLER_BUTTON_B), "Right Button")); - availableInputs.append(UserInputMapper::InputPair(makeInput(SDL_CONTROLLER_BUTTON_X), "Left Button")); - availableInputs.append(UserInputMapper::InputPair(makeInput(SDL_CONTROLLER_BUTTON_Y), "Top Button")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(SDL_CONTROLLER_BUTTON_DPAD_UP), "DPad Up")); - availableInputs.append(UserInputMapper::InputPair(makeInput(SDL_CONTROLLER_BUTTON_DPAD_DOWN), "DPad Down")); - availableInputs.append(UserInputMapper::InputPair(makeInput(SDL_CONTROLLER_BUTTON_DPAD_LEFT), "DPad Left")); - availableInputs.append(UserInputMapper::InputPair(makeInput(SDL_CONTROLLER_BUTTON_DPAD_RIGHT), "DPad Right")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(SDL_CONTROLLER_BUTTON_LEFTSHOULDER), "L1")); - availableInputs.append(UserInputMapper::InputPair(makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), "R1")); - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_SHOULDER), "L2")); - availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_SHOULDER), "R2")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_AXIS_Y_NEG), "Left Stick Up")); - availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_AXIS_Y_POS), "Left Stick Down")); - availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_AXIS_X_POS), "Left Stick Right")); - availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_AXIS_X_NEG), "Left Stick Left")); - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_AXIS_Y_NEG), "Right Stick Up")); - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_AXIS_Y_POS), "Right Stick Down")); - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_AXIS_X_POS), "Right Stick Right")); - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_AXIS_X_NEG), "Right Stick Left")); + // Buttons + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::A), "A")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::B), "B")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::X), "X")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::Y), "Y")); + + // DPad + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::DU), "DU")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::DD), "DD")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::DL), "DL")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::DR), "DR")); + + // Bumpers + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::LB), "LB")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::RB), "RB")); + + // Stick press + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::LS), "LS")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::RS), "RS")); + + // Center buttons + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::START), "Start")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::BACK), "Back")); + + // Analog sticks + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::LY), "LY")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::LX), "LX")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::RY), "RY")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::RX), "RX")); + + // Triggers + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::LT), "LT")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::RT), "RT")); + + // Aliases, PlayStation style names + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::LB), "L1")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::RB), "R1")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::LT), "L2")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::RT), "R2")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::LS), "L3")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::RS), "R3")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::BACK), "Select")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::A), "Cross")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::B), "Circle")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::X), "Square")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::Y), "Triangle")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::DU), "Up")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::DD), "Down")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::DL), "Left")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::DR), "Right")); -#endif return availableInputs; }; proxy->resetDeviceBindings = [this, &mapper] () -> bool { @@ -150,76 +153,15 @@ void Joystick::assignDefaultInputMapping(UserInputMapper& mapper) { const float JOYSTICK_YAW_SPEED = 0.5f; const float JOYSTICK_PITCH_SPEED = 0.25f; const float BOOM_SPEED = 0.1f; - - // Y axes are flipped (up is negative) - // Left Joystick: Movement, strafing - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(LEFT_AXIS_Y_NEG), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(LEFT_AXIS_Y_POS), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(LEFT_AXIS_X_POS), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(LEFT_AXIS_X_NEG), JOYSTICK_MOVE_SPEED); - - // Right Joystick: Camera orientation - mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(RIGHT_AXIS_X_POS), JOYSTICK_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(RIGHT_AXIS_X_NEG), JOYSTICK_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(RIGHT_AXIS_Y_NEG), JOYSTICK_PITCH_SPEED); - mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(RIGHT_AXIS_Y_POS), JOYSTICK_PITCH_SPEED); - - // Dpad movement - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(SDL_CONTROLLER_BUTTON_DPAD_UP), DPAD_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(SDL_CONTROLLER_BUTTON_DPAD_DOWN), DPAD_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(SDL_CONTROLLER_BUTTON_DPAD_RIGHT), DPAD_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(SDL_CONTROLLER_BUTTON_DPAD_LEFT), DPAD_MOVE_SPEED); - - // Button controls - mapper.addInputChannel(UserInputMapper::VERTICAL_UP, makeInput(SDL_CONTROLLER_BUTTON_Y), DPAD_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::VERTICAL_DOWN, makeInput(SDL_CONTROLLER_BUTTON_X), DPAD_MOVE_SPEED); - - // Zoom - mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(RIGHT_SHOULDER), BOOM_SPEED); - mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(LEFT_SHOULDER), BOOM_SPEED); - - - // Hold front right shoulder button for precision controls - // Left Joystick: Movement, strafing - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(LEFT_AXIS_Y_NEG), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(LEFT_AXIS_Y_POS), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(LEFT_AXIS_X_POS), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(LEFT_AXIS_X_NEG), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_MOVE_SPEED/2.0f); - - // Right Joystick: Camera orientation - mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(RIGHT_AXIS_X_POS), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_YAW_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(RIGHT_AXIS_X_NEG), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_YAW_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(RIGHT_AXIS_Y_NEG), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_PITCH_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(RIGHT_AXIS_Y_POS), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_PITCH_SPEED/2.0f); - - // Dpad movement - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(SDL_CONTROLLER_BUTTON_DPAD_UP), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(SDL_CONTROLLER_BUTTON_DPAD_DOWN), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(SDL_CONTROLLER_BUTTON_DPAD_RIGHT), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(SDL_CONTROLLER_BUTTON_DPAD_LEFT), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - - // Button controls - mapper.addInputChannel(UserInputMapper::VERTICAL_UP, makeInput(SDL_CONTROLLER_BUTTON_Y), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::VERTICAL_DOWN, makeInput(SDL_CONTROLLER_BUTTON_X), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - - // Zoom - mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(RIGHT_SHOULDER), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), BOOM_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(LEFT_SHOULDER), makeInput(SDL_CONTROLLER_BUTTON_RIGHTSHOULDER), BOOM_SPEED/2.0f); - - mapper.addInputChannel(UserInputMapper::SHIFT, makeInput(SDL_CONTROLLER_BUTTON_LEFTSHOULDER)); - - mapper.addInputChannel(UserInputMapper::ACTION1, makeInput(SDL_CONTROLLER_BUTTON_B)); - mapper.addInputChannel(UserInputMapper::ACTION2, makeInput(SDL_CONTROLLER_BUTTON_A)); + #endif } -#ifdef HAVE_SDL2 -UserInputMapper::Input Joystick::makeInput(SDL_GameControllerButton button) { +UserInputMapper::Input Joystick::makeInput(Controllers::StandardButtonChannel button) { return UserInputMapper::Input(_deviceID, button, UserInputMapper::ChannelType::BUTTON); } -#endif -UserInputMapper::Input Joystick::makeInput(Joystick::JoystickAxisChannel axis) { +UserInputMapper::Input Joystick::makeInput(Controllers::StandardAxisChannel axis) { return UserInputMapper::Input(_deviceID, axis, UserInputMapper::ChannelType::AXIS); } diff --git a/libraries/input-plugins/src/input-plugins/Joystick.h b/libraries/input-plugins/src/input-plugins/Joystick.h index 2ba89da052..70949a8b83 100644 --- a/libraries/input-plugins/src/input-plugins/Joystick.h +++ b/libraries/input-plugins/src/input-plugins/Joystick.h @@ -21,6 +21,7 @@ #endif #include "InputDevice.h" +#include "StandardControls.h" class Joystick : public QObject, public InputDevice { Q_OBJECT @@ -31,18 +32,6 @@ class Joystick : public QObject, public InputDevice { #endif public: - enum JoystickAxisChannel { - LEFT_AXIS_X_POS = 0, - LEFT_AXIS_X_NEG, - LEFT_AXIS_Y_POS, - LEFT_AXIS_Y_NEG, - RIGHT_AXIS_X_POS, - RIGHT_AXIS_X_NEG, - RIGHT_AXIS_Y_POS, - RIGHT_AXIS_Y_NEG, - RIGHT_SHOULDER, - LEFT_SHOULDER, - }; const QString& getName() const { return _name; } @@ -55,10 +44,8 @@ public: Joystick() : InputDevice("Joystick") {} ~Joystick(); -#ifdef HAVE_SDL2 - UserInputMapper::Input makeInput(SDL_GameControllerButton button); -#endif - UserInputMapper::Input makeInput(Joystick::JoystickAxisChannel axis); + UserInputMapper::Input makeInput(Controllers::StandardButtonChannel button); + UserInputMapper::Input makeInput(Controllers::StandardAxisChannel axis); #ifdef HAVE_SDL2 Joystick(SDL_JoystickID instanceId, const QString& name, SDL_GameController* sdlGameController); diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index 36ae643a8e..202a767244 100755 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -10,6 +10,11 @@ // #include "KeyboardMouseDevice.h" +#include +#include +#include + + const QString KeyboardMouseDevice::NAME = "Keyboard/Mouse"; void KeyboardMouseDevice::update(float deltaTime, bool jointsCaptured) { @@ -81,7 +86,7 @@ void KeyboardMouseDevice::wheelEvent(QWheelEvent* event) { _axisStateMap[makeInput(MOUSE_AXIS_WHEEL_Y_NEG).getChannel()] = (currentMove.y() < 0 ? -currentMove.y() : 0.0f); } -glm::vec2 KeyboardMouseDevice::evalAverageTouchPoints(const QList& points) const { +glm::vec2 evalAverageTouchPoints(const QList& points) { glm::vec2 averagePoint(0.0f); if (points.count() > 0) { for (auto& point : points) { diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h index 6f703bc6f9..d96566e9d1 100644 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h @@ -12,11 +12,15 @@ #ifndef hifi_KeyboardMouseDevice_h #define hifi_KeyboardMouseDevice_h -#include #include #include "InputDevice.h" #include "InputPlugin.h" +class QTouchEvent; +class QKeyEvent; +class QMouseEvent; +class QWheelEvent; + class KeyboardMouseDevice : public InputPlugin, public InputDevice { Q_OBJECT public: @@ -100,7 +104,6 @@ protected: glm::vec2 _lastTouch; bool _isTouching = false; - glm::vec2 evalAverageTouchPoints(const QList& points) const; std::chrono::high_resolution_clock _clock; std::chrono::high_resolution_clock::time_point _lastTouchTime; }; diff --git a/libraries/input-plugins/src/input-plugins/StandardController.cpp b/libraries/input-plugins/src/input-plugins/StandardController.cpp index 4fb4a23654..040efca794 100644 --- a/libraries/input-plugins/src/input-plugins/StandardController.cpp +++ b/libraries/input-plugins/src/input-plugins/StandardController.cpp @@ -23,11 +23,6 @@ StandardController::~StandardController() { } void StandardController::update(float deltaTime, bool jointsCaptured) { - for (auto axisState : _axisStateMap) { - if (fabsf(axisState.second) < CONTROLLER_THRESHOLD) { - _axisStateMap[axisState.first] = 0.0f; - } - } } void StandardController::focusOutEvent() { @@ -44,119 +39,85 @@ void StandardController::registerToUserInputMapper(UserInputMapper& mapper) { proxy->getAxis = [this] (const UserInputMapper::Input& input, int timestamp) -> float { return this->getAxis(input.getChannel()); }; proxy->getAvailabeInputs = [this] () -> QVector { QVector availableInputs; - availableInputs.append(UserInputMapper::InputPair(makeInput(STANDARD_CONTROLLER_BUTTON_A), "Bottom Button")); - availableInputs.append(UserInputMapper::InputPair(makeInput(STANDARD_CONTROLLER_BUTTON_B), "Right Button")); - availableInputs.append(UserInputMapper::InputPair(makeInput(STANDARD_CONTROLLER_BUTTON_X), "Left Button")); - availableInputs.append(UserInputMapper::InputPair(makeInput(STANDARD_CONTROLLER_BUTTON_Y), "Top Button")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_UP), "DPad Up")); - availableInputs.append(UserInputMapper::InputPair(makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_DOWN), "DPad Down")); - availableInputs.append(UserInputMapper::InputPair(makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_LEFT), "DPad Left")); - availableInputs.append(UserInputMapper::InputPair(makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_RIGHT), "DPad Right")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(STANDARD_CONTROLLER_BUTTON_LEFTSHOULDER), "L1")); - availableInputs.append(UserInputMapper::InputPair(makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), "R1")); - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_SHOULDER), "L2")); - availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_SHOULDER), "R2")); - - availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_AXIS_Y_NEG), "Left Stick Up")); - availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_AXIS_Y_POS), "Left Stick Down")); - availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_AXIS_X_POS), "Left Stick Right")); - availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_AXIS_X_NEG), "Left Stick Left")); - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_AXIS_Y_NEG), "Right Stick Up")); - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_AXIS_Y_POS), "Right Stick Down")); - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_AXIS_X_POS), "Right Stick Right")); - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_AXIS_X_NEG), "Right Stick Left")); + // Buttons + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::A), "A")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::B), "B")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::X), "X")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::Y), "Y")); + + // DPad + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::DU), "DU")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::DD), "DD")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::DL), "DL")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::DR), "DR")); + + // Bumpers + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::LB), "LB")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::RB), "RB")); + + // Stick press + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::LS), "LS")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::RS), "RS")); + + // Center buttons + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::START), "Start")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::BACK), "Back")); + + // Analog sticks + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::LY), "LY")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::LX), "LX")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::RY), "RY")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::RX), "RX")); + + // Triggers + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::LT), "LT")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::RT), "RT")); + + // Poses + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::LeftPose), "LeftPose")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::RightPose), "RightPose")); + + // Aliases, PlayStation style names + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::LB), "L1")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::RB), "R1")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::LT), "L2")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::RT), "R2")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::LS), "L3")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::RS), "R3")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::BACK), "Select")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::A), "Cross")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::B), "Circle")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::X), "Square")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::Y), "Triangle")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::DU), "Up")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::DD), "Down")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::DL), "Left")); + availableInputs.append(UserInputMapper::InputPair(makeInput(Controllers::DR), "Right")); - availableInputs.append(UserInputMapper::InputPair(makeInput(LEFT_HAND), "Left Hand")); - availableInputs.append(UserInputMapper::InputPair(makeInput(RIGHT_HAND), "Right Hand")); return availableInputs; }; + proxy->resetDeviceBindings = [this, &mapper] () -> bool { mapper.removeAllInputChannelsForDevice(_deviceID); this->assignDefaultInputMapping(mapper); return true; }; + mapper.registerStandardDevice(proxy); } void StandardController::assignDefaultInputMapping(UserInputMapper& mapper) { - const float JOYSTICK_MOVE_SPEED = 1.0f; - const float DPAD_MOVE_SPEED = 0.5f; - const float JOYSTICK_YAW_SPEED = 0.5f; - const float JOYSTICK_PITCH_SPEED = 0.25f; - const float BOOM_SPEED = 0.1f; - - // Y axes are flipped (up is negative) - // Left StandardController: Movement, strafing - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(LEFT_AXIS_Y_NEG), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(LEFT_AXIS_Y_POS), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(LEFT_AXIS_X_POS), JOYSTICK_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(LEFT_AXIS_X_NEG), JOYSTICK_MOVE_SPEED); - - // Right StandardController: Camera orientation - mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(RIGHT_AXIS_X_POS), JOYSTICK_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(RIGHT_AXIS_X_NEG), JOYSTICK_YAW_SPEED); - mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(RIGHT_AXIS_Y_NEG), JOYSTICK_PITCH_SPEED); - mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(RIGHT_AXIS_Y_POS), JOYSTICK_PITCH_SPEED); - - // Dpad movement - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_UP), DPAD_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_DOWN), DPAD_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_RIGHT), DPAD_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_LEFT), DPAD_MOVE_SPEED); - - // Button controls - mapper.addInputChannel(UserInputMapper::VERTICAL_UP, makeInput(STANDARD_CONTROLLER_BUTTON_Y), DPAD_MOVE_SPEED); - mapper.addInputChannel(UserInputMapper::VERTICAL_DOWN, makeInput(STANDARD_CONTROLLER_BUTTON_X), DPAD_MOVE_SPEED); - - // Zoom - mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(RIGHT_SHOULDER), BOOM_SPEED); - mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(LEFT_SHOULDER), BOOM_SPEED); - - - // Hold front right shoulder button for precision controls - // Left StandardController: Movement, strafing - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(LEFT_AXIS_Y_NEG), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(LEFT_AXIS_Y_POS), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(LEFT_AXIS_X_POS), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(LEFT_AXIS_X_NEG), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_MOVE_SPEED/2.0f); - - // Right StandardController: Camera orientation - mapper.addInputChannel(UserInputMapper::YAW_RIGHT, makeInput(RIGHT_AXIS_X_POS), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_YAW_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::YAW_LEFT, makeInput(RIGHT_AXIS_X_NEG), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_YAW_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::PITCH_UP, makeInput(RIGHT_AXIS_Y_NEG), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_PITCH_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::PITCH_DOWN, makeInput(RIGHT_AXIS_Y_POS), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), JOYSTICK_PITCH_SPEED/2.0f); - - // Dpad movement - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_FORWARD, makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_UP), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LONGITUDINAL_BACKWARD, makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_DOWN), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LATERAL_RIGHT, makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_RIGHT), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::LATERAL_LEFT, makeInput(STANDARD_CONTROLLER_BUTTON_DPAD_LEFT), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - - // Button controls - mapper.addInputChannel(UserInputMapper::VERTICAL_UP, makeInput(STANDARD_CONTROLLER_BUTTON_Y), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::VERTICAL_DOWN, makeInput(STANDARD_CONTROLLER_BUTTON_X), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), DPAD_MOVE_SPEED/2.0f); - - // Zoom - mapper.addInputChannel(UserInputMapper::BOOM_IN, makeInput(RIGHT_SHOULDER), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), BOOM_SPEED/2.0f); - mapper.addInputChannel(UserInputMapper::BOOM_OUT, makeInput(LEFT_SHOULDER), makeInput(STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER), BOOM_SPEED/2.0f); - - mapper.addInputChannel(UserInputMapper::SHIFT, makeInput(STANDARD_CONTROLLER_BUTTON_LEFTSHOULDER)); - - mapper.addInputChannel(UserInputMapper::ACTION1, makeInput(STANDARD_CONTROLLER_BUTTON_B)); - mapper.addInputChannel(UserInputMapper::ACTION2, makeInput(STANDARD_CONTROLLER_BUTTON_A)); } -UserInputMapper::Input StandardController::makeInput(StandardController::StandardControllerButtonChannel button) { +UserInputMapper::Input StandardController::makeInput(Controllers::StandardButtonChannel button) { return UserInputMapper::Input(_deviceID, button, UserInputMapper::ChannelType::BUTTON); } -UserInputMapper::Input StandardController::makeInput(StandardController::StandardControllerAxisChannel axis) { +UserInputMapper::Input StandardController::makeInput(Controllers::StandardAxisChannel axis) { return UserInputMapper::Input(_deviceID, axis, UserInputMapper::ChannelType::AXIS); } -UserInputMapper::Input StandardController::makeInput(StandardController::StandardControllerPoseChannel pose) { +UserInputMapper::Input StandardController::makeInput(Controllers::StandardPoseChannel pose) { return UserInputMapper::Input(_deviceID, pose, UserInputMapper::ChannelType::POSE); } diff --git a/libraries/input-plugins/src/input-plugins/StandardController.h b/libraries/input-plugins/src/input-plugins/StandardController.h index f7a4215242..fa660e15b8 100644 --- a/libraries/input-plugins/src/input-plugins/StandardController.h +++ b/libraries/input-plugins/src/input-plugins/StandardController.h @@ -17,6 +17,8 @@ #include "InputDevice.h" +#include "StandardControls.h" + typedef std::shared_ptr StandardControllerPointer; class StandardController : public QObject, public InputDevice { @@ -24,37 +26,6 @@ class StandardController : public QObject, public InputDevice { Q_PROPERTY(QString name READ getName) public: - enum StandardControllerAxisChannel { - LEFT_AXIS_X_POS = 0, - LEFT_AXIS_X_NEG, - LEFT_AXIS_Y_POS, - LEFT_AXIS_Y_NEG, - RIGHT_AXIS_X_POS, - RIGHT_AXIS_X_NEG, - RIGHT_AXIS_Y_POS, - RIGHT_AXIS_Y_NEG, - RIGHT_SHOULDER, - LEFT_SHOULDER, - }; - enum StandardControllerButtonChannel { - STANDARD_CONTROLLER_BUTTON_A = 0, - STANDARD_CONTROLLER_BUTTON_B, - STANDARD_CONTROLLER_BUTTON_X, - STANDARD_CONTROLLER_BUTTON_Y, - - STANDARD_CONTROLLER_BUTTON_DPAD_UP, - STANDARD_CONTROLLER_BUTTON_DPAD_DOWN, - STANDARD_CONTROLLER_BUTTON_DPAD_LEFT, - STANDARD_CONTROLLER_BUTTON_DPAD_RIGHT, - - STANDARD_CONTROLLER_BUTTON_LEFTSHOULDER, - STANDARD_CONTROLLER_BUTTON_RIGHTSHOULDER, - }; - - enum StandardControllerPoseChannel { - LEFT_HAND = 0, - RIGHT_HAND, - }; const QString& getName() const { return _name; } @@ -67,9 +38,9 @@ public: StandardController() : InputDevice("Standard") {} ~StandardController(); - UserInputMapper::Input makeInput(StandardController::StandardControllerButtonChannel button); - UserInputMapper::Input makeInput(StandardController::StandardControllerAxisChannel axis); - UserInputMapper::Input makeInput(StandardController::StandardControllerPoseChannel pose); + UserInputMapper::Input makeInput(Controllers::StandardButtonChannel button); + UserInputMapper::Input makeInput(Controllers::StandardAxisChannel axis); + UserInputMapper::Input makeInput(Controllers::StandardPoseChannel pose); private: }; diff --git a/libraries/input-plugins/src/input-plugins/StandardControls.h b/libraries/input-plugins/src/input-plugins/StandardControls.h new file mode 100644 index 0000000000..9b79bbdae1 --- /dev/null +++ b/libraries/input-plugins/src/input-plugins/StandardControls.h @@ -0,0 +1,55 @@ +// +// 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 + +namespace Controllers { + + // Needs to match order and values of SDL_GameControllerButton + enum StandardButtonChannel { + // Button quad + A = 0, + B, + X, + Y, + // Center buttons + BACK, + GUIDE, + START, + // Stick press + LS, + RS, + // Bumper press + LB, + RB, + // DPad + DU, + DD, + DL, + DR + }; + + // Needs to match order and values of SDL_GameControllerAxis + enum StandardAxisChannel { + // Left Analog stick + LX = 0, + LY, + // Right Analog stick + RX, + RY, + // Triggers + LT, + RT + }; + + // No correlation to SDL + enum StandardPoseChannel { + LeftPose = 0, + RightPose + }; + +} diff --git a/libraries/input-plugins/src/input-plugins/UserInputMapper.cpp b/libraries/input-plugins/src/input-plugins/UserInputMapper.cpp index fad962345c..c29acc09af 100755 --- a/libraries/input-plugins/src/input-plugins/UserInputMapper.cpp +++ b/libraries/input-plugins/src/input-plugins/UserInputMapper.cpp @@ -1,335 +1,368 @@ -// -// UserInputMapper.cpp -// input-plugins/src/input-plugins -// -// Created by Sam Gateau on 4/27/15. -// 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 "UserInputMapper.h" -#include "StandardController.h" - -// Default contruct allocate the poutput size with the current hardcoded action channels -UserInputMapper::UserInputMapper() { - registerStandardDevice(); - assignDefaulActionScales(); - createActionNames(); -} - -UserInputMapper::~UserInputMapper() { -} - - -bool UserInputMapper::registerDevice(uint16 deviceID, const DeviceProxy::Pointer& proxy){ - proxy->_name += " (" + QString::number(deviceID) + ")"; - _registeredDevices[deviceID] = proxy; - return true; -} - -UserInputMapper::DeviceProxy::Pointer UserInputMapper::getDeviceProxy(const Input& input) { - auto device = _registeredDevices.find(input.getDevice()); - if (device != _registeredDevices.end()) { - return (device->second); - } else { - return DeviceProxy::Pointer(); - } -} - -QString UserInputMapper::getDeviceName(uint16 deviceID) { - if (_registeredDevices.find(deviceID) != _registeredDevices.end()) { - return _registeredDevices[deviceID]->_name; - } - return QString("unknown"); -} - - -void UserInputMapper::resetAllDeviceBindings() { - for (auto device : _registeredDevices) { - device.second->resetDeviceBindings(); - } -} - -void UserInputMapper::resetDevice(uint16 deviceID) { - auto device = _registeredDevices.find(deviceID); - if (device != _registeredDevices.end()) { - device->second->resetDeviceBindings(); - } -} - -int UserInputMapper::findDevice(QString name) { - for (auto device : _registeredDevices) { - if (device.second->_name.split(" (")[0] == name) { - return device.first; - } - } - return 0; -} - -QVector UserInputMapper::getDeviceNames() { - QVector result; - for (auto device : _registeredDevices) { - QString deviceName = device.second->_name.split(" (")[0]; - result << deviceName; - } - return result; -} - - -bool UserInputMapper::addInputChannel(Action action, const Input& input, float scale) { - return addInputChannel(action, input, Input(), scale); -} - -bool UserInputMapper::addInputChannel(Action action, const Input& input, const Input& modifier, float scale) { - // Check that the device is registered - if (!getDeviceProxy(input)) { - 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 - if (inputChannel.hasModifier()) { - auto& modifiers = _inputToModifiersMap[input.getID()]; - modifiers.push_back(inputChannel._modifier); - std::sort(modifiers.begin(), modifiers.end()); - } - - // Now update the action To Inputs side of things - _actionToInputsMap.insert(ActionToInputsMap::value_type(action, inputChannel)); - - return true; -} - -int UserInputMapper::addInputChannels(const InputChannels& channels) { - int nbAdded = 0; - for (auto& channel : channels) { - nbAdded += addInputChannel(channel._action, channel._input, channel._modifier, channel._scale); - } - 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 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 channels = getAllInputsForDevice(device); - for (auto& channel : channels) { - removeInputChannel(channel); - } -} - -void UserInputMapper::removeDevice(int device) { - removeAllInputChannelsForDevice((uint16) device); - _registeredDevices.erase(device); -} - -int UserInputMapper::getInputChannels(InputChannels& channels) const { - for (auto& channel : _actionToInputsMap) { - channels.push_back(channel.second); - } - - return _actionToInputsMap.size(); -} - -QVector UserInputMapper::getAllInputsForDevice(uint16 device) { - InputChannels allChannels; - getInputChannels(allChannels); - - QVector 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 - for (auto& channel : _actionStates) { - channel = 0.0f; - } - - for (auto& channel : _poseStates) { - channel = PoseValue(); - } - - int currentTimestamp = 0; - - for (auto& channelInput : _actionToInputsMap) { - auto& inputMapping = channelInput.second; - auto& inputID = inputMapping._input; - bool enabled = true; - - // Check if this input channel has modifiers and collect the possibilities - auto modifiersIt = _inputToModifiersMap.find(inputID.getID()); - if (modifiersIt != _inputToModifiersMap.end()) { - Modifiers validModifiers; - bool isActiveModifier = false; - for (auto& modifier : modifiersIt->second) { - auto deviceProxy = getDeviceProxy(modifier); - if (deviceProxy->getButton(modifier, currentTimestamp)) { - validModifiers.push_back(modifier); - isActiveModifier |= (modifier.getID() == inputMapping._modifier.getID()); - } - } - enabled = (validModifiers.empty() && !inputMapping.hasModifier()) || isActiveModifier; - } - - // if enabled: default input or all modifiers on - if (enabled) { - auto deviceProxy = getDeviceProxy(inputID); - switch (inputMapping._input.getType()) { - case ChannelType::BUTTON: { - _actionStates[channelInput.first] += inputMapping._scale * float(deviceProxy->getButton(inputID, currentTimestamp));// * deltaTime; // weight the impulse by the deltaTime - break; - } - case ChannelType::AXIS: { - _actionStates[channelInput.first] += inputMapping._scale * deviceProxy->getAxis(inputID, currentTimestamp); - break; - } - case ChannelType::POSE: { - if (!_poseStates[channelInput.first].isValid()) { - _poseStates[channelInput.first] = deviceProxy->getPose(inputID, currentTimestamp); - } - break; - } - default: { - break; //silence please - } - } - } else{ - // Channel input not enabled - enabled = false; - } - } - - // Scale all the channel step with the scale - static const float EPSILON = 0.01f; - for (auto i = 0; i < NUM_ACTIONS; i++) { - _actionStates[i] *= _actionScales[i]; - // Emit only on change, and emit when moving back to 0 - if (fabsf(_actionStates[i] - _lastActionStates[i]) > EPSILON) { - _lastActionStates[i] = _actionStates[i]; - emit actionEvent(i, _actionStates[i]); - } - // TODO: emit signal for pose changes - } -} - -QVector UserInputMapper::getAllActions() const { - QVector actions; - for (auto i = 0; i < NUM_ACTIONS; i++) { - actions.append(Action(i)); - } - return actions; -} - -QVector UserInputMapper::getInputChannelsForAction(UserInputMapper::Action action) { - QVector inputChannels; - std::pair ret; - ret = _actionToInputsMap.equal_range(action); - for (ActionToInputsMap::iterator it=ret.first; it!=ret.second; ++it) { - inputChannels.append(it->second); - } - return inputChannels; -} - -int UserInputMapper::findAction(const QString& actionName) const { - auto actions = getAllActions(); - for (auto action : actions) { - if (getActionName(action) == actionName) { - return action; - } - } - // If the action isn't found, return -1 - return -1; -} - -QVector UserInputMapper::getActionNames() const { - QVector result; - for (auto i = 0; i < NUM_ACTIONS; i++) { - result << _actionNames[i]; - } - return result; -} - -void UserInputMapper::assignDefaulActionScales() { - _actionScales[LONGITUDINAL_BACKWARD] = 1.0f; // 1m per unit - _actionScales[LONGITUDINAL_FORWARD] = 1.0f; // 1m per unit - _actionScales[LATERAL_LEFT] = 1.0f; // 1m per unit - _actionScales[LATERAL_RIGHT] = 1.0f; // 1m per unit - _actionScales[VERTICAL_DOWN] = 1.0f; // 1m per unit - _actionScales[VERTICAL_UP] = 1.0f; // 1m per unit - _actionScales[YAW_LEFT] = 1.0f; // 1 degree per unit - _actionScales[YAW_RIGHT] = 1.0f; // 1 degree per unit - _actionScales[PITCH_DOWN] = 1.0f; // 1 degree per unit - _actionScales[PITCH_UP] = 1.0f; // 1 degree per unit - _actionScales[BOOM_IN] = 0.5f; // .5m per unit - _actionScales[BOOM_OUT] = 0.5f; // .5m per unit - _actionScales[LEFT_HAND] = 1.0f; // default - _actionScales[RIGHT_HAND] = 1.0f; // default - _actionScales[LEFT_HAND_CLICK] = 1.0f; // on - _actionScales[RIGHT_HAND_CLICK] = 1.0f; // on - _actionStates[SHIFT] = 1.0f; // on - _actionStates[ACTION1] = 1.0f; // default - _actionStates[ACTION2] = 1.0f; // default -} - -// 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"; - _actionNames[LEFT_HAND] = "LEFT_HAND"; - _actionNames[RIGHT_HAND] = "RIGHT_HAND"; - _actionNames[LEFT_HAND_CLICK] = "LEFT_HAND_CLICK"; - _actionNames[RIGHT_HAND_CLICK] = "RIGHT_HAND_CLICK"; - _actionNames[SHIFT] = "SHIFT"; - _actionNames[ACTION1] = "ACTION1"; - _actionNames[ACTION2] = "ACTION2"; - _actionNames[CONTEXT_MENU] = "CONTEXT_MENU"; - _actionNames[TOGGLE_MUTE] = "TOGGLE_MUTE"; -} - -void UserInputMapper::registerStandardDevice() { - _standardController = std::make_shared(); - _standardController->registerToUserInputMapper(*this); -} \ No newline at end of file +// +// UserInputMapper.cpp +// input-plugins/src/input-plugins +// +// Created by Sam Gateau on 4/27/15. +// 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 "UserInputMapper.h" +#include "StandardController.h" + +const UserInputMapper::Input UserInputMapper::Input::INVALID_INPUT = UserInputMapper::Input(UINT16_MAX); +const uint16_t UserInputMapper::Input::INVALID_DEVICE = INVALID_INPUT.getDevice(); +const uint16_t UserInputMapper::Input::INVALID_CHANNEL = INVALID_INPUT.getChannel(); +const uint16_t UserInputMapper::Input::INVALID_TYPE = (uint16_t)INVALID_INPUT.getType(); + +// Default contruct allocate the poutput size with the current hardcoded action channels +UserInputMapper::UserInputMapper() { + registerStandardDevice(); + assignDefaulActionScales(); + createActionNames(); +} + +UserInputMapper::~UserInputMapper() { +} + + +bool UserInputMapper::registerDevice(uint16 deviceID, const DeviceProxy::Pointer& proxy){ + proxy->_name += " (" + QString::number(deviceID) + ")"; + _registeredDevices[deviceID] = proxy; + return true; +} + +UserInputMapper::DeviceProxy::Pointer UserInputMapper::getDeviceProxy(const Input& input) { + auto device = _registeredDevices.find(input.getDevice()); + if (device != _registeredDevices.end()) { + return (device->second); + } else { + return DeviceProxy::Pointer(); + } +} + +QString UserInputMapper::getDeviceName(uint16 deviceID) { + if (_registeredDevices.find(deviceID) != _registeredDevices.end()) { + return _registeredDevices[deviceID]->_name; + } + return QString("unknown"); +} + + +void UserInputMapper::resetAllDeviceBindings() { + for (auto device : _registeredDevices) { + device.second->resetDeviceBindings(); + } +} + +void UserInputMapper::resetDevice(uint16 deviceID) { + auto device = _registeredDevices.find(deviceID); + if (device != _registeredDevices.end()) { + device->second->resetDeviceBindings(); + } +} + +int UserInputMapper::findDevice(QString name) { + for (auto device : _registeredDevices) { + if (device.second->_name.split(" (")[0] == name) { + return device.first; + } + } + return 0; +} + +QVector UserInputMapper::getDeviceNames() { + QVector result; + for (auto device : _registeredDevices) { + QString deviceName = device.second->_name.split(" (")[0]; + result << deviceName; + } + return result; +} + + +bool UserInputMapper::addInputChannel(Action action, const Input& input, float scale) { + return addInputChannel(action, input, Input(), scale); +} + +bool UserInputMapper::addInputChannel(Action action, const Input& input, const Input& modifier, float scale) { + // Check that the device is registered + if (!getDeviceProxy(input)) { + 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 + if (inputChannel.hasModifier()) { + auto& modifiers = _inputToModifiersMap[input.getID()]; + modifiers.push_back(inputChannel._modifier); + std::sort(modifiers.begin(), modifiers.end()); + } + + // Now update the action To Inputs side of things + _actionToInputsMap.insert(ActionToInputsMap::value_type(action, inputChannel)); + + return true; +} + +int UserInputMapper::addInputChannels(const InputChannels& channels) { + int nbAdded = 0; + for (auto& channel : channels) { + nbAdded += addInputChannel(channel._action, channel._input, channel._modifier, channel._scale); + } + 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 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 channels = getAllInputsForDevice(device); + for (auto& channel : channels) { + removeInputChannel(channel); + } +} + +void UserInputMapper::removeDevice(int device) { + removeAllInputChannelsForDevice((uint16) device); + _registeredDevices.erase(device); +} + +int UserInputMapper::getInputChannels(InputChannels& channels) const { + for (auto& channel : _actionToInputsMap) { + channels.push_back(channel.second); + } + + return _actionToInputsMap.size(); +} + +QVector UserInputMapper::getAllInputsForDevice(uint16 device) { + InputChannels allChannels; + getInputChannels(allChannels); + + QVector 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 + for (auto& channel : _actionStates) { + channel = 0.0f; + } + + for (auto& channel : _poseStates) { + channel = PoseValue(); + } + + int currentTimestamp = 0; + + for (auto& channelInput : _actionToInputsMap) { + auto& inputMapping = channelInput.second; + auto& inputID = inputMapping._input; + bool enabled = true; + + // Check if this input channel has modifiers and collect the possibilities + auto modifiersIt = _inputToModifiersMap.find(inputID.getID()); + if (modifiersIt != _inputToModifiersMap.end()) { + Modifiers validModifiers; + bool isActiveModifier = false; + for (auto& modifier : modifiersIt->second) { + auto deviceProxy = getDeviceProxy(modifier); + if (deviceProxy->getButton(modifier, currentTimestamp)) { + validModifiers.push_back(modifier); + isActiveModifier |= (modifier.getID() == inputMapping._modifier.getID()); + } + } + enabled = (validModifiers.empty() && !inputMapping.hasModifier()) || isActiveModifier; + } + + // if enabled: default input or all modifiers on + if (enabled) { + auto deviceProxy = getDeviceProxy(inputID); + switch (inputMapping._input.getType()) { + case ChannelType::BUTTON: { + _actionStates[channelInput.first] += inputMapping._scale * float(deviceProxy->getButton(inputID, currentTimestamp));// * deltaTime; // weight the impulse by the deltaTime + break; + } + case ChannelType::AXIS: { + _actionStates[channelInput.first] += inputMapping._scale * deviceProxy->getAxis(inputID, currentTimestamp); + break; + } + case ChannelType::POSE: { + if (!_poseStates[channelInput.first].isValid()) { + _poseStates[channelInput.first] = deviceProxy->getPose(inputID, currentTimestamp); + } + break; + } + default: { + break; //silence please + } + } + } else{ + // Channel input not enabled + enabled = false; + } + } + + // Scale all the channel step with the scale + static const float EPSILON = 0.01f; + for (auto i = 0; i < NUM_ACTIONS; i++) { + _actionStates[i] *= _actionScales[i]; + // Emit only on change, and emit when moving back to 0 + if (fabsf(_actionStates[i] - _lastActionStates[i]) > EPSILON) { + _lastActionStates[i] = _actionStates[i]; + emit actionEvent(i, _actionStates[i]); + } + // TODO: emit signal for pose changes + } +} + +QVector UserInputMapper::getAllActions() const { + QVector actions; + for (auto i = 0; i < NUM_ACTIONS; i++) { + actions.append(Action(i)); + } + return actions; +} + +QVector UserInputMapper::getInputChannelsForAction(UserInputMapper::Action action) { + QVector inputChannels; + std::pair ret; + ret = _actionToInputsMap.equal_range(action); + for (ActionToInputsMap::iterator it=ret.first; it!=ret.second; ++it) { + inputChannels.append(it->second); + } + return inputChannels; +} + +int UserInputMapper::findAction(const QString& actionName) const { + auto actions = getAllActions(); + for (auto action : actions) { + if (getActionName(action) == actionName) { + return action; + } + } + // If the action isn't found, return -1 + return -1; +} + +QVector UserInputMapper::getActionNames() const { + QVector result; + for (auto i = 0; i < NUM_ACTIONS; i++) { + result << _actionNames[i]; + } + return result; +} + +void UserInputMapper::assignDefaulActionScales() { + _actionScales[LONGITUDINAL_BACKWARD] = 1.0f; // 1m per unit + _actionScales[LONGITUDINAL_FORWARD] = 1.0f; // 1m per unit + _actionScales[LATERAL_LEFT] = 1.0f; // 1m per unit + _actionScales[LATERAL_RIGHT] = 1.0f; // 1m per unit + _actionScales[VERTICAL_DOWN] = 1.0f; // 1m per unit + _actionScales[VERTICAL_UP] = 1.0f; // 1m per unit + _actionScales[YAW_LEFT] = 1.0f; // 1 degree per unit + _actionScales[YAW_RIGHT] = 1.0f; // 1 degree per unit + _actionScales[PITCH_DOWN] = 1.0f; // 1 degree per unit + _actionScales[PITCH_UP] = 1.0f; // 1 degree per unit + _actionScales[BOOM_IN] = 0.5f; // .5m per unit + _actionScales[BOOM_OUT] = 0.5f; // .5m per unit + _actionScales[LEFT_HAND] = 1.0f; // default + _actionScales[RIGHT_HAND] = 1.0f; // default + _actionScales[LEFT_HAND_CLICK] = 1.0f; // on + _actionScales[RIGHT_HAND_CLICK] = 1.0f; // on + _actionStates[SHIFT] = 1.0f; // on + _actionStates[ACTION1] = 1.0f; // default + _actionStates[ACTION2] = 1.0f; // default + _actionStates[TranslateX] = 1.0f; // default + _actionStates[TranslateY] = 1.0f; // default + _actionStates[TranslateZ] = 1.0f; // default + _actionStates[Roll] = 1.0f; // default + _actionStates[Pitch] = 1.0f; // default + _actionStates[Yaw] = 1.0f; // default +} + +// 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"; + _actionNames[LEFT_HAND] = "LEFT_HAND"; + _actionNames[RIGHT_HAND] = "RIGHT_HAND"; + _actionNames[LEFT_HAND_CLICK] = "LEFT_HAND_CLICK"; + _actionNames[RIGHT_HAND_CLICK] = "RIGHT_HAND_CLICK"; + _actionNames[SHIFT] = "SHIFT"; + _actionNames[ACTION1] = "ACTION1"; + _actionNames[ACTION2] = "ACTION2"; + _actionNames[CONTEXT_MENU] = "CONTEXT_MENU"; + _actionNames[TOGGLE_MUTE] = "TOGGLE_MUTE"; + _actionNames[TranslateX] = "TranslateX"; + _actionNames[TranslateY] = "TranslateY"; + _actionNames[TranslateZ] = "TranslateZ"; + _actionNames[Roll] = "Roll"; + _actionNames[Pitch] = "Pitch"; + _actionNames[Yaw] = "Yaw"; +} + +void UserInputMapper::registerStandardDevice() { + _standardController = std::make_shared(); + _standardController->registerToUserInputMapper(*this); +} + +float UserInputMapper::DeviceProxy::getValue(const Input& input, int timestamp) const { + switch (input.getType()) { + case UserInputMapper::ChannelType::BUTTON: + return getButton(input, timestamp) ? 1.0f : 0.0f; + + case UserInputMapper::ChannelType::AXIS: + return getAxis(input, timestamp); + + case UserInputMapper::ChannelType::POSE: + return getPose(input, timestamp)._valid ? 1.0f : 0.0f; + + default: + return 0.0f; + } +} diff --git a/libraries/input-plugins/src/input-plugins/UserInputMapper.h b/libraries/input-plugins/src/input-plugins/UserInputMapper.h index 1d64638ee1..304e74e8cc 100755 --- a/libraries/input-plugins/src/input-plugins/UserInputMapper.h +++ b/libraries/input-plugins/src/input-plugins/UserInputMapper.h @@ -48,8 +48,9 @@ public: union { struct { uint16 _device; // Up to 64K possible devices - uint16 _channel : 14; // 2^14 possible channel per Device + uint16 _channel : 13; // 2^13 possible channel per Device uint16 _type : 2; // 2 bits to store the Type directly in the ID + uint16 _padding : 1; // 2 bits to store the Type directly in the ID }; uint32 _id = 0; // by default Input is 0 meaning invalid }; @@ -74,13 +75,19 @@ public: // where the default initializer (a C++-11ism) for the union data above is not applied. explicit Input() : _id(0) {} explicit Input(uint32 id) : _id(id) {} - explicit Input(uint16 device, uint16 channel, ChannelType type) : _device(device), _channel(channel), _type(uint16(type)) {} + explicit Input(uint16 device, uint16 channel, ChannelType type) : _device(device), _channel(channel), _type(uint16(type)), _padding(0) {} 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; } + + static const Input INVALID_INPUT; + static const uint16 INVALID_DEVICE; + static const uint16 INVALID_CHANNEL; + static const uint16 INVALID_TYPE; }; + // Modifiers are just button inputID typedef std::vector< Input > Modifiers; @@ -121,7 +128,7 @@ public: PoseGetter getPose = [] (const Input& input, int timestamp) -> PoseValue { return PoseValue(); }; AvailableInputGetter getAvailabeInputs = [] () -> AvailableInput { return QVector(); }; ResetBindings resetDeviceBindings = [] () -> bool { return true; }; - + float getValue(const Input& input, int timestamp = 0) const; typedef std::shared_ptr Pointer; }; // GetFreeDeviceID should be called before registering a device to use an ID not used by a different device. @@ -171,6 +178,13 @@ public: CONTEXT_MENU, TOGGLE_MUTE, + TranslateX, + TranslateY, + TranslateZ, + Roll, + Pitch, + Yaw, + NUM_ACTIONS, }; diff --git a/tests/controllers/qml/Xbox.qml b/tests/controllers/qml/Xbox.qml new file mode 100644 index 0000000000..ae66081162 --- /dev/null +++ b/tests/controllers/qml/Xbox.qml @@ -0,0 +1,99 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +import "./xbox" +import "./controls" + +Item { + id: root + + property var device + + property real scale: 1.0 + width: 300 * scale + height: 215 * scale + + Image { + anchors.fill: parent + source: "xbox/xbox360-controller-md.png" + + LeftAnalogStick { + device: root.device + x: (65 * root.scale) - width / 2; y: (42 * root.scale) - height / 2 + } + + // Left stick press + ToggleButton { + controlId: root.device.LS + width: 16 * root.scale; height: 16 * root.scale + x: (65 * root.scale) - width / 2; y: (42 * root.scale) - height / 2 + } + + + RightAnalogStick { + device: root.device + x: (193 * root.scale) - width / 2; y: (96 * root.scale) - height / 2 + } + + // Right stick press + ToggleButton { + controlId: root.device.RS + width: 16 * root.scale; height: 16 * root.scale + x: (193 * root.scale) - width / 2; y: (96 * root.scale) - height / 2 + } + + // Left trigger + AnalogButton { + controlId: root.device.LT + width: 8; height: 64 + x: (20 * root.scale); y: (7 * root.scale) + } + + // Right trigger + AnalogButton { + controlId: root.device.RT + width: 8; height: 64 + x: (272 * root.scale); y: (7 * root.scale) + } + + // Left bumper + ToggleButton { + controlId: root.device.LB + width: 32 * root.scale; height: 16 * root.scale + x: (40 * root.scale); y: (7 * root.scale) + } + + // Right bumper + ToggleButton { + controlId: root.device.RB + width: 32 * root.scale; height: 16 * root.scale + x: (root.width - width) - (40 * root.scale); y: (7 * root.scale) + } + + DPad { + device: root.device + size: 48 * root.scale + x: (80 * root.scale); y: (71 * root.scale) + } + + XboxButtons { + device: root.device + size: 65 * root.scale + x: (206 * root.scale); y: (19 * root.scale) + } + + ToggleButton { + controlId: root.device.Back + width: 16 * root.scale; height: 12 * root.scale + x: (112 * root.scale); y: (45 * root.scale) + } + + ToggleButton { + controlId: root.device.Start + width: 16 * root.scale; height: 12 * root.scale + x: (177 * root.scale); y: (45 * root.scale) + } + } +} diff --git a/tests/controllers/qml/content.qml b/tests/controllers/qml/content.qml index ac171ac42c..ce8a491419 100644 --- a/tests/controllers/qml/content.qml +++ b/tests/controllers/qml/content.qml @@ -1,137 +1,97 @@ -import QtQuick 2.1 -import QtQuick.Controls 1.0 -import QtQuick.Layouts 1.0 -import QtQuick.Dialogs 1.0 - -Rectangle { - id: root - implicitHeight: column1.height + 24 - implicitWidth: column1.width + 24 - color: "lightgray" - - property real itemSize: 128 - - Component { - id: graphTemplate - Item { - implicitHeight: canvas.height + 2 + text.height - implicitWidth: canvas.width - property string text: loadText - - Canvas { - id: canvas - width: root.itemSize; height: root.itemSize; - antialiasing: false - property int controlId: control - property real value: 0.0 - property int drawWidth: 1 - - Timer { - interval: 50; running: true; repeat: true - onTriggered: { - parent.value = NewControllers.getValue(canvas.controlId) - parent.requestPaint(); - } - } - - onPaint: { - var ctx = canvas.getContext('2d'); - ctx.save(); - - var image = ctx.getImageData(0, 0, canvas.width, canvas.height); - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.drawImage(image, -drawWidth, 0, canvas.width, canvas.height) - ctx.fillStyle = 'green' - // draw a filles rectangle - var height = canvas.height * canvas.value - ctx.fillRect(canvas.width - drawWidth, canvas.height - height, - drawWidth, height) - ctx.restore() - } - } - - Text { - id: text - text: parent.text - anchors.topMargin: 2 - anchors.horizontalCenter: canvas.horizontalCenter - anchors.top: canvas.bottom - font.pointSize: 12; - } - - } - } - - Column { - id: column1 - x: 12; y: 12 - spacing: 24 - Row { - spacing: 16 - Loader { - sourceComponent: graphTemplate; - property string loadText: "Key Left" - property int control: ControllerIds.Hardware.Keyboard2.Left - } - Loader { - sourceComponent: graphTemplate; - property string loadText: "DPad Up" - property int control: ControllerIds.Hardware.X360Controller1.DPadUp - } - /* - Loader { - sourceComponent: graphTemplate; - property string loadText: "Yaw Left" - property int control: ControllerIds.Actions.YAW_LEFT - } - Loader { - sourceComponent: graphTemplate; - property string loadText: "Yaw Left" - property int control: ControllerIds.Actions.YAW_LEFT - } -*/ - -// Loader { sourceComponent: graphTemplate; } -// Loader { sourceComponent: graphTemplate; } -// Loader { sourceComponent: graphTemplate; } - } - /* - Row { - spacing: 16 - Loader { sourceComponent: graphTemplate; } - Loader { sourceComponent: graphTemplate; } - Loader { sourceComponent: graphTemplate; } - Loader { sourceComponent: graphTemplate; } - } - Row { - spacing: 16 - Loader { sourceComponent: graphTemplate; } - Loader { sourceComponent: graphTemplate; } - Loader { sourceComponent: graphTemplate; } - Loader { sourceComponent: graphTemplate; } - } - */ - - - Button { - text: "Go!" - onClicked: { - // - -// var newMapping = NewControllers.newMapping(); -// console.log("Mapping Object " + newMapping); -// var routeBuilder = newMapping.from("Hello"); -// console.log("Route Builder " + routeBuilder); -// routeBuilder.clamp(0, 1).clamp(0, 1).to("Goodbye"); - } - } - - Timer { - interval: 50; running: true; repeat: true - onTriggered: { - NewControllers.update(); - } - } - - } -} +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +import "./xbox" +import "./controls" + +Column { + id: root + property var xbox: NewControllers.Hardware.X360Controller1 + property var actions: NewControllers.Actions + property var standard: NewControllers.Standard + property string mappingName: "TestMapping" + + spacing: 12 + + Timer { + interval: 50; running: true; repeat: true + onTriggered: { + NewControllers.update(); + } + } + + Row { + spacing: 8 + Button { + text: "Default Mapping" + onClicked: { + var mapping = NewControllers.newMapping("Default"); + mapping.from(xbox.A).to(standard.A); + mapping.from(xbox.B).to(standard.B); + mapping.from(xbox.X).to(standard.X); + mapping.from(xbox.Y).to(standard.Y); + mapping.from(xbox.Up).to(standard.DU); + mapping.from(xbox.Down).to(standard.DD); + mapping.from(xbox.Left).to(standard.DL); + mapping.from(xbox.Right).to(standard.Right); + mapping.from(xbox.LB).to(standard.LB); + mapping.from(xbox.RB).to(standard.RB); + mapping.from(xbox.LS).to(standard.LS); + mapping.from(xbox.RS).to(standard.RS); + mapping.from(xbox.Start).to(standard.Start); + mapping.from(xbox.Back).to(standard.Back); + mapping.from(xbox.LY).to(standard.LY); + mapping.from(xbox.LX).to(standard.LX); + mapping.from(xbox.RY).to(standard.RY); + mapping.from(xbox.RX).to(standard.RX); + mapping.from(xbox.LT).to(standard.LT); + mapping.from(xbox.RT).to(standard.RT); + NewControllers.enableMapping("Default"); + } + } + + Button { + text: "Build Mapping" + onClicked: { + var mapping = NewControllers.newMapping(root.mappingName); + // Inverting a value + mapping.from(xbox.RY).invert().to(standard.RY); + // Assigning a value from a function + mapping.from(function() { return Math.sin(Date.now() / 250); }).to(standard.RX); + // Constrainting a value to -1, 0, or 1, with a deadzone + mapping.from(xbox.LY).deadZone(0.5).constrainToInteger().to(standard.LY); + mapping.join(standard.LB, standard.RB).pulse(0.5).to(actions.Yaw); + } + } + + Button { + text: "Enable Mapping" + onClicked: NewControllers.enableMapping(root.mappingName) + } + + Button { + text: "Disable Mapping" + onClicked: NewControllers.disableMapping(root.mappingName) + } + } + + Row { + spacing: 8 + Xbox { device: root.xbox } + Xbox { device: root.standard } + } + + + Row { + ScrollingGraph { + controlId: NewControllers.Actions.Yaw + label: "Yaw" + min: -3.0 + max: 3.0 + size: 128 + } + } +} + diff --git a/tests/controllers/qml/controls/AnalogButton.qml b/tests/controllers/qml/controls/AnalogButton.qml new file mode 100644 index 0000000000..26f91458ac --- /dev/null +++ b/tests/controllers/qml/controls/AnalogButton.qml @@ -0,0 +1,45 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +Item { + id: root + property int size: 64 + width: size + height: size + property int controlId: 0 + property real value: 0 + property color color: 'black' + + function update() { + value = NewControllers.getValue(controlId); + canvas.requestPaint(); + } + + Timer { + interval: 50; running: true; repeat: true + onTriggered: root.update(); + } + + Canvas { + id: canvas + anchors.fill: parent + antialiasing: false + + onPaint: { + var ctx = canvas.getContext('2d'); + ctx.save(); + ctx.beginPath(); + ctx.clearRect(0, 0, canvas.width, canvas.height); + var fillHeight = root.value * canvas.height; + + ctx.fillStyle = 'red' + ctx.fillRect(0, canvas.height - fillHeight, canvas.width, fillHeight); + ctx.fill(); + ctx.restore() + } + } +} + + diff --git a/tests/controllers/qml/controls/AnalogStick.qml b/tests/controllers/qml/controls/AnalogStick.qml new file mode 100644 index 0000000000..8860aea49c --- /dev/null +++ b/tests/controllers/qml/controls/AnalogStick.qml @@ -0,0 +1,50 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +Item { + id: root + property int size: 64 + width: size + height: size + + property int halfSize: size / 2 + property var controlIds: [ 0, 0 ] + property vector2d value: Qt.vector2d(0, 0) + + function update() { + value = Qt.vector2d( + NewControllers.getValue(controlIds[0]), + NewControllers.getValue(controlIds[1]) + ); + canvas.requestPaint(); + } + + Timer { + interval: 50; running: true; repeat: true + onTriggered: root.update() + } + + Canvas { + id: canvas + anchors.fill: parent + antialiasing: false + + onPaint: { + var ctx = canvas.getContext('2d'); + ctx.save(); + ctx.beginPath(); + ctx.clearRect(0, 0, width, height); + ctx.fill(); + ctx.translate(root.halfSize, root.halfSize) + ctx.lineWidth = 4 + ctx.strokeStyle = Qt.rgba(Math.max(Math.abs(value.x), Math.abs(value.y)), 0, 0, 1) + ctx.moveTo(0, 0).lineTo(root.value.x * root.halfSize, root.value.y * root.halfSize) + ctx.stroke() + ctx.restore() + } + } +} + + diff --git a/tests/controllers/qml/controls/ScrollingGraph.qml b/tests/controllers/qml/controls/ScrollingGraph.qml new file mode 100644 index 0000000000..69f919aaf1 --- /dev/null +++ b/tests/controllers/qml/controls/ScrollingGraph.qml @@ -0,0 +1,104 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +Item { + id: root + property int size: 64 + width: size + height: size + + property int controlId: 0 + property real value: 0.5 + property int scrollWidth: 1 + property real min: 0.0 + property real max: 1.0 + property bool log: false + property real range: max - min + property color color: 'blue' + property bool bar: false + property real lastHeight: -1 + property string label: "" + + function update() { + value = NewControllers.getValue(controlId); + canvas.requestPaint(); + } + + function drawHeight() { + if (value < min) { + return 0; + } + if (value > max) { + return height; + } + return ((value - min) / range) * height; + } + + Timer { + interval: 50; running: true; repeat: true + onTriggered: root.update() + } + + Canvas { + id: canvas + anchors.fill: parent + antialiasing: false + + Text { + anchors.top: parent.top + text: root.label + + } + + Text { + anchors.right: parent.right + anchors.top: parent.top + text: root.max + } + + Text { + anchors.right: parent.right + anchors.bottom: parent.bottom + text: root.min + } + + function scroll() { + var ctx = canvas.getContext('2d'); + var image = ctx.getImageData(0, 0, canvas.width, canvas.height); + ctx.beginPath(); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(image, -root.scrollWidth, 0, canvas.width, canvas.height) + ctx.restore() + } + + onPaint: { + scroll(); + var ctx = canvas.getContext('2d'); + ctx.save(); + var currentHeight = root.drawHeight(); + if (root.lastHeight == -1) { + root.lastHeight = currentHeight + } + +// var x = canvas.width - root.drawWidth; +// var y = canvas.height - drawHeight; +// ctx.fillStyle = root.color +// ctx.fillRect(x, y, root.drawWidth, root.bar ? drawHeight : 1) +// ctx.fill(); +// ctx.restore() + + + ctx.beginPath(); + ctx.lineWidth = 1 + ctx.strokeStyle = root.color + ctx.moveTo(canvas.width - root.scrollWidth, root.lastHeight).lineTo(canvas.width, currentHeight) + ctx.stroke() + ctx.restore() + root.lastHeight = currentHeight + } + } +} + + diff --git a/tests/controllers/qml/controls/ToggleButton.qml b/tests/controllers/qml/controls/ToggleButton.qml new file mode 100644 index 0000000000..9ef54f5971 --- /dev/null +++ b/tests/controllers/qml/controls/ToggleButton.qml @@ -0,0 +1,43 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +Item { + id: root + width: size + height: size + property int size: 64 + property int controlId: 0 + property real value: 0 + property color color: 'black' + + Timer { + interval: 50; running: true; repeat: true + onTriggered: { + root.value = NewControllers.getValue(root.controlId); + canvas.requestPaint(); + } + } + + Canvas { + id: canvas + anchors.fill: parent + antialiasing: false + + onPaint: { + var ctx = canvas.getContext('2d'); + ctx.save(); + ctx.beginPath(); + ctx.clearRect(0, 0, width, height); + if (root.value > 0.0) { + ctx.fillStyle = root.color + ctx.fillRect(0, 0, canvas.width, canvas.height); + } + ctx.fill(); + ctx.restore() + } + } +} + + diff --git a/tests/controllers/qml/xbox/DPad.qml b/tests/controllers/qml/xbox/DPad.qml new file mode 100644 index 0000000000..8efe6c2b30 --- /dev/null +++ b/tests/controllers/qml/xbox/DPad.qml @@ -0,0 +1,43 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +import "./../controls" + + +Item { + id: root + property int size: 64 + width: size + height: size + property int spacer: size / 3 + property var device + property color color: 'black' + + ToggleButton { + controlId: device.Up + x: spacer + width: spacer; height: spacer + } + + ToggleButton { + controlId: device.Left + y: spacer + width: spacer; height: spacer + } + + ToggleButton { + controlId: device.Right + x: spacer * 2; y: spacer + width: spacer; height: spacer + } + + ToggleButton { + controlId: device.Down + x: spacer; y: spacer * 2 + width: spacer; height: spacer + } +} + + diff --git a/tests/controllers/qml/xbox/LeftAnalogStick.qml b/tests/controllers/qml/xbox/LeftAnalogStick.qml new file mode 100644 index 0000000000..ed2689e7c8 --- /dev/null +++ b/tests/controllers/qml/xbox/LeftAnalogStick.qml @@ -0,0 +1,21 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +import "./../controls" + +Item { + id: root + property int size: 64 + width: size + height: size + property var device + + AnalogStick { + size: size + controlIds: [ device.LX, device.LY ] + } +} + + diff --git a/tests/controllers/qml/xbox/RightAnalogStick.qml b/tests/controllers/qml/xbox/RightAnalogStick.qml new file mode 100644 index 0000000000..611b4d8f92 --- /dev/null +++ b/tests/controllers/qml/xbox/RightAnalogStick.qml @@ -0,0 +1,21 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +import "./../controls" + +Item { + id: root + property int size: 64 + width: size + height: size + property var device + + AnalogStick { + size: size + controlIds: [ device.RX, device.RY ] + } +} + + diff --git a/tests/controllers/qml/xbox/XboxButtons.qml b/tests/controllers/qml/xbox/XboxButtons.qml new file mode 100644 index 0000000000..4a9e87799e --- /dev/null +++ b/tests/controllers/qml/xbox/XboxButtons.qml @@ -0,0 +1,46 @@ +import QtQuick 2.1 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.0 +import QtQuick.Dialogs 1.0 + +import "./../controls" + +Item { + id: root + property int size: 64 + width: size + height: size + property int spacer: size / 3 + property var device + property color color: 'black' + + ToggleButton { + controlId: device.Y + x: spacer + width: spacer; height: spacer + color: 'yellow' + } + + ToggleButton { + controlId: device.X + y: spacer + width: spacer; height: spacer + color: 'blue' + } + + ToggleButton { + controlId: device.B + x: spacer * 2; y: spacer + width: spacer; height: spacer + color: 'red' + } + + ToggleButton { + controlId: device.A + x: spacer; y: spacer * 2 + width: spacer; height: spacer + color: 'green' + } +} + + diff --git a/tests/controllers/qml/xbox/xbox360-controller-md.png b/tests/controllers/qml/xbox/xbox360-controller-md.png new file mode 100644 index 0000000000000000000000000000000000000000..bdb596455fc3bb066c70cc1f2cf989a8ee05e0e9 GIT binary patch literal 29588 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 xLwBT)|06F4JVkqVI|p`EXD -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include +// +// main.cpp +// tests/gpu-test/src +// +// 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 +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include #include #include - -#include -#include -#include -#include -#include -#include - -const QString& getQmlDir() { - static QString dir; - if (dir.isEmpty()) { - QDir path(__FILE__); - path.cdUp(); - dir = path.cleanPath(path.absoluteFilePath("../qml/")) + "/"; - qDebug() << "Qml Path: " << dir; - } - return dir; -} - -class AppHook : public QQuickItem { - Q_OBJECT - -public: - AppHook() { - qDebug() << "Hook Created"; - } -}; - -using namespace Controllers; - - -QString sanatizeName(const QString& name) { - QString cleanName{ name }; - cleanName.remove(QRegularExpression{ "[\\(\\)\\.\\s]" }); - return cleanName; -} - -const QVariantMap& getInputMap() { - static std::once_flag once; - static QVariantMap map; - std::call_once(once, [&] { - { - QVariantMap hardwareMap; - // Controller.Hardware.* - auto devices = DependencyManager::get()->getDevices(); - for (const auto& deviceMapping : devices) { - auto device = deviceMapping.second.get(); - auto deviceName = sanatizeName(device->getName()); - auto deviceInputs = device->getAvailabeInputs(); - QVariantMap deviceMap; - for (const auto& inputMapping : deviceInputs) { - auto input = inputMapping.first; - auto inputName = sanatizeName(inputMapping.second); - deviceMap.insert(inputName, input.getID()); - } - hardwareMap.insert(deviceName, deviceMap); - } - map.insert("Hardware", hardwareMap); - } - - // Controller.Actions.* - { - QVariantMap actionMap; - auto actionNames = DependencyManager::get()->getActionNames(); - int actionNumber = 0; - for (const auto& actionName : actionNames) { - actionMap.insert(sanatizeName(actionName), actionNumber++); - } - map.insert("Actions", actionMap); - } - }); - return map; -} - -int main(int argc, char** argv) { - DependencyManager::set(); - PluginManager::getInstance()->getInputPlugins(); - - foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { - QString name = inputPlugin->getName(); - auto userInputMapper = DependencyManager::get(); - if (name == KeyboardMouseDevice::NAME) { - auto keyboardMouseDevice = static_cast(inputPlugin.data()); // TODO: this seems super hacky - keyboardMouseDevice->registerToUserInputMapper(*userInputMapper); - keyboardMouseDevice->assignDefaultInputMapping(*userInputMapper); - } - } - - // Register our component type with QML. - qmlRegisterType("com.highfidelity.test", 1, 0, "AppHook"); - //qmlRegisterType("com.highfidelity.test", 1, 0, "NewControllers"); - QGuiApplication app(argc, argv); + +#include +#include +#include +#include +#include +#include + +#include +#include + +const QString& getQmlDir() { + static QString dir; + if (dir.isEmpty()) { + QDir path(__FILE__); + path.cdUp(); + dir = path.cleanPath(path.absoluteFilePath("../qml/")) + "/"; + qDebug() << "Qml Path: " << dir; + } + return dir; +} + +using namespace controller; + + +class PluginContainerProxy : public QObject, PluginContainer { + Q_OBJECT +public: + PluginContainerProxy() { + Plugin::setContainer(this); + } + virtual ~PluginContainerProxy() {} + virtual void addMenu(const QString& menuName) override {} + virtual void removeMenu(const QString& menuName) override {} + virtual QAction* addMenuItem(const QString& path, const QString& name, std::function onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") override { return nullptr; } + virtual void removeMenuItem(const QString& menuName, const QString& menuItem) override {} + virtual bool isOptionChecked(const QString& name) override { return false; } + virtual void setIsOptionChecked(const QString& path, bool checked) override {} + virtual void setFullscreen(const QScreen* targetScreen, bool hideMenu = true) override {} + virtual void unsetFullscreen(const QScreen* avoidScreen = nullptr) override {} + virtual void showDisplayPluginsTools() override {} + virtual void requestReset() override {} + virtual QGLWidget* getPrimarySurface() override { return nullptr; } + virtual bool isForeground() override { return true; } + virtual const DisplayPlugin* getActiveDisplayPlugin() const override { return nullptr; } +}; + +int main(int argc, char** argv) { + QGuiApplication app(argc, argv); QQmlApplicationEngine engine; - engine.rootContext()->setContextProperty("NewControllers", new NewControllerScriptingInterface()); - engine.rootContext()->setContextProperty("ControllerIds", getInputMap()); - engine.load(getQmlDir() + "main.qml"); - app.exec(); - return 0; -} - - - -//QQmlEngine engine; -//QQmlComponent *component = new QQmlComponent(&engine); -// -//QObject::connect(&engine, SIGNAL(quit()), QCoreApplication::instance(), SLOT(quit())); -// -//component->loadUrl(QUrl("main.qml")); -// -//if (!component->isReady()) { -// qWarning("%s", qPrintable(component->errorString())); -// return -1; -//} -// -//QObject *topLevel = component->create(); -//QQuickWindow *window = qobject_cast(topLevel); -// -//QSurfaceFormat surfaceFormat = window->requestedFormat(); -//window->setFormat(surfaceFormat); -//window->show(); -// -//rc = app.exec(); -// -//delete component; -//return rc; -//} - - - -#include "main.moc" + + + { + DependencyManager::set(); + foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { + QString name = inputPlugin->getName(); + inputPlugin->activate(); + auto userInputMapper = DependencyManager::get(); + if (name == KeyboardMouseDevice::NAME) { + auto keyboardMouseDevice = static_cast(inputPlugin.data()); // TODO: this seems super hacky + keyboardMouseDevice->registerToUserInputMapper(*userInputMapper); + } + } + + + //new PluginContainerProxy(); + auto rootContext = engine.rootContext(); + auto controllers = new NewControllerScriptingInterface(); + rootContext->setContextProperty("NewControllers", controllers); + QVariantMap map; + map.insert("Hardware", controllers->property("Hardware")); + map.insert("Actions", controllers->property("Actions")); + rootContext->setContextProperty("ControllerIds", map); + } + engine.load(getQmlDir() + "main.qml"); + app.exec(); + return 0; +} + +#include "main.moc" +