Large diffs are not rendered by default.

Expand Up @@ -18,8 +18,9 @@
*/
public final class InputOverlayDrawableButton
{
// The ID identifying what type of button this Drawable represents.
private int mButtonType;
// The legacy ID identifying what type of button this Drawable represents.
private int mLegacyId;
private int mControl;
private int mTrackId;
private int mPreviousTouchX, mPreviousTouchY;
private int mControlPositionX, mControlPositionY;
Expand All @@ -35,28 +36,33 @@
* @param res {@link Resources} instance.
* @param defaultStateBitmap {@link Bitmap} to use with the default state Drawable.
* @param pressedStateBitmap {@link Bitmap} to use with the pressed state Drawable.
* @param buttonType Identifier for this type of button.
* @param legacyId Legacy identifier (ButtonType) for this type of button.
* @param control Control ID for this type of button.
*/
public InputOverlayDrawableButton(Resources res, Bitmap defaultStateBitmap,
Bitmap pressedStateBitmap, int buttonType)
Bitmap pressedStateBitmap, int legacyId, int control)
{
mTrackId = -1;
mDefaultStateBitmap = new BitmapDrawable(res, defaultStateBitmap);
mPressedStateBitmap = new BitmapDrawable(res, pressedStateBitmap);
mButtonType = buttonType;
mLegacyId = legacyId;
mControl = control;

mWidth = mDefaultStateBitmap.getIntrinsicWidth();
mHeight = mDefaultStateBitmap.getIntrinsicHeight();
}

/**
* Gets this InputOverlayDrawableButton's button ID.
*
* @return this InputOverlayDrawableButton's button ID.
* Gets this InputOverlayDrawableButton's legacy button ID.
*/
public int getId()
public int getLegacyId()
{
return mLegacyId;
}

public int getControl()
{
return mButtonType;
return mControl;
}

public void setTrackId(int trackId)
Expand Down
Expand Up @@ -18,8 +18,9 @@
*/
public final class InputOverlayDrawableDpad
{
// The ID identifying what type of button this Drawable represents.
private int[] mButtonType = new int[4];
// The legacy ID identifying what type of button this Drawable represents.
private int mLegacyId;
private int[] mControls = new int[4];
private int mTrackId;
private int mPreviousTouchX, mPreviousTouchY;
private int mControlPositionX, mControlPositionY;
Expand Down Expand Up @@ -47,17 +48,15 @@
* @param defaultStateBitmap {@link Bitmap} of the default state.
* @param pressedOneDirectionStateBitmap {@link Bitmap} of the pressed state in one direction.
* @param pressedTwoDirectionsStateBitmap {@link Bitmap} of the pressed state in two direction.
* @param buttonUp Identifier for the up button.
* @param buttonDown Identifier for the down button.
* @param buttonLeft Identifier for the left button.
* @param buttonRight Identifier for the right button.
* @param legacyId Legacy identifier (ButtonType) for the up button.
* @param upControl Control identifier for the up button.
* @param downControl Control identifier for the down button.
* @param leftControl Control identifier for the left button.
* @param rightControl Control identifier for the right button.
*/
public InputOverlayDrawableDpad(Resources res,
Bitmap defaultStateBitmap,
Bitmap pressedOneDirectionStateBitmap,
Bitmap pressedTwoDirectionsStateBitmap,
int buttonUp, int buttonDown,
int buttonLeft, int buttonRight)
public InputOverlayDrawableDpad(Resources res, Bitmap defaultStateBitmap,
Bitmap pressedOneDirectionStateBitmap, Bitmap pressedTwoDirectionsStateBitmap,
int legacyId, int upControl, int downControl, int leftControl, int rightControl)
{
mTrackId = -1;
mDefaultStateBitmap = new BitmapDrawable(res, defaultStateBitmap);
Expand All @@ -67,10 +66,11 @@ public InputOverlayDrawableDpad(Resources res,
mWidth = mDefaultStateBitmap.getIntrinsicWidth();
mHeight = mDefaultStateBitmap.getIntrinsicHeight();

mButtonType[0] = buttonUp;
mButtonType[1] = buttonDown;
mButtonType[2] = buttonLeft;
mButtonType[3] = buttonRight;
mLegacyId = legacyId;
mControls[0] = upControl;
mControls[1] = downControl;
mControls[2] = leftControl;
mControls[3] = rightControl;
}

public void draw(Canvas canvas)
Expand Down Expand Up @@ -127,14 +127,17 @@ public void draw(Canvas canvas)
}
}

public int getLegacyId()
{
return mLegacyId;
}

/**
* Gets one of the InputOverlayDrawableDpad's button IDs.
*
* @return the requested InputOverlayDrawableDpad's button ID.
* Gets one of the InputOverlayDrawableDpad's control IDs.
*/
public int getId(int direction)
public int getControl(int direction)
{
return mButtonType[direction];
return mControls[direction];
}

public void setTrackId(int trackId)
Expand Down
Expand Up @@ -12,7 +12,7 @@
import android.graphics.drawable.BitmapDrawable;
import android.view.MotionEvent;

import org.dolphinemu.dolphinemu.NativeLibrary;
import org.dolphinemu.dolphinemu.features.input.model.InputOverrider;
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting;

/**
Expand All @@ -21,10 +21,12 @@
*/
public final class InputOverlayDrawableJoystick
{
private final int[] axisIDs = {0, 0, 0, 0};
private final float[] axises = {0f, 0f};
private float mCurrentX = 0.0f;
private float mCurrentY = 0.0f;
private int trackId = -1;
private final int mJoystickType;
private final int mJoystickLegacyId;
private final int mJoystickXControl;
private final int mJoystickYControl;
private int mControlPositionX, mControlPositionY;
private int mPreviousTouchX, mPreviousTouchY;
private final int mWidth;
Expand All @@ -47,16 +49,17 @@
* @param bitmapInnerPressed {@link Bitmap} which represents the pressed inner movable part of the joystick.
* @param rectOuter {@link Rect} which represents the outer joystick bounds.
* @param rectInner {@link Rect} which represents the inner joystick bounds.
* @param joystick Identifier for which joystick this is.
* @param legacyId Legacy identifier (ButtonType) for which joystick this is.
* @param xControl The control which the x value of the joystick will be written to.
* @param yControl The control which the y value of the joystick will be written to.
*/
public InputOverlayDrawableJoystick(Resources res, Bitmap bitmapOuter, Bitmap bitmapInnerDefault,
Bitmap bitmapInnerPressed, Rect rectOuter, Rect rectInner, int joystick)
Bitmap bitmapInnerPressed, Rect rectOuter, Rect rectInner, int legacyId, int xControl,
int yControl)
{
axisIDs[0] = joystick + 1;
axisIDs[1] = joystick + 2;
axisIDs[2] = joystick + 3;
axisIDs[3] = joystick + 4;
mJoystickType = joystick;
mJoystickLegacyId = legacyId;
mJoystickXControl = xControl;
mJoystickYControl = yControl;

mOuterBitmap = new BitmapDrawable(res, bitmapOuter);
mDefaultStateInnerBitmap = new BitmapDrawable(res, bitmapInnerDefault);
Expand All @@ -76,13 +79,13 @@ public InputOverlayDrawableJoystick(Resources res, Bitmap bitmapOuter, Bitmap bi
}

/**
* Gets this InputOverlayDrawableJoystick's button ID.
* Gets this InputOverlayDrawableJoystick's legacy ID.
*
* @return this InputOverlayDrawableJoystick's button ID.
* @return this InputOverlayDrawableJoystick's legacy ID.
*/
public int getId()
public int getLegacyId()
{
return mJoystickType;
return mJoystickLegacyId;
}

public void draw(Canvas canvas)
Expand Down Expand Up @@ -125,7 +128,7 @@ public boolean TrackEvent(MotionEvent event)
{
pressed = true;
mPressedState = false;
axises[0] = axises[1] = 0.0f;
mCurrentX = mCurrentY = 0.0f;
mOuterBitmap.setAlpha(mOpacity);
mBoundsBoxBitmap.setAlpha(0);
setVirtBounds(new Rect(mOrigBounds.left, mOrigBounds.top, mOrigBounds.right,
Expand Down Expand Up @@ -153,10 +156,8 @@ public boolean TrackEvent(MotionEvent event)
maxX -= getVirtBounds().centerX();
touchY -= getVirtBounds().centerY();
maxY -= getVirtBounds().centerY();
final float AxisX = touchX / maxX;
final float AxisY = touchY / maxY;
axises[0] = AxisY;
axises[1] = AxisX;
mCurrentX = touchX / maxX;
mCurrentY = touchY / maxY;

SetInnerBounds();
}
Expand Down Expand Up @@ -193,36 +194,40 @@ public void onConfigureTouch(MotionEvent event)
}
}

public float getX()
{
return mCurrentX;
}

public float getY()
{
return mCurrentY;
}

public float[] getAxisValues()
public int getXControl()
{
float[] joyaxises = {0f, 0f, 0f, 0f};
joyaxises[1] = Math.min(axises[0], 1.0f);
joyaxises[0] = Math.min(axises[0], 0.0f);
joyaxises[3] = Math.min(axises[1], 1.0f);
joyaxises[2] = Math.min(axises[1], 0.0f);
return joyaxises;
return mJoystickXControl;
}

public int[] getAxisIDs()
public int getYControl()
{
return axisIDs;
return mJoystickYControl;
}

private void SetInnerBounds()
{
double y = axises[0];
double x = axises[1];
double x = mCurrentX;
double y = mCurrentY;

double angle = Math.atan2(y, x) + Math.PI + Math.PI;
double radius = Math.hypot(y, x);
double maxRadius = NativeLibrary.GetInputRadiusAtAngle(0, mJoystickType, angle);
double maxRadius = InputOverrider.getGateRadiusAtAngle(0, mJoystickXControl, angle);
if (radius > maxRadius)
{
y = maxRadius * Math.sin(angle);
x = maxRadius * Math.cos(angle);
axises[0] = (float) y;
axises[1] = (float) x;
mCurrentY = (float) y;
mCurrentX = (float) x;
}

int pixelX = getVirtBounds().centerX() + (int) (x * (getVirtBounds().width() / 2));
Expand Down
Expand Up @@ -7,22 +7,20 @@
import android.view.MotionEvent;

import org.dolphinemu.dolphinemu.NativeLibrary;
import org.dolphinemu.dolphinemu.features.input.model.InputOverrider;

import java.util.ArrayList;

public class InputOverlayPointer
{
public static final int DOUBLE_TAP_A = 0;
public static final int DOUBLE_TAP_B = 1;
public static final int DOUBLE_TAP_2 = 2;
public static final int DOUBLE_TAP_CLASSIC_A = 3;

public static final int MODE_DISABLED = 0;
public static final int MODE_FOLLOW = 1;
public static final int MODE_DRAG = 2;

private final float[] axes = {0f, 0f};
private final float[] oldaxes = {0f, 0f};
private float mCurrentX = 0.0f;
private float mCurrentY = 0.0f;
private float mOldX = 0.0f;
private float mOldY = 0.0f;

private float mGameCenterX;
private float mGameCenterY;
Expand All @@ -36,7 +34,7 @@
private boolean mRecenter;

private boolean doubleTap = false;
private int doubleTapButton;
private int mDoubleTapControl;
private int trackId = -1;

public static ArrayList<Integer> DOUBLE_TAP_OPTIONS = new ArrayList<>();
Expand All @@ -49,9 +47,9 @@
DOUBLE_TAP_OPTIONS.add(NativeLibrary.ButtonType.CLASSIC_BUTTON_A);
}

public InputOverlayPointer(Rect surfacePosition, int button, int mode, boolean recenter)
public InputOverlayPointer(Rect surfacePosition, int doubleTapControl, int mode, boolean recenter)
{
doubleTapButton = button;
mDoubleTapControl = doubleTapControl;
mMode = mode;
mRecenter = recenter;

Expand Down Expand Up @@ -112,15 +110,15 @@ public void onTouch(MotionEvent event)

if (mMode == MODE_FOLLOW)
{
axes[0] = (event.getY(event.findPointerIndex(trackId)) - mGameCenterY) * mGameHeightHalfInv;
axes[1] = (event.getX(event.findPointerIndex(trackId)) - mGameCenterX) * mGameWidthHalfInv;
mCurrentX = (event.getX(event.findPointerIndex(trackId)) - mGameCenterX) * mGameWidthHalfInv;
mCurrentY = (event.getY(event.findPointerIndex(trackId)) - mGameCenterY) * mGameHeightHalfInv;
}
else if (mMode == MODE_DRAG)
{
axes[0] = oldaxes[0] +
(event.getY(event.findPointerIndex(trackId)) - mTouchStartY) * mGameHeightHalfInv;
axes[1] = oldaxes[1] +
mCurrentX = mOldX +
(event.getX(event.findPointerIndex(trackId)) - mTouchStartX) * mGameWidthHalfInv;
mCurrentY = mOldY +
(event.getY(event.findPointerIndex(trackId)) - mTouchStartY) * mGameHeightHalfInv;
}
}

Expand All @@ -130,11 +128,9 @@ private void touchPress()
{
if (doubleTap)
{
NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice,
doubleTapButton, NativeLibrary.ButtonState.PRESSED);
new Handler()
.postDelayed(() -> NativeLibrary.onGamePadEvent(NativeLibrary.TouchScreenDevice,
doubleTapButton, NativeLibrary.ButtonState.RELEASED), 50);
InputOverrider.setControlState(0, mDoubleTapControl, 1.0);
new Handler().postDelayed(() -> InputOverrider.setControlState(0, mDoubleTapControl, 0.0),
50);
}
else
{
Expand All @@ -146,23 +142,23 @@ private void touchPress()

private void updateOldAxes()
{
oldaxes[0] = axes[0];
oldaxes[1] = axes[1];
mOldX = mCurrentX;
mOldY = mCurrentY;
}

private void reset()
{
axes[0] = axes[1] = oldaxes[0] = oldaxes[1] = 0f;
mCurrentX = mCurrentY = mOldX = mOldY = 0.0f;
}

public float getX()
{
return mCurrentX;
}

public float[] getAxisValues()
public float getY()
{
float[] iraxes = {0f, 0f, 0f, 0f};
iraxes[1] = axes[0];
iraxes[0] = axes[0];
iraxes[3] = axes[1];
iraxes[2] = axes[1];
return iraxes;
return mCurrentY;
}

public void setMode(int mode)
Expand Down
1 change: 1 addition & 0 deletions Source/Android/jni/CMakeLists.txt
Expand Up @@ -10,6 +10,7 @@ add_library(main SHARED
GameList/GameFile.cpp
GameList/GameFile.h
GameList/GameFileCache.cpp
Input/InputOverrider.cpp
IniFile.cpp
MainAndroid.cpp
RiivolutionPatches.cpp
Expand Down
62 changes: 62 additions & 0 deletions Source/Android/jni/Input/InputOverrider.cpp
@@ -0,0 +1,62 @@
// Copyright 2021 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include <jni.h>

#include "InputCommon/ControllerInterface/Touch/InputOverrider.h"

extern "C" {

JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_features_input_model_InputOverrider_registerGameCube(
JNIEnv*, jclass, int controller_index)
{
ciface::Touch::RegisterGameCubeInputOverrider(controller_index);
}

JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_features_input_model_InputOverrider_registerWii(JNIEnv*, jclass,
int controller_index)
{
ciface::Touch::RegisterWiiInputOverrider(controller_index);
}

JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_features_input_model_InputOverrider_unregisterGameCube(
JNIEnv*, jclass, int controller_index)
{
ciface::Touch::UnregisterGameCubeInputOverrider(controller_index);
}

JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_features_input_model_InputOverrider_unregisterWii(
JNIEnv*, jclass, int controller_index)
{
ciface::Touch::UnregisterWiiInputOverrider(controller_index);
}

JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_features_input_model_InputOverrider_setControlState(
JNIEnv*, jclass, int controller_index, int control, double state)
{
ciface::Touch::SetControlState(controller_index, static_cast<ciface::Touch::ControlID>(control),
state);
}

JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_features_input_model_InputOverrider_clearControlState(
JNIEnv*, jclass, int controller_index, int control)
{
ciface::Touch::ClearControlState(controller_index,
static_cast<ciface::Touch::ControlID>(control));
}

JNIEXPORT double JNICALL
Java_org_dolphinemu_dolphinemu_features_input_model_InputOverrider_getGateRadiusAtAngle(
JNIEnv*, jclass, int controller_index, int stick, double angle)
{
const auto casted_stick = static_cast<ciface::Touch::ControlID>(stick);
return ciface::Touch::GetGateRadiusAtAngle(controller_index, casted_stick, angle);
}
};
7 changes: 0 additions & 7 deletions Source/Android/jni/MainAndroid.cpp
Expand Up @@ -302,13 +302,6 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SetMotionSen
ciface::Android::SetMotionSensorsEnabled(accelerometer_enabled, gyroscope_enabled);
}

JNIEXPORT double JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetInputRadiusAtAngle(
JNIEnv*, jclass, int emu_pad_id, int stick, double angle)
{
const auto casted_stick = static_cast<ButtonManager::ButtonType>(stick);
return ButtonManager::GetInputRadiusAtAngle(emu_pad_id, casted_stick, angle);
}

JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_GetVersionString(JNIEnv* env,
jclass)
{
Expand Down
54 changes: 20 additions & 34 deletions Source/Core/Core/HW/GCPadEmu.cpp
Expand Up @@ -36,62 +36,47 @@ static const u16 trigger_bitmasks[] = {
static const u16 dpad_bitmasks[] = {PAD_BUTTON_UP, PAD_BUTTON_DOWN, PAD_BUTTON_LEFT,
PAD_BUTTON_RIGHT};

static const char* const named_buttons[] = {"A", "B", "X", "Y", "Z", "Start"};

static const char* const named_triggers[] = {
// i18n: The left trigger button (labeled L on real controllers)
_trans("L"),
// i18n: The right trigger button (labeled R on real controllers)
_trans("R"),
// i18n: The left trigger button (labeled L on real controllers) used as an analog input
_trans("L-Analog"),
// i18n: The right trigger button (labeled R on real controllers) used as an analog input
_trans("R-Analog")};

GCPad::GCPad(const unsigned int index) : m_index(index)
{
// buttons
groups.emplace_back(m_buttons = new ControllerEmu::Buttons(_trans("Buttons")));
for (const char* named_button : named_buttons)
groups.emplace_back(m_buttons = new ControllerEmu::Buttons(BUTTONS_GROUP));
for (const char* named_button : {A_BUTTON, B_BUTTON, X_BUTTON, Y_BUTTON, Z_BUTTON})
{
const bool is_start = named_button == std::string("Start");
const ControllerEmu::Translatability translate =
is_start ? ControllerEmu::Translate : ControllerEmu::DoNotTranslate;
// i18n: The START/PAUSE button on GameCube controllers
std::string ui_name = is_start ? _trans("START") : named_button;
m_buttons->AddInput(translate, named_button, std::move(ui_name));
m_buttons->AddInput(ControllerEmu::DoNotTranslate, named_button);
}
// i18n: The START/PAUSE button on GameCube controllers
m_buttons->AddInput(ControllerEmu::Translate, START_BUTTON, _trans("START"));

// sticks
groups.emplace_back(m_main_stick = new ControllerEmu::OctagonAnalogStick(
"Main Stick", _trans("Control Stick"), MAIN_STICK_GATE_RADIUS));
MAIN_STICK_GROUP, _trans("Control Stick"), MAIN_STICK_GATE_RADIUS));
groups.emplace_back(m_c_stick = new ControllerEmu::OctagonAnalogStick(
"C-Stick", _trans("C Stick"), C_STICK_GATE_RADIUS));
C_STICK_GROUP, _trans("C Stick"), C_STICK_GATE_RADIUS));

// triggers
groups.emplace_back(m_triggers = new ControllerEmu::MixedTriggers(_trans("Triggers")));
for (const char* named_trigger : named_triggers)
groups.emplace_back(m_triggers = new ControllerEmu::MixedTriggers(TRIGGERS_GROUP));
for (const char* named_trigger : {L_DIGITAL, R_DIGITAL, L_ANALOG, R_ANALOG})
{
m_triggers->AddInput(ControllerEmu::Translate, named_trigger);
}

// rumble
groups.emplace_back(m_rumble = new ControllerEmu::ControlGroup(_trans("Rumble")));
groups.emplace_back(m_rumble = new ControllerEmu::ControlGroup(RUMBLE_GROUP));
m_rumble->AddOutput(ControllerEmu::Translate, _trans("Motor"));

// Microphone
groups.emplace_back(m_mic = new ControllerEmu::Buttons(_trans("Microphone")));
groups.emplace_back(m_mic = new ControllerEmu::Buttons(MIC_GROUP));
m_mic->AddInput(ControllerEmu::Translate, _trans("Button"));

// dpad
groups.emplace_back(m_dpad = new ControllerEmu::Buttons(_trans("D-Pad")));
groups.emplace_back(m_dpad = new ControllerEmu::Buttons(DPAD_GROUP));
for (const char* named_direction : named_directions)
{
m_dpad->AddInput(ControllerEmu::Translate, named_direction);
}

// options
groups.emplace_back(m_options = new ControllerEmu::ControlGroup(_trans("Options")));
groups.emplace_back(m_options = new ControllerEmu::ControlGroup(OPTIONS_GROUP));
m_options->AddSetting(
&m_always_connected_setting,
// i18n: Treat a controller as always being connected regardless of what
Expand Down Expand Up @@ -138,14 +123,15 @@ GCPadStatus GCPad::GetInput() const
const auto lock = GetStateLock();
GCPadStatus pad = {};

if (!(m_always_connected_setting.GetValue() || IsDefaultDeviceConnected()))
if (!(m_always_connected_setting.GetValue() || IsDefaultDeviceConnected() ||
m_input_override_function))
{
pad.isConnected = false;
return pad;
}

// buttons
m_buttons->GetState(&pad.button, button_bitmasks);
m_buttons->GetState(&pad.button, button_bitmasks, m_input_override_function);

// set analog A/B analog to full or w/e, prolly not needed
if (pad.button & PAD_BUTTON_A)
Expand All @@ -154,20 +140,20 @@ GCPadStatus GCPad::GetInput() const
pad.analogB = 0xFF;

// dpad
m_dpad->GetState(&pad.button, dpad_bitmasks);
m_dpad->GetState(&pad.button, dpad_bitmasks, m_input_override_function);

// sticks
const auto main_stick_state = m_main_stick->GetState();
const auto main_stick_state = m_main_stick->GetState(m_input_override_function);
pad.stickX = MapFloat<u8>(main_stick_state.x, GCPadStatus::MAIN_STICK_CENTER_X, 1);
pad.stickY = MapFloat<u8>(main_stick_state.y, GCPadStatus::MAIN_STICK_CENTER_Y, 1);

const auto c_stick_state = m_c_stick->GetState();
const auto c_stick_state = m_c_stick->GetState(m_input_override_function);
pad.substickX = MapFloat<u8>(c_stick_state.x, GCPadStatus::C_STICK_CENTER_X, 1);
pad.substickY = MapFloat<u8>(c_stick_state.y, GCPadStatus::C_STICK_CENTER_Y, 1);

// triggers
std::array<ControlState, 2> triggers;
m_triggers->GetState(&pad.button, trigger_bitmasks, triggers.data());
m_triggers->GetState(&pad.button, trigger_bitmasks, triggers.data(), m_input_override_function);
pad.triggerLeft = MapFloat<u8>(triggers[0], 0);
pad.triggerRight = MapFloat<u8>(triggers[1], 0);

Expand Down
27 changes: 27 additions & 0 deletions Source/Core/Core/HW/GCPadEmu.h
Expand Up @@ -5,6 +5,8 @@

#include <string>

#include "Common/Common.h"

#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
#include "InputCommon/ControllerEmu/ControllerEmu.h"
#include "InputCommon/ControllerEmu/Setting/NumericSetting.h"
Expand Down Expand Up @@ -49,6 +51,31 @@ class GCPad : public ControllerEmu::EmulatedController
static constexpr ControlState MAIN_STICK_GATE_RADIUS = 0.7937125;
static constexpr ControlState C_STICK_GATE_RADIUS = 0.7221375;

static constexpr const char* BUTTONS_GROUP = _trans("Buttons");
static constexpr const char* MAIN_STICK_GROUP = "Main Stick";
static constexpr const char* C_STICK_GROUP = "C-Stick";
static constexpr const char* DPAD_GROUP = _trans("D-Pad");
static constexpr const char* TRIGGERS_GROUP = _trans("Triggers");
static constexpr const char* RUMBLE_GROUP = _trans("Rumble");
static constexpr const char* MIC_GROUP = _trans("Microphone");
static constexpr const char* OPTIONS_GROUP = _trans("Options");

static constexpr const char* A_BUTTON = "A";
static constexpr const char* B_BUTTON = "B";
static constexpr const char* X_BUTTON = "X";
static constexpr const char* Y_BUTTON = "Y";
static constexpr const char* Z_BUTTON = "Z";
static constexpr const char* START_BUTTON = "Start";

// i18n: The left trigger button (labeled L on real controllers)
static constexpr const char* L_DIGITAL = _trans("L");
// i18n: The right trigger button (labeled R on real controllers)
static constexpr const char* R_DIGITAL = _trans("R");
// i18n: The left trigger button (labeled L on real controllers) used as an analog input
static constexpr const char* L_ANALOG = _trans("L-Analog");
// i18n: The right trigger button (labeled R on real controllers) used as an analog input
static constexpr const char* R_ANALOG = _trans("R-Analog");

private:
ControllerEmu::Buttons* m_buttons;
ControllerEmu::AnalogStick* m_main_stick;
Expand Down
2 changes: 0 additions & 2 deletions Source/Core/Core/HW/SI/SI_DeviceGCController.cpp
Expand Up @@ -119,8 +119,6 @@ int CSIDevice_GCController::RunBuffer(u8* buffer, int request_length)

void CSIDevice_GCController::HandleMoviePadStatus(int device_number, GCPadStatus* pad_status)
{
Movie::CallGCInputManip(pad_status, device_number);

Movie::SetPolledDevice();
if (NetPlay_GetInput(device_number, pad_status))
{
Expand Down
6 changes: 4 additions & 2 deletions Source/Core/Core/HW/WiimoteEmu/Dynamics.cpp
Expand Up @@ -5,6 +5,7 @@

#include <algorithm>
#include <cmath>
#include <optional>

#include "Common/MathUtil.h"
#include "Core/Config/SYSCONFSettings.h"
Expand Down Expand Up @@ -221,9 +222,10 @@ WiimoteCommon::AccelData ConvertAccelData(const Common::Vec3& accel, u16 zero_g,
u16(std::clamp(std::lround(scaled_accel.z + zero_g), 0l, MAX_VALUE))});
}

void EmulatePoint(MotionState* state, ControllerEmu::Cursor* ir_group, float time_elapsed)
void EmulatePoint(MotionState* state, ControllerEmu::Cursor* ir_group,
const ControllerEmu::InputOverrideFunction& override_func, float time_elapsed)
{
const auto cursor = ir_group->GetState(true);
const auto cursor = ir_group->GetState(true, override_func);

if (!cursor.IsVisible())
{
Expand Down
4 changes: 3 additions & 1 deletion Source/Core/Core/HW/WiimoteEmu/Dynamics.h
Expand Up @@ -15,6 +15,7 @@
#include "InputCommon/ControllerEmu/ControlGroup/IMUCursor.h"
#include "InputCommon/ControllerEmu/ControlGroup/IMUGyroscope.h"
#include "InputCommon/ControllerEmu/ControlGroup/Tilt.h"
#include "InputCommon/ControllerEmu/ControllerEmu.h"

namespace WiimoteEmu
{
Expand Down Expand Up @@ -81,7 +82,8 @@ void ApproachAngleWithAccel(RotationalState* state, const Common::Vec3& target,
void EmulateShake(PositionalState* state, ControllerEmu::Shake* shake_group, float time_elapsed);
void EmulateTilt(RotationalState* state, ControllerEmu::Tilt* tilt_group, float time_elapsed);
void EmulateSwing(MotionState* state, ControllerEmu::Force* swing_group, float time_elapsed);
void EmulatePoint(MotionState* state, ControllerEmu::Cursor* ir_group, float time_elapsed);
void EmulatePoint(MotionState* state, ControllerEmu::Cursor* ir_group,
const ControllerEmu::InputOverrideFunction& override_func, float time_elapsed);
void EmulateIMUCursor(IMUCursorState* state, ControllerEmu::IMUCursor* imu_ir_group,
ControllerEmu::IMUAccelerometer* imu_accelerometer_group,
ControllerEmu::IMUGyroscope* imu_gyroscope_group, float time_elapsed);
Expand Down
74 changes: 27 additions & 47 deletions Source/Core/Core/HW/WiimoteEmu/Extension/Classic.cpp
Expand Up @@ -39,34 +39,11 @@ constexpr std::array<u16, 9> classic_button_bitmasks{{
Classic::BUTTON_HOME,
}};

constexpr std::array<std::string_view, 9> classic_button_names{{
"A",
"B",
"X",
"Y",
"ZL",
"ZR",
"-",
"+",
"Home",
}};

constexpr std::array<u16, 2> classic_trigger_bitmasks{{
Classic::TRIGGER_L,
Classic::TRIGGER_R,
}};

constexpr std::array<const char*, 4> classic_trigger_names{{
// i18n: The left trigger button (labeled L on real controllers)
_trans("L"),
// i18n: The right trigger button (labeled R on real controllers)
_trans("R"),
// i18n: The left trigger button (labeled L on real controllers) used as an analog input
_trans("L-Analog"),
// i18n: The right trigger button (labeled R on real controllers) used as an analog input
_trans("R-Analog"),
}};

constexpr std::array<u16, 4> classic_dpad_bitmasks{{
Classic::PAD_UP,
Classic::PAD_DOWN,
Expand All @@ -77,30 +54,30 @@ constexpr std::array<u16, 4> classic_dpad_bitmasks{{
Classic::Classic() : Extension1stParty("Classic", _trans("Classic Controller"))
{
// buttons
groups.emplace_back(m_buttons = new ControllerEmu::Buttons(_trans("Buttons")));
for (auto& button_name : classic_button_names)
groups.emplace_back(m_buttons = new ControllerEmu::Buttons(BUTTONS_GROUP));
for (auto& button_name :
{A_BUTTON, B_BUTTON, X_BUTTON, Y_BUTTON, ZL_BUTTON, ZR_BUTTON, MINUS_BUTTON, PLUS_BUTTON})
{
std::string_view ui_name = (button_name == "Home") ? "HOME" : button_name;
m_buttons->AddInput(ControllerEmu::DoNotTranslate, std::string(button_name),
std::string(ui_name));
m_buttons->AddInput(ControllerEmu::DoNotTranslate, button_name);
}
m_buttons->AddInput(ControllerEmu::DoNotTranslate, HOME_BUTTON, "HOME");

// sticks
constexpr auto gate_radius = ControlState(STICK_GATE_RADIUS) / CAL_STICK_RANGE;
constexpr auto gate_radius = ControlState(STICK_GATE_RADIUS) / CAL_STICK_RADIUS;
groups.emplace_back(m_left_stick =
new ControllerEmu::OctagonAnalogStick(_trans("Left Stick"), gate_radius));
groups.emplace_back(
m_right_stick = new ControllerEmu::OctagonAnalogStick(_trans("Right Stick"), gate_radius));
new ControllerEmu::OctagonAnalogStick(LEFT_STICK_GROUP, gate_radius));
groups.emplace_back(m_right_stick =
new ControllerEmu::OctagonAnalogStick(RIGHT_STICK_GROUP, gate_radius));

// triggers
groups.emplace_back(m_triggers = new ControllerEmu::MixedTriggers(_trans("Triggers")));
for (const char* trigger_name : classic_trigger_names)
groups.emplace_back(m_triggers = new ControllerEmu::MixedTriggers(TRIGGERS_GROUP));
for (const char* trigger_name : {L_DIGITAL, R_DIGITAL, L_ANALOG, R_ANALOG})
{
m_triggers->AddInput(ControllerEmu::Translate, trigger_name);
}

// dpad
groups.emplace_back(m_dpad = new ControllerEmu::Buttons(_trans("D-Pad")));
groups.emplace_back(m_dpad = new ControllerEmu::Buttons(DPAD_GROUP));
for (const char* named_direction : named_directions)
{
m_dpad->AddInput(ControllerEmu::Translate, named_direction);
Expand All @@ -113,20 +90,22 @@ void Classic::BuildDesiredExtensionState(DesiredExtensionState* target_state)

// left stick
{
const ControllerEmu::AnalogStick::StateData left_stick_state = m_left_stick->GetState();
const ControllerEmu::AnalogStick::StateData left_stick_state =
m_left_stick->GetState(m_input_override_function);

const u8 x = static_cast<u8>(LEFT_STICK_CENTER + (left_stick_state.x * LEFT_STICK_RADIUS));
const u8 y = static_cast<u8>(LEFT_STICK_CENTER + (left_stick_state.y * LEFT_STICK_RADIUS));
const u8 x = MapFloat<u8>(left_stick_state.x, LEFT_STICK_CENTER, 0, LEFT_STICK_RANGE);
const u8 y = MapFloat<u8>(left_stick_state.y, LEFT_STICK_CENTER, 0, LEFT_STICK_RANGE);

classic_data.SetLeftStick({x, y});
}

// right stick
{
const ControllerEmu::AnalogStick::StateData right_stick_data = m_right_stick->GetState();
const ControllerEmu::AnalogStick::StateData right_stick_data =
m_right_stick->GetState(m_input_override_function);

const u8 x = static_cast<u8>(RIGHT_STICK_CENTER + (right_stick_data.x * RIGHT_STICK_RADIUS));
const u8 y = static_cast<u8>(RIGHT_STICK_CENTER + (right_stick_data.y * RIGHT_STICK_RADIUS));
const u8 x = MapFloat<u8>(right_stick_data.x, RIGHT_STICK_CENTER, 0, RIGHT_STICK_RANGE);
const u8 y = MapFloat<u8>(right_stick_data.y, RIGHT_STICK_CENTER, 0, RIGHT_STICK_RANGE);

classic_data.SetRightStick({x, y});
}
Expand All @@ -135,19 +114,20 @@ void Classic::BuildDesiredExtensionState(DesiredExtensionState* target_state)

// triggers
{
ControlState trigs[2] = {0, 0};
m_triggers->GetState(&buttons, classic_trigger_bitmasks.data(), trigs);
ControlState triggers[2] = {0, 0};
m_triggers->GetState(&buttons, classic_trigger_bitmasks.data(), triggers,
m_input_override_function);

const u8 lt = static_cast<u8>(trigs[0] * TRIGGER_RANGE);
const u8 rt = static_cast<u8>(trigs[1] * TRIGGER_RANGE);
const u8 lt = MapFloat<u8>(triggers[0], 0, 0, TRIGGER_RANGE);
const u8 rt = MapFloat<u8>(triggers[1], 0, 0, TRIGGER_RANGE);

classic_data.SetLeftTrigger(lt);
classic_data.SetRightTrigger(rt);
}

// buttons and dpad
m_buttons->GetState(&buttons, classic_button_bitmasks.data());
m_dpad->GetState(&buttons, classic_dpad_bitmasks.data());
m_buttons->GetState(&buttons, classic_button_bitmasks.data(), m_input_override_function);
m_dpad->GetState(&buttons, classic_dpad_bitmasks.data(), m_input_override_function);

classic_data.SetButtons(buttons);

Expand Down
32 changes: 29 additions & 3 deletions Source/Core/Core/HW/WiimoteEmu/Extension/Classic.h
Expand Up @@ -205,16 +205,42 @@ class Classic : public Extension1stParty
static constexpr u8 STICK_GATE_RADIUS = 0x61;

static constexpr u8 CAL_STICK_CENTER = 0x80;
static constexpr u8 CAL_STICK_RANGE = 0x7f;
static constexpr u8 CAL_STICK_RADIUS = 0x7f;
static constexpr u8 CAL_STICK_RANGE = 0xff;

static constexpr u8 LEFT_STICK_CENTER = CAL_STICK_CENTER >> (CAL_STICK_BITS - LEFT_STICK_BITS);
static constexpr u8 LEFT_STICK_RADIUS = CAL_STICK_RANGE >> (CAL_STICK_BITS - LEFT_STICK_BITS);
static constexpr u8 LEFT_STICK_RANGE = CAL_STICK_RANGE >> (CAL_STICK_BITS - LEFT_STICK_BITS);

static constexpr u8 RIGHT_STICK_CENTER = CAL_STICK_CENTER >> (CAL_STICK_BITS - RIGHT_STICK_BITS);
static constexpr u8 RIGHT_STICK_RADIUS = CAL_STICK_RANGE >> (CAL_STICK_BITS - RIGHT_STICK_BITS);
static constexpr u8 RIGHT_STICK_RANGE = CAL_STICK_RANGE >> (CAL_STICK_BITS - RIGHT_STICK_BITS);

static constexpr u8 TRIGGER_RANGE = 0x1F;

static constexpr const char* BUTTONS_GROUP = _trans("Buttons");
static constexpr const char* LEFT_STICK_GROUP = _trans("Left Stick");
static constexpr const char* RIGHT_STICK_GROUP = _trans("Right Stick");
static constexpr const char* TRIGGERS_GROUP = _trans("Triggers");
static constexpr const char* DPAD_GROUP = _trans("D-Pad");

static constexpr const char* A_BUTTON = "A";
static constexpr const char* B_BUTTON = "B";
static constexpr const char* X_BUTTON = "X";
static constexpr const char* Y_BUTTON = "Y";
static constexpr const char* ZL_BUTTON = "ZL";
static constexpr const char* ZR_BUTTON = "ZR";
static constexpr const char* MINUS_BUTTON = "-";
static constexpr const char* PLUS_BUTTON = "+";
static constexpr const char* HOME_BUTTON = "Home";

// i18n: The left trigger button (labeled L on real controllers)
static constexpr const char* L_DIGITAL = _trans("L");
// i18n: The right trigger button (labeled R on real controllers)
static constexpr const char* R_DIGITAL = _trans("R");
// i18n: The left trigger button (labeled L on real controllers) used as an analog input
static constexpr const char* L_ANALOG = _trans("L-Analog");
// i18n: The right trigger button (labeled R on real controllers) used as an analog input
static constexpr const char* R_ANALOG = _trans("R-Analog");

private:
ControllerEmu::Buttons* m_buttons;
ControllerEmu::MixedTriggers* m_triggers;
Expand Down
14 changes: 7 additions & 7 deletions Source/Core/Core/HW/WiimoteEmu/Extension/DrawsomeTablet.cpp
Expand Up @@ -44,12 +44,12 @@ void DrawsomeTablet::BuildDesiredExtensionState(DesiredExtensionState* target_st
// the "Drawsome" game expects you to go "off screen" a bit to access some menu items.
constexpr u16 MIN_Y = 0x15ff + 0x100;
constexpr u16 MAX_Y = 0x00;
constexpr double CENTER_X = (MAX_X + MIN_X) / 2.0;
constexpr double CENTER_Y = (MAX_Y + MIN_Y) / 2.0;
constexpr u16 CENTER_X = (MAX_X + MIN_X + 1) / 2;
constexpr u16 CENTER_Y = (MAX_Y + MIN_Y + 1) / 2;

const auto stylus_state = m_stylus->GetState();
const auto stylus_x = u16(std::lround(CENTER_X + stylus_state.x * (MAX_X - CENTER_X)));
const auto stylus_y = u16(std::lround(CENTER_Y + stylus_state.y * (MAX_Y - CENTER_Y)));
const auto stylus_state = m_stylus->GetState(m_input_override_function);
const u16 stylus_x = MapFloat<u16>(stylus_state.x, CENTER_X, MIN_X, MAX_X);
const u16 stylus_y = MapFloat<u16>(-stylus_state.y, CENTER_Y, MAX_Y, MIN_Y);

tablet_data.stylus_x1 = u8(stylus_x);
tablet_data.stylus_x2 = u8(stylus_x >> 8);
Expand All @@ -74,8 +74,8 @@ void DrawsomeTablet::BuildDesiredExtensionState(DesiredExtensionState* target_st
// Pressure (0 - 0x7ff):
constexpr u16 MAX_PRESSURE = 0x7ff;

const auto touch_state = m_touch->GetState();
const auto pressure = u16(std::lround(touch_state.data[0] * MAX_PRESSURE));
const auto touch_state = m_touch->GetState(m_input_override_function);
const u16 pressure = MapFloat<u16>(touch_state.data[0], 0, 0, MAX_PRESSURE);

tablet_data.pressure1 = u8(pressure);
tablet_data.pressure2 = u8(pressure >> 8);
Expand Down
9 changes: 6 additions & 3 deletions Source/Core/Core/HW/WiimoteEmu/Extension/Drums.cpp
Expand Up @@ -84,17 +84,20 @@ void Drums::BuildDesiredExtensionState(DesiredExtensionState* target_state)
DesiredState& state = target_state->data.emplace<DesiredState>();

{
const ControllerEmu::AnalogStick::StateData stick_state = m_stick->GetState();
const ControllerEmu::AnalogStick::StateData stick_state =
m_stick->GetState(m_input_override_function);

state.stick_x = MapFloat<u8>(stick_state.x, STICK_CENTER, STICK_MIN, STICK_MAX);
state.stick_y = MapFloat<u8>(stick_state.y, STICK_CENTER, STICK_MIN, STICK_MAX);
state.stick_x = MapFloat(stick_state.x, STICK_CENTER, STICK_MIN, STICK_MAX);
state.stick_y = MapFloat(stick_state.y, STICK_CENTER, STICK_MIN, STICK_MAX);
}

state.buttons = 0;
m_buttons->GetState(&state.buttons, drum_button_bitmasks.data());
m_buttons->GetState(&state.buttons, drum_button_bitmasks.data(), m_input_override_function);

state.drum_pads = 0;
m_pads->GetState(&state.drum_pads, drum_pad_bitmasks.data());
m_pads->GetState(&state.drum_pads, drum_pad_bitmasks.data(), m_input_override_function);

state.softness = u8(7 - std::lround(m_hit_strength_setting.GetValue() * 7 / 100));
}
Expand Down
21 changes: 12 additions & 9 deletions Source/Core/Core/HW/WiimoteEmu/Extension/Guitar.cpp
Expand Up @@ -101,17 +101,19 @@ void Guitar::BuildDesiredExtensionState(DesiredExtensionState* target_state)

// stick
{
const ControllerEmu::AnalogStick::StateData stick_state = m_stick->GetState();
const ControllerEmu::AnalogStick::StateData stick_state =
m_stick->GetState(m_input_override_function);

guitar_data.sx = static_cast<u8>((stick_state.x * STICK_RADIUS) + STICK_CENTER);
guitar_data.sy = static_cast<u8>((stick_state.y * STICK_RADIUS) + STICK_CENTER);
guitar_data.sx = MapFloat<u8>(stick_state.x, STICK_CENTER, 0, STICK_RANGE);
guitar_data.sy = MapFloat<u8>(stick_state.y, STICK_CENTER, 0, STICK_RANGE);
}

// slider bar
if (m_slider_bar->controls[0]->control_ref->BoundCount() &&
m_slider_bar->controls[1]->control_ref->BoundCount())
{
const ControllerEmu::Slider::StateData slider_data = m_slider_bar->GetState();
const ControllerEmu::Slider::StateData slider_data =
m_slider_bar->GetState(m_input_override_function);

guitar_data.sb = s_slider_bar_control_codes.lower_bound(slider_data.value)->second;
}
Expand All @@ -122,17 +124,18 @@ void Guitar::BuildDesiredExtensionState(DesiredExtensionState* target_state)
}

// whammy bar
const ControllerEmu::Triggers::StateData whammy_state = m_whammy->GetState();
guitar_data.whammy = static_cast<u8>(whammy_state.data[0] * 0x1F);
const ControllerEmu::Triggers::StateData whammy_state =
m_whammy->GetState(m_input_override_function);
guitar_data.whammy = MapFloat<u8>(whammy_state.data[0], 0, 0, 0x1F);

// buttons
m_buttons->GetState(&guitar_data.bt, guitar_button_bitmasks.data());
m_buttons->GetState(&guitar_data.bt, guitar_button_bitmasks.data(), m_input_override_function);

// frets
m_frets->GetState(&guitar_data.bt, guitar_fret_bitmasks.data());
m_frets->GetState(&guitar_data.bt, guitar_fret_bitmasks.data(), m_input_override_function);

// strum
m_strum->GetState(&guitar_data.bt, guitar_strum_bitmasks.data());
m_strum->GetState(&guitar_data.bt, guitar_strum_bitmasks.data(), m_input_override_function);

// flip button bits
guitar_data.bt ^= 0xFFFF;
Expand Down
1 change: 1 addition & 0 deletions Source/Core/Core/HW/WiimoteEmu/Extension/Guitar.h
Expand Up @@ -69,6 +69,7 @@ class Guitar : public Extension1stParty

static const u8 STICK_CENTER = 0x20;
static const u8 STICK_RADIUS = 0x1f;
static const u8 STICK_RANGE = 0x3f;

// TODO: Test real hardware. Is this accurate?
static const u8 STICK_GATE_RADIUS = 0x16;
Expand Down
54 changes: 30 additions & 24 deletions Source/Core/Core/HW/WiimoteEmu/Extension/Nunchuk.cpp
Expand Up @@ -37,14 +37,13 @@ constexpr std::array<u8, 2> nunchuk_button_bitmasks{{
Nunchuk::Nunchuk() : Extension1stParty(_trans("Nunchuk"))
{
// buttons
groups.emplace_back(m_buttons = new ControllerEmu::Buttons(_trans("Buttons")));
m_buttons->AddInput(ControllerEmu::DoNotTranslate, "C");
m_buttons->AddInput(ControllerEmu::DoNotTranslate, "Z");
groups.emplace_back(m_buttons = new ControllerEmu::Buttons(BUTTONS_GROUP));
m_buttons->AddInput(ControllerEmu::DoNotTranslate, C_BUTTON);
m_buttons->AddInput(ControllerEmu::DoNotTranslate, Z_BUTTON);

// stick
constexpr auto gate_radius = ControlState(STICK_GATE_RADIUS) / STICK_RADIUS;
groups.emplace_back(m_stick =
new ControllerEmu::OctagonAnalogStick(_trans("Stick"), gate_radius));
groups.emplace_back(m_stick = new ControllerEmu::OctagonAnalogStick(STICK_GROUP, gate_radius));

// swing
groups.emplace_back(m_swing = new ControllerEmu::Force(_trans("Swing")));
Expand All @@ -59,37 +58,42 @@ Nunchuk::Nunchuk() : Extension1stParty(_trans("Nunchuk"))

// accelerometer
groups.emplace_back(m_imu_accelerometer = new ControllerEmu::IMUAccelerometer(
"IMUAccelerometer", _trans("Accelerometer")));
ACCELEROMETER_GROUP, _trans("Accelerometer")));
}

void Nunchuk::BuildDesiredExtensionState(DesiredExtensionState* target_state)
{
DataFormat nc_data = {};

// stick
const ControllerEmu::AnalogStick::StateData stick_state = m_stick->GetState();
nc_data.jx = u8(STICK_CENTER + stick_state.x * STICK_RADIUS);
nc_data.jy = u8(STICK_CENTER + stick_state.y * STICK_RADIUS);

// Some terribly coded games check whether to move with a check like
//
// if (x != 0 && y != 0)
// do_movement(x, y);
//
// With keyboard controls, these games break if you simply hit
// of the axes. Adjust this if you're hitting one of the axes so that
// we slightly tweak the other axis.
if (nc_data.jx != STICK_CENTER || nc_data.jy != STICK_CENTER)
bool override_occurred = false;
const ControllerEmu::AnalogStick::StateData stick_state =
m_stick->GetState(m_input_override_function, &override_occurred);
nc_data.jx = MapFloat<u8>(stick_state.x, STICK_CENTER, 0, STICK_RANGE);
nc_data.jy = MapFloat<u8>(stick_state.y, STICK_CENTER, 0, STICK_RANGE);

if (!override_occurred)
{
if (nc_data.jx == STICK_CENTER)
++nc_data.jx;
if (nc_data.jy == STICK_CENTER)
++nc_data.jy;
// Some terribly coded games check whether to move with a check like
//
// if (x != 0 && y != 0)
// do_movement(x, y);
//
// With keyboard controls, these games break if you simply hit one
// of the axes. Adjust this if you're hitting one of the axes so that
// we slightly tweak the other axis.
if (nc_data.jx != STICK_CENTER || nc_data.jy != STICK_CENTER)
{
if (nc_data.jx == STICK_CENTER)
++nc_data.jx;
if (nc_data.jy == STICK_CENTER)
++nc_data.jy;
}
}

// buttons
u8 buttons = 0;
m_buttons->GetState(&buttons, nunchuk_button_bitmasks.data());
m_buttons->GetState(&buttons, nunchuk_button_bitmasks.data(), m_input_override_function);
nc_data.SetButtons(buttons);

// Acceleration data:
Expand All @@ -108,6 +112,8 @@ void Nunchuk::BuildDesiredExtensionState(DesiredExtensionState* target_state)
// shake
accel += m_shake_state.acceleration;

accel = Wiimote::OverrideVec3(m_imu_accelerometer, accel, m_input_override_function);

// Calibration values are 8-bit but we want 10-bit precision, so << 2.
const auto acc = ConvertAccelData(accel, ACCEL_ZERO_G << 2, ACCEL_ONE_G << 2);
nc_data.SetAccel(acc.value);
Expand Down
12 changes: 11 additions & 1 deletion Source/Core/Core/HW/WiimoteEmu/Extension/Nunchuk.h
Expand Up @@ -5,6 +5,8 @@

#include <array>

#include "Common/Common.h"

#include "Core/HW/WiimoteCommon/WiimoteReport.h"
#include "Core/HW/WiimoteEmu/Dynamics.h"
#include "Core/HW/WiimoteEmu/Extension/Extension.h"
Expand Down Expand Up @@ -156,6 +158,8 @@ class Nunchuk : public Extension1stParty

ControllerEmu::ControlGroup* GetGroup(NunchukGroup group);

void LoadDefaults(const ControllerInterface& ciface) override;

static constexpr u8 BUTTON_C = 0x02;
static constexpr u8 BUTTON_Z = 0x01;

Expand All @@ -166,8 +170,14 @@ class Nunchuk : public Extension1stParty

static constexpr u8 STICK_CENTER = 0x80;
static constexpr u8 STICK_RADIUS = 0x7F;
static constexpr u8 STICK_RANGE = 0xFF;

void LoadDefaults(const ControllerInterface& ciface) override;
static constexpr const char* BUTTONS_GROUP = _trans("Buttons");
static constexpr const char* STICK_GROUP = _trans("Stick");
static constexpr const char* ACCELEROMETER_GROUP = "IMUAccelerometer";

static constexpr const char* C_BUTTON = "C";
static constexpr const char* Z_BUTTON = "Z";

private:
ControllerEmu::Tilt* m_tilt;
Expand Down
5 changes: 3 additions & 2 deletions Source/Core/Core/HW/WiimoteEmu/Extension/Shinkansen.cpp
Expand Up @@ -64,8 +64,9 @@ void Shinkansen::BuildDesiredExtensionState(DesiredExtensionState* target_state)
// guesses).
const u8 brake_values[] = {0, 53, 79, 105, 132, 159, 187, 217, 250};
const u8 power_values[] = {255, 229, 208, 189, 170, 153, 135, 118, 101, 85, 68, 51, 35, 17};
state.brake = brake_values[size_t(analog[0] * (sizeof(brake_values) - 1))];
state.power = power_values[size_t(analog[1] * (sizeof(power_values) - 1))];
// Not casting from size_t would trigger a static assert in MapFloat due to its use of llround
state.brake = brake_values[MapFloat(analog[0], 0, 0, static_cast<int>(sizeof(brake_values) - 1))];
state.power = power_values[MapFloat(analog[1], 0, 0, static_cast<int>(sizeof(power_values) - 1))];

// Note: This currently assumes a little-endian host.
const u16 button_bitmasks[] = {
Expand Down
4 changes: 2 additions & 2 deletions Source/Core/Core/HW/WiimoteEmu/Extension/TaTaCon.cpp
Expand Up @@ -54,8 +54,8 @@ void TaTaCon::BuildDesiredExtensionState(DesiredExtensionState* target_state)
{
DataFormat tatacon_data = {};

m_center->GetState(&tatacon_data.state, center_bitmasks.data());
m_rim->GetState(&tatacon_data.state, rim_bitmasks.data());
m_center->GetState(&tatacon_data.state, center_bitmasks.data(), m_input_override_function);
m_rim->GetState(&tatacon_data.state, rim_bitmasks.data(), m_input_override_function);

// Flip button bits.
tatacon_data.state ^= 0xff;
Expand Down
25 changes: 13 additions & 12 deletions Source/Core/Core/HW/WiimoteEmu/Extension/Turntable.cpp
Expand Up @@ -86,25 +86,26 @@ void Turntable::BuildDesiredExtensionState(DesiredExtensionState* target_state)

// stick
{
const ControllerEmu::AnalogStick::StateData stick_state = m_stick->GetState();
const ControllerEmu::AnalogStick::StateData stick_state =
m_stick->GetState(m_input_override_function);

tt_data.sx = static_cast<u8>((stick_state.x * STICK_RADIUS) + STICK_CENTER);
tt_data.sy = static_cast<u8>((stick_state.y * STICK_RADIUS) + STICK_CENTER);
tt_data.sx = MapFloat<u8>(stick_state.x, STICK_CENTER, 0, STICK_RANGE);
tt_data.sy = MapFloat<u8>(stick_state.y, STICK_CENTER, 0, STICK_RANGE);
}

// left table
{
const ControllerEmu::Slider::StateData lt = m_left_table->GetState();
const s8 tt = static_cast<s8>(lt.value * TABLE_RANGE);
const ControllerEmu::Slider::StateData lt = m_left_table->GetState(m_input_override_function);
const s8 tt = MapFloat<u8>(lt.value, 0, 0, TABLE_RANGE);

tt_data.ltable1 = tt;
tt_data.ltable2 = tt >> 5;
}

// right table
{
const ControllerEmu::Slider::StateData rt = m_right_table->GetState();
const s8 tt = static_cast<s8>(rt.value * TABLE_RANGE);
const ControllerEmu::Slider::StateData rt = m_right_table->GetState(m_input_override_function);
const s8 tt = MapFloat<u8>(rt.value, 0, 0, TABLE_RANGE);

tt_data.rtable1 = tt;
tt_data.rtable2 = tt >> 1;
Expand All @@ -114,22 +115,22 @@ void Turntable::BuildDesiredExtensionState(DesiredExtensionState* target_state)

// effect dial
{
const auto dial_state = m_effect_dial->GetState();
const u8 dial = static_cast<u8>(dial_state.value * EFFECT_DIAL_RANGE) + EFFECT_DIAL_CENTER;
const auto dial_state = m_effect_dial->GetState(m_input_override_function);
const u8 dial = MapFloat<u8>(dial_state.value, EFFECT_DIAL_CENTER, 0, EFFECT_DIAL_RANGE);

tt_data.dial1 = dial;
tt_data.dial2 = dial >> 3;
}

// crossfade slider
{
const ControllerEmu::Slider::StateData cfs = m_crossfade->GetState();
const ControllerEmu::Slider::StateData cfs = m_crossfade->GetState(m_input_override_function);

tt_data.slider = static_cast<u8>((cfs.value * CROSSFADE_RANGE) + CROSSFADE_CENTER);
tt_data.slider = MapFloat<u8>(cfs.value, CROSSFADE_CENTER, 0, CROSSFADE_RANGE);
}

// buttons
m_buttons->GetState(&tt_data.bt, turntable_button_bitmasks.data());
m_buttons->GetState(&tt_data.bt, turntable_button_bitmasks.data(), m_input_override_function);

// flip button bits :/
tt_data.bt ^= (BUTTON_L_GREEN | BUTTON_L_RED | BUTTON_L_BLUE | BUTTON_R_GREEN | BUTTON_R_RED |
Expand Down
5 changes: 3 additions & 2 deletions Source/Core/Core/HW/WiimoteEmu/Extension/Turntable.h
Expand Up @@ -78,6 +78,7 @@ class Turntable : public Extension1stParty
static constexpr int STICK_BIT_COUNT = 6;
static constexpr u8 STICK_CENTER = (1 << STICK_BIT_COUNT) / 2;
static constexpr u8 STICK_RADIUS = STICK_CENTER - 1;
static constexpr u8 STICK_RANGE = (1 << STICK_BIT_COUNT) - 1;
// TODO: Test real hardware. Is this accurate?
static constexpr u8 STICK_GATE_RADIUS = 0x16;

Expand All @@ -86,11 +87,11 @@ class Turntable : public Extension1stParty

static constexpr int EFFECT_DIAL_BIT_COUNT = 5;
static constexpr u8 EFFECT_DIAL_CENTER = (1 << EFFECT_DIAL_BIT_COUNT) / 2;
static constexpr u8 EFFECT_DIAL_RANGE = EFFECT_DIAL_CENTER - 1;
static constexpr u8 EFFECT_DIAL_RANGE = (1 << EFFECT_DIAL_BIT_COUNT) - 1;

static constexpr int CROSSFADE_BIT_COUNT = 4;
static constexpr u8 CROSSFADE_CENTER = (1 << CROSSFADE_BIT_COUNT) / 2;
static constexpr u8 CROSSFADE_RANGE = CROSSFADE_CENTER - 1;
static constexpr u8 CROSSFADE_RANGE = (1 << CROSSFADE_BIT_COUNT) - 1;

private:
ControllerEmu::Buttons* m_buttons;
Expand Down
117 changes: 90 additions & 27 deletions Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.cpp
Expand Up @@ -5,11 +5,13 @@

#include <algorithm>
#include <memory>
#include <optional>
#include <string_view>

#include <fmt/format.h>

#include "Common/Assert.h"
#include "Common/Common.h"
#include "Common/CommonTypes.h"
#include "Common/Config/Config.h"
#include "Common/FileUtil.h"
Expand Down Expand Up @@ -63,10 +65,6 @@ static const u16 dpad_bitmasks[] = {Wiimote::PAD_UP, Wiimote::PAD_DOWN, Wiimote:
static const u16 dpad_sideways_bitmasks[] = {Wiimote::PAD_RIGHT, Wiimote::PAD_LEFT, Wiimote::PAD_UP,
Wiimote::PAD_DOWN};

constexpr std::array<std::string_view, 7> named_buttons{
"A", "B", "1", "2", "-", "+", "Home",
};

void Wiimote::Reset()
{
const bool want_determinism = Core::WantsDeterminism();
Expand Down Expand Up @@ -211,24 +209,23 @@ void Wiimote::Reset()
Wiimote::Wiimote(const unsigned int index) : m_index(index), m_bt_device_index(index)
{
// Buttons
groups.emplace_back(m_buttons = new ControllerEmu::Buttons(_trans("Buttons")));
for (auto& named_button : named_buttons)
groups.emplace_back(m_buttons = new ControllerEmu::Buttons(BUTTONS_GROUP));
for (auto& named_button : {A_BUTTON, B_BUTTON, ONE_BUTTON, TWO_BUTTON, MINUS_BUTTON, PLUS_BUTTON})
{
std::string_view ui_name = (named_button == "Home") ? "HOME" : named_button;
m_buttons->AddInput(ControllerEmu::DoNotTranslate, std::string(named_button),
std::string(ui_name));
m_buttons->AddInput(ControllerEmu::DoNotTranslate, named_button);
}
m_buttons->AddInput(ControllerEmu::DoNotTranslate, HOME_BUTTON, "HOME");

// Pointing (IR)
// i18n: "Point" refers to the action of pointing a Wii Remote.
groups.emplace_back(m_ir = new ControllerEmu::Cursor("IR", _trans("Point")));
groups.emplace_back(m_ir = new ControllerEmu::Cursor(IR_GROUP, _trans("Point")));
groups.emplace_back(m_swing = new ControllerEmu::Force(_trans("Swing")));
groups.emplace_back(m_tilt = new ControllerEmu::Tilt(_trans("Tilt")));
groups.emplace_back(m_shake = new ControllerEmu::Shake(_trans("Shake")));
groups.emplace_back(m_imu_accelerometer = new ControllerEmu::IMUAccelerometer(
"IMUAccelerometer", _trans("Accelerometer")));
ACCELEROMETER_GROUP, _trans("Accelerometer")));
groups.emplace_back(m_imu_gyroscope =
new ControllerEmu::IMUGyroscope("IMUGyroscope", _trans("Gyroscope")));
new ControllerEmu::IMUGyroscope(GYROSCOPE_GROUP, _trans("Gyroscope")));
groups.emplace_back(m_imu_ir = new ControllerEmu::IMUCursor("IMUIR", _trans("Point")));

const auto fov_default =
Expand Down Expand Up @@ -272,7 +269,7 @@ Wiimote::Wiimote(const unsigned int index) : m_index(index), m_bt_device_index(i
m_rumble->AddOutput(ControllerEmu::Translate, _trans("Motor"));

// D-Pad
groups.emplace_back(m_dpad = new ControllerEmu::Buttons(_trans("D-Pad")));
groups.emplace_back(m_dpad = new ControllerEmu::Buttons(DPAD_GROUP));
for (const char* named_direction : named_directions)
{
m_dpad->AddInput(ControllerEmu::Translate, named_direction);
Expand Down Expand Up @@ -458,9 +455,10 @@ void Wiimote::BuildDesiredWiimoteState(DesiredWiimoteState* target_state)

// Fetch pressed buttons from user input.
target_state->buttons.hex = 0;
m_buttons->GetState(&target_state->buttons.hex, button_bitmasks);
m_buttons->GetState(&target_state->buttons.hex, button_bitmasks, m_input_override_function);
m_dpad->GetState(&target_state->buttons.hex,
IsSideways() ? dpad_sideways_bitmasks : dpad_bitmasks);
IsSideways() ? dpad_sideways_bitmasks : dpad_bitmasks,
m_input_override_function);

// Calculate accelerometer state.
// Calibration values are 8-bit but we want 10-bit precision, so << 2.
Expand Down Expand Up @@ -628,9 +626,6 @@ void Wiimote::SendDataReport(const DesiredWiimoteState& target_state)
std::fill_n(ext_data, ext_size, u8(0xff));
}
}

Movie::CallWiiInputManip(rpt_builder, m_bt_device_index, m_active_extension,
GetExtensionEncryptionKey());
}

Movie::CheckWiimoteStatus(m_bt_device_index, rpt_builder, m_active_extension,
Expand All @@ -651,8 +646,9 @@ ButtonData Wiimote::GetCurrentlyPressedButtons()
const auto lock = GetStateLock();

ButtonData buttons{};
m_buttons->GetState(&buttons.hex, button_bitmasks);
m_dpad->GetState(&buttons.hex, IsSideways() ? dpad_sideways_bitmasks : dpad_bitmasks);
m_buttons->GetState(&buttons.hex, button_bitmasks, m_input_override_function);
m_dpad->GetState(&buttons.hex, IsSideways() ? dpad_sideways_bitmasks : dpad_bitmasks,
m_input_override_function);

return buttons;
}
Expand Down Expand Up @@ -789,7 +785,7 @@ void Wiimote::StepDynamics()
{
EmulateSwing(&m_swing_state, m_swing, 1.f / ::Wiimote::UPDATE_FREQ);
EmulateTilt(&m_tilt_state, m_tilt, 1.f / ::Wiimote::UPDATE_FREQ);
EmulatePoint(&m_point_state, m_ir, 1.f / ::Wiimote::UPDATE_FREQ);
EmulatePoint(&m_point_state, m_ir, m_input_override_function, 1.f / ::Wiimote::UPDATE_FREQ);
EmulateShake(&m_shake_state, m_shake, 1.f / ::Wiimote::UPDATE_FREQ);
EmulateIMUCursor(&m_imu_cursor_state, m_imu_ir, m_imu_accelerometer, m_imu_gyroscope,
1.f / ::Wiimote::UPDATE_FREQ);
Expand Down Expand Up @@ -831,20 +827,87 @@ Common::Quaternion Wiimote::GetOrientation() const
Common::Quaternion::RotateX(float(MathUtil::TAU / 4 * IsUpright()));
}

std::optional<Common::Vec3> Wiimote::OverrideVec3(const ControllerEmu::ControlGroup* control_group,
std::optional<Common::Vec3> optional_vec) const
{
bool has_value = optional_vec.has_value();
Common::Vec3 vec = has_value ? *optional_vec : Common::Vec3{};

if (m_input_override_function)
{
if (const std::optional<ControlState> x_override = m_input_override_function(
control_group->name, ControllerEmu::ReshapableInput::X_INPUT_OVERRIDE, vec.x))
{
has_value = true;
vec.x = *x_override;
}

if (const std::optional<ControlState> y_override = m_input_override_function(
control_group->name, ControllerEmu::ReshapableInput::Y_INPUT_OVERRIDE, vec.y))
{
has_value = true;
vec.y = *y_override;
}

if (const std::optional<ControlState> z_override = m_input_override_function(
control_group->name, ControllerEmu::ReshapableInput::Z_INPUT_OVERRIDE, vec.z))
{
has_value = true;
vec.z = *z_override;
}
}

return has_value ? std::make_optional(vec) : std::nullopt;
}

Common::Vec3 Wiimote::OverrideVec3(const ControllerEmu::ControlGroup* control_group,
Common::Vec3 vec) const
{
return OverrideVec3(control_group, vec, m_input_override_function);
}

Common::Vec3
Wiimote::OverrideVec3(const ControllerEmu::ControlGroup* control_group, Common::Vec3 vec,
const ControllerEmu::InputOverrideFunction& input_override_function)
{
if (input_override_function)
{
if (const std::optional<ControlState> x_override = input_override_function(
control_group->name, ControllerEmu::ReshapableInput::X_INPUT_OVERRIDE, vec.x))
{
vec.x = *x_override;
}

if (const std::optional<ControlState> y_override = input_override_function(
control_group->name, ControllerEmu::ReshapableInput::Y_INPUT_OVERRIDE, vec.y))
{
vec.y = *y_override;
}

if (const std::optional<ControlState> z_override = input_override_function(
control_group->name, ControllerEmu::ReshapableInput::Z_INPUT_OVERRIDE, vec.z))
{
vec.z = *z_override;
}
}

return vec;
}

Common::Vec3 Wiimote::GetTotalAcceleration() const
{
if (const auto accel = m_imu_accelerometer->GetState())
return GetAcceleration(*accel);
const Common::Vec3 default_accel = Common::Vec3(0, 0, float(GRAVITY_ACCELERATION));
const Common::Vec3 accel = m_imu_accelerometer->GetState().value_or(default_accel);

return GetAcceleration();
return OverrideVec3(m_imu_accelerometer, GetAcceleration(accel));
}

Common::Vec3 Wiimote::GetTotalAngularVelocity() const
{
if (const auto ang_vel = m_imu_gyroscope->GetState())
return GetAngularVelocity(*ang_vel);
const Common::Vec3 default_ang_vel = {};
const Common::Vec3 ang_vel = m_imu_gyroscope->GetState().value_or(default_ang_vel);

return GetAngularVelocity();
return OverrideVec3(m_imu_gyroscope, GetAngularVelocity(ang_vel));
}

Common::Matrix44 Wiimote::GetTotalTransformation() const
Expand Down
30 changes: 27 additions & 3 deletions Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h
Expand Up @@ -5,8 +5,11 @@

#include <array>
#include <numeric>
#include <optional>
#include <string>

#include "Common/Common.h"

#include "Core/HW/WiimoteCommon/WiimoteReport.h"

#include "Core/HW/WiimoteEmu/Camera.h"
Expand Down Expand Up @@ -112,6 +115,20 @@ class Wiimote : public ControllerEmu::EmulatedController, public WiimoteCommon::
static constexpr u16 BUTTON_MINUS = 0x1000;
static constexpr u16 BUTTON_HOME = 0x8000;

static constexpr const char* BUTTONS_GROUP = _trans("Buttons");
static constexpr const char* DPAD_GROUP = _trans("D-Pad");
static constexpr const char* ACCELEROMETER_GROUP = "IMUAccelerometer";
static constexpr const char* GYROSCOPE_GROUP = "IMUGyroscope";
static constexpr const char* IR_GROUP = "IR";

static constexpr const char* A_BUTTON = "A";
static constexpr const char* B_BUTTON = "B";
static constexpr const char* ONE_BUTTON = "1";
static constexpr const char* TWO_BUTTON = "2";
static constexpr const char* MINUS_BUTTON = "-";
static constexpr const char* PLUS_BUTTON = "+";
static constexpr const char* HOME_BUTTON = "Home";

explicit Wiimote(unsigned int index);
~Wiimote();

Expand Down Expand Up @@ -146,6 +163,10 @@ class Wiimote : public ControllerEmu::EmulatedController, public WiimoteCommon::
// Active extension number is exposed for TAS.
ExtensionNumber GetActiveExtensionNumber() const;

static Common::Vec3
OverrideVec3(const ControllerEmu::ControlGroup* control_group, Common::Vec3 vec,
const ControllerEmu::InputOverrideFunction& input_override_function);

private:
// Used only for error generation:
static constexpr u8 EEPROM_I2C_ADDR = 0x50;
Expand All @@ -161,11 +182,10 @@ class Wiimote : public ControllerEmu::EmulatedController, public WiimoteCommon::
void BuildDesiredWiimoteState(DesiredWiimoteState* target_state);

// Returns simulated accelerometer data in m/s^2.
Common::Vec3 GetAcceleration(
Common::Vec3 extra_acceleration = Common::Vec3(0, 0, float(GRAVITY_ACCELERATION))) const;
Common::Vec3 GetAcceleration(Common::Vec3 extra_acceleration) const;

// Returns simulated gyroscope data in radians/s.
Common::Vec3 GetAngularVelocity(Common::Vec3 extra_angular_velocity = {}) const;
Common::Vec3 GetAngularVelocity(Common::Vec3 extra_angular_velocity) const;

// Returns the transformation of the world around the wiimote.
// Used for simulating camera data and for rotating acceleration data.
Expand All @@ -176,6 +196,10 @@ class Wiimote : public ControllerEmu::EmulatedController, public WiimoteCommon::
// Returns the world rotation from the effects of sideways/upright settings.
Common::Quaternion GetOrientation() const;

std::optional<Common::Vec3> OverrideVec3(const ControllerEmu::ControlGroup* control_group,
std::optional<Common::Vec3> optional_vec) const;
Common::Vec3 OverrideVec3(const ControllerEmu::ControlGroup* control_group,
Common::Vec3 vec) const;
Common::Vec3 GetTotalAcceleration() const;
Common::Vec3 GetTotalAngularVelocity() const;
Common::Matrix44 GetTotalTransformation() const;
Expand Down
25 changes: 0 additions & 25 deletions Source/Core/Core/Movie.cpp
Expand Up @@ -122,9 +122,6 @@ static bool s_bPolled = false;
static std::mutex s_input_display_lock;
static std::string s_InputDisplay[8];

static GCManipFunction s_gc_manip_func;
static WiiManipFunction s_wii_manip_func;

static std::string s_current_file_name;

static void GetSettings();
Expand Down Expand Up @@ -1426,28 +1423,6 @@ void SaveRecording(const std::string& filename)
Core::DisplayMessage(fmt::format("Failed to save {}", filename), 2000);
}

void SetGCInputManip(GCManipFunction func)
{
s_gc_manip_func = std::move(func);
}
void SetWiiInputManip(WiiManipFunction func)
{
s_wii_manip_func = std::move(func);
}

// NOTE: CPU Thread
void CallGCInputManip(GCPadStatus* PadStatus, int controllerID)
{
if (s_gc_manip_func)
s_gc_manip_func(PadStatus, controllerID);
}
// NOTE: CPU Thread
void CallWiiInputManip(DataReportBuilder& rpt, int controllerID, int ext, const EncryptionKey& key)
{
if (s_wii_manip_func)
s_wii_manip_func(rpt, controllerID, ext, key);
}

// NOTE: GPU Thread
void SetGraphicsConfig()
{
Expand Down
11 changes: 0 additions & 11 deletions Source/Core/Core/Movie.h
Expand Up @@ -5,7 +5,6 @@

#include <array>
#include <cstring>
#include <functional>
#include <optional>
#include <string>
#include <string_view>
Expand Down Expand Up @@ -200,14 +199,4 @@ std::string GetInputDisplay();
std::string GetRTCDisplay();
std::string GetRerecords();

// Done this way to avoid mixing of core and gui code
using GCManipFunction = std::function<void(GCPadStatus*, int)>;
using WiiManipFunction = std::function<void(WiimoteCommon::DataReportBuilder&, int, int,
const WiimoteEmu::EncryptionKey&)>;

void SetGCInputManip(GCManipFunction);
void SetWiiInputManip(WiiManipFunction);
void CallGCInputManip(GCPadStatus* PadStatus, int controllerID);
void CallWiiInputManip(WiimoteCommon::DataReportBuilder& rpt, int controllerID, int ext,
const WiimoteEmu::EncryptionKey& key);
} // namespace Movie
9 changes: 0 additions & 9 deletions Source/Core/DolphinQt/MainWindow.cpp
Expand Up @@ -403,15 +403,6 @@ void MainWindow::CreateComponents()
m_wii_tas_input_windows[i] = new WiiTASInputWindow(nullptr, i);
}

Movie::SetGCInputManip([this](GCPadStatus* pad_status, int controller_id) {
m_gc_tas_input_windows[controller_id]->GetValues(pad_status);
});

Movie::SetWiiInputManip([this](WiimoteCommon::DataReportBuilder& rpt, int controller_id, int ext,
const WiimoteEmu::EncryptionKey& key) {
m_wii_tas_input_windows[controller_id]->GetValues(rpt, ext, key);
});

m_jit_widget = new JITWidget(this);
m_log_widget = new LogWidget(this);
m_log_config_widget = new LogConfigWidget(this);
Expand Down
111 changes: 54 additions & 57 deletions Source/Core/DolphinQt/TAS/GCTASInputWindow.cpp
Expand Up @@ -13,18 +13,25 @@

#include "Common/CommonTypes.h"

#include "Core/HW/GCPad.h"
#include "Core/HW/GCPadEmu.h"

#include "DolphinQt/TAS/TASCheckBox.h"

#include "InputCommon/GCPadStatus.h"
#include "InputCommon/ControllerEmu/ControllerEmu.h"
#include "InputCommon/InputConfig.h"

GCTASInputWindow::GCTASInputWindow(QWidget* parent, int num) : TASInputWindow(parent)
GCTASInputWindow::GCTASInputWindow(QWidget* parent, int controller_id)
: TASInputWindow(parent), m_controller_id(controller_id)
{
setWindowTitle(tr("GameCube TAS Input %1").arg(num + 1));
setWindowTitle(tr("GameCube TAS Input %1").arg(controller_id + 1));

m_main_stick_box = CreateStickInputs(tr("Main Stick"), m_x_main_stick_value, m_y_main_stick_value,
255, 255, Qt::Key_F, Qt::Key_G);
m_c_stick_box = CreateStickInputs(tr("C Stick"), m_x_c_stick_value, m_y_c_stick_value, 255, 255,
Qt::Key_H, Qt::Key_J);
m_main_stick_box = CreateStickInputs(tr("Main Stick"), GCPad::MAIN_STICK_GROUP, &m_overrider,
m_x_main_stick_value, m_y_main_stick_value, 1, 1, 255, 255,
Qt::Key_F, Qt::Key_G);
m_c_stick_box =
CreateStickInputs(tr("C Stick"), GCPad::C_STICK_GROUP, &m_overrider, m_x_c_stick_value,
m_y_c_stick_value, 1, 1, 255, 255, Qt::Key_H, Qt::Key_J);

auto* top_layout = new QHBoxLayout;
top_layout->addWidget(m_main_stick_box);
Expand All @@ -33,27 +40,43 @@ GCTASInputWindow::GCTASInputWindow(QWidget* parent, int num) : TASInputWindow(pa
m_triggers_box = new QGroupBox(tr("Triggers"));

auto* l_trigger_layout =
CreateSliderValuePairLayout(tr("Left"), m_l_trigger_value, 0, 255, Qt::Key_N, m_triggers_box);
auto* r_trigger_layout = CreateSliderValuePairLayout(tr("Right"), m_r_trigger_value, 0, 255,
Qt::Key_M, m_triggers_box);
CreateSliderValuePairLayout(tr("Left"), GCPad::TRIGGERS_GROUP, GCPad::L_ANALOG, &m_overrider,
m_l_trigger_value, 0, 0, 0, 255, Qt::Key_N, m_triggers_box);

auto* r_trigger_layout =
CreateSliderValuePairLayout(tr("Right"), GCPad::TRIGGERS_GROUP, GCPad::R_ANALOG, &m_overrider,
m_r_trigger_value, 0, 0, 0, 255, Qt::Key_M, m_triggers_box);

auto* triggers_layout = new QVBoxLayout;
triggers_layout->addLayout(l_trigger_layout);
triggers_layout->addLayout(r_trigger_layout);
m_triggers_box->setLayout(triggers_layout);

m_a_button = CreateButton(QStringLiteral("&A"));
m_b_button = CreateButton(QStringLiteral("&B"));
m_x_button = CreateButton(QStringLiteral("&X"));
m_y_button = CreateButton(QStringLiteral("&Y"));
m_z_button = CreateButton(QStringLiteral("&Z"));
m_l_button = CreateButton(QStringLiteral("&L"));
m_r_button = CreateButton(QStringLiteral("&R"));
m_start_button = CreateButton(QStringLiteral("&START"));
m_left_button = CreateButton(QStringLiteral("L&eft"));
m_up_button = CreateButton(QStringLiteral("&Up"));
m_down_button = CreateButton(QStringLiteral("&Down"));
m_right_button = CreateButton(QStringLiteral("R&ight"));
m_a_button =
CreateButton(QStringLiteral("&A"), GCPad::BUTTONS_GROUP, GCPad::A_BUTTON, &m_overrider);
m_b_button =
CreateButton(QStringLiteral("&B"), GCPad::BUTTONS_GROUP, GCPad::B_BUTTON, &m_overrider);
m_x_button =
CreateButton(QStringLiteral("&X"), GCPad::BUTTONS_GROUP, GCPad::X_BUTTON, &m_overrider);
m_y_button =
CreateButton(QStringLiteral("&Y"), GCPad::BUTTONS_GROUP, GCPad::Y_BUTTON, &m_overrider);
m_z_button =
CreateButton(QStringLiteral("&Z"), GCPad::BUTTONS_GROUP, GCPad::Z_BUTTON, &m_overrider);
m_start_button = CreateButton(QStringLiteral("&START"), GCPad::BUTTONS_GROUP, GCPad::START_BUTTON,
&m_overrider);

m_l_button =
CreateButton(QStringLiteral("&L"), GCPad::TRIGGERS_GROUP, GCPad::L_DIGITAL, &m_overrider);
m_r_button =
CreateButton(QStringLiteral("&R"), GCPad::TRIGGERS_GROUP, GCPad::R_DIGITAL, &m_overrider);

m_left_button =
CreateButton(QStringLiteral("L&eft"), GCPad::DPAD_GROUP, DIRECTION_LEFT, &m_overrider);
m_up_button = CreateButton(QStringLiteral("&Up"), GCPad::DPAD_GROUP, DIRECTION_UP, &m_overrider);
m_down_button =
CreateButton(QStringLiteral("&Down"), GCPad::DPAD_GROUP, DIRECTION_DOWN, &m_overrider);
m_right_button =
CreateButton(QStringLiteral("R&ight"), GCPad::DPAD_GROUP, DIRECTION_RIGHT, &m_overrider);

auto* buttons_layout = new QGridLayout;
buttons_layout->addWidget(m_a_button, 0, 0);
Expand Down Expand Up @@ -84,40 +107,14 @@ GCTASInputWindow::GCTASInputWindow(QWidget* parent, int num) : TASInputWindow(pa
setLayout(layout);
}

void GCTASInputWindow::GetValues(GCPadStatus* pad)
void GCTASInputWindow::hideEvent(QHideEvent* event)
{
Pad::GetConfig()->GetController(m_controller_id)->ClearInputOverrideFunction();
}

void GCTASInputWindow::showEvent(QShowEvent* event)
{
if (!isVisible())
return;

GetButton<u16>(m_a_button, pad->button, PAD_BUTTON_A);
GetButton<u16>(m_b_button, pad->button, PAD_BUTTON_B);
GetButton<u16>(m_x_button, pad->button, PAD_BUTTON_X);
GetButton<u16>(m_y_button, pad->button, PAD_BUTTON_Y);
GetButton<u16>(m_z_button, pad->button, PAD_TRIGGER_Z);
GetButton<u16>(m_l_button, pad->button, PAD_TRIGGER_L);
GetButton<u16>(m_r_button, pad->button, PAD_TRIGGER_R);
GetButton<u16>(m_left_button, pad->button, PAD_BUTTON_LEFT);
GetButton<u16>(m_up_button, pad->button, PAD_BUTTON_UP);
GetButton<u16>(m_down_button, pad->button, PAD_BUTTON_DOWN);
GetButton<u16>(m_right_button, pad->button, PAD_BUTTON_RIGHT);
GetButton<u16>(m_start_button, pad->button, PAD_BUTTON_START);

if (m_a_button->isChecked())
pad->analogA = 0xFF;
else
pad->analogA = 0x00;

if (m_b_button->isChecked())
pad->analogB = 0xFF;
else
pad->analogB = 0x00;

GetSpinBoxU8(m_l_trigger_value, pad->triggerLeft);
GetSpinBoxU8(m_r_trigger_value, pad->triggerRight);

GetSpinBoxU8(m_x_main_stick_value, pad->stickX);
GetSpinBoxU8(m_y_main_stick_value, pad->stickY);

GetSpinBoxU8(m_x_c_stick_value, pad->substickX);
GetSpinBoxU8(m_y_c_stick_value, pad->substickY);
Pad::GetConfig()
->GetController(m_controller_id)
->SetInputOverrideFunction(m_overrider.GetInputOverrideFunction());
}
13 changes: 10 additions & 3 deletions Source/Core/DolphinQt/TAS/GCTASInputWindow.h
Expand Up @@ -6,18 +6,25 @@
#include "DolphinQt/TAS/TASInputWindow.h"

class QGroupBox;
class QHideEvent;
class QShowEvent;
class QSpinBox;
class TASCheckBox;
struct GCPadStatus;

class GCTASInputWindow : public TASInputWindow
{
Q_OBJECT
public:
explicit GCTASInputWindow(QWidget* parent, int num);
void GetValues(GCPadStatus* pad);
explicit GCTASInputWindow(QWidget* parent, int controller_id);

void hideEvent(QHideEvent* event) override;
void showEvent(QShowEvent* event) override;

private:
int m_controller_id;

InputOverrider m_overrider;

TASCheckBox* m_a_button;
TASCheckBox* m_b_button;
TASCheckBox* m_x_button;
Expand Down
8 changes: 4 additions & 4 deletions Source/Core/DolphinQt/TAS/IRWidget.cpp
Expand Up @@ -54,8 +54,8 @@ void IRWidget::paintEvent(QPaintEvent* event)
painter.drawLine(PADDING + w / 2, PADDING, PADDING + w / 2, PADDING + h);

// convert from value space to widget space
u16 x = PADDING + (w - (m_x * w) / ir_max_x);
u16 y = PADDING + ((m_y * h) / ir_max_y);
u16 x = PADDING + ((m_x * w) / ir_max_x);
u16 y = PADDING + (h - (m_y * h) / ir_max_y);

painter.drawLine(PADDING + w / 2, PADDING + h / 2, x, y);

Expand Down Expand Up @@ -87,8 +87,8 @@ void IRWidget::handleMouseEvent(QMouseEvent* event)
else
{
// convert from widget space to value space
int new_x = ir_max_x - (event->pos().x() * ir_max_x) / width();
int new_y = (event->pos().y() * ir_max_y) / height();
int new_x = (event->pos().x() * ir_max_x) / width();
int new_y = ir_max_y - (event->pos().y() * ir_max_y) / height();

m_x = std::max(0, std::min(static_cast<int>(ir_max_x), new_x));
m_y = std::max(0, std::min(static_cast<int>(ir_max_y), new_y));
Expand Down
2 changes: 2 additions & 0 deletions Source/Core/DolphinQt/TAS/IRWidget.h
Expand Up @@ -34,5 +34,7 @@ public slots:
};

// Should be part of class but fails to compile on mac os
static const u16 ir_min_x = 0;
static const u16 ir_min_y = 0;
static const u16 ir_max_x = 1023;
static const u16 ir_max_y = 767;
138 changes: 103 additions & 35 deletions Source/Core/DolphinQt/TAS/TASInputWindow.cpp
Expand Up @@ -4,6 +4,7 @@
#include "DolphinQt/TAS/TASInputWindow.h"

#include <cmath>
#include <utility>

#include <QCheckBox>
#include <QGroupBox>
Expand All @@ -23,7 +24,23 @@
#include "DolphinQt/TAS/TASCheckBox.h"
#include "DolphinQt/TAS/TASSlider.h"

#include "InputCommon/GCPadStatus.h"
#include "InputCommon/ControllerEmu/ControllerEmu.h"
#include "InputCommon/ControllerEmu/StickGate.h"

void InputOverrider::AddFunction(std::string_view group_name, std::string_view control_name,
OverrideFunction function)
{
m_functions.emplace(std::make_pair(group_name, control_name), std::move(function));
}

ControllerEmu::InputOverrideFunction InputOverrider::GetInputOverrideFunction() const
{
return [this](std::string_view group_name, std::string_view control_name,
ControlState controller_state) {
const auto it = m_functions.find(std::make_pair(group_name, control_name));
return it != m_functions.end() ? it->second(controller_state) : std::nullopt;
};
}

TASInputWindow::TASInputWindow(QWidget* parent) : QDialog(parent)
{
Expand Down Expand Up @@ -63,21 +80,30 @@ int TASInputWindow::GetTurboReleaseFrames() const
return m_turbo_release_frames->value();
}

TASCheckBox* TASInputWindow::CreateButton(const QString& name)
TASCheckBox* TASInputWindow::CreateButton(const QString& text, std::string_view group_name,
std::string_view control_name, InputOverrider* overrider)
{
return new TASCheckBox(name, this);
TASCheckBox* checkbox = new TASCheckBox(text, this);

overrider->AddFunction(group_name, control_name, [this, checkbox](ControlState controller_state) {
return GetButton(checkbox, controller_state);
});

return checkbox;
}

QGroupBox* TASInputWindow::CreateStickInputs(QString name, QSpinBox*& x_value, QSpinBox*& y_value,
u16 max_x, u16 max_y, Qt::Key x_shortcut_key,
QGroupBox* TASInputWindow::CreateStickInputs(const QString& text, std::string_view group_name,
InputOverrider* overrider, QSpinBox*& x_value,
QSpinBox*& y_value, u16 min_x, u16 min_y, u16 max_x,
u16 max_y, Qt::Key x_shortcut_key,
Qt::Key y_shortcut_key)
{
const QKeySequence x_shortcut_key_sequence = QKeySequence(Qt::ALT | x_shortcut_key);
const QKeySequence y_shortcut_key_sequence = QKeySequence(Qt::ALT | y_shortcut_key);

auto* box =
new QGroupBox(QStringLiteral("%1 (%2/%3)")
.arg(name, x_shortcut_key_sequence.toString(QKeySequence::NativeText),
.arg(text, x_shortcut_key_sequence.toString(QKeySequence::NativeText),
y_shortcut_key_sequence.toString(QKeySequence::NativeText)));

const int x_default = static_cast<int>(std::round(max_x / 2.));
Expand Down Expand Up @@ -112,33 +138,72 @@ QGroupBox* TASInputWindow::CreateStickInputs(QString name, QSpinBox*& x_value, Q
layout->addLayout(visual_layout);
box->setLayout(layout);

overrider->AddFunction(group_name, ControllerEmu::ReshapableInput::X_INPUT_OVERRIDE,
[this, x_value, x_default, min_x, max_x](ControlState controller_state) {
return GetSpinBox(x_value, x_default, min_x, max_x, controller_state);
});

overrider->AddFunction(group_name, ControllerEmu::ReshapableInput::Y_INPUT_OVERRIDE,
[this, y_value, y_default, min_y, max_y](ControlState controller_state) {
return GetSpinBox(y_value, y_default, min_y, max_y, controller_state);
});

return box;
}

QBoxLayout* TASInputWindow::CreateSliderValuePairLayout(QString name, QSpinBox*& value,
int default_, u16 max, Qt::Key shortcut_key,
QWidget* shortcut_widget, bool invert)
QBoxLayout* TASInputWindow::CreateSliderValuePairLayout(
const QString& text, std::string_view group_name, std::string_view control_name,
InputOverrider* overrider, QSpinBox*& value, u16 zero, int default_, u16 min, u16 max,
Qt::Key shortcut_key, QWidget* shortcut_widget, std::optional<ControlState> scale)
{
const QKeySequence shortcut_key_sequence = QKeySequence(Qt::ALT | shortcut_key);

auto* label = new QLabel(QStringLiteral("%1 (%2)").arg(
name, shortcut_key_sequence.toString(QKeySequence::NativeText)));
text, shortcut_key_sequence.toString(QKeySequence::NativeText)));

QBoxLayout* layout = new QHBoxLayout;
layout->addWidget(label);

value = CreateSliderValuePair(layout, default_, max, shortcut_key_sequence, Qt::Horizontal,
shortcut_widget, invert);
value = CreateSliderValuePair(group_name, control_name, overrider, layout, zero, default_, min,
max, shortcut_key_sequence, Qt::Horizontal, shortcut_widget, scale);

return layout;
}

QSpinBox* TASInputWindow::CreateSliderValuePair(
std::string_view group_name, std::string_view control_name, InputOverrider* overrider,
QBoxLayout* layout, u16 zero, int default_, u16 min, u16 max,
QKeySequence shortcut_key_sequence, Qt::Orientation orientation, QWidget* shortcut_widget,
std::optional<ControlState> scale)
{
QSpinBox* value = CreateSliderValuePair(layout, default_, max, shortcut_key_sequence, orientation,
shortcut_widget);

InputOverrider::OverrideFunction func;
if (scale)
{
func = [this, value, zero, scale](ControlState controller_state) {
return GetSpinBox(value, zero, controller_state, *scale);
};
}
else
{
func = [this, value, zero, min, max](ControlState controller_state) {
return GetSpinBox(value, zero, min, max, controller_state);
};
}

overrider->AddFunction(group_name, control_name, std::move(func));

return value;
}

// The shortcut_widget argument needs to specify the container widget that will be hidden/shown.
// This is done to avoid ambigous shortcuts
QSpinBox* TASInputWindow::CreateSliderValuePair(QBoxLayout* layout, int default_, u16 max,
QKeySequence shortcut_key_sequence,
Qt::Orientation orientation,
QWidget* shortcut_widget, bool invert)
QWidget* shortcut_widget)
{
auto* value = new QSpinBox();
value->setRange(0, 99999);
Expand All @@ -151,7 +216,6 @@ QSpinBox* TASInputWindow::CreateSliderValuePair(QBoxLayout* layout, int default_
slider->setRange(0, max);
slider->setValue(default_);
slider->setFocusPolicy(Qt::ClickFocus);
slider->setInvertedAppearance(invert);

connect(slider, &QSlider::valueChanged, value, &QSpinBox::setValue);
connect(value, qOverload<int>(&QSpinBox::valueChanged), slider, &QSlider::setValue);
Expand All @@ -170,10 +234,10 @@ QSpinBox* TASInputWindow::CreateSliderValuePair(QBoxLayout* layout, int default_
return value;
}

template <typename UX>
void TASInputWindow::GetButton(TASCheckBox* checkbox, UX& buttons, UX mask)
std::optional<ControlState> TASInputWindow::GetButton(TASCheckBox* checkbox,
ControlState controller_state)
{
const bool pressed = (buttons & mask) != 0;
const bool pressed = std::llround(controller_state) > 0;
if (m_use_controller->isChecked())
{
if (pressed)
Expand All @@ -188,50 +252,54 @@ void TASInputWindow::GetButton(TASCheckBox* checkbox, UX& buttons, UX mask)
}
}

if (checkbox->GetValue())
buttons |= mask;
else
buttons &= ~mask;
return checkbox->GetValue() ? 1.0 : 0.0;
}
template void TASInputWindow::GetButton<u8>(TASCheckBox* button, u8& pad, u8 mask);
template void TASInputWindow::GetButton<u16>(TASCheckBox* button, u16& pad, u16 mask);

void TASInputWindow::GetSpinBoxU8(QSpinBox* spin, u8& controller_value)
std::optional<ControlState> TASInputWindow::GetSpinBox(QSpinBox* spin, u16 zero, u16 min, u16 max,
ControlState controller_state)
{
const u16 controller_value =
ControllerEmu::EmulatedController::MapFloat<u16>(controller_state, zero, 0, max);

if (m_use_controller->isChecked())
{
if (!m_spinbox_most_recent_values_u8.count(spin) ||
m_spinbox_most_recent_values_u8[spin] != controller_value)
if (!m_spinbox_most_recent_values.count(spin) ||
m_spinbox_most_recent_values[spin] != controller_value)
{
QueueOnObjectBlocking(spin, [spin, controller_value] { spin->setValue(controller_value); });
}

m_spinbox_most_recent_values_u8[spin] = controller_value;
m_spinbox_most_recent_values[spin] = controller_value;
}
else
{
m_spinbox_most_recent_values_u8.clear();
m_spinbox_most_recent_values.clear();
}

controller_value = spin->value();
return ControllerEmu::EmulatedController::MapToFloat<ControlState, u16>(spin->value(), zero, min,
max);
}

void TASInputWindow::GetSpinBoxU16(QSpinBox* spin, u16& controller_value)
std::optional<ControlState> TASInputWindow::GetSpinBox(QSpinBox* spin, u16 zero,
ControlState controller_state,
ControlState scale)
{
const u16 controller_value = static_cast<u16>(std::llround(controller_state * scale + zero));

if (m_use_controller->isChecked())
{
if (!m_spinbox_most_recent_values_u16.count(spin) ||
m_spinbox_most_recent_values_u16[spin] != controller_value)
if (!m_spinbox_most_recent_values.count(spin) ||
m_spinbox_most_recent_values[spin] != controller_value)
{
QueueOnObjectBlocking(spin, [spin, controller_value] { spin->setValue(controller_value); });
}

m_spinbox_most_recent_values_u16[spin] = controller_value;
m_spinbox_most_recent_values[spin] = controller_value;
}
else
{
m_spinbox_most_recent_values_u16.clear();
m_spinbox_most_recent_values.clear();
}

controller_value = spin->value();
return (spin->value() - zero) / scale;
}
60 changes: 46 additions & 14 deletions Source/Core/DolphinQt/TAS/TASInputWindow.h
Expand Up @@ -3,11 +3,18 @@

#pragma once

#include <map>
#include <optional>
#include <string_view>
#include <utility>

#include <QDialog>

#include "Common/CommonTypes.h"

struct GCPadStatus;
#include "InputCommon/ControllerEmu/ControlGroup/ControlGroup.h"
#include "InputCommon/ControllerInterface/CoreDevice.h"

class QBoxLayout;
class QCheckBox;
class QDialog;
Expand All @@ -16,6 +23,20 @@ class QSpinBox;
class QString;
class TASCheckBox;

class InputOverrider final
{
public:
using OverrideFunction = std::function<std::optional<ControlState>(ControlState)>;

void AddFunction(std::string_view group_name, std::string_view control_name,
OverrideFunction function);

ControllerEmu::InputOverrideFunction GetInputOverrideFunction() const;

private:
std::map<std::pair<std::string_view, std::string_view>, OverrideFunction> m_functions;
};

class TASInputWindow : public QDialog
{
Q_OBJECT
Expand All @@ -26,27 +47,38 @@ class TASInputWindow : public QDialog
int GetTurboReleaseFrames() const;

protected:
TASCheckBox* CreateButton(const QString& name);
QGroupBox* CreateStickInputs(QString name, QSpinBox*& x_value, QSpinBox*& y_value, u16 max_x,
u16 max_y, Qt::Key x_shortcut_key, Qt::Key y_shortcut_key);
QBoxLayout* CreateSliderValuePairLayout(QString name, QSpinBox*& value, int default_, u16 max,
Qt::Key shortcut_key, QWidget* shortcut_widget,
bool invert = false);
TASCheckBox* CreateButton(const QString& text, std::string_view group_name,
std::string_view control_name, InputOverrider* overrider);
QGroupBox* CreateStickInputs(const QString& text, std::string_view group_name,
InputOverrider* overrider, QSpinBox*& x_value, QSpinBox*& y_value,
u16 min_x, u16 min_y, u16 max_x, u16 max_y, Qt::Key x_shortcut_key,
Qt::Key y_shortcut_key);
QBoxLayout* CreateSliderValuePairLayout(const QString& text, std::string_view group_name,
std::string_view control_name, InputOverrider* overrider,
QSpinBox*& value, u16 zero, int default_, u16 min,
u16 max, Qt::Key shortcut_key, QWidget* shortcut_widget,
std::optional<ControlState> scale = {});
QSpinBox* CreateSliderValuePair(std::string_view group_name, std::string_view control_name,
InputOverrider* overrider, QBoxLayout* layout, u16 zero,
int default_, u16 min, u16 max,
QKeySequence shortcut_key_sequence, Qt::Orientation orientation,
QWidget* shortcut_widget, std::optional<ControlState> scale = {});
QSpinBox* CreateSliderValuePair(QBoxLayout* layout, int default_, u16 max,
QKeySequence shortcut_key_sequence, Qt::Orientation orientation,
QWidget* shortcut_widget, bool invert = false);
template <typename UX>
void GetButton(TASCheckBox* button, UX& pad, UX mask);
void GetSpinBoxU8(QSpinBox* spin, u8& controller_value);
void GetSpinBoxU16(QSpinBox* spin, u16& controller_value);
QWidget* shortcut_widget);

QGroupBox* m_settings_box;
QCheckBox* m_use_controller;
QSpinBox* m_turbo_press_frames;
QSpinBox* m_turbo_release_frames;

private:
std::optional<ControlState> GetButton(TASCheckBox* checkbox, ControlState controller_state);
std::optional<ControlState> GetSpinBox(QSpinBox* spin, u16 zero, u16 min, u16 max,
ControlState controller_state);
std::optional<ControlState> GetSpinBox(QSpinBox* spin, u16 zero, ControlState controller_state,
ControlState scale);

std::map<TASCheckBox*, bool> m_checkbox_set_by_controller;
std::map<QSpinBox*, u8> m_spinbox_most_recent_values_u8;
std::map<QSpinBox*, u8> m_spinbox_most_recent_values_u16;
std::map<QSpinBox*, u16> m_spinbox_most_recent_values;
};