dolphin/Source/Core/InputCommon/ControllerEmu/ControllerEmu.h
Michael Maltese a509f56116 InputCommon: Extract ControlReference from ControllerInterface
Better separation of concerns. Relegates `ControllerInterface` to
enumerating input controls, and the new `ControlReference` deals with
combining inputs and configuration expression parsing.
2017-02-07 22:59:10 -08:00

509 lines
15 KiB
C++

// 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/ControlReference/ControlReference.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(ControlReference* const _ref, const std::string& _name)
: control_ref(_ref), name(_name)
{
}
public:
virtual ~Control() {}
std::unique_ptr<ControlReference> const control_ref;
const std::string name;
};
class Input : public Control
{
public:
Input(const std::string& _name) : Control(new InputReference, _name) {}
};
class Output : public Control
{
public:
Output(const std::string& _name) : Control(new 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;
};