Large diffs are not rendered by default.

@@ -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;
@@ -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)
@@ -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;
@@ -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);
@@ -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)
@@ -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)
@@ -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;

/**
@@ -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;
@@ -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);
@@ -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)
@@ -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,
@@ -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();
}
@@ -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));
@@ -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;
@@ -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<>();
@@ -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;

@@ -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;
}
}

@@ -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
{
@@ -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)
@@ -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
@@ -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);
}
};
@@ -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)
{
@@ -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
@@ -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)
@@ -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);

@@ -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"
@@ -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;
@@ -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))
{
@@ -5,6 +5,7 @@

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

#include "Common/MathUtil.h"
#include "Core/Config/SYSCONFSettings.h"
@@ -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())
{
@@ -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
{
@@ -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);
@@ -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,
@@ -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);
@@ -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});
}
@@ -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);

@@ -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;
@@ -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);
@@ -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);
@@ -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));
}
@@ -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;
}
@@ -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;
@@ -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;
@@ -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")));
@@ -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:
@@ -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);
@@ -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"
@@ -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;

@@ -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;
@@ -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[] = {
@@ -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;
@@ -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;
@@ -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 |
@@ -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;

@@ -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;
@@ -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"
@@ -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();
@@ -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 =
@@ -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);
@@ -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.
@@ -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,
@@ -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;
}
@@ -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);
@@ -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
@@ -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"
@@ -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();

@@ -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;
@@ -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.
@@ -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;
@@ -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();
@@ -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()
{
@@ -5,7 +5,6 @@

#include <array>
#include <cstring>
#include <functional>
#include <optional>
#include <string>
#include <string_view>
@@ -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
@@ -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);
@@ -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);
@@ -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);
@@ -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());
}
@@ -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;
@@ -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);

@@ -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));
@@ -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;
@@ -4,6 +4,7 @@
#include "DolphinQt/TAS/TASInputWindow.h"

#include <cmath>
#include <utility>

#include <QCheckBox>
#include <QGroupBox>
@@ -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)
{
@@ -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.));
@@ -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);
@@ -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);
@@ -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)
@@ -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;
}
@@ -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;
@@ -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
@@ -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;
};