From e46f0e084c73420f8c76c514079952ca0acf1ebe Mon Sep 17 00:00:00 2001 From: german Date: Tue, 17 Nov 2020 22:55:09 -0600 Subject: [PATCH] Implement full mouse support --- src/core/frontend/input.h | 9 +- src/input_common/CMakeLists.txt | 6 +- src/input_common/main.cpp | 73 ++++- src/input_common/main.h | 41 ++- src/input_common/motion_emu.cpp | 179 ------------ src/input_common/motion_emu.h | 46 --- src/input_common/mouse/mouse_input.cpp | 125 +++++++++ src/input_common/mouse/mouse_input.h | 99 +++++++ src/input_common/mouse/mouse_poller.cpp | 261 ++++++++++++++++++ src/input_common/mouse/mouse_poller.h | 109 ++++++++ src/yuzu/bootmanager.cpp | 26 +- .../configuration/configure_input_player.cpp | 87 +++++- src/yuzu_cmd/emu_window/emu_window_sdl2.cpp | 8 +- .../emu_window/emu_window_sdl2_gl.cpp | 1 - 14 files changed, 793 insertions(+), 277 deletions(-) delete mode 100644 src/input_common/motion_emu.cpp delete mode 100644 src/input_common/motion_emu.h create mode 100644 src/input_common/mouse/mouse_input.cpp create mode 100644 src/input_common/mouse/mouse_input.h create mode 100644 src/input_common/mouse/mouse_poller.cpp create mode 100644 src/input_common/mouse/mouse_poller.h diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h index 25ac5af469..93251e3aae 100644 --- a/src/core/frontend/input.h +++ b/src/core/frontend/input.h @@ -161,10 +161,15 @@ using MotionStatus = std::tuple, Common::Vec3, Common using MotionDevice = InputDevice; /** - * A touch device is an input device that returns a tuple of two floats and a bool. The floats are + * A touch status is an object that returns a tuple of two floats and a bool. The floats are * x and y coordinates in the range 0.0 - 1.0, and the bool indicates whether it is pressed. */ -using TouchDevice = InputDevice>; +using TouchStatus = std::tuple; + +/** + * A touch device is an input device that returns a touch status object + */ +using TouchDevice = InputDevice; /** * A mouse device is an input device that returns a tuple of two floats and four ints. diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index 7b39a38c14..1619e5e4e6 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -5,8 +5,6 @@ add_library(input_common STATIC keyboard.h main.cpp main.h - motion_emu.cpp - motion_emu.h motion_from_button.cpp motion_from_button.h motion_input.cpp @@ -19,6 +17,10 @@ add_library(input_common STATIC gcadapter/gc_adapter.h gcadapter/gc_poller.cpp gcadapter/gc_poller.h + mouse/mouse_input.cpp + mouse/mouse_input.h + mouse/mouse_poller.cpp + mouse/mouse_poller.h sdl/sdl.cpp sdl/sdl.h udp/client.cpp diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index e59ad4ff52..880ea73b86 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -10,8 +10,9 @@ #include "input_common/gcadapter/gc_poller.h" #include "input_common/keyboard.h" #include "input_common/main.h" -#include "input_common/motion_emu.h" #include "input_common/motion_from_button.h" +#include "input_common/mouse/mouse_input.h" +#include "input_common/mouse/mouse_poller.h" #include "input_common/touch_from_button.h" #include "input_common/udp/client.h" #include "input_common/udp/udp.h" @@ -37,8 +38,6 @@ struct InputSubsystem::Impl { std::make_shared()); Input::RegisterFactory("keyboard", std::make_shared()); - motion_emu = std::make_shared(); - Input::RegisterFactory("motion_emu", motion_emu); Input::RegisterFactory("touch_from_button", std::make_shared()); @@ -51,6 +50,16 @@ struct InputSubsystem::Impl { Input::RegisterFactory("cemuhookudp", udpmotion); udptouch = std::make_shared(udp); Input::RegisterFactory("cemuhookudp", udptouch); + + mouse = std::make_shared(); + mousebuttons = std::make_shared(mouse); + Input::RegisterFactory("mouse", mousebuttons); + mouseanalog = std::make_shared(mouse); + Input::RegisterFactory("mouse", mouseanalog); + mousemotion = std::make_shared(mouse); + Input::RegisterFactory("mouse", mousemotion); + mousetouch = std::make_shared(mouse); + Input::RegisterFactory("mouse", mousetouch); } void Shutdown() { @@ -58,8 +67,6 @@ struct InputSubsystem::Impl { Input::UnregisterFactory("keyboard"); keyboard.reset(); Input::UnregisterFactory("analog_from_button"); - Input::UnregisterFactory("motion_emu"); - motion_emu.reset(); Input::UnregisterFactory("touch_from_button"); #ifdef HAVE_SDL2 sdl.reset(); @@ -77,6 +84,16 @@ struct InputSubsystem::Impl { udpmotion.reset(); udptouch.reset(); + + Input::UnregisterFactory("mouse"); + Input::UnregisterFactory("mouse"); + Input::UnregisterFactory("mouse"); + Input::UnregisterFactory("mouse"); + + mousebuttons.reset(); + mouseanalog.reset(); + mousemotion.reset(); + mousetouch.reset(); } [[nodiscard]] std::vector GetInputDevices() const { @@ -140,7 +157,6 @@ struct InputSubsystem::Impl { } std::shared_ptr keyboard; - std::shared_ptr motion_emu; #ifdef HAVE_SDL2 std::unique_ptr sdl; #endif @@ -149,8 +165,13 @@ struct InputSubsystem::Impl { std::shared_ptr gcvibration; std::shared_ptr udpmotion; std::shared_ptr udptouch; + std::shared_ptr mousebuttons; + std::shared_ptr mouseanalog; + std::shared_ptr mousemotion; + std::shared_ptr mousetouch; std::shared_ptr udp; std::shared_ptr gcadapter; + std::shared_ptr mouse; }; InputSubsystem::InputSubsystem() : impl{std::make_unique()} {} @@ -173,12 +194,12 @@ const Keyboard* InputSubsystem::GetKeyboard() const { return impl->keyboard.get(); } -MotionEmu* InputSubsystem::GetMotionEmu() { - return impl->motion_emu.get(); +MouseInput::Mouse* InputSubsystem::GetMouse() { + return impl->mouse.get(); } -const MotionEmu* InputSubsystem::GetMotionEmu() const { - return impl->motion_emu.get(); +const MouseInput::Mouse* InputSubsystem::GetMouse() const { + return impl->mouse.get(); } std::vector InputSubsystem::GetInputDevices() const { @@ -229,6 +250,38 @@ const UDPTouchFactory* InputSubsystem::GetUDPTouch() const { return impl->udptouch.get(); } +MouseButtonFactory* InputSubsystem::GetMouseButtons() { + return impl->mousebuttons.get(); +} + +const MouseButtonFactory* InputSubsystem::GetMouseButtons() const { + return impl->mousebuttons.get(); +} + +MouseAnalogFactory* InputSubsystem::GetMouseAnalogs() { + return impl->mouseanalog.get(); +} + +const MouseAnalogFactory* InputSubsystem::GetMouseAnalogs() const { + return impl->mouseanalog.get(); +} + +MouseMotionFactory* InputSubsystem::GetMouseMotions() { + return impl->mousemotion.get(); +} + +const MouseMotionFactory* InputSubsystem::GetMouseMotions() const { + return impl->mousemotion.get(); +} + +MouseTouchFactory* InputSubsystem::GetMouseTouch() { + return impl->mousetouch.get(); +} + +const MouseTouchFactory* InputSubsystem::GetMouseTouch() const { + return impl->mousetouch.get(); +} + void InputSubsystem::ReloadInputDevices() { if (!impl->udp) { return; diff --git a/src/input_common/main.h b/src/input_common/main.h index dded3f1efb..5d6f26385f 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -25,6 +25,10 @@ namespace Settings::NativeMotion { enum Values : int; } +namespace MouseInput { +class Mouse; +} + namespace InputCommon { namespace Polling { @@ -56,8 +60,11 @@ class GCAnalogFactory; class GCButtonFactory; class UDPMotionFactory; class UDPTouchFactory; +class MouseButtonFactory; +class MouseAnalogFactory; +class MouseMotionFactory; +class MouseTouchFactory; class Keyboard; -class MotionEmu; /** * Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default @@ -90,11 +97,11 @@ public: /// Retrieves the underlying keyboard device. [[nodiscard]] const Keyboard* GetKeyboard() const; - /// Retrieves the underlying motion emulation factory. - [[nodiscard]] MotionEmu* GetMotionEmu(); + /// Retrieves the underlying mouse device. + [[nodiscard]] MouseInput::Mouse* GetMouse(); - /// Retrieves the underlying motion emulation factory. - [[nodiscard]] const MotionEmu* GetMotionEmu() const; + /// Retrieves the underlying mouse device. + [[nodiscard]] const MouseInput::Mouse* GetMouse() const; /** * Returns all available input devices that this Factory can create a new device with. @@ -137,6 +144,30 @@ public: /// Retrieves the underlying udp touch handler. [[nodiscard]] const UDPTouchFactory* GetUDPTouch() const; + /// Retrieves the underlying GameCube button handler. + [[nodiscard]] MouseButtonFactory* GetMouseButtons(); + + /// Retrieves the underlying GameCube button handler. + [[nodiscard]] const MouseButtonFactory* GetMouseButtons() const; + + /// Retrieves the underlying udp touch handler. + [[nodiscard]] MouseAnalogFactory* GetMouseAnalogs(); + + /// Retrieves the underlying udp touch handler. + [[nodiscard]] const MouseAnalogFactory* GetMouseAnalogs() const; + + /// Retrieves the underlying udp motion handler. + [[nodiscard]] MouseMotionFactory* GetMouseMotions(); + + /// Retrieves the underlying udp motion handler. + [[nodiscard]] const MouseMotionFactory* GetMouseMotions() const; + + /// Retrieves the underlying udp touch handler. + [[nodiscard]] MouseTouchFactory* GetMouseTouch(); + + /// Retrieves the underlying udp touch handler. + [[nodiscard]] const MouseTouchFactory* GetMouseTouch() const; + /// Reloads the input devices void ReloadInputDevices(); diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp deleted file mode 100644 index d4da5596be..0000000000 --- a/src/input_common/motion_emu.cpp +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include -#include -#include -#include -#include -#include "common/math_util.h" -#include "common/quaternion.h" -#include "common/thread.h" -#include "common/vector_math.h" -#include "input_common/motion_emu.h" - -namespace InputCommon { - -// Implementation class of the motion emulation device -class MotionEmuDevice { -public: - explicit MotionEmuDevice(int update_millisecond_, float sensitivity_) - : update_millisecond(update_millisecond_), - update_duration(std::chrono::duration_cast( - std::chrono::milliseconds(update_millisecond))), - sensitivity(sensitivity_), motion_emu_thread(&MotionEmuDevice::MotionEmuThread, this) {} - - ~MotionEmuDevice() { - if (motion_emu_thread.joinable()) { - shutdown_event.Set(); - motion_emu_thread.join(); - } - } - - void BeginTilt(int x, int y) { - mouse_origin = Common::MakeVec(x, y); - is_tilting = true; - } - - void Tilt(int x, int y) { - if (!is_tilting) { - return; - } - - std::lock_guard guard{tilt_mutex}; - const auto mouse_move = Common::MakeVec(x, y) - mouse_origin; - if (mouse_move.x == 0 && mouse_move.y == 0) { - tilt_angle = 0; - } else { - tilt_direction = mouse_move.Cast(); - tilt_angle = - std::clamp(tilt_direction.Normalize() * sensitivity, 0.0f, Common::PI * 0.5f); - } - } - - void EndTilt() { - std::lock_guard guard{tilt_mutex}; - tilt_angle = 0; - is_tilting = false; - } - - Input::MotionStatus GetStatus() { - std::lock_guard guard{status_mutex}; - return status; - } - -private: - const int update_millisecond; - const std::chrono::steady_clock::duration update_duration; - const float sensitivity; - - Common::Vec2 mouse_origin; - - std::mutex tilt_mutex; - Common::Vec2 tilt_direction; - float tilt_angle = 0; - - bool is_tilting = false; - - Common::Event shutdown_event; - - Input::MotionStatus status; - std::mutex status_mutex; - - // Note: always keep the thread declaration at the end so that other objects are initialized - // before this! - std::thread motion_emu_thread; - - void MotionEmuThread() { - auto update_time = std::chrono::steady_clock::now(); - Common::Quaternion q = Common::MakeQuaternion(Common::Vec3(), 0); - - while (!shutdown_event.WaitUntil(update_time)) { - update_time += update_duration; - const Common::Quaternion old_q = q; - - { - std::lock_guard guard{tilt_mutex}; - - // Find the quaternion describing current 3DS tilting - q = Common::MakeQuaternion( - Common::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x), tilt_angle); - } - - const auto inv_q = q.Inverse(); - - // Set the gravity vector in world space - auto gravity = Common::MakeVec(0.0f, -1.0f, 0.0f); - - // Find the angular rate vector in world space - auto angular_rate = ((q - old_q) * inv_q).xyz * 2; - angular_rate *= static_cast(1000 / update_millisecond) / Common::PI * 180.0f; - - // Transform the two vectors from world space to 3DS space - gravity = QuaternionRotate(inv_q, gravity); - angular_rate = QuaternionRotate(inv_q, angular_rate); - - // TODO: Calculate the correct rotation vector and orientation matrix - const auto matrix4x4 = q.ToMatrix(); - const auto rotation = Common::MakeVec(0.0f, 0.0f, 0.0f); - const std::array orientation{ - Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]), - Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]), - Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10]), - }; - - // Update the sensor state - { - std::lock_guard guard{status_mutex}; - status = std::make_tuple(gravity, angular_rate, rotation, orientation); - } - } - } -}; - -// Interface wrapper held by input receiver as a unique_ptr. It holds the implementation class as -// a shared_ptr, which is also observed by the factory class as a weak_ptr. In this way the factory -// can forward all the inputs to the implementation only when it is valid. -class MotionEmuDeviceWrapper : public Input::MotionDevice { -public: - explicit MotionEmuDeviceWrapper(int update_millisecond, float sensitivity) { - device = std::make_shared(update_millisecond, sensitivity); - } - - Input::MotionStatus GetStatus() const override { - return device->GetStatus(); - } - - std::shared_ptr device; -}; - -std::unique_ptr MotionEmu::Create(const Common::ParamPackage& params) { - const int update_period = params.Get("update_period", 100); - const float sensitivity = params.Get("sensitivity", 0.01f); - auto device_wrapper = std::make_unique(update_period, sensitivity); - // Previously created device is disconnected here. Having two motion devices for 3DS is not - // expected. - current_device = device_wrapper->device; - return device_wrapper; -} - -void MotionEmu::BeginTilt(int x, int y) { - if (auto ptr = current_device.lock()) { - ptr->BeginTilt(x, y); - } -} - -void MotionEmu::Tilt(int x, int y) { - if (auto ptr = current_device.lock()) { - ptr->Tilt(x, y); - } -} - -void MotionEmu::EndTilt() { - if (auto ptr = current_device.lock()) { - ptr->EndTilt(); - } -} - -} // namespace InputCommon diff --git a/src/input_common/motion_emu.h b/src/input_common/motion_emu.h deleted file mode 100644 index 7a7e224672..0000000000 --- a/src/input_common/motion_emu.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "core/frontend/input.h" - -namespace InputCommon { - -class MotionEmuDevice; - -class MotionEmu : public Input::Factory { -public: - /** - * Creates a motion device emulated from mouse input - * @param params contains parameters for creating the device: - * - "update_period": update period in milliseconds - * - "sensitivity": the coefficient converting mouse movement to tilting angle - */ - std::unique_ptr Create(const Common::ParamPackage& params) override; - - /** - * Signals that a motion sensor tilt has begun. - * @param x the x-coordinate of the cursor - * @param y the y-coordinate of the cursor - */ - void BeginTilt(int x, int y); - - /** - * Signals that a motion sensor tilt is occurring. - * @param x the x-coordinate of the cursor - * @param y the y-coordinate of the cursor - */ - void Tilt(int x, int y); - - /** - * Signals that a motion sensor tilt has ended. - */ - void EndTilt(); - -private: - std::weak_ptr current_device; -}; - -} // namespace InputCommon diff --git a/src/input_common/mouse/mouse_input.cpp b/src/input_common/mouse/mouse_input.cpp new file mode 100644 index 0000000000..3f4264caaf --- /dev/null +++ b/src/input_common/mouse/mouse_input.cpp @@ -0,0 +1,125 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "common/math_util.h" +#include "common/param_package.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) { + 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(); + } + std::this_thread::sleep_for(std::chrono::milliseconds(update_time)); + } +} + +void Mouse::UpdateYuzuSettings() { + MouseStatus pad_status{}; + if (buttons != 0) { + pad_status.button = last_button; + mouse_queue.Push(pad_status); + } +} + +void Mouse::PressButton(int x, int y, int button_) { + if (button_ >= static_cast(mouse_info.size())) { + return; + } + + int button = 1 << button_; + buttons |= static_cast(button); + last_button = static_cast(button_); + + mouse_info[button_].mouse_origin = Common::MakeVec(x, y); + mouse_info[button_].last_mouse_position = Common::MakeVec(x, y); + mouse_info[button_].data.pressed = true; +} + +void Mouse::MouseMove(int x, int y) { + for (MouseInfo& info : mouse_info) { + if (info.data.pressed) { + auto mouse_move = Common::MakeVec(x, y) - info.mouse_origin; + 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(); + info.tilt_speed = info.tilt_direction.Normalize() * info.sensitivity; + } + } + } +} + +void Mouse::ReleaseButton(int button_) { + if (button_ >= static_cast(mouse_info.size())) { + return; + } + + int button = 1 << button_; + buttons &= static_cast(0xFF - button); + + mouse_info[button_].tilt_speed = 0; + mouse_info[button_].data.pressed = false; + mouse_info[button_].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& Mouse::GetMouseQueue() { + return mouse_queue; +} + +const Common::SPSCQueue& 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 0000000000..761663334b --- /dev/null +++ b/src/input_common/mouse/mouse_input.h @@ -0,0 +1,99 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include "common/common_types.h" +#include "common/threadsafe_queue.h" +#include "core/frontend/input.h" +#include "input_common/main.h" +#include "input_common/motion_input.h" + +namespace MouseInput { + +enum class MouseButton { + Left, + Wheel, + Right, + Foward, + Backward, + Undefined, +}; + +struct MouseStatus { + MouseButton button{MouseButton::Undefined}; +}; + +struct MouseData { + bool pressed{}; + std::array 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 + */ + void MouseMove(int x, int y); + + /** + * Signals that a motion sensor tilt has ended. + */ + void ReleaseButton(int button_); + + [[nodiscard]] Common::SPSCQueue& GetMouseQueue(); + [[nodiscard]] const Common::SPSCQueue& GetMouseQueue() const; + + [[nodiscard]] MouseData& GetMouseState(std::size_t button); + [[nodiscard]] const MouseData& GetMouseState(std::size_t button) const; + +private: + void UpdateThread(); + void UpdateYuzuSettings(); + + struct MouseInfo { + InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f}; + Common::Vec2 mouse_origin; + Common::Vec2 last_mouse_position; + bool is_tilting = false; + float sensitivity{0.120f}; + + float tilt_speed = 0; + Common::Vec2 tilt_direction; + MouseData data; + }; + + u16 buttons{}; + std::thread update_thread; + MouseButton last_button{MouseButton::Undefined}; + std::array mouse_info; + Common::SPSCQueue mouse_queue; + bool configuring{false}; + bool update_thread_running{true}; +}; +} // 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 0000000000..6213f3dbdb --- /dev/null +++ b/src/input_common/mouse/mouse_poller.cpp @@ -0,0 +1,261 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include "common/assert.h" +#include "common/threadsafe_queue.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 mouse_input_) + : mouse_input(std::move(mouse_input_)) {} + +std::unique_ptr MouseButtonFactory::Create( + const Common::ParamPackage& params) { + const auto button_id = params.Get("button", 0); + + return std::make_unique(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(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_, float deadzone_, float range_, + const MouseInput::Mouse* mouse_input_) + : button(port_), axis_x(axis_x_), axis_y(axis_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(mouse_input->GetMouseState(button).axis.at(axis)); + return axis_value / (100.0f * range); + } + + std::pair GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const { + float x = GetAxis(analog_axis_x); + float y = GetAxis(analog_axis_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 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}; + } + +private: + const u32 button; + const u32 axis_x; + const u32 axis_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 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 MouseAnalogFactory::Create( + const Common::ParamPackage& params) { + const auto port = static_cast(params.Get("port", 0)); + const auto axis_x = static_cast(params.Get("axis_x", 0)); + const auto axis_y = static_cast(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); + + return std::make_unique(port, axis_x, axis_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(pad.button)); + params.Set("axis_x", 0); + params.Set("axis_y", 1); + 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 mouse_input_) + : mouse_input(std::move(mouse_input_)) {} + +std::unique_ptr MouseMotionFactory::Create( + const Common::ParamPackage& params) { + const auto button_id = params.Get("button", 0); + + return std::make_unique(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(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 mouse_input_) + : mouse_input(std::move(mouse_input_)) {} + +std::unique_ptr MouseTouchFactory::Create(const Common::ParamPackage& params) { + const auto button_id = params.Get("button", 0); + + return std::make_unique(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(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 0000000000..cf331293b8 --- /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 +#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 { +public: + explicit MouseButtonFactory(std::shared_ptr 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 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 mouse_input; + bool polling = false; +}; + +/// An analog device factory that creates analog devices from mouse +class MouseAnalogFactory final : public Input::Factory { +public: + explicit MouseAnalogFactory(std::shared_ptr mouse_input_); + + std::unique_ptr 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 mouse_input; + bool polling = false; +}; + +/// A motion device factory that creates motion devices from mouse +class MouseMotionFactory final : public Input::Factory { +public: + explicit MouseMotionFactory(std::shared_ptr mouse_input_); + + std::unique_ptr 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 mouse_input; + bool polling = false; +}; + +/// An touch device factory that creates touch devices from mouse +class MouseTouchFactory final : public Input::Factory { +public: + explicit MouseTouchFactory(std::shared_ptr mouse_input_); + + std::unique_ptr 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 mouse_input; + bool polling = false; +}; + +} // namespace InputCommon diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index d62b0efc20..d239ffbbd3 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -35,7 +35,7 @@ #include "core/settings.h" #include "input_common/keyboard.h" #include "input_common/main.h" -#include "input_common/motion_emu.h" +#include "input_common/mouse/mouse_input.h" #include "video_core/renderer_base.h" #include "video_core/video_core.h" #include "yuzu/bootmanager.h" @@ -382,23 +382,19 @@ void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { } void GRenderWindow::mousePressEvent(QMouseEvent* event) { - if (!Settings::values.touchscreen.enabled) { - input_subsystem->GetKeyboard()->PressKey(event->button()); - return; - } - // Touch input is handled in TouchBeginEvent if (event->source() == Qt::MouseEventSynthesizedBySystem) { return; } auto pos = event->pos(); + const auto [x, y] = ScaleTouch(pos); + input_subsystem->GetMouse()->PressButton(x, y, event->button()); + if (event->button() == Qt::LeftButton) { - const auto [x, y] = ScaleTouch(pos); this->TouchPressed(x, y); - } else if (event->button() == Qt::RightButton) { - input_subsystem->GetMotionEmu()->BeginTilt(pos.x(), pos.y()); } + QWidget::mousePressEvent(event); } @@ -410,26 +406,22 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) { auto pos = event->pos(); const auto [x, y] = ScaleTouch(pos); + input_subsystem->GetMouse()->MouseMove(x, y); this->TouchMoved(x, y); - input_subsystem->GetMotionEmu()->Tilt(pos.x(), pos.y()); + QWidget::mouseMoveEvent(event); } void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { - if (!Settings::values.touchscreen.enabled) { - input_subsystem->GetKeyboard()->ReleaseKey(event->button()); - return; - } - // Touch input is handled in TouchEndEvent if (event->source() == Qt::MouseEventSynthesizedBySystem) { return; } + input_subsystem->GetMouse()->ReleaseButton(event->button()); + if (event->button() == Qt::LeftButton) { this->TouchReleased(); - } else if (event->button() == Qt::RightButton) { - input_subsystem->GetMotionEmu()->EndTilt(); } } diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 72640f5e7b..87440a013d 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -19,6 +19,7 @@ #include "core/hle/service/sm/sm.h" #include "input_common/gcadapter/gc_poller.h" #include "input_common/main.h" +#include "input_common/mouse/mouse_poller.h" #include "input_common/udp/udp.h" #include "ui_configure_input_player.h" #include "yuzu/configuration/config.h" @@ -186,6 +187,14 @@ QString ButtonToText(const Common::ParamPackage& param) { return {}; } + if (param.Get("engine", "") == "mouse") { + if (param.Has("button")) { + const QString button_str = QString::number(int(param.Get("button", 0))); + return QObject::tr("Click %1").arg(button_str); + } + return GetKeyName(param.Get("code", 0)); + } + return QObject::tr("[unknown]"); } @@ -237,6 +246,26 @@ QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) return {}; } + + if (param.Get("engine", "") == "mouse") { + if (dir == "modifier") { + return QObject::tr("[unused]"); + } + + if (dir == "left" || dir == "right") { + const QString axis_x_str = QString::fromStdString(param.Get("axis_x", "")); + + return QObject::tr("Mouse %1").arg(axis_x_str); + } + + if (dir == "up" || dir == "down") { + const QString axis_y_str = QString::fromStdString(param.Get("axis_y", "")); + + return QObject::tr("Mouse %1").arg(axis_y_str); + } + + return {}; + } return QObject::tr("[unknown]"); } } // namespace @@ -532,6 +561,34 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i return; } } + if (input_subsystem->GetMouseButtons()->IsPolling()) { + params = input_subsystem->GetMouseButtons()->GetNextInput(); + if (params.Has("engine") && IsInputAcceptable(params)) { + SetPollingResult(params, false); + return; + } + } + if (input_subsystem->GetMouseAnalogs()->IsPolling()) { + params = input_subsystem->GetMouseAnalogs()->GetNextInput(); + if (params.Has("engine") && IsInputAcceptable(params)) { + SetPollingResult(params, false); + return; + } + } + if (input_subsystem->GetMouseMotions()->IsPolling()) { + params = input_subsystem->GetMouseMotions()->GetNextInput(); + if (params.Has("engine") && IsInputAcceptable(params)) { + SetPollingResult(params, false); + return; + } + } + if (input_subsystem->GetMouseTouch()->IsPolling()) { + params = input_subsystem->GetMouseTouch()->GetNextInput(); + if (params.Has("engine") && IsInputAcceptable(params)) { + SetPollingResult(params, false); + return; + } + } for (auto& poller : device_pollers) { params = poller->GetNextInput(); if (params.Has("engine") && IsInputAcceptable(params)) { @@ -809,8 +866,9 @@ void ConfigureInputPlayer::UpdateUI() { int slider_value; auto& param = analogs_param[analog_id]; - const bool is_controller = - param.Get("engine", "") == "sdl" || param.Get("engine", "") == "gcpad"; + const bool is_controller = param.Get("engine", "") == "sdl" || + param.Get("engine", "") == "gcpad" || + param.Get("engine", "") == "mouse"; if (is_controller) { if (!param.Has("deadzone")) { @@ -1050,6 +1108,16 @@ void ConfigureInputPlayer::HandleClick( input_subsystem->GetUDPMotions()->BeginConfiguration(); } + if (type == InputCommon::Polling::DeviceType::Button) { + input_subsystem->GetMouseButtons()->BeginConfiguration(); + } else if (type == InputCommon::Polling::DeviceType::AnalogPreferred) { + input_subsystem->GetMouseAnalogs()->BeginConfiguration(); + } else if (type == InputCommon::Polling::DeviceType::Motion) { + input_subsystem->GetMouseMotions()->BeginConfiguration(); + } else { + input_subsystem->GetMouseTouch()->BeginConfiguration(); + } + timeout_timer->start(2500); // Cancel after 2.5 seconds poll_timer->start(50); // Check for new inputs every 50ms } @@ -1069,6 +1137,11 @@ void ConfigureInputPlayer::SetPollingResult(const Common::ParamPackage& params, input_subsystem->GetUDPMotions()->EndConfiguration(); + input_subsystem->GetMouseButtons()->EndConfiguration(); + input_subsystem->GetMouseAnalogs()->EndConfiguration(); + input_subsystem->GetMouseMotions()->EndConfiguration(); + input_subsystem->GetMouseTouch()->EndConfiguration(); + if (!abort) { (*input_setter)(params); } @@ -1100,15 +1173,7 @@ void ConfigureInputPlayer::mousePressEvent(QMouseEvent* event) { return; } - if (want_keyboard_mouse) { - SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->button())}, - false); - } else { - // We don't want any mouse buttons, so don't stop polling - return; - } - - SetPollingResult({}, true); + input_subsystem->GetMouse()->PressButton(0, 0, event->button()); } void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index 5212096227..72ec25c996 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -9,7 +9,7 @@ #include "core/perf_stats.h" #include "input_common/keyboard.h" #include "input_common/main.h" -#include "input_common/motion_emu.h" +#include "input_common/mouse/mouse_input.h" #include "input_common/sdl/sdl.h" #include "yuzu_cmd/emu_window/emu_window_sdl2.h" @@ -30,7 +30,7 @@ EmuWindow_SDL2::~EmuWindow_SDL2() { void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) { TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0)); - input_subsystem->GetMotionEmu()->Tilt(x, y); + input_subsystem->GetMouse()->MouseMove(x, y); } void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { @@ -42,9 +42,9 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) { } } else if (button == SDL_BUTTON_RIGHT) { if (state == SDL_PRESSED) { - input_subsystem->GetMotionEmu()->BeginTilt(x, y); + input_subsystem->GetMouse()->PressButton(x, y, button); } else { - input_subsystem->GetMotionEmu()->EndTilt(); + input_subsystem->GetMouse()->ReleaseButton(button); } } } diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp index 5f35233b5e..a103b04bd3 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp @@ -17,7 +17,6 @@ #include "core/settings.h" #include "input_common/keyboard.h" #include "input_common/main.h" -#include "input_common/motion_emu.h" #include "video_core/renderer_base.h" #include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h"