diff options
Diffstat (limited to 'src/input_common/mouse')
-rw-r--r-- | src/input_common/mouse/mouse_input.cpp | 159 | ||||
-rw-r--r-- | src/input_common/mouse/mouse_input.h | 103 | ||||
-rw-r--r-- | src/input_common/mouse/mouse_poller.cpp | 285 | ||||
-rw-r--r-- | src/input_common/mouse/mouse_poller.h | 109 |
4 files changed, 656 insertions, 0 deletions
diff --git a/src/input_common/mouse/mouse_input.cpp b/src/input_common/mouse/mouse_input.cpp new file mode 100644 index 000000000..67a584d53 --- /dev/null +++ b/src/input_common/mouse/mouse_input.cpp @@ -0,0 +1,159 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "core/settings.h" +#include "input_common/mouse/mouse_input.h" + +namespace MouseInput { + +Mouse::Mouse() { + update_thread = std::thread(&Mouse::UpdateThread, this); +} + +Mouse::~Mouse() { + update_thread_running = false; + if (update_thread.joinable()) { + update_thread.join(); + } +} + +void Mouse::UpdateThread() { + constexpr int update_time = 10; + while (update_thread_running) { + for (MouseInfo& info : mouse_info) { + const Common::Vec3f angular_direction{ + -info.tilt_direction.y, + 0.0f, + -info.tilt_direction.x, + }; + + info.motion.SetGyroscope(angular_direction * info.tilt_speed); + info.motion.UpdateRotation(update_time * 1000); + info.motion.UpdateOrientation(update_time * 1000); + info.tilt_speed = 0; + info.data.motion = info.motion.GetMotion(); + } + if (configuring) { + UpdateYuzuSettings(); + } + if (mouse_panning_timout++ > 8) { + StopPanning(); + } + std::this_thread::sleep_for(std::chrono::milliseconds(update_time)); + } +} + +void Mouse::UpdateYuzuSettings() { + if (buttons == 0) { + return; + } + + mouse_queue.Push(MouseStatus{ + .button = last_button, + }); +} + +void Mouse::PressButton(int x, int y, int button_) { + const auto button_index = static_cast<std::size_t>(button_); + if (button_index >= mouse_info.size()) { + return; + } + + const auto button = 1U << button_index; + buttons |= static_cast<u16>(button); + last_button = static_cast<MouseButton>(button_index); + + mouse_info[button_index].mouse_origin = Common::MakeVec(x, y); + mouse_info[button_index].last_mouse_position = Common::MakeVec(x, y); + mouse_info[button_index].data.pressed = true; +} + +void Mouse::StopPanning() { + for (MouseInfo& info : mouse_info) { + if (Settings::values.mouse_panning) { + info.data.axis = {}; + info.tilt_speed = 0; + info.last_mouse_change = {}; + } + } +} + +void Mouse::MouseMove(int x, int y, int center_x, int center_y) { + for (MouseInfo& info : mouse_info) { + if (Settings::values.mouse_panning) { + const auto mouse_change = Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y); + mouse_panning_timout = 0; + + if (mouse_change.y == 0 && mouse_change.x == 0) { + continue; + } + + info.last_mouse_change = (info.last_mouse_change * 0.8f) + (mouse_change * 0.2f); + info.data.axis = {static_cast<int>(16 * info.last_mouse_change.x), + static_cast<int>(16 * -info.last_mouse_change.y)}; + info.tilt_direction = info.last_mouse_change; + info.tilt_speed = info.tilt_direction.Normalize() * info.sensitivity; + continue; + } + + if (info.data.pressed) { + const auto mouse_move = Common::MakeVec(x, y) - info.mouse_origin; + const auto mouse_change = Common::MakeVec(x, y) - info.last_mouse_position; + info.last_mouse_position = Common::MakeVec(x, y); + info.data.axis = {mouse_move.x, -mouse_move.y}; + + if (mouse_change.x == 0 && mouse_change.y == 0) { + info.tilt_speed = 0; + } else { + info.tilt_direction = mouse_change.Cast<float>(); + info.tilt_speed = info.tilt_direction.Normalize() * info.sensitivity; + } + } + } +} + +void Mouse::ReleaseButton(int button_) { + const auto button_index = static_cast<std::size_t>(button_); + if (button_index >= mouse_info.size()) { + return; + } + + const auto button = 1U << button_index; + buttons &= static_cast<u16>(0xFF - button); + + mouse_info[button_index].tilt_speed = 0; + mouse_info[button_index].data.pressed = false; + mouse_info[button_index].data.axis = {0, 0}; +} + +void Mouse::BeginConfiguration() { + buttons = 0; + last_button = MouseButton::Undefined; + mouse_queue.Clear(); + configuring = true; +} + +void Mouse::EndConfiguration() { + buttons = 0; + last_button = MouseButton::Undefined; + mouse_queue.Clear(); + configuring = false; +} + +Common::SPSCQueue<MouseStatus>& Mouse::GetMouseQueue() { + return mouse_queue; +} + +const Common::SPSCQueue<MouseStatus>& Mouse::GetMouseQueue() const { + return mouse_queue; +} + +MouseData& Mouse::GetMouseState(std::size_t button) { + return mouse_info[button].data; +} + +const MouseData& Mouse::GetMouseState(std::size_t button) const { + return mouse_info[button].data; +} +} // namespace MouseInput diff --git a/src/input_common/mouse/mouse_input.h b/src/input_common/mouse/mouse_input.h new file mode 100644 index 000000000..46aa676c1 --- /dev/null +++ b/src/input_common/mouse/mouse_input.h @@ -0,0 +1,103 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <mutex> +#include <thread> + +#include "common/common_types.h" +#include "common/threadsafe_queue.h" +#include "common/vector_math.h" +#include "core/frontend/input.h" +#include "input_common/motion_input.h" + +namespace MouseInput { + +enum class MouseButton { + Left, + Wheel, + Right, + Forward, + Backward, + Undefined, +}; + +struct MouseStatus { + MouseButton button{MouseButton::Undefined}; +}; + +struct MouseData { + bool pressed{}; + std::array<int, 2> axis{}; + Input::MotionStatus motion{}; + Input::TouchStatus touch{}; +}; + +class Mouse { +public: + Mouse(); + ~Mouse(); + + /// Used for polling + void BeginConfiguration(); + void EndConfiguration(); + + /** + * Signals that a button is pressed. + * @param x the x-coordinate of the cursor + * @param y the y-coordinate of the cursor + * @param button_ the button pressed + */ + void PressButton(int x, int y, int button_); + + /** + * Signals that mouse has moved. + * @param x the x-coordinate of the cursor + * @param y the y-coordinate of the cursor + * @param center_x the x-coordinate of the middle of the screen + * @param center_y the y-coordinate of the middle of the screen + */ + void MouseMove(int x, int y, int center_x, int center_y); + + /** + * Signals that a motion sensor tilt has ended. + */ + void ReleaseButton(int button_); + + [[nodiscard]] Common::SPSCQueue<MouseStatus>& GetMouseQueue(); + [[nodiscard]] const Common::SPSCQueue<MouseStatus>& GetMouseQueue() const; + + [[nodiscard]] MouseData& GetMouseState(std::size_t button); + [[nodiscard]] const MouseData& GetMouseState(std::size_t button) const; + +private: + void UpdateThread(); + void UpdateYuzuSettings(); + void StopPanning(); + + struct MouseInfo { + InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f}; + Common::Vec2<int> mouse_origin; + Common::Vec2<int> last_mouse_position; + Common::Vec2<float> last_mouse_change; + bool is_tilting = false; + float sensitivity{0.120f}; + + float tilt_speed = 0; + Common::Vec2<float> tilt_direction; + MouseData data; + }; + + u16 buttons{}; + std::thread update_thread; + MouseButton last_button{MouseButton::Undefined}; + std::array<MouseInfo, 5> mouse_info; + Common::SPSCQueue<MouseStatus> mouse_queue; + bool configuring{false}; + bool update_thread_running{true}; + int mouse_panning_timout{}; +}; +} // namespace MouseInput diff --git a/src/input_common/mouse/mouse_poller.cpp b/src/input_common/mouse/mouse_poller.cpp new file mode 100644 index 000000000..bb56787ee --- /dev/null +++ b/src/input_common/mouse/mouse_poller.cpp @@ -0,0 +1,285 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <mutex> +#include <utility> + +#include "common/threadsafe_queue.h" +#include "core/settings.h" +#include "input_common/mouse/mouse_input.h" +#include "input_common/mouse/mouse_poller.h" + +namespace InputCommon { + +class MouseButton final : public Input::ButtonDevice { +public: + explicit MouseButton(u32 button_, const MouseInput::Mouse* mouse_input_) + : button(button_), mouse_input(mouse_input_) {} + + bool GetStatus() const override { + return mouse_input->GetMouseState(button).pressed; + } + +private: + const u32 button; + const MouseInput::Mouse* mouse_input; +}; + +MouseButtonFactory::MouseButtonFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_) + : mouse_input(std::move(mouse_input_)) {} + +std::unique_ptr<Input::ButtonDevice> MouseButtonFactory::Create( + const Common::ParamPackage& params) { + const auto button_id = params.Get("button", 0); + + return std::make_unique<MouseButton>(button_id, mouse_input.get()); +} + +Common::ParamPackage MouseButtonFactory::GetNextInput() const { + MouseInput::MouseStatus pad; + Common::ParamPackage params; + auto& queue = mouse_input->GetMouseQueue(); + while (queue.Pop(pad)) { + // This while loop will break on the earliest detected button + if (pad.button != MouseInput::MouseButton::Undefined) { + params.Set("engine", "mouse"); + params.Set("button", static_cast<u16>(pad.button)); + return params; + } + } + return params; +} + +void MouseButtonFactory::BeginConfiguration() { + polling = true; + mouse_input->BeginConfiguration(); +} + +void MouseButtonFactory::EndConfiguration() { + polling = false; + mouse_input->EndConfiguration(); +} + +class MouseAnalog final : public Input::AnalogDevice { +public: + explicit MouseAnalog(u32 port_, u32 axis_x_, u32 axis_y_, bool invert_x_, bool invert_y_, + float deadzone_, float range_, const MouseInput::Mouse* mouse_input_) + : button(port_), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), invert_y(invert_y_), + deadzone(deadzone_), range(range_), mouse_input(mouse_input_) {} + + float GetAxis(u32 axis) const { + std::lock_guard lock{mutex}; + const auto axis_value = + static_cast<float>(mouse_input->GetMouseState(button).axis.at(axis)); + return axis_value * Settings::values.mouse_panning_sensitivity / (100.0f * range); + } + + std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const { + float x = GetAxis(analog_axis_x); + float y = GetAxis(analog_axis_y); + if (invert_x) { + x = -x; + } + if (invert_y) { + y = -y; + } + + // Make sure the coordinates are in the unit circle, + // otherwise normalize it. + float r = x * x + y * y; + if (r > 1.0f) { + r = std::sqrt(r); + x /= r; + y /= r; + } + + return {x, y}; + } + + std::tuple<float, float> GetStatus() const override { + const auto [x, y] = GetAnalog(axis_x, axis_y); + const float r = std::sqrt((x * x) + (y * y)); + if (r > deadzone) { + return {x / r * (r - deadzone) / (1 - deadzone), + y / r * (r - deadzone) / (1 - deadzone)}; + } + return {0.0f, 0.0f}; + } + + std::tuple<float, float> GetRawStatus() const override { + const float x = GetAxis(axis_x); + const float y = GetAxis(axis_y); + return {x, y}; + } + + Input::AnalogProperties GetAnalogProperties() const override { + return {deadzone, range, 0.5f}; + } + +private: + const u32 button; + const u32 axis_x; + const u32 axis_y; + const bool invert_x; + const bool invert_y; + const float deadzone; + const float range; + const MouseInput::Mouse* mouse_input; + mutable std::mutex mutex; +}; + +/// An analog device factory that creates analog devices from GC Adapter +MouseAnalogFactory::MouseAnalogFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_) + : mouse_input(std::move(mouse_input_)) {} + +/** + * Creates analog device from joystick axes + * @param params contains parameters for creating the device: + * - "port": the nth gcpad on the adapter + * - "axis_x": the index of the axis to be bind as x-axis + * - "axis_y": the index of the axis to be bind as y-axis + */ +std::unique_ptr<Input::AnalogDevice> MouseAnalogFactory::Create( + const Common::ParamPackage& params) { + const auto port = static_cast<u32>(params.Get("port", 0)); + const auto axis_x = static_cast<u32>(params.Get("axis_x", 0)); + const auto axis_y = static_cast<u32>(params.Get("axis_y", 1)); + const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); + const auto range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f); + const std::string invert_x_value = params.Get("invert_x", "+"); + const std::string invert_y_value = params.Get("invert_y", "+"); + const bool invert_x = invert_x_value == "-"; + const bool invert_y = invert_y_value == "-"; + + return std::make_unique<MouseAnalog>(port, axis_x, axis_y, invert_x, invert_y, deadzone, range, + mouse_input.get()); +} + +void MouseAnalogFactory::BeginConfiguration() { + polling = true; + mouse_input->BeginConfiguration(); +} + +void MouseAnalogFactory::EndConfiguration() { + polling = false; + mouse_input->EndConfiguration(); +} + +Common::ParamPackage MouseAnalogFactory::GetNextInput() const { + MouseInput::MouseStatus pad; + Common::ParamPackage params; + auto& queue = mouse_input->GetMouseQueue(); + while (queue.Pop(pad)) { + // This while loop will break on the earliest detected button + if (pad.button != MouseInput::MouseButton::Undefined) { + params.Set("engine", "mouse"); + params.Set("port", static_cast<u16>(pad.button)); + params.Set("axis_x", 0); + params.Set("axis_y", 1); + params.Set("invert_x", "+"); + params.Set("invert_y", "+"); + return params; + } + } + return params; +} + +class MouseMotion final : public Input::MotionDevice { +public: + explicit MouseMotion(u32 button_, const MouseInput::Mouse* mouse_input_) + : button(button_), mouse_input(mouse_input_) {} + + Input::MotionStatus GetStatus() const override { + return mouse_input->GetMouseState(button).motion; + } + +private: + const u32 button; + const MouseInput::Mouse* mouse_input; +}; + +MouseMotionFactory::MouseMotionFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_) + : mouse_input(std::move(mouse_input_)) {} + +std::unique_ptr<Input::MotionDevice> MouseMotionFactory::Create( + const Common::ParamPackage& params) { + const auto button_id = params.Get("button", 0); + + return std::make_unique<MouseMotion>(button_id, mouse_input.get()); +} + +Common::ParamPackage MouseMotionFactory::GetNextInput() const { + MouseInput::MouseStatus pad; + Common::ParamPackage params; + auto& queue = mouse_input->GetMouseQueue(); + while (queue.Pop(pad)) { + // This while loop will break on the earliest detected button + if (pad.button != MouseInput::MouseButton::Undefined) { + params.Set("engine", "mouse"); + params.Set("button", static_cast<u16>(pad.button)); + return params; + } + } + return params; +} + +void MouseMotionFactory::BeginConfiguration() { + polling = true; + mouse_input->BeginConfiguration(); +} + +void MouseMotionFactory::EndConfiguration() { + polling = false; + mouse_input->EndConfiguration(); +} + +class MouseTouch final : public Input::TouchDevice { +public: + explicit MouseTouch(u32 button_, const MouseInput::Mouse* mouse_input_) + : button(button_), mouse_input(mouse_input_) {} + + Input::TouchStatus GetStatus() const override { + return mouse_input->GetMouseState(button).touch; + } + +private: + const u32 button; + const MouseInput::Mouse* mouse_input; +}; + +MouseTouchFactory::MouseTouchFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_) + : mouse_input(std::move(mouse_input_)) {} + +std::unique_ptr<Input::TouchDevice> MouseTouchFactory::Create(const Common::ParamPackage& params) { + const auto button_id = params.Get("button", 0); + + return std::make_unique<MouseTouch>(button_id, mouse_input.get()); +} + +Common::ParamPackage MouseTouchFactory::GetNextInput() const { + MouseInput::MouseStatus pad; + Common::ParamPackage params; + auto& queue = mouse_input->GetMouseQueue(); + while (queue.Pop(pad)) { + // This while loop will break on the earliest detected button + if (pad.button != MouseInput::MouseButton::Undefined) { + params.Set("engine", "mouse"); + params.Set("button", static_cast<u16>(pad.button)); + return params; + } + } + return params; +} + +void MouseTouchFactory::BeginConfiguration() { + polling = true; + mouse_input->BeginConfiguration(); +} + +void MouseTouchFactory::EndConfiguration() { + polling = false; + mouse_input->EndConfiguration(); +} + +} // namespace InputCommon diff --git a/src/input_common/mouse/mouse_poller.h b/src/input_common/mouse/mouse_poller.h new file mode 100644 index 000000000..cf331293b --- /dev/null +++ b/src/input_common/mouse/mouse_poller.h @@ -0,0 +1,109 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "core/frontend/input.h" +#include "input_common/mouse/mouse_input.h" + +namespace InputCommon { + +/** + * A button device factory representing a mouse. It receives mouse events and forward them + * to all button devices it created. + */ +class MouseButtonFactory final : public Input::Factory<Input::ButtonDevice> { +public: + explicit MouseButtonFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_); + + /** + * Creates a button device from a button press + * @param params contains parameters for creating the device: + * - "code": the code of the key to bind with the button + */ + std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override; + + Common::ParamPackage GetNextInput() const; + + /// For device input configuration/polling + void BeginConfiguration(); + void EndConfiguration(); + + bool IsPolling() const { + return polling; + } + +private: + std::shared_ptr<MouseInput::Mouse> mouse_input; + bool polling = false; +}; + +/// An analog device factory that creates analog devices from mouse +class MouseAnalogFactory final : public Input::Factory<Input::AnalogDevice> { +public: + explicit MouseAnalogFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_); + + std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override; + + Common::ParamPackage GetNextInput() const; + + /// For device input configuration/polling + void BeginConfiguration(); + void EndConfiguration(); + + bool IsPolling() const { + return polling; + } + +private: + std::shared_ptr<MouseInput::Mouse> mouse_input; + bool polling = false; +}; + +/// A motion device factory that creates motion devices from mouse +class MouseMotionFactory final : public Input::Factory<Input::MotionDevice> { +public: + explicit MouseMotionFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_); + + std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override; + + Common::ParamPackage GetNextInput() const; + + /// For device input configuration/polling + void BeginConfiguration(); + void EndConfiguration(); + + bool IsPolling() const { + return polling; + } + +private: + std::shared_ptr<MouseInput::Mouse> mouse_input; + bool polling = false; +}; + +/// An touch device factory that creates touch devices from mouse +class MouseTouchFactory final : public Input::Factory<Input::TouchDevice> { +public: + explicit MouseTouchFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_); + + std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override; + + Common::ParamPackage GetNextInput() const; + + /// For device input configuration/polling + void BeginConfiguration(); + void EndConfiguration(); + + bool IsPolling() const { + return polling; + } + +private: + std::shared_ptr<MouseInput::Mouse> mouse_input; + bool polling = false; +}; + +} // namespace InputCommon |