Last active
October 10, 2023 01:24
-
-
Save JibbSmart/8cbaba568c1c2e1193771459aa5385df to your computer and use it in GitHub Desktop.
Quick ImGui application I made to figure out all the quality of life stuff folks might want for JoyShockLibrary 3.0
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Looks like this: https://x.com/JibbSmart/status/1642565648403550208?s=20 | |
// Settings | |
int _gyroSpace = 2; | |
float _sensitivity = 1.f; | |
float _tightening = 2.f; | |
int _focusedDevice = 0; | |
bool _gyroActive = false; | |
int _defaultCalibrationMode = 1; | |
// Callback for applying our settings to new connections | |
void ConnectCallback(int deviceId) | |
{ | |
JslSetGyroSpace(deviceId, _gyroSpace); | |
if (_defaultCalibrationMode > 0) | |
{ | |
JslSetAutomaticCalibration(deviceId, true); | |
} | |
} | |
void MoveMouse(float x, float y) | |
{ | |
static float accumulatedX = 0.f; | |
static float accumulatedY = 0.f; | |
accumulatedX += x; | |
accumulatedY += y; | |
int applicableX = (int)accumulatedX; | |
int applicableY = (int)accumulatedY; | |
accumulatedX -= applicableX; | |
accumulatedY -= applicableY; | |
INPUT input; | |
input.type = INPUT_MOUSE; | |
input.mi.mouseData = 0; | |
input.mi.time = 0; | |
input.mi.dx = applicableX; | |
input.mi.dy = applicableY; | |
input.mi.dwFlags = MOUSEEVENTF_MOVE; | |
SendInput(1, &input, sizeof(input)); | |
} | |
void DoGyro(float x, float y, float z, float deltaSeconds) | |
{ | |
const float inputSize = sqrtf(x * x + y * y + z * z); | |
float tightenedSensitivity = _sensitivity * 50.f; | |
if (inputSize < _tightening && _tightening > 0) | |
{ | |
tightenedSensitivity *= inputSize / _tightening; | |
} | |
MoveMouse(-y * tightenedSensitivity * deltaSeconds, -x * tightenedSensitivity * deltaSeconds); | |
} | |
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) | |
{ | |
if (ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam)) | |
return true; | |
switch (msg) | |
{ | |
case WM_DEVICECHANGE: | |
JslConnectDevices(); | |
break; | |
case WM_KEYDOWN: | |
if (wParam == VK_ESCAPE) | |
{ | |
_gyroActive = false; | |
} | |
break; | |
... | |
} | |
// Main code | |
int main(int, char**) | |
{ | |
// Adapted from the Dear ImGui examples | |
// Initialize Direct3D | |
... | |
// Setup Dear ImGui context | |
... | |
JslSetConnectCallback(ConnectCallback); | |
JslConnectDevices(); | |
// Main loop | |
bool done = false; | |
while (!done) | |
{ | |
// Poll and handle messages (inputs, window resize, etc.) | |
// See the WndProc() function below for our to dispatch events to the Win32 backend. | |
MSG msg; | |
while (::PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)) | |
{ | |
::TranslateMessage(&msg); | |
::DispatchMessage(&msg); | |
if (msg.message == WM_QUIT) | |
done = true; | |
} | |
if (done) | |
break; | |
// Start the Dear ImGui frame | |
ImGui_ImplDX11_NewFrame(); | |
ImGui_ImplWin32_NewFrame(); | |
ImGui::NewFrame(); | |
// Where most of the JSL stuff happens... | |
{ | |
static int numConnectedDevices = 0; | |
ImGui::Begin("JoyShockLibrary Sample", NULL, ImGuiWindowFlags_AlwaysAutoResize); | |
ImGui::Text("For demonstrating many of the features of JoyShockLibrary"); | |
int deviceHandles[16]; | |
numConnectedDevices = JslGetConnectedDeviceHandles(deviceHandles, 16); | |
ImGui::PushID("##SideBySide"); | |
ImGui::BeginGroup(); | |
{ | |
ImGui::Text("Connected Devices (%d)", numConnectedDevices); | |
if (_focusedDevice >= numConnectedDevices) | |
{ | |
_focusedDevice = numConnectedDevices - 1; | |
} | |
if (_focusedDevice < 0) | |
{ | |
_focusedDevice = 0; | |
} | |
for (int deviceIndex = 0; deviceIndex < numConnectedDevices; ++deviceIndex) | |
{ | |
ImGui::PushID(deviceIndex); | |
const int deviceHandle = deviceHandles[deviceIndex]; | |
switch (JslGetControllerType(deviceHandle)) | |
{ | |
case JS_TYPE_JOYCON_LEFT: | |
ImGui::RadioButton("Joy-Con (Left)", &_focusedDevice, deviceIndex); | |
break; | |
case JS_TYPE_JOYCON_RIGHT: | |
ImGui::RadioButton("Joy-Con (Right)", &_focusedDevice, deviceIndex); | |
break; | |
case JS_TYPE_PRO_CONTROLLER: | |
ImGui::RadioButton("Pro Controller", &_focusedDevice, deviceIndex); | |
break; | |
case JS_TYPE_DS4: | |
ImGui::RadioButton("DualShock 4", &_focusedDevice, deviceIndex); | |
break; | |
case JS_TYPE_DS: | |
ImGui::RadioButton("DualSense", &_focusedDevice, deviceIndex); | |
break; | |
default: | |
ImGui::RadioButton("Unknown Controller", &_focusedDevice, deviceIndex); | |
break; | |
} | |
ImGui::PopID(); | |
} | |
if (numConnectedDevices > 0) | |
{ | |
const int deviceHandle = deviceHandles[_focusedDevice]; | |
float velocityX, velocityY, velocityZ; | |
JslGetAndFlushAccumulatedGyro(deviceHandle, velocityX, velocityY, velocityZ); | |
JSL_SETTINGS infoAndSettings = JslGetControllerInfoAndSettings(deviceHandle); | |
ImVec4 deviceColour; | |
deviceColour.w = 1.f; | |
deviceColour.x = ((infoAndSettings.colour >> 16) & 0xFF) / 255.f; | |
deviceColour.y = ((infoAndSettings.colour >> 8) & 0xFF) / 255.f; | |
deviceColour.z = (infoAndSettings.colour & 0xFF) / 255.f; | |
// if this controller has a light, use the colour picker here | |
if (infoAndSettings.controllerType <= JS_TYPE_PRO_CONTROLLER) | |
{ | |
ImGui::PushStyleColor(ImGuiCol_Button, deviceColour); | |
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, deviceColour); | |
ImGui::PushStyleColor(ImGuiCol_ButtonActive, deviceColour); | |
ImGui::Button("##Colour", ImVec2(300, 20)); | |
ImGui::PopStyleColor(3); | |
} | |
else | |
{ | |
// playstation controller has LED we can set | |
float ledColour[3] = { deviceColour.x, deviceColour.y, deviceColour.z }; | |
if (ImGui::ColorEdit3("LED", ledColour)) | |
{ | |
ImColor selectedColour = ImColor(ledColour[0], ledColour[1], ledColour[2]); | |
ImU32 colourAsInt = (ImU32)selectedColour; | |
// but, imgui's colour is ABGR, and JSL is just 0RGB, so... | |
int jslColour = (0xFF0000 & (colourAsInt << 16)) | (0xFF00 & colourAsInt) | (0xFF & (colourAsInt >> 16)); | |
JslSetLightColour(deviceHandle, (ImU32)jslColour); | |
} | |
} | |
float offsetX, offsetY, offsetZ; | |
JslGetCalibrationOffset(deviceHandle, offsetX, offsetY, offsetZ); | |
ImGui::Text("Calibration Offset =\n\t(%04.3f, %04.3f, %04.3f)", offsetX, offsetY, offsetZ); | |
if (infoAndSettings.autoCalibrationEnabled) | |
{ | |
ImGui::Text("Calibration Mode: Automatic"); | |
} | |
else | |
{ | |
ImGui::Text("Calibration Mode: Manual"); | |
} | |
JSL_AUTO_CALIBRATION autoCalibrationStatus = JslGetAutoCalibrationStatus(deviceHandle); | |
if (autoCalibrationStatus.autoCalibrationEnabled) | |
{ | |
if (autoCalibrationStatus.isSteady) | |
{ | |
ImGui::Text("Steady, Calibration Confidence: %1.1f", autoCalibrationStatus.confidence); | |
} | |
else | |
{ | |
ImGui::Text("Moving! Calibration Confidence: %1.1f", autoCalibrationStatus.confidence); | |
} | |
} | |
if (ImGui::Button("Reset")) | |
{ | |
JslResetContinuousCalibration(deviceHandle); | |
} | |
ImGui::SameLine(); | |
if (infoAndSettings.isCalibrating) | |
{ | |
if (ImGui::Button("Finish Manual")) | |
{ | |
JslPauseContinuousCalibration(deviceHandle); | |
} | |
} | |
else if (ImGui::Button("Start Manual")) | |
{ | |
JslSetAutomaticCalibration(deviceHandle, false); | |
JslStartContinuousCalibration(deviceHandle); | |
} | |
if (!infoAndSettings.isCalibrating && !infoAndSettings.autoCalibrationEnabled) | |
{ | |
ImGui::SameLine(); | |
if (ImGui::Button("Start Automatic")) | |
{ | |
JslPauseContinuousCalibration(deviceHandle); | |
JslSetAutomaticCalibration(deviceHandle, true); | |
} | |
} | |
if (_gyroActive) | |
{ | |
if (ImGui::Button("[ESC] Disable Gyro Mouse", ImVec2(300, 80))) | |
{ | |
_gyroActive = false; | |
} | |
else | |
{ | |
float frameTime = 1.f / io.Framerate; | |
DoGyro(velocityX, velocityY, velocityZ, frameTime); | |
} | |
} | |
else if (ImGui::Button("Enable Gyro Mouse", ImVec2(300, 80))) | |
{ | |
_gyroActive = true; | |
} | |
MOTION_STATE motionState = JslGetMotionState(deviceHandle); | |
ImGui::Text("Gravity =\n\t(%04.3f, %04.3f, %04.3f)", motionState.gravX, motionState.gravY, motionState.gravZ); | |
ImGui::Text("Acceleration =\n\t(%04.3f, %04.3f, %04.3f)", motionState.accelX, motionState.accelY, motionState.accelZ); | |
ImGui::Text("Quaternion =\n\t(%04.3f, %04.3f, %04.3f, %04.3f)", motionState.quatW, motionState.quatX, motionState.quatY, motionState.quatZ); | |
} | |
else | |
{ | |
ImGui::Button("No Gyro Devices Connected", ImVec2(300, 80)); | |
} | |
} | |
ImGui::EndGroup(); ImGui::SameLine(); | |
ImGui::BeginGroup(); | |
{ | |
ImGui::PushItemWidth(300); | |
ImGui::SliderFloat("##Sensitivity", &_sensitivity, 0.0f, 10.0f, "Sensitivity = %.1f"); | |
ImGui::SliderFloat("##Tightening", &_tightening, 0.0f, 20.0f, "Tightening = %.1f"); | |
ImGui::Text("Gyro Space"); | |
bool updateGyroSpace = ImGui::RadioButton("Local", &_gyroSpace, 0); ImGui::SameLine(); | |
updateGyroSpace |= ImGui::RadioButton("World", &_gyroSpace, 1); ImGui::SameLine(); | |
updateGyroSpace |= ImGui::RadioButton("Player", &_gyroSpace, 2); | |
if (updateGyroSpace) | |
{ | |
for (int deviceIndex = 0; deviceIndex < numConnectedDevices; ++deviceIndex) | |
{ | |
const int deviceHandle = deviceHandles[deviceIndex]; | |
JslSetGyroSpace(deviceHandle, _gyroSpace); | |
} | |
} | |
ImGui::PopItemWidth(); | |
} | |
ImGui::EndGroup(); | |
ImGui::PopID(); | |
ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); | |
ImGui::End(); | |
} | |
// Render | |
... | |
} | |
// Cleanup | |
... | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment