Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ControllerEmu: Make mapping indicators pretty #7662

Merged
merged 8 commits into from Jan 11, 2019
368 changes: 218 additions & 150 deletions Source/Core/DolphinQt/Config/Mapping/MappingIndicator.cpp

Large diffs are not rendered by default.

35 changes: 6 additions & 29 deletions Source/Core/DolphinQt/Config/Mapping/MappingIndicator.h
Expand Up @@ -10,50 +10,27 @@ namespace ControllerEmu
{
class Control;
class ControlGroup;
class Cursor;
class NumericSetting;
}
class ReshapableInput;
} // namespace ControllerEmu

class QPaintEvent;
class QTimer;

class ControlReference;

class MappingIndicator : public QWidget
{
public:
explicit MappingIndicator(ControllerEmu::ControlGroup* group);

private:
void BindCursorControls(bool tilt);
void BindMixedTriggersControls();

void DrawCursor(bool tilt);
void DrawStick();
void DrawCursor(ControllerEmu::Cursor& cursor);
void DrawReshapableInput(ControllerEmu::ReshapableInput& stick);
void DrawMixedTriggers();

void paintEvent(QPaintEvent*) override;
ControllerEmu::ControlGroup* m_group;

// Cursor settings
ControlReference* m_cursor_up;
ControlReference* m_cursor_down;
ControlReference* m_cursor_left;
ControlReference* m_cursor_right;
ControlReference* m_cursor_forward;
ControlReference* m_cursor_backward;

ControllerEmu::NumericSetting* m_cursor_center;
ControllerEmu::NumericSetting* m_cursor_width;
ControllerEmu::NumericSetting* m_cursor_height;
ControllerEmu::NumericSetting* m_cursor_deadzone;

// Triggers settings
ControlReference* m_mixed_triggers_r_analog;
ControlReference* m_mixed_triggers_r_button;
ControlReference* m_mixed_triggers_l_analog;
ControlReference* m_mixed_triggers_l_button;

ControllerEmu::NumericSetting* m_mixed_triggers_threshold;
ControllerEmu::ControlGroup* m_group;

QTimer* m_timer;
};
38 changes: 22 additions & 16 deletions Source/Core/DolphinQt/Config/Mapping/WiimoteEmuExtension.cpp
Expand Up @@ -54,14 +54,14 @@ void WiimoteEmuExtension::CreateDrumsLayout()
auto* hbox = new QHBoxLayout();
m_drums_box = new QGroupBox(tr("Drums"), this);

hbox->addWidget(CreateGroupBox(
tr("Buttons"), Wiimote::GetDrumsGroup(GetPort(), WiimoteEmu::DrumsGroup::Buttons)));
hbox->addWidget(CreateGroupBox(tr("Stick"),
Wiimote::GetDrumsGroup(GetPort(), WiimoteEmu::DrumsGroup::Stick)));

auto* vbox = new QVBoxLayout();
vbox->addWidget(
CreateGroupBox(tr("Pads"), Wiimote::GetDrumsGroup(GetPort(), WiimoteEmu::DrumsGroup::Pads)));
vbox->addWidget(CreateGroupBox(tr("Stick"),
Wiimote::GetDrumsGroup(GetPort(), WiimoteEmu::DrumsGroup::Stick)));
vbox->addWidget(CreateGroupBox(
tr("Buttons"), Wiimote::GetDrumsGroup(GetPort(), WiimoteEmu::DrumsGroup::Buttons)));
hbox->addLayout(vbox);

m_drums_box->setLayout(hbox);
Expand Down Expand Up @@ -107,23 +107,26 @@ void WiimoteEmuExtension::CreateGuitarLayout()
m_guitar_box = new QGroupBox(tr("Guitar"), this);

auto* vbox = new QVBoxLayout();
vbox->addWidget(CreateGroupBox(
tr("Buttons"), Wiimote::GetGuitarGroup(GetPort(), WiimoteEmu::GuitarGroup::Buttons)));
vbox->addWidget(CreateGroupBox(
tr("Stick"), Wiimote::GetGuitarGroup(GetPort(), WiimoteEmu::GuitarGroup::Stick)));
vbox->addWidget(CreateGroupBox(
tr("Slider Bar"), Wiimote::GetGuitarGroup(GetPort(), WiimoteEmu::GuitarGroup::SliderBar)));
hbox->addLayout(vbox);

auto* vbox2 = new QVBoxLayout();
vbox2->addWidget(CreateGroupBox(
tr("Strum"), Wiimote::GetGuitarGroup(GetPort(), WiimoteEmu::GuitarGroup::Strum)));
vbox2->addWidget(CreateGroupBox(
tr("Frets"), Wiimote::GetGuitarGroup(GetPort(), WiimoteEmu::GuitarGroup::Frets)));
vbox2->addWidget(CreateGroupBox(
tr("Whammy"), Wiimote::GetGuitarGroup(GetPort(), WiimoteEmu::GuitarGroup::Whammy)));
hbox->addLayout(vbox2);

