Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Stick input: Fix issue where deadzone noise from one device could drown out signal from another. #16419

Merged
merged 3 commits into from
Nov 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Common/Input/InputState.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ enum {
DEVICE_ID_ACCELEROMETER = 30,
DEVICE_ID_XR_CONTROLLER_LEFT = 40,
DEVICE_ID_XR_CONTROLLER_RIGHT = 41,
DEVICE_ID_TOUCH = 42,
};

//number of contiguous generic joypad IDs
Expand Down
115 changes: 70 additions & 45 deletions Core/ControlMapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ static float MapAxisValue(float v) {
const float invDeadzone = g_Config.fAnalogInverseDeadzone;
const float sensitivity = g_Config.fAnalogSensitivity;
const float sign = v >= 0.0f ? 1.0f : -1.0f;

return sign * Clamp(invDeadzone + (abs(v) - deadzone) / (1.0f - deadzone) * (sensitivity - invDeadzone), 0.0f, 1.0f);
}

Expand Down Expand Up @@ -49,24 +50,48 @@ void ControlMapper::SetRawCallback(std::function<void(int, float, float)> setRaw
setRawAnalog_ = setRawAnalog;
}


void ControlMapper::SetPSPAxis(char axis, float value, int stick) {
static float history[2][2] = {};

void ControlMapper::SetPSPAxis(int device, char axis, float value, int stick) {
int axisId = axis == 'X' ? 0 : 1;

history[stick][axisId] = value;
float position[2];
position[0] = history[stick][0];
position[1] = history[stick][1];

float x = history[stick][0];
float y = history[stick][1];
position[axisId] = value;

float x = position[0];
float y = position[1];

if (setRawAnalog_) {
setRawAnalog_(stick, x, y);
}

ConvertAnalogStick(x, y);
// NOTE: We need to use single-axis checks, since the other axis might be from another device,
// so we'll add a little leeway.
bool inDeadZone = fabsf(value) < g_Config.fAnalogDeadzone * 0.7f;

bool ignore = false;

if (inDeadZone && lastNonDeadzoneDeviceID_[stick] != device) {
// Ignore this event! See issue #15465
NOTICE_LOG(COMMON, "Ignoring deadzone event from device %d (%d, %f)", device, axis, value);
ignore = true;
}

if (!inDeadZone) {
NOTICE_LOG(COMMON, "Got a non deadzone event from device %d (%d, %f > %f)", device, axis, value, g_Config.fAnalogDeadzone);
hrydgard marked this conversation as resolved.
Show resolved Hide resolved
lastNonDeadzoneDeviceID_[stick] = device;
}

if (!ignore) {
history[stick][axisId] = value;

float x = history[stick][0];
float y = history[stick][1];

setPSPAnalog_(stick, x, y);
ConvertAnalogStick(x, y);
setPSPAnalog_(stick, x, y);
}
}

bool ControlMapper::Key(const KeyInput &key, bool *pauseTrigger) {
Expand All @@ -79,7 +104,7 @@ bool ControlMapper::Key(const KeyInput &key, bool *pauseTrigger) {
}

for (size_t i = 0; i < pspKeys.size(); i++) {
pspKey(pspKeys[i], key.flags);
pspKey(key.deviceId, pspKeys[i], key.flags);
}

DEBUG_LOG(SYSTEM, "Key: %d DeviceId: %d", key.keyCode, key.deviceId);
Expand Down Expand Up @@ -155,7 +180,7 @@ static int RotatePSPKeyCode(int x) {
}
}

void ControlMapper::setVKeyAnalog(char axis, int stick, int virtualKeyMin, int virtualKeyMax, bool setZero) {
void ControlMapper::setVKeyAnalog(int deviceId, char axis, int stick, int virtualKeyMin, int virtualKeyMax, bool setZero) {
// The down events can repeat, so just trust the virtKeys array.
bool minDown = virtKeys[virtualKeyMin - VIRTKEY_FIRST];
bool maxDown = virtKeys[virtualKeyMax - VIRTKEY_FIRST];
Expand All @@ -167,11 +192,11 @@ void ControlMapper::setVKeyAnalog(char axis, int stick, int virtualKeyMin, int v
if (maxDown)
value += scale;
if (setZero || minDown || maxDown) {
SetPSPAxis(axis, value, stick);
SetPSPAxis(deviceId, axis, value, stick);
}
}

void ControlMapper::pspKey(int pspKeyCode, int flags) {
void ControlMapper::pspKey(int deviceId, int pspKeyCode, int flags) {
int rotations = 0;
switch (g_Config.iInternalScreenRotation) {
case ROTATION_LOCKED_HORIZONTAL180:
Expand All @@ -193,11 +218,11 @@ void ControlMapper::pspKey(int pspKeyCode, int flags) {
int vk = pspKeyCode - VIRTKEY_FIRST;
if (flags & KEY_DOWN) {
virtKeys[vk] = true;
onVKeyDown(pspKeyCode);
onVKeyDown(deviceId, pspKeyCode);
}
if (flags & KEY_UP) {
virtKeys[vk] = false;
onVKeyUp(pspKeyCode);
onVKeyUp(deviceId, pspKeyCode);
}
} else {
// INFO_LOG(SYSTEM, "pspKey %i %i", pspKeyCode, flags);
Expand All @@ -208,32 +233,32 @@ void ControlMapper::pspKey(int pspKeyCode, int flags) {
}
}

void ControlMapper::onVKeyDown(int vkey) {
void ControlMapper::onVKeyDown(int deviceId, int vkey) {
switch (vkey) {

case VIRTKEY_AXIS_X_MIN:
case VIRTKEY_AXIS_X_MAX:
setVKeyAnalog('X', CTRL_STICK_LEFT, VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX);
setVKeyAnalog(deviceId, 'X', CTRL_STICK_LEFT, VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX);
break;
case VIRTKEY_AXIS_Y_MIN:
case VIRTKEY_AXIS_Y_MAX:
setVKeyAnalog('Y', CTRL_STICK_LEFT, VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_Y_MAX);
setVKeyAnalog(deviceId, 'Y', CTRL_STICK_LEFT, VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_Y_MAX);
break;

case VIRTKEY_AXIS_RIGHT_X_MIN:
case VIRTKEY_AXIS_RIGHT_X_MAX:
setVKeyAnalog('X', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX);
setVKeyAnalog(deviceId, 'X', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX);
break;
case VIRTKEY_AXIS_RIGHT_Y_MIN:
case VIRTKEY_AXIS_RIGHT_Y_MAX:
setVKeyAnalog('Y', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_Y_MAX);
setVKeyAnalog(deviceId, 'Y', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_Y_MAX);
break;

case VIRTKEY_ANALOG_LIGHTLY:
setVKeyAnalog('X', CTRL_STICK_LEFT, VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX, false);
setVKeyAnalog('Y', CTRL_STICK_LEFT, VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_Y_MAX, false);
setVKeyAnalog('X', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX, false);
setVKeyAnalog('Y', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_Y_MAX, false);
setVKeyAnalog(deviceId, 'X', CTRL_STICK_LEFT, VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX, false);
setVKeyAnalog(deviceId, 'Y', CTRL_STICK_LEFT, VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_Y_MAX, false);
setVKeyAnalog(deviceId, 'X', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX, false);
setVKeyAnalog(deviceId, 'Y', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_Y_MAX, false);
break;

case VIRTKEY_ANALOG_ROTATE_CW:
Expand All @@ -252,32 +277,32 @@ void ControlMapper::onVKeyDown(int vkey) {
}
}

void ControlMapper::onVKeyUp(int vkey) {
void ControlMapper::onVKeyUp(int deviceId, int vkey) {
switch (vkey) {

case VIRTKEY_AXIS_X_MIN:
case VIRTKEY_AXIS_X_MAX:
setVKeyAnalog('X', CTRL_STICK_LEFT, VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX);
setVKeyAnalog(deviceId, 'X', CTRL_STICK_LEFT, VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX);
break;
case VIRTKEY_AXIS_Y_MIN:
case VIRTKEY_AXIS_Y_MAX:
setVKeyAnalog('Y', CTRL_STICK_LEFT, VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_Y_MAX);
setVKeyAnalog(deviceId, 'Y', CTRL_STICK_LEFT, VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_Y_MAX);
break;

case VIRTKEY_AXIS_RIGHT_X_MIN:
case VIRTKEY_AXIS_RIGHT_X_MAX:
setVKeyAnalog('X', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX);
setVKeyAnalog(deviceId, 'X', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX);
break;
case VIRTKEY_AXIS_RIGHT_Y_MIN:
case VIRTKEY_AXIS_RIGHT_Y_MAX:
setVKeyAnalog('Y', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_Y_MAX);
setVKeyAnalog(deviceId, 'Y', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_Y_MAX);
break;

case VIRTKEY_ANALOG_LIGHTLY:
setVKeyAnalog('X', CTRL_STICK_LEFT, VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX, false);
setVKeyAnalog('Y', CTRL_STICK_LEFT, VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_Y_MAX, false);
setVKeyAnalog('X', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX, false);
setVKeyAnalog('Y', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_Y_MAX, false);
setVKeyAnalog(deviceId, 'X', CTRL_STICK_LEFT, VIRTKEY_AXIS_X_MIN, VIRTKEY_AXIS_X_MAX, false);
setVKeyAnalog(deviceId, 'Y', CTRL_STICK_LEFT, VIRTKEY_AXIS_Y_MIN, VIRTKEY_AXIS_Y_MAX, false);
setVKeyAnalog(deviceId, 'X', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_X_MIN, VIRTKEY_AXIS_RIGHT_X_MAX, false);
setVKeyAnalog(deviceId, 'Y', CTRL_STICK_RIGHT, VIRTKEY_AXIS_RIGHT_Y_MIN, VIRTKEY_AXIS_RIGHT_Y_MAX, false);
break;

case VIRTKEY_ANALOG_ROTATE_CW:
Expand Down Expand Up @@ -312,29 +337,29 @@ void ControlMapper::processAxis(const AxisInput &axis, int direction) {
float value = fabs(axis.value) * scale;
switch (result) {
case VIRTKEY_AXIS_X_MIN:
SetPSPAxis('X', -value, CTRL_STICK_LEFT);
SetPSPAxis(axis.deviceId, 'X', -value, CTRL_STICK_LEFT);
break;
case VIRTKEY_AXIS_X_MAX:
SetPSPAxis('X', value, CTRL_STICK_LEFT);
SetPSPAxis(axis.deviceId, 'X', value, CTRL_STICK_LEFT);
break;
case VIRTKEY_AXIS_Y_MIN:
SetPSPAxis('Y', -value, CTRL_STICK_LEFT);
SetPSPAxis(axis.deviceId, 'Y', -value, CTRL_STICK_LEFT);
break;
case VIRTKEY_AXIS_Y_MAX:
SetPSPAxis('Y', value, CTRL_STICK_LEFT);
SetPSPAxis(axis.deviceId, 'Y', value, CTRL_STICK_LEFT);
break;

case VIRTKEY_AXIS_RIGHT_X_MIN:
SetPSPAxis('X', -value, CTRL_STICK_RIGHT);
SetPSPAxis(axis.deviceId, 'X', -value, CTRL_STICK_RIGHT);
break;
case VIRTKEY_AXIS_RIGHT_X_MAX:
SetPSPAxis('X', value, CTRL_STICK_RIGHT);
SetPSPAxis(axis.deviceId, 'X', value, CTRL_STICK_RIGHT);
break;
case VIRTKEY_AXIS_RIGHT_Y_MIN:
SetPSPAxis('Y', -value, CTRL_STICK_RIGHT);
SetPSPAxis(axis.deviceId, 'Y', -value, CTRL_STICK_RIGHT);
break;
case VIRTKEY_AXIS_RIGHT_Y_MAX:
SetPSPAxis('Y', value, CTRL_STICK_RIGHT);
SetPSPAxis(axis.deviceId, 'Y', value, CTRL_STICK_RIGHT);
break;

case VIRTKEY_SPEED_ANALOG:
Expand Down Expand Up @@ -366,22 +391,22 @@ void ControlMapper::processAxis(const AxisInput &axis, int direction) {
if (axisState != 0) {
for (size_t i = 0; i < results.size(); i++) {
if (!IsAnalogStickKey(results[i]))
pspKey(results[i], KEY_DOWN);
pspKey(axis.deviceId, results[i], KEY_DOWN);
}
// Also unpress the other direction (unless both directions press the same key.)
for (size_t i = 0; i < resultsOpposite.size(); i++) {
if (!IsAnalogStickKey(resultsOpposite[i]) && std::find(results.begin(), results.end(), resultsOpposite[i]) == results.end())
pspKey(resultsOpposite[i], KEY_UP);
pspKey(axis.deviceId, resultsOpposite[i], KEY_UP);
}
} else if (axisState == 0) {
// Release both directions, trying to deal with some erratic controllers that can cause it to stick.
for (size_t i = 0; i < results.size(); i++) {
if (!IsAnalogStickKey(results[i]))
pspKey(results[i], KEY_UP);
pspKey(axis.deviceId, results[i], KEY_UP);
}
for (size_t i = 0; i < resultsOpposite.size(); i++) {
if (!IsAnalogStickKey(resultsOpposite[i]))
pspKey(resultsOpposite[i], KEY_UP);
pspKey(axis.deviceId, resultsOpposite[i], KEY_UP);
}
}
}
Expand Down
14 changes: 9 additions & 5 deletions Core/ControlMapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class ControlMapper {
void Update();

bool Key(const KeyInput &key, bool *pauseTrigger);
void pspKey(int pspKeyCode, int flags);
void pspKey(int deviceId, int pspKeyCode, int flags);
bool Axis(const AxisInput &axis);

// Required callbacks
Expand All @@ -30,20 +30,24 @@ class ControlMapper {

private:
void processAxis(const AxisInput &axis, int direction);
void setVKeyAnalog(char axis, int stick, int virtualKeyMin, int virtualKeyMax, bool setZero = true);
void setVKeyAnalog(int deviceId, char axis, int stick, int virtualKeyMin, int virtualKeyMax, bool setZero = true);

void SetPSPAxis(char axis, float value, int stick);
void SetPSPAxis(int deviceId, char axis, float value, int stick);
void ProcessAnalogSpeed(const AxisInput &axis, bool opposite);

void onVKeyDown(int vkey);
void onVKeyUp(int vkey);
void onVKeyDown(int deviceId, int vkey);
void onVKeyUp(int deviceId, int vkey);

// To track mappable virtual keys. We can have as many as we want.
bool virtKeys[VIRTKEY_COUNT]{};

// De-noise mapped axis updates
int axisState_[JOYSTICK_AXIS_MAX]{};

int lastNonDeadzoneDeviceID_[2]{};

float history[2][2] = {};

// Mappable auto-rotation. Useful for keyboard/dpad->analog in a few games.
bool autoRotatingAnalogCW_ = false;
bool autoRotatingAnalogCCW_ = false;
Expand Down
6 changes: 6 additions & 0 deletions Core/KeyMapDefaults.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ static const DefMappingStruct defaultXInputKeyMap[] = {
{VIRTKEY_SPEED_TOGGLE, NKCODE_BUTTON_THUMBR},
{VIRTKEY_PAUSE , JOYSTICK_AXIS_LTRIGGER, +1},
{VIRTKEY_PAUSE, NKCODE_HOME},
{VIRTKEY_FASTFORWARD , JOYSTICK_AXIS_GAS, +1},
{VIRTKEY_PAUSE , JOYSTICK_AXIS_BRAKE, +1},
};

static const DefMappingStruct defaultShieldKeyMap[] = {
Expand Down Expand Up @@ -188,6 +190,8 @@ static const DefMappingStruct defaultAndroidXboxControllerMap[] = {
{CTRL_RTRIGGER , NKCODE_BUTTON_R1},
{VIRTKEY_FASTFORWARD , JOYSTICK_AXIS_RTRIGGER, +1},
{VIRTKEY_PAUSE , JOYSTICK_AXIS_LTRIGGER, +1},
{VIRTKEY_FASTFORWARD , JOYSTICK_AXIS_GAS, +1},
{VIRTKEY_PAUSE , JOYSTICK_AXIS_BRAKE, +1},
{VIRTKEY_AXIS_X_MIN, JOYSTICK_AXIS_X, -1},
{VIRTKEY_AXIS_X_MAX, JOYSTICK_AXIS_X, +1},
{VIRTKEY_AXIS_Y_MIN, JOYSTICK_AXIS_Y, +1},
Expand Down Expand Up @@ -242,6 +246,8 @@ static const DefMappingStruct defaultPadMapAndroid[] = {
{VIRTKEY_FASTFORWARD , NKCODE_BUTTON_R2},
{VIRTKEY_PAUSE , JOYSTICK_AXIS_LTRIGGER, +1},
{VIRTKEY_PAUSE , NKCODE_BUTTON_L2 },
{VIRTKEY_FASTFORWARD , JOYSTICK_AXIS_GAS, +1},
{VIRTKEY_PAUSE , JOYSTICK_AXIS_BRAKE, +1},
{VIRTKEY_AXIS_X_MIN, JOYSTICK_AXIS_X, -1},
{VIRTKEY_AXIS_X_MAX, JOYSTICK_AXIS_X, +1},
{VIRTKEY_AXIS_Y_MIN, JOYSTICK_AXIS_Y, +1},
Expand Down
5 changes: 3 additions & 2 deletions UI/ControlMappingScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,14 @@ void SingleControlMapper::Refresh() {
std::string keyName = KeyMap::GetKeyOrAxisName(mappings[i].keyCode);

LinearLayout *row = rightColumn->Add(new LinearLayout(ORIENT_HORIZONTAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)));
row->SetSpacing(1.0f);
row->SetSpacing(2.0f);
rows_.push_back(row);

Choice *c = row->Add(new Choice(deviceName + "." + keyName, new LinearLayoutParams(FILL_PARENT, itemH, 1.0f)));
c->SetTag(StringFromFormat("%d_Change%d", (int)i, pspKey_));
c->OnClick.Handle(this, &SingleControlMapper::OnReplace);

Choice *d = row->Add(new Choice(" X ", new LayoutParams(WRAP_CONTENT, itemH)));
Choice *d = row->Add(new Choice(ImageID("I_TRASHCAN"), new LayoutParams(WRAP_CONTENT, itemH)));
d->SetTag(StringFromFormat("%d_Del%d", (int)i, pspKey_));
d->OnClick.Handle(this, &SingleControlMapper::OnDelete);
}
Expand Down Expand Up @@ -583,6 +583,7 @@ void JoystickHistoryView::Update() {

AnalogSetupScreen::AnalogSetupScreen() {
mapper_.SetCallbacks([](int vkey) {}, [](int vkey) {}, [&](int stick, float x, float y) {
NOTICE_LOG(COMMON, "analog value from mapper: %f %f", x, y);
hrydgard marked this conversation as resolved.
Show resolved Hide resolved
analogX_[stick] = x;
analogY_[stick] = y;
});
Expand Down
3 changes: 0 additions & 3 deletions UI/EmuScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,6 @@ static void __EmuScreenVblank()
}

// Handles control rotation due to internal screen rotation.
// TODO: This should be a callback too, so we don't actually call the __Ctrl functions
// from settings screens, etc.
static void SetPSPAnalog(int stick, float x, float y) {
switch (g_Config.iInternalScreenRotation) {
case ROTATION_LOCKED_HORIZONTAL:
Expand All @@ -164,7 +162,6 @@ static void SetPSPAnalog(int stick, float x, float y) {
default:
break;
}

__CtrlSetAnalogXY(stick, x, y);
}

Expand Down
4 changes: 2 additions & 2 deletions UI/GameSettingsScreen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1863,11 +1863,11 @@ void DeveloperToolsScreen::CreateViews() {

list->Add(new CheckBox(&g_Config.bShowOnScreenMessages, dev->T("Show on-screen messages")));
list->Add(new CheckBox(&g_Config.bEnableLogging, dev->T("Enable Logging")))->OnClick.Handle(this, &DeveloperToolsScreen::OnLoggingChanged);
list->Add(new Choice(dev->T("Logging Channels")))->OnClick.Handle(this, &DeveloperToolsScreen::OnLogConfig);
list->Add(new CheckBox(&g_Config.bLogFrameDrops, dev->T("Log Dropped Frame Statistics")));
if (GetGPUBackend() == GPUBackend::VULKAN) {
list->Add(new CheckBox(&g_Config.bGpuLogProfiler, gr->T("GPU log profiler")));
}
list->Add(new CheckBox(&g_Config.bLogFrameDrops, dev->T("Log Dropped Frame Statistics")));
list->Add(new Choice(dev->T("Logging Channels")))->OnClick.Handle(this, &DeveloperToolsScreen::OnLogConfig);
list->Add(new ItemHeader(dev->T("Language")));
list->Add(new Choice(dev->T("Load language ini")))->OnClick.Handle(this, &DeveloperToolsScreen::OnLoadLanguageIni);
list->Add(new Choice(dev->T("Save language ini")))->OnClick.Handle(this, &DeveloperToolsScreen::OnSaveLanguageIni);
Expand Down
Loading