From 03b574ae2272fc8465e7d38f21b198fcb1885186 Mon Sep 17 00:00:00 2001
From: german <german@thesoftwareartisans.com>
Date: Thu, 17 Sep 2020 20:26:34 -0500
Subject: Add random motion input to SDL

---
 src/input_common/sdl/sdl_impl.cpp | 190 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 190 insertions(+)

(limited to 'src/input_common/sdl/sdl_impl.cpp')

diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp
index a9e676f4b2..0b00959783 100644
--- a/src/input_common/sdl/sdl_impl.cpp
+++ b/src/input_common/sdl/sdl_impl.cpp
@@ -21,6 +21,7 @@
 #include "common/param_package.h"
 #include "common/threadsafe_queue.h"
 #include "core/frontend/input.h"
+#include "input_common/motion_input.h"
 #include "input_common/sdl/sdl_impl.h"
 #include "input_common/settings.h"
 
@@ -95,6 +96,10 @@ public:
         return std::make_tuple(x, y);
     }
 
+    const InputCommon::MotionInput& GetMotion() const {
+        return motion;
+    }
+
     void SetHat(int hat, Uint8 direction) {
         std::lock_guard lock{mutex};
         state.hats.insert_or_assign(hat, direction);
@@ -142,6 +147,9 @@ private:
     std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
     std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller;
     mutable std::mutex mutex;
+
+    // motion is initalized without PID values as motion input is not aviable for SDL2
+    InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f};
 };
 
 std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) {
@@ -386,6 +394,68 @@ private:
     const float range;
 };
 
+class SDLDirectionMotion final : public Input::MotionDevice {
+public:
+    explicit SDLDirectionMotion(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
+        : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {}
+
+    Input::MotionStatus GetStatus() const override {
+        if (joystick->GetHatDirection(hat, direction)) {
+            return joystick->GetMotion().GetRandomMotion(2, 6);
+        }
+        return joystick->GetMotion().GetRandomMotion(0, 0);
+    }
+
+private:
+    std::shared_ptr<SDLJoystick> joystick;
+    int hat;
+    Uint8 direction;
+};
+
+class SDLAxisMotion final : public Input::MotionDevice {
+public:
+    explicit SDLAxisMotion(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_,
+                           bool trigger_if_greater_)
+        : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_),
+          trigger_if_greater(trigger_if_greater_) {}
+
+    Input::MotionStatus GetStatus() const override {
+        const float axis_value = joystick->GetAxis(axis, 1.0f);
+        bool trigger = axis_value < threshold;
+        if (trigger_if_greater) {
+            trigger = axis_value > threshold;
+        }
+
+        if (trigger) {
+            return joystick->GetMotion().GetRandomMotion(2, 6);
+        }
+        return joystick->GetMotion().GetRandomMotion(0, 0);
+    }
+
+private:
+    std::shared_ptr<SDLJoystick> joystick;
+    int axis;
+    float threshold;
+    bool trigger_if_greater;
+};
+
+class SDLButtonMotion final : public Input::MotionDevice {
+public:
+    explicit SDLButtonMotion(std::shared_ptr<SDLJoystick> joystick_, int button_)
+        : joystick(std::move(joystick_)), button(button_) {}
+
+    Input::MotionStatus GetStatus() const override {
+        if (joystick->GetButton(button)) {
+            return joystick->GetMotion().GetRandomMotion(2, 6);
+        }
+        return joystick->GetMotion().GetRandomMotion(0, 0);
+    }
+
+private:
+    std::shared_ptr<SDLJoystick> joystick;
+    int button;
+};
+
 /// A button device factory that creates button devices from SDL joystick
 class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> {
 public:
@@ -492,12 +562,78 @@ private:
     SDLState& state;
 };
 
+/// A motion device factory that creates motion devices from SDL joystick
+class SDLMotionFactory final : public Input::Factory<Input::MotionDevice> {
+public:
+    explicit SDLMotionFactory(SDLState& state_) : state(state_) {}
+    /**
+     * Creates motion device from joystick axes
+     * @param params contains parameters for creating the device:
+     *     - "guid": the guid of the joystick to bind
+     *     - "port": the nth joystick of the same type
+     */
+    std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override {
+        const std::string guid = params.Get("guid", "0");
+        const int port = params.Get("port", 0);
+
+        auto joystick = state.GetSDLJoystickByGUID(guid, port);
+
+        if (params.Has("hat")) {
+            const int hat = params.Get("hat", 0);
+            const std::string direction_name = params.Get("direction", "");
+            Uint8 direction;
+            if (direction_name == "up") {
+                direction = SDL_HAT_UP;
+            } else if (direction_name == "down") {
+                direction = SDL_HAT_DOWN;
+            } else if (direction_name == "left") {
+                direction = SDL_HAT_LEFT;
+            } else if (direction_name == "right") {
+                direction = SDL_HAT_RIGHT;
+            } else {
+                direction = 0;
+            }
+            // This is necessary so accessing GetHat with hat won't crash
+            joystick->SetHat(hat, SDL_HAT_CENTERED);
+            return std::make_unique<SDLDirectionMotion>(joystick, hat, direction);
+        }
+
+        if (params.Has("axis")) {
+            const int axis = params.Get("axis", 0);
+            const float threshold = params.Get("threshold", 0.5f);
+            const std::string direction_name = params.Get("direction", "");
+            bool trigger_if_greater;
+            if (direction_name == "+") {
+                trigger_if_greater = true;
+            } else if (direction_name == "-") {
+                trigger_if_greater = false;
+            } else {
+                trigger_if_greater = true;
+                LOG_ERROR(Input, "Unknown direction {}", direction_name);
+            }
+            // This is necessary so accessing GetAxis with axis won't crash
+            joystick->SetAxis(axis, 0);
+            return std::make_unique<SDLAxisMotion>(joystick, axis, threshold, trigger_if_greater);
+        }
+
+        const int button = params.Get("button", 0);
+        // This is necessary so accessing GetButton with button won't crash
+        joystick->SetButton(button, false);
+        return std::make_unique<SDLButtonMotion>(joystick, button);
+    }
+
+private:
+    SDLState& state;
+};
+
 SDLState::SDLState() {
     using namespace Input;
     analog_factory = std::make_shared<SDLAnalogFactory>(*this);
     button_factory = std::make_shared<SDLButtonFactory>(*this);
+    motion_factory = std::make_shared<SDLMotionFactory>(*this);
     RegisterFactory<AnalogDevice>("sdl", analog_factory);
     RegisterFactory<ButtonDevice>("sdl", button_factory);
+    RegisterFactory<MotionDevice>("sdl", motion_factory);
 
     // If the frontend is going to manage the event loop, then we dont start one here
     start_thread = !SDL_WasInit(SDL_INIT_JOYSTICK);
@@ -533,6 +669,7 @@ SDLState::~SDLState() {
     using namespace Input;
     UnregisterFactory<ButtonDevice>("sdl");
     UnregisterFactory<AnalogDevice>("sdl");
+    UnregisterFactory<MotionDevice>("sdl");
 
     CloseJoysticks();
     SDL_DelEventWatch(&SDLEventWatcher, this);
@@ -644,6 +781,27 @@ Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Eve
     return {};
 }
 
+Common::ParamPackage SDLEventToMotionParamPackage(SDLState& state, const SDL_Event& event) {
+    switch (event.type) {
+    case SDL_JOYAXISMOTION: {
+        const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
+        return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+                                                event.jaxis.axis, event.jaxis.value);
+    }
+    case SDL_JOYBUTTONUP: {
+        const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which);
+        return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+                                                event.jbutton.button);
+    }
+    case SDL_JOYHATMOTION: {
+        const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which);
+        return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(),
+                                             event.jhat.hat, event.jhat.value);
+    }
+    }
+    return {};
+}
+
 Common::ParamPackage BuildParamPackageForBinding(int port, const std::string& guid,
                                                  const SDL_GameControllerButtonBind& binding) {
     switch (binding.bindType) {
@@ -809,6 +967,35 @@ public:
     }
 };
 
+class SDLMotionPoller final : public SDLPoller {
+public:
+    explicit SDLMotionPoller(SDLState& state_) : SDLPoller(state_) {}
+
+    Common::ParamPackage GetNextInput() override {
+        SDL_Event event;
+        while (state.event_queue.Pop(event)) {
+            const auto package = FromEvent(event);
+            if (package) {
+                return *package;
+            }
+        }
+        return {};
+    }
+    [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) const {
+        switch (event.type) {
+        case SDL_JOYAXISMOTION:
+            if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
+                break;
+            }
+            [[fallthrough]];
+        case SDL_JOYBUTTONUP:
+        case SDL_JOYHATMOTION:
+            return {SDLEventToMotionParamPackage(state, event)};
+        }
+        return std::nullopt;
+    }
+};
+
 /**
  * Attempts to match the press to a controller joy axis (left/right stick) and if a match
  * isn't found, checks if the event matches anything from SDLButtonPoller and uses that
@@ -900,6 +1087,9 @@ SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) {
     case InputCommon::Polling::DeviceType::Button:
         pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this));
         break;
+    case InputCommon::Polling::DeviceType::Motion:
+        pollers.emplace_back(std::make_unique<Polling::SDLMotionPoller>(*this));
+        break;
     }
 
     return pollers;
-- 
cgit v1.2.3-70-g09d2