| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- // SPDX-FileCopyrightText: 2017 Citra Emulator Project
- // SPDX-License-Identifier: GPL-2.0-or-later
- #include <chrono>
- #include <cmath>
- #include "common/math_util.h"
- #include "common/settings.h"
- #include "input_common/helpers/stick_from_buttons.h"
- namespace InputCommon {
- class Stick final : public Common::Input::InputDevice {
- public:
- using Button = std::unique_ptr<Common::Input::InputDevice>;
- Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_,
- float modifier_scale_, float modifier_angle_)
- : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)),
- right(std::move(right_)), modifier(std::move(modifier_)), modifier_scale(modifier_scale_),
- modifier_angle(modifier_angle_) {
- up->SetCallback({
- .on_change =
- [this](const Common::Input::CallbackStatus& callback_) {
- UpdateUpButtonStatus(callback_);
- },
- });
- down->SetCallback({
- .on_change =
- [this](const Common::Input::CallbackStatus& callback_) {
- UpdateDownButtonStatus(callback_);
- },
- });
- left->SetCallback({
- .on_change =
- [this](const Common::Input::CallbackStatus& callback_) {
- UpdateLeftButtonStatus(callback_);
- },
- });
- right->SetCallback({
- .on_change =
- [this](const Common::Input::CallbackStatus& callback_) {
- UpdateRightButtonStatus(callback_);
- },
- });
- modifier->SetCallback({
- .on_change =
- [this](const Common::Input::CallbackStatus& callback_) {
- UpdateModButtonStatus(callback_);
- },
- });
- last_x_axis_value = 0.0f;
- last_y_axis_value = 0.0f;
- }
- bool IsAngleGreater(float old_angle, float new_angle) const {
- constexpr float TAU = Common::PI * 2.0f;
- // Use wider angle to ease the transition.
- constexpr float aperture = TAU * 0.15f;
- const float top_limit = new_angle + aperture;
- return (old_angle > new_angle && old_angle <= top_limit) ||
- (old_angle + TAU > new_angle && old_angle + TAU <= top_limit);
- }
- bool IsAngleSmaller(float old_angle, float new_angle) const {
- constexpr float TAU = Common::PI * 2.0f;
- // Use wider angle to ease the transition.
- constexpr float aperture = TAU * 0.15f;
- const float bottom_limit = new_angle - aperture;
- return (old_angle >= bottom_limit && old_angle < new_angle) ||
- (old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle);
- }
- float GetAngle(std::chrono::time_point<std::chrono::steady_clock> now) const {
- constexpr float TAU = Common::PI * 2.0f;
- float new_angle = angle;
- auto time_difference = static_cast<float>(
- std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count());
- time_difference /= 1000.0f * 1000.0f;
- if (time_difference > 0.5f) {
- time_difference = 0.5f;
- }
- if (IsAngleGreater(new_angle, goal_angle)) {
- new_angle -= modifier_angle * time_difference;
- if (new_angle < 0) {
- new_angle += TAU;
- }
- if (!IsAngleGreater(new_angle, goal_angle)) {
- return goal_angle;
- }
- } else if (IsAngleSmaller(new_angle, goal_angle)) {
- new_angle += modifier_angle * time_difference;
- if (new_angle >= TAU) {
- new_angle -= TAU;
- }
- if (!IsAngleSmaller(new_angle, goal_angle)) {
- return goal_angle;
- }
- } else {
- return goal_angle;
- }
- return new_angle;
- }
- void SetGoalAngle(bool r, bool l, bool u, bool d) {
- // Move to the right
- if (r && !u && !d) {
- goal_angle = 0.0f;
- }
- // Move to the upper right
- if (r && u && !d) {
- goal_angle = Common::PI * 0.25f;
- }
- // Move up
- if (u && !l && !r) {
- goal_angle = Common::PI * 0.5f;
- }
- // Move to the upper left
- if (l && u && !d) {
- goal_angle = Common::PI * 0.75f;
- }
- // Move to the left
- if (l && !u && !d) {
- goal_angle = Common::PI;
- }
- // Move to the bottom left
- if (l && !u && d) {
- goal_angle = Common::PI * 1.25f;
- }
- // Move down
- if (d && !l && !r) {
- goal_angle = Common::PI * 1.5f;
- }
- // Move to the bottom right
- if (r && !u && d) {
- goal_angle = Common::PI * 1.75f;
- }
- }
- void UpdateUpButtonStatus(const Common::Input::CallbackStatus& button_callback) {
- up_status = button_callback.button_status.value;
- UpdateStatus();
- }
- void UpdateDownButtonStatus(const Common::Input::CallbackStatus& button_callback) {
- down_status = button_callback.button_status.value;
- UpdateStatus();
- }
- void UpdateLeftButtonStatus(const Common::Input::CallbackStatus& button_callback) {
- left_status = button_callback.button_status.value;
- UpdateStatus();
- }
- void UpdateRightButtonStatus(const Common::Input::CallbackStatus& button_callback) {
- right_status = button_callback.button_status.value;
- UpdateStatus();
- }
- void UpdateModButtonStatus(const Common::Input::CallbackStatus& button_callback) {
- const auto& new_status = button_callback.button_status;
- const bool new_button_value = new_status.inverted ? !new_status.value : new_status.value;
- modifier_status.toggle = new_status.toggle;
- // Update button status with current
- if (!modifier_status.toggle) {
- modifier_status.locked = false;
- if (modifier_status.value != new_button_value) {
- modifier_status.value = new_button_value;
- }
- } else {
- // Toggle button and lock status
- if (new_button_value && !modifier_status.locked) {
- modifier_status.locked = true;
- modifier_status.value = !modifier_status.value;
- }
- // Unlock button ready for next press
- if (!new_button_value && modifier_status.locked) {
- modifier_status.locked = false;
- }
- }
- UpdateStatus();
- }
- void UpdateStatus() {
- const float coef = modifier_status.value ? modifier_scale : 1.0f;
- bool r = right_status;
- bool l = left_status;
- bool u = up_status;
- bool d = down_status;
- // Eliminate contradictory movements
- if (r && l) {
- r = false;
- l = false;
- }
- if (u && d) {
- u = false;
- d = false;
- }
- // Move if a key is pressed
- if (r || l || u || d) {
- amplitude = coef;
- } else {
- amplitude = 0;
- }
- const auto now = std::chrono::steady_clock::now();
- const auto time_difference = static_cast<u64>(
- std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update).count());
- if (time_difference < 10) {
- // Disable analog mode if inputs are too fast
- SetGoalAngle(r, l, u, d);
- angle = goal_angle;
- } else {
- angle = GetAngle(now);
- SetGoalAngle(r, l, u, d);
- }
- last_update = now;
- Common::Input::CallbackStatus status{
- .type = Common::Input::InputType::Stick,
- .stick_status = GetStatus(),
- };
- last_x_axis_value = status.stick_status.x.raw_value;
- last_y_axis_value = status.stick_status.y.raw_value;
- TriggerOnChange(status);
- }
- void ForceUpdate() override {
- up->ForceUpdate();
- down->ForceUpdate();
- left->ForceUpdate();
- right->ForceUpdate();
- modifier->ForceUpdate();
- }
- void SoftUpdate() override {
- Common::Input::CallbackStatus status{
- .type = Common::Input::InputType::Stick,
- .stick_status = GetStatus(),
- };
- if (last_x_axis_value == status.stick_status.x.raw_value &&
- last_y_axis_value == status.stick_status.y.raw_value) {
- return;
- }
- last_x_axis_value = status.stick_status.x.raw_value;
- last_y_axis_value = status.stick_status.y.raw_value;
- TriggerOnChange(status);
- }
- Common::Input::StickStatus GetStatus() const {
- Common::Input::StickStatus status{};
- status.x.properties = properties;
- status.y.properties = properties;
- if (Settings::values.emulate_analog_keyboard) {
- const auto now = std::chrono::steady_clock::now();
- float angle_ = GetAngle(now);
- status.x.raw_value = std::cos(angle_) * amplitude;
- status.y.raw_value = std::sin(angle_) * amplitude;
- return status;
- }
- constexpr float SQRT_HALF = 0.707106781f;
- int x = 0, y = 0;
- if (right_status) {
- ++x;
- }
- if (left_status) {
- --x;
- }
- if (up_status) {
- ++y;
- }
- if (down_status) {
- --y;
- }
- const float coef = modifier_status.value ? modifier_scale : 1.0f;
- status.x.raw_value = static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF);
- status.y.raw_value = static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF);
- return status;
- }
- private:
- Button up;
- Button down;
- Button left;
- Button right;
- Button modifier;
- float modifier_scale{};
- float modifier_angle{};
- float angle{};
- float goal_angle{};
- float amplitude{};
- bool up_status{};
- bool down_status{};
- bool left_status{};
- bool right_status{};
- float last_x_axis_value{};
- float last_y_axis_value{};
- Common::Input::ButtonStatus modifier_status{};
- const Common::Input::AnalogProperties properties{0.0f, 1.0f, 0.5f, 0.0f, false};
- std::chrono::time_point<std::chrono::steady_clock> last_update;
- };
- std::unique_ptr<Common::Input::InputDevice> StickFromButton::Create(
- const Common::ParamPackage& params) {
- const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
- auto up = Common::Input::CreateInputDeviceFromString(params.Get("up", null_engine));
- auto down = Common::Input::CreateInputDeviceFromString(params.Get("down", null_engine));
- auto left = Common::Input::CreateInputDeviceFromString(params.Get("left", null_engine));
- auto right = Common::Input::CreateInputDeviceFromString(params.Get("right", null_engine));
- auto modifier = Common::Input::CreateInputDeviceFromString(params.Get("modifier", null_engine));
- auto modifier_scale = params.Get("modifier_scale", 0.5f);
- auto modifier_angle = params.Get("modifier_angle", 5.5f);
- return std::make_unique<Stick>(std::move(up), std::move(down), std::move(left),
- std::move(right), std::move(modifier), modifier_scale,
- modifier_angle);
- }
- } // namespace InputCommon
|