Implement full mouse support

This commit is contained in:
german
2020-11-17 22:55:09 -06:00
parent 1889b641d9
commit e46f0e084c
14 changed files with 793 additions and 277 deletions

View File

@ -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<int>(mouse_info.size())) {
return;
}
int button = 1 << button_;
buttons |= static_cast<u16>(button);
last_button = static_cast<MouseButton>(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<float>();
info.tilt_speed = info.tilt_direction.Normalize() * info.sensitivity;
}
}
}
}
void Mouse::ReleaseButton(int button_) {
if (button_ >= static_cast<int>(mouse_info.size())) {
return;
}
int button = 1 << button_;
buttons &= static_cast<u16>(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<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

View File

@ -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 <algorithm>
#include <functional>
#include <mutex>
#include <thread>
#include <unordered_map>
#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<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
*/
void MouseMove(int x, int 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();
struct MouseInfo {
InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f};
Common::Vec2<int> mouse_origin;
Common::Vec2<int> last_mouse_position;
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};
};
} // namespace MouseInput

View File

@ -0,0 +1,261 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <atomic>
#include <list>
#include <mutex>
#include <utility>
#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<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_, 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<float>(mouse_input->GetMouseState(button).axis.at(axis));
return axis_value / (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);
// 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};
}
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<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);
return std::make_unique<MouseAnalog>(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<u16>(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<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

View File

@ -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