mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2024-11-15 05:47:56 -07:00
00f680b830
Removed the unecessary forced tabbed layout, removed the layout part of the constructor and remade some method in preparation for tabbed styled input dialog such as the new hotkey configuration one. It breaks every inputconfigDialog, but this will get fixed in the next commits. Also moved to a folder since there will be many more files created in the next commits so it gives better separation.
507 lines
15 KiB
C++
507 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/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 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;
|
|
};
|