diff options
| author | Matthew Parlane <parlane@gmail.com> | 2017-02-08 18:16:14 +1300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-02-08 18:16:14 +1300 |
| commit | 7b3b8f26a3f97c01eefc1e596e17be391245a3e5 (patch) | |
| tree | 1d81f30ac0db5b62aa9409e142e6327ddb2f52de /Source/Core/InputCommon/ControllerEmu | |
| parent | dac6a662524917cf55e9a301f1279e4d42095b92 (diff) | |
| parent | 31f037b187525d72c10b83695f780ede1ad9db68 (diff) | |
Merge pull request #4789 from lioncash/input
InputCommon: Move ControllerEmu to its own directory
Diffstat (limited to 'Source/Core/InputCommon/ControllerEmu')
| -rw-r--r-- | Source/Core/InputCommon/ControllerEmu/ControllerEmu.cpp | 328 | ||||
| -rw-r--r-- | Source/Core/InputCommon/ControllerEmu/ControllerEmu.h | 507 |
2 files changed, 835 insertions, 0 deletions
diff --git a/Source/Core/InputCommon/ControllerEmu/ControllerEmu.cpp b/Source/Core/InputCommon/ControllerEmu/ControllerEmu.cpp new file mode 100644 index 0000000000..e66e682e48 --- /dev/null +++ b/Source/Core/InputCommon/ControllerEmu/ControllerEmu.cpp @@ -0,0 +1,328 @@ +// Copyright 2010 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "InputCommon/ControllerEmu/ControllerEmu.h" +#include <memory> +#include "Common/Common.h" +#include "VideoCommon/OnScreenDisplay.h" + +// This should be called before calling GetState() or State() on a control reference +// to prevent a race condition. +// This is a recursive mutex because UpdateReferences is recursive. +static std::recursive_mutex s_get_state_mutex; +std::unique_lock<std::recursive_mutex> ControllerEmu::GetStateLock() +{ + std::unique_lock<std::recursive_mutex> lock(s_get_state_mutex); + return lock; +} + +void ControllerEmu::UpdateReferences(ControllerInterface& devi) +{ + auto lock = ControllerEmu::GetStateLock(); + for (auto& ctrlGroup : groups) + { + for (auto& control : ctrlGroup->controls) + devi.UpdateReference(control->control_ref.get(), default_device); + + // extension + if (ctrlGroup->type == GROUP_TYPE_EXTENSION) + { + for (auto& attachment : ((Extension*)ctrlGroup.get())->attachments) + attachment->UpdateReferences(devi); + } + } +} + +void ControllerEmu::UpdateDefaultDevice() +{ + for (auto& ctrlGroup : groups) + { + // extension + if (ctrlGroup->type == GROUP_TYPE_EXTENSION) + { + for (auto& ai : ((Extension*)ctrlGroup.get())->attachments) + { + ai->default_device = default_device; + ai->UpdateDefaultDevice(); + } + } + } +} + +void ControllerEmu::ControlGroup::LoadConfig(IniFile::Section* sec, const std::string& defdev, + const std::string& base) +{ + std::string group(base + name + "/"); + + // settings + for (auto& s : numeric_settings) + { + if (s->m_type == SettingType::VIRTUAL) + continue; + sec->Get(group + s->m_name, &s->m_value, s->m_default_value * 100); + s->m_value /= 100; + } + for (auto& s : boolean_settings) + { + if (s->m_type == SettingType::VIRTUAL) + continue; + sec->Get(group + s->m_name, &s->m_value, s->m_default_value); + } + + for (auto& c : controls) + { + // control expression + sec->Get(group + c->name, &c->control_ref->expression, ""); + + // range + sec->Get(group + c->name + "/Range", &c->control_ref->range, 100.0); + c->control_ref->range /= 100; + } + + // extensions + if (type == GROUP_TYPE_EXTENSION) + { + Extension* const ext = (Extension*)this; + + ext->switch_extension = 0; + unsigned int n = 0; + std::string extname; + sec->Get(base + name, &extname, ""); + + for (auto& ai : ext->attachments) + { + ai->default_device.FromString(defdev); + ai->LoadConfig(sec, base + ai->GetName() + "/"); + + if (ai->GetName() == extname) + ext->switch_extension = n; + + n++; + } + } +} + +ControllerEmu::ControlGroup::BooleanSetting::~BooleanSetting() = default; + +void ControllerEmu::LoadConfig(IniFile::Section* sec, const std::string& base) +{ + std::string defdev = default_device.ToString(); + if (base.empty()) + { + sec->Get(base + "Device", &defdev, ""); + default_device.FromString(defdev); + } + + for (auto& cg : groups) + cg->LoadConfig(sec, defdev, base); +} + +void ControllerEmu::ControlGroup::SaveConfig(IniFile::Section* sec, const std::string& defdev, + const std::string& base) +{ + std::string group(base + name + "/"); + + for (auto& s : numeric_settings) + { + if (s->m_type == SettingType::VIRTUAL) + continue; + sec->Set(group + s->m_name, s->m_value * 100.0, s->m_default_value * 100.0); + } + for (auto& s : boolean_settings) + { + if (s->m_type == SettingType::VIRTUAL) + continue; + sec->Set(group + s->m_name, s->m_value, s->m_default_value); + } + + for (auto& c : controls) + { + // control expression + sec->Set(group + c->name, c->control_ref->expression, ""); + + // range + sec->Set(group + c->name + "/Range", c->control_ref->range * 100.0, 100.0); + } + + // extensions + if (type == GROUP_TYPE_EXTENSION) + { + Extension* const ext = (Extension*)this; + sec->Set(base + name, ext->attachments[ext->switch_extension]->GetName(), "None"); + + for (auto& ai : ext->attachments) + ai->SaveConfig(sec, base + ai->GetName() + "/"); + } +} + +void ControllerEmu::SaveConfig(IniFile::Section* sec, const std::string& base) +{ + const std::string defdev = default_device.ToString(); + if (base.empty()) + sec->Set(/*std::string(" ") +*/ base + "Device", defdev, ""); + + for (auto& ctrlGroup : groups) + ctrlGroup->SaveConfig(sec, defdev, base); +} + +void ControllerEmu::ControlGroup::SetControlExpression(int index, const std::string& expression) +{ + controls.at(index)->control_ref->expression = expression; +} + +ControllerEmu::AnalogStick::AnalogStick(const char* const _name, ControlState default_radius) + : AnalogStick(_name, _name, GROUP_TYPE_STICK) +{ +} + +ControllerEmu::AnalogStick::AnalogStick(const char* const _name, const char* const _ui_name, + ControlState default_radius) + : ControlGroup(_name, _ui_name, GROUP_TYPE_STICK) +{ + for (auto& named_direction : named_directions) + controls.emplace_back(std::make_unique<Input>(named_direction)); + + controls.emplace_back(std::make_unique<Input>(_trans("Modifier"))); + numeric_settings.emplace_back( + std::make_unique<NumericSetting>(_trans("Radius"), default_radius, 0, 100)); + numeric_settings.emplace_back(std::make_unique<NumericSetting>(_trans("Dead Zone"), 0, 0, 50)); +} + +ControllerEmu::Buttons::Buttons(const std::string& _name) + : ControlGroup(_name, _name, GROUP_TYPE_BUTTONS) +{ + numeric_settings.emplace_back(std::make_unique<NumericSetting>(_trans("Threshold"), 0.5)); +} + +ControllerEmu::Buttons::Buttons(const std::string& ini_name, const std::string& group_name) + : ControlGroup(ini_name, group_name, GROUP_TYPE_BUTTONS) +{ + numeric_settings.emplace_back(std::make_unique<NumericSetting>(_trans("Threshold"), 0.5)); +} + +ControllerEmu::ModifySettingsButton::ModifySettingsButton(std::string button_name) + : Buttons(std::move(button_name)) +{ + numeric_settings.emplace_back(std::make_unique<NumericSetting>(_trans("Threshold"), 0.5)); +} + +void ControllerEmu::ModifySettingsButton::AddInput(std::string button_name, bool toggle) +{ + controls.emplace_back(new ControlGroup::Input(std::move(button_name))); + threshold_exceeded.emplace_back(false); + associated_settings.emplace_back(false); + associated_settings_toggle.emplace_back(toggle); +} + +void ControllerEmu::ModifySettingsButton::GetState() +{ + for (size_t i = 0; i < controls.size(); ++i) + { + ControlState state = controls[i]->control_ref->State(); + + if (!associated_settings_toggle[i]) + { + // not toggled + associated_settings[i] = state > numeric_settings[0]->GetValue(); + } + else + { + // toggle (loading savestates does not en-/disable toggle) + // after we passed the threshold, we en-/disable. but after that, we don't change it + // anymore + if (!threshold_exceeded[i] && state > numeric_settings[0]->GetValue()) + { + associated_settings[i] = !associated_settings[i]; + if (associated_settings[i]) + OSD::AddMessage(controls[i]->name + ": " + _trans("on")); + else + OSD::AddMessage(controls[i]->name + ": " + _trans("off")); + threshold_exceeded[i] = true; + } + if (state < numeric_settings[0]->GetValue()) + threshold_exceeded[i] = false; + } + } +} + +ControllerEmu::MixedTriggers::MixedTriggers(const std::string& _name) + : ControlGroup(_name, GROUP_TYPE_MIXED_TRIGGERS) +{ + numeric_settings.emplace_back(std::make_unique<NumericSetting>(_trans("Threshold"), 0.9)); +} + +ControllerEmu::Triggers::Triggers(const std::string& _name) + : ControlGroup(_name, GROUP_TYPE_TRIGGERS) +{ + numeric_settings.emplace_back(std::make_unique<NumericSetting>(_trans("Dead Zone"), 0, 0, 50)); +} + +ControllerEmu::Slider::Slider(const std::string& _name) : ControlGroup(_name, GROUP_TYPE_SLIDER) +{ + controls.emplace_back(std::make_unique<Input>("Left")); + controls.emplace_back(std::make_unique<Input>("Right")); + + numeric_settings.emplace_back(std::make_unique<NumericSetting>(_trans("Dead Zone"), 0, 0, 50)); +} + +ControllerEmu::Force::Force(const std::string& _name) : ControlGroup(_name, GROUP_TYPE_FORCE) +{ + memset(m_swing, 0, sizeof(m_swing)); + + controls.emplace_back(std::make_unique<Input>(_trans("Up"))); + controls.emplace_back(std::make_unique<Input>(_trans("Down"))); + controls.emplace_back(std::make_unique<Input>(_trans("Left"))); + controls.emplace_back(std::make_unique<Input>(_trans("Right"))); + controls.emplace_back(std::make_unique<Input>(_trans("Forward"))); + controls.emplace_back(std::make_unique<Input>(_trans("Backward"))); + + numeric_settings.emplace_back(std::make_unique<NumericSetting>(_trans("Dead Zone"), 0, 0, 50)); +} + +ControllerEmu::Tilt::Tilt(const std::string& _name) : ControlGroup(_name, GROUP_TYPE_TILT) +{ + memset(m_tilt, 0, sizeof(m_tilt)); + + controls.emplace_back(std::make_unique<Input>("Forward")); + controls.emplace_back(std::make_unique<Input>("Backward")); + controls.emplace_back(std::make_unique<Input>("Left")); + controls.emplace_back(std::make_unique<Input>("Right")); + + controls.emplace_back(std::make_unique<Input>(_trans("Modifier"))); + + numeric_settings.emplace_back(std::make_unique<NumericSetting>(_trans("Dead Zone"), 0, 0, 50)); + numeric_settings.emplace_back(std::make_unique<NumericSetting>(_trans("Circle Stick"), 0)); + numeric_settings.emplace_back(std::make_unique<NumericSetting>(_trans("Angle"), 0.9, 0, 180)); +} + +ControllerEmu::Cursor::Cursor(const std::string& _name) + : ControlGroup(_name, GROUP_TYPE_CURSOR), m_z(0) +{ + for (auto& named_direction : named_directions) + controls.emplace_back(std::make_unique<Input>(named_direction)); + controls.emplace_back(std::make_unique<Input>("Forward")); + controls.emplace_back(std::make_unique<Input>("Backward")); + controls.emplace_back(std::make_unique<Input>(_trans("Hide"))); + controls.emplace_back(std::make_unique<Input>("Recenter")); + + numeric_settings.emplace_back(std::make_unique<NumericSetting>(_trans("Center"), 0.5)); + numeric_settings.emplace_back(std::make_unique<NumericSetting>(_trans("Width"), 0.5)); + numeric_settings.emplace_back(std::make_unique<NumericSetting>(_trans("Height"), 0.5)); + numeric_settings.emplace_back(std::make_unique<NumericSetting>(_trans("Dead Zone"), 0, 0, 20)); + boolean_settings.emplace_back(std::make_unique<BooleanSetting>(_trans("Relative Input"), false)); +} + +void ControllerEmu::LoadDefaults(const ControllerInterface& ciface) +{ + // load an empty inifile section, clears everything + IniFile::Section sec; + LoadConfig(&sec); + + const std::string& default_device_string = ciface.GetDefaultDeviceString(); + if (!default_device_string.empty()) + { + default_device.FromString(default_device_string); + UpdateDefaultDevice(); + } +} diff --git a/Source/Core/InputCommon/ControllerEmu/ControllerEmu.h b/Source/Core/InputCommon/ControllerEmu/ControllerEmu.h new file mode 100644 index 0000000000..3dc028da15 --- /dev/null +++ b/Source/Core/InputCommon/ControllerEmu/ControllerEmu.h @@ -0,0 +1,507 @@ +// Copyright 2010 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include <algorithm> +#include <cmath> +#include <memory> +#include <mutex> +#include <string> +#include <vector> + +#include "Common/IniFile.h" +#include "Common/MathUtil.h" +#include "Core/ConfigManager.h" +#include "InputCommon/ControllerInterface/ControllerInterface.h" +#include "InputCommon/GCPadStatus.h" + +#define sign(x) ((x) ? (x) < 0 ? -1 : 1 : 0) + +enum +{ + GROUP_TYPE_OTHER, + GROUP_TYPE_STICK, + GROUP_TYPE_MIXED_TRIGGERS, + GROUP_TYPE_BUTTONS, + GROUP_TYPE_FORCE, + GROUP_TYPE_EXTENSION, + GROUP_TYPE_TILT, + GROUP_TYPE_CURSOR, + GROUP_TYPE_TRIGGERS, + GROUP_TYPE_SLIDER +}; + +enum +{ + SETTING_RADIUS, + SETTING_DEADZONE, +}; + +const char* const named_directions[] = {"Up", "Down", "Left", "Right"}; + +class ControllerEmu +{ +public: + class ControlGroup + { + public: + class Control + { + protected: + Control(ControllerInterface::ControlReference* const _ref, const std::string& _name) + : control_ref(_ref), name(_name) + { + } + + public: + virtual ~Control() {} + std::unique_ptr<ControllerInterface::ControlReference> const control_ref; + const std::string name; + }; + + class Input : public Control + { + public: + Input(const std::string& _name) : Control(new ControllerInterface::InputReference, _name) {} + }; + + class Output : public Control + { + public: + Output(const std::string& _name) : Control(new ControllerInterface::OutputReference, _name) {} + }; + + enum class SettingType + { + NORMAL, // normal settings are saved to configuration files + VIRTUAL, // virtual settings are not saved at all + }; + + class NumericSetting + { + public: + NumericSetting(const std::string& setting_name, const ControlState default_value, + const unsigned int low = 0, const unsigned int high = 100, + const SettingType setting_type = SettingType::NORMAL) + : m_type(setting_type), m_name(setting_name), m_default_value(default_value), m_low(low), + m_high(high) + { + } + + ControlState GetValue() const { return m_value; } + void SetValue(ControlState value) { m_value = value; } + const SettingType m_type; + const std::string m_name; + const ControlState m_default_value; + const unsigned int m_low, m_high; + ControlState m_value; + }; + + class BooleanSetting + { + public: + BooleanSetting(const std::string& setting_name, const bool default_value, + const SettingType setting_type = SettingType::NORMAL) + : m_type(setting_type), m_name(setting_name), m_default_value(default_value) + { + } + virtual ~BooleanSetting(); + + virtual bool GetValue() const { return m_value; } + virtual void SetValue(bool value) { m_value = value; } + const SettingType m_type; + const std::string m_name; + const bool m_default_value; + bool m_value; + }; + + class BackgroundInputSetting : public BooleanSetting + { + public: + BackgroundInputSetting(const std::string& setting_name) + : BooleanSetting(setting_name, false, SettingType::VIRTUAL) + { + } + + bool GetValue() const override { return SConfig::GetInstance().m_BackgroundInput; } + void SetValue(bool value) override + { + m_value = value; + SConfig::GetInstance().m_BackgroundInput = value; + } + }; + + ControlGroup(const std::string& _name, const unsigned int _type = GROUP_TYPE_OTHER) + : name(_name), ui_name(_name), type(_type) + { + } + ControlGroup(const std::string& _name, const std::string& _ui_name, + const unsigned int _type = GROUP_TYPE_OTHER) + : name(_name), ui_name(_ui_name), type(_type) + { + } + virtual ~ControlGroup() {} + virtual void LoadConfig(IniFile::Section* sec, const std::string& defdev = "", + const std::string& base = ""); + virtual void SaveConfig(IniFile::Section* sec, const std::string& defdev = "", + const std::string& base = ""); + + void SetControlExpression(int index, const std::string& expression); + + const std::string name; + const std::string ui_name; + const unsigned int type; + + std::vector<std::unique_ptr<Control>> controls; + std::vector<std::unique_ptr<NumericSetting>> numeric_settings; + std::vector<std::unique_ptr<BooleanSetting>> boolean_settings; + }; + + class AnalogStick : public ControlGroup + { + public: + // The GameCube controller and Wiimote attachments have a different default radius + AnalogStick(const char* const _name, ControlState default_radius); + AnalogStick(const char* const _name, const char* const _ui_name, ControlState default_radius); + + void GetState(ControlState* const x, ControlState* const y) + { + ControlState yy = controls[0]->control_ref->State() - controls[1]->control_ref->State(); + ControlState xx = controls[3]->control_ref->State() - controls[2]->control_ref->State(); + + ControlState radius = numeric_settings[SETTING_RADIUS]->GetValue(); + ControlState deadzone = numeric_settings[SETTING_DEADZONE]->GetValue(); + ControlState m = controls[4]->control_ref->State(); + + ControlState ang = atan2(yy, xx); + ControlState ang_sin = sin(ang); + ControlState ang_cos = cos(ang); + + ControlState dist = sqrt(xx * xx + yy * yy); + + // dead zone code + dist = std::max(0.0, dist - deadzone); + dist /= (1 - deadzone); + + // radius + dist *= radius; + + // The modifier halves the distance by 50%, which is useful + // for keyboard controls. + if (m) + dist *= 0.5; + + yy = std::max(-1.0, std::min(1.0, ang_sin * dist)); + xx = std::max(-1.0, std::min(1.0, ang_cos * dist)); + + *y = yy; + *x = xx; + } + }; + + class Buttons : public ControlGroup + { + public: + Buttons(const std::string& _name); + Buttons(const std::string& ini_name, const std::string& group_name); + + template <typename C> + void GetState(C* const buttons, const C* bitmasks) + { + for (auto& control : controls) + { + if (control->control_ref->State() > numeric_settings[0]->GetValue()) // threshold + *buttons |= *bitmasks; + + bitmasks++; + } + } + }; + + class ModifySettingsButton : public Buttons + { + public: + ModifySettingsButton(std::string button_name); + void AddInput(std::string button_name, bool toggle = false); + + void GetState(); + + const std::vector<bool>& isSettingToggled() const { return associated_settings_toggle; } + const std::vector<bool>& getSettingsModifier() const { return associated_settings; } + private: + std::vector<bool> threshold_exceeded; // internal calculation (if "state" was above threshold) + std::vector<bool> associated_settings_toggle; // is setting toggled or hold? + std::vector<bool> associated_settings; // result + }; + + class MixedTriggers : public ControlGroup + { + public: + MixedTriggers(const std::string& _name); + + void GetState(u16* const digital, const u16* bitmasks, ControlState* analog) + { + const unsigned int trig_count = ((unsigned int)(controls.size() / 2)); + for (unsigned int i = 0; i < trig_count; ++i, ++bitmasks, ++analog) + { + if (controls[i]->control_ref->State() > numeric_settings[0]->GetValue()) // threshold + { + *analog = 1.0; + *digital |= *bitmasks; + } + else + { + *analog = controls[i + trig_count]->control_ref->State(); + } + } + } + }; + + class Triggers : public ControlGroup + { + public: + Triggers(const std::string& _name); + + void GetState(ControlState* analog) + { + const unsigned int trig_count = ((unsigned int)(controls.size())); + const ControlState deadzone = numeric_settings[0]->GetValue(); + for (unsigned int i = 0; i < trig_count; ++i, ++analog) + *analog = std::max(controls[i]->control_ref->State() - deadzone, 0.0) / (1 - deadzone); + } + }; + + class Slider : public ControlGroup + { + public: + Slider(const std::string& _name); + + void GetState(ControlState* const slider) + { + const ControlState deadzone = numeric_settings[0]->GetValue(); + const ControlState state = + controls[1]->control_ref->State() - controls[0]->control_ref->State(); + + if (fabs(state) > deadzone) + *slider = (state - (deadzone * sign(state))) / (1 - deadzone); + else + *slider = 0; + } + }; + + class Force : public ControlGroup + { + public: + Force(const std::string& _name); + + void GetState(ControlState* axis) + { + const ControlState deadzone = numeric_settings[0]->GetValue(); + for (unsigned int i = 0; i < 6; i += 2) + { + ControlState tmpf = 0; + const ControlState state = + controls[i + 1]->control_ref->State() - controls[i]->control_ref->State(); + if (fabs(state) > deadzone) + tmpf = ((state - (deadzone * sign(state))) / (1 - deadzone)); + + ControlState& ax = m_swing[i >> 1]; + *axis++ = (tmpf - ax); + ax = tmpf; + } + } + + private: + ControlState m_swing[3]; + }; + + class Tilt : public ControlGroup + { + public: + Tilt(const std::string& _name); + + void GetState(ControlState* const x, ControlState* const y, const bool step = true) + { + // this is all a mess + + ControlState yy = controls[0]->control_ref->State() - controls[1]->control_ref->State(); + ControlState xx = controls[3]->control_ref->State() - controls[2]->control_ref->State(); + + ControlState deadzone = numeric_settings[0]->GetValue(); + ControlState circle = numeric_settings[1]->GetValue(); + auto const angle = numeric_settings[2]->GetValue() / 1.8; + ControlState m = controls[4]->control_ref->State(); + + // deadzone / circle stick code + // this section might be all wrong, but its working good enough, I think + + ControlState ang = atan2(yy, xx); + ControlState ang_sin = sin(ang); + ControlState ang_cos = cos(ang); + + // the amt a full square stick would have at current angle + ControlState square_full = + std::min(ang_sin ? 1 / fabs(ang_sin) : 2, ang_cos ? 1 / fabs(ang_cos) : 2); + + // the amt a full stick would have that was (user setting circular) at current angle + // I think this is more like a pointed circle rather than a rounded square like it should be + ControlState stick_full = (square_full * (1 - circle)) + (circle); + + ControlState dist = sqrt(xx * xx + yy * yy); + + // dead zone code + dist = std::max(0.0, dist - deadzone * stick_full); + dist /= (1 - deadzone); + + // circle stick code + ControlState amt = dist / stick_full; + dist += (square_full - 1) * amt * circle; + + if (m) + dist *= 0.5; + + yy = std::max(-1.0, std::min(1.0, ang_sin * dist)); + xx = std::max(-1.0, std::min(1.0, ang_cos * dist)); + + // this is kinda silly here + // gui being open will make this happen 2x as fast, o well + + // silly + if (step) + { + if (xx > m_tilt[0]) + m_tilt[0] = std::min(m_tilt[0] + 0.1, xx); + else if (xx < m_tilt[0]) + m_tilt[0] = std::max(m_tilt[0] - 0.1, xx); + + if (yy > m_tilt[1]) + m_tilt[1] = std::min(m_tilt[1] + 0.1, yy); + else if (yy < m_tilt[1]) + m_tilt[1] = std::max(m_tilt[1] - 0.1, yy); + } + + *y = m_tilt[1] * angle; + *x = m_tilt[0] * angle; + } + + private: + ControlState m_tilt[2]; + }; + + class Cursor : public ControlGroup + { + public: + Cursor(const std::string& _name); + + void GetState(ControlState* const x, ControlState* const y, ControlState* const z, + const bool adjusted = false) + { + const ControlState zz = controls[4]->control_ref->State() - controls[5]->control_ref->State(); + + // silly being here + if (zz > m_z) + m_z = std::min(m_z + 0.1, zz); + else if (zz < m_z) + m_z = std::max(m_z - 0.1, zz); + + *z = m_z; + + // hide + if (controls[6]->control_ref->State() > 0.5) + { + *x = 10000; + *y = 0; + } + else + { + ControlState yy = controls[0]->control_ref->State() - controls[1]->control_ref->State(); + ControlState xx = controls[3]->control_ref->State() - controls[2]->control_ref->State(); + + // adjust cursor according to settings + if (adjusted) + { + xx *= (numeric_settings[1]->GetValue() * 2); + yy *= (numeric_settings[2]->GetValue() * 2); + yy += (numeric_settings[0]->GetValue() - 0.5); + } + + // relative input + if (boolean_settings[0]->GetValue()) + { + const ControlState deadzone = numeric_settings[3]->GetValue(); + // deadzone to avoid the cursor slowly drifting + if (std::abs(xx) > deadzone) + m_x = MathUtil::Clamp(m_x + xx * SPEED_MULTIPLIER, -1.0, 1.0); + if (std::abs(yy) > deadzone) + m_y = MathUtil::Clamp(m_y + yy * SPEED_MULTIPLIER, -1.0, 1.0); + + // recenter + if (controls[7]->control_ref->State() > 0.5) + { + m_x = 0.0; + m_y = 0.0; + } + } + else + { + m_x = xx; + m_y = yy; + } + + *x = m_x; + *y = m_y; + } + } + + ControlState m_z; + + private: + // This is used to reduce the cursor speed for relative input + // to something that makes sense with the default range. + static constexpr double SPEED_MULTIPLIER = 0.04; + + ControlState m_x = 0.0; + ControlState m_y = 0.0; + }; + + class Extension : public ControlGroup + { + public: + Extension(const std::string& _name) + : ControlGroup(_name, GROUP_TYPE_EXTENSION), switch_extension(0), active_extension(0) + { + } + + ~Extension() {} + void GetState(u8* const data); + bool IsButtonPressed() const; + + std::vector<std::unique_ptr<ControllerEmu>> attachments; + + int switch_extension; + int active_extension; + }; + + virtual ~ControllerEmu() {} + virtual std::string GetName() const = 0; + + virtual void LoadDefaults(const ControllerInterface& ciface); + + virtual void LoadConfig(IniFile::Section* sec, const std::string& base = ""); + virtual void SaveConfig(IniFile::Section* sec, const std::string& base = ""); + void UpdateDefaultDevice(); + + void UpdateReferences(ControllerInterface& devi); + + // This returns a lock that should be held before calling State() on any control + // references and GetState(), by extension. This prevents a race condition + // which happens while handling a hotplug event because a control reference's State() + // could be called before we have finished updating the reference. + static std::unique_lock<std::recursive_mutex> GetStateLock(); + + std::vector<std::unique_ptr<ControlGroup>> groups; + + ciface::Core::DeviceQualifier default_device; +}; |