auto* vbox3 = new QVBoxLayout();
vbox3->addWidget(CreateGroupBox(
tr("Buttons"), Wiimote::GetGuitarGroup(GetPort(), WiimoteEmu::GuitarGroup::Buttons)));
vbox3->addWidget(CreateGroupBox(
tr("Whammy"), Wiimote::GetGuitarGroup(GetPort(), WiimoteEmu::GuitarGroup::Whammy)));
vbox3->addWidget(CreateGroupBox(
tr("Slider Bar"), Wiimote::GetGuitarGroup(GetPort(), WiimoteEmu::GuitarGroup::SliderBar)));
hbox->addLayout(vbox3);

m_guitar_box->setLayout(hbox);
}

Expand All @@ -134,24 +137,27 @@ void WiimoteEmuExtension::CreateTurntableLayout()

hbox->addWidget(CreateGroupBox(
tr("Stick"), Wiimote::GetTurntableGroup(GetPort(), WiimoteEmu::TurntableGroup::Stick)));
hbox->addWidget(CreateGroupBox(
tr("Buttons"), Wiimote::GetTurntableGroup(GetPort(), WiimoteEmu::TurntableGroup::Buttons)));

auto* vbox = new QVBoxLayout();
vbox->addWidget(CreateGroupBox(
tr("Buttons"), Wiimote::GetTurntableGroup(GetPort(), WiimoteEmu::TurntableGroup::Buttons)));
vbox->addWidget(CreateGroupBox(
tr("Effect"), Wiimote::GetTurntableGroup(GetPort(), WiimoteEmu::TurntableGroup::EffectDial)));
vbox->addWidget(
hbox->addLayout(vbox);

auto* vbox2 = new QVBoxLayout();
vbox2->addWidget(
// i18n: "Table" refers to a turntable
CreateGroupBox(tr("Left Table"),
Wiimote::GetTurntableGroup(GetPort(), WiimoteEmu::TurntableGroup::LeftTable)));
vbox->addWidget(CreateGroupBox(
vbox2->addWidget(CreateGroupBox(
// i18n: "Table" refers to a turntable
tr("Right Table"),
Wiimote::GetTurntableGroup(GetPort(), WiimoteEmu::TurntableGroup::RightTable)));
vbox->addWidget(
vbox2->addWidget(
CreateGroupBox(tr("Crossfade"),
Wiimote::GetTurntableGroup(GetPort(), WiimoteEmu::TurntableGroup::Crossfade)));
hbox->addLayout(vbox);
hbox->addLayout(vbox2);

m_turntable_box->setLayout(hbox);
}
Expand Down
88 changes: 13 additions & 75 deletions Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.cpp
Expand Up @@ -24,103 +24,41 @@ AnalogStick::AnalogStick(const char* const name_, std::unique_ptr<StickGate>&& s

AnalogStick::AnalogStick(const char* const name_, const char* const ui_name_,
std::unique_ptr<StickGate>&& stick_gate)
: ControlGroup(name_, ui_name_, GroupType::Stick), m_stick_gate(std::move(stick_gate))
: ReshapableInput(name_, ui_name_, GroupType::Stick), m_stick_gate(std::move(stick_gate))
{
for (auto& named_direction : named_directions)
controls.emplace_back(std::make_unique<Input>(Translate, named_direction));

controls.emplace_back(std::make_unique<Input>(Translate, _trans("Modifier")));

// Set default input radius to that of the gate radius (no resizing)
// Allow radius greater than 1.0 for definitions of rounded squares
// This is ideal for Xbox controllers (and probably others)
numeric_settings.emplace_back(
std::make_unique<NumericSetting>(_trans("Input Radius"), GetGateRadiusAtAngle(0.0), 0, 140));
// Set default input shape to an octagon (no reshaping)
numeric_settings.emplace_back(
std::make_unique<NumericSetting>(_trans("Input Shape"), 0.0, 0, 50));
numeric_settings.emplace_back(std::make_unique<NumericSetting>(_trans("Dead Zone"), 0, 0, 50));
// Default input radius to that of the gate radius (no resizing)
// Default input shape to an octagon (no reshaping)
// Max deadzone to 50%
AddReshapingSettings(GetGateRadiusAtAngle(0.0), 0.0, 50);
}

AnalogStick::StateData AnalogStick::GetState(bool adjusted)
AnalogStick::StateData AnalogStick::GetReshapableState(bool adjusted)
{
ControlState y = controls[0]->control_ref->State() - controls[1]->control_ref->State();
ControlState x = controls[3]->control_ref->State() - controls[2]->control_ref->State();
const ControlState y = controls[0]->control_ref->State() - controls[1]->control_ref->State();
const ControlState x = controls[3]->control_ref->State() - controls[2]->control_ref->State();

// Return raw values. (used in UI)
if (!adjusted)
return {x, y};

// TODO: make the AtAngle functions work with negative angles:
const ControlState ang = std::atan2(y, x) + MathUtil::TAU;

const ControlState gate_max_dist = GetGateRadiusAtAngle(ang);
const ControlState input_max_dist = GetInputRadiusAtAngle(ang);

// If input radius is zero we apply no scaling.
// This is useful when mapping native controllers without knowing intimate radius details.
const ControlState max_dist = input_max_dist ? input_max_dist : gate_max_dist;

ControlState dist = std::sqrt(x * x + y * y) / max_dist;

// If the modifier is pressed, scale the distance by the modifier's value.
// This is affected by the modifier's "range" setting which defaults to 50%.
const ControlState modifier = controls[4]->control_ref->State();
if (modifier)
{
// TODO: Modifier's range setting gets reset to 100% when the clear button is clicked.
// This causes the modifier to not behave how a user might suspect.
// Retaining the old scale-by-50% behavior until range is fixed to clear to 50%.
dist *= 0.5;
// dist *= modifier;
}

// Apply deadzone as a percentage of the user-defined radius/shape:
const ControlState deadzone = GetDeadzoneRadiusAtAngle(ang);
dist = std::max(0.0, dist - deadzone) / (1.0 - deadzone);

// Scale to the gate shape/radius:
dist = dist *= gate_max_dist;

x = MathUtil::Clamp(std::cos(ang) * dist, -1.0, 1.0);
y = MathUtil::Clamp(std::sin(ang) * dist, -1.0, 1.0);
return {x, y};
}

ControlState AnalogStick::GetGateRadiusAtAngle(double ang) const
{
return m_stick_gate->GetRadiusAtAngle(ang);
}

ControlState AnalogStick::GetDeadzoneRadiusAtAngle(double ang) const
{
return CalculateInputShapeRadiusAtAngle(ang) * numeric_settings[SETTING_DEADZONE]->GetValue();
return Reshape(x, y, modifier);
}

ControlState AnalogStick::GetInputRadiusAtAngle(double ang) const
AnalogStick::StateData AnalogStick::GetState()
{
const ControlState radius =
CalculateInputShapeRadiusAtAngle(ang) * numeric_settings[SETTING_INPUT_RADIUS]->GetValue();
// Clamp within the -1 to +1 square as input radius may be greater than 1.0:
return std::min(radius, SquareStickGate(1).GetRadiusAtAngle(ang));
return GetReshapableState(true);
}

ControlState AnalogStick::CalculateInputShapeRadiusAtAngle(double ang) const
ControlState AnalogStick::GetGateRadiusAtAngle(double ang) const
{
const auto shape = numeric_settings[SETTING_INPUT_SHAPE]->GetValue() * 4.0;

if (shape < 1.0)
{
// Between 0 and 25 return a shape between octagon and circle
const auto amt = shape;
return OctagonStickGate(1).GetRadiusAtAngle(ang) * (1 - amt) + amt;
}
else
{
// Between 25 and 50 return a shape between circle and square
const auto amt = shape - 1.0;
return (1 - amt) + SquareStickGate(1).GetRadiusAtAngle(ang) * amt;
}
return m_stick_gate->GetRadiusAtAngle(ang);
}

OctagonAnalogStick::OctagonAnalogStick(const char* name, ControlState gate_radius)
Expand Down
25 changes: 5 additions & 20 deletions Source/Core/InputCommon/ControllerEmu/ControlGroup/AnalogStick.h
Expand Up @@ -10,35 +10,20 @@

namespace ControllerEmu
{
class AnalogStick : public ControlGroup
class AnalogStick : public ReshapableInput
{
public:
enum
{
SETTING_INPUT_RADIUS,
SETTING_INPUT_SHAPE,
SETTING_DEADZONE,
};

struct StateData
{
ControlState x{};
ControlState y{};
};
typedef ReshapeData StateData;
jordan-woyak marked this conversation as resolved.
Show resolved Hide resolved

AnalogStick(const char* name, std::unique_ptr<StickGate>&& stick_gate);
AnalogStick(const char* name, const char* ui_name, std::unique_ptr<StickGate>&& stick_gate);

StateData GetState(bool adjusted = true);
StateData GetReshapableState(bool adjusted) final override;
ControlState GetGateRadiusAtAngle(double ang) const override;

// Angle is in radians and should be non-negative
ControlState GetGateRadiusAtAngle(double ang) const;
ControlState GetDeadzoneRadiusAtAngle(double ang) const;
ControlState GetInputRadiusAtAngle(double ang) const;
StateData GetState();

private:
ControlState CalculateInputShapeRadiusAtAngle(double ang) const;

std::unique_ptr<StickGate> m_stick_gate;
};

Expand Down