Skip to main content

GamepadWidget.ixx File

ImGui widget for visualizing real-time gamepad input state. More...

Included Headers

#include <string> #include <vector> #include <format> #include <cmath> #include <algorithm> #include "imgui.h" #include <helios.math.types> #include <helios.input.gamepad.GamepadSettings> #include <helios.input.gamepad.GamepadState> #include <helios.input.InputManager> #include <helios.input.types.Gamepad> #include <helios.ext.imgui.ImGuiWidget>

Namespaces Index

namespacehelios
namespaceext

Platform-specific extensions and backend implementations. More...

namespaceimgui
namespacewidgets

Debug and developer widgets for ImGui overlays. More...

Classes Index

classGamepadWidget

A debug widget to visualize the state of a specific gamepad. More...

Description

ImGui widget for visualizing real-time gamepad input state.

File Listing

The file content with the documentation metadata removed is:

1/**
2 * @file GamepadWidget.ixx
3 * @brief ImGui widget for visualizing real-time gamepad input state.
4 */
5module;
6
7#include <string>
8#include <vector>
9#include <format>
10#include <cmath>
11#include <algorithm> // for std::clamp
12
13// External ImGui dependency
14#include "imgui.h"
15
16export module helios.ext.imgui.widgets.GamepadWidget;
17
18import helios.ext.imgui.ImGuiWidget;
19import helios.input.InputManager;
20import helios.input.gamepad.GamepadState;
21import helios.input.gamepad.GamepadSettings;
22import helios.input.types.Gamepad;
23import helios.math.types;
24
25export namespace helios::ext::imgui::widgets {
26
27 /**
28 * @brief A debug widget to visualize the state of a specific gamepad.
29 *
30 * This widget provides a comprehensive visual overview of a connected gamepad's state.
31 * It includes:
32 * - **Connection Status:** Live indicator if the gamepad is connected.
33 * - **Stick Visualization:** 2D plot showing the position of left/right analog sticks within a circular boundary.
34 * - **Triggers:** Progress bars visualizing the pressure level of left/right triggers.
35 * - **Buttons:** Read-only checkboxes showing the current pressed state of all standard gamepad buttons.
36 * - **Settings Panel:** Integrated configuration for deadzone and axis inversion (toggle via button).
37 *
38 * The widget allows switching between different Gamepad IDs (1-4) at runtime via a dropdown menu.
39 * Settings configuration is automatically available through the InputManager's InputAdapter.
40 *
41 * @note This widget is not thread-safe. Use from the main/render thread only.
42 */
43 class GamepadWidget : public ImGuiWidget {
44
45 private:
46 /**
47 * @brief Pointer to the InputManager used to query gamepad states and access settings.
48 * Non-owning pointer; must be valid for the lifetime of this widget.
49 */
50 helios::input::InputManager* inputManager_ = nullptr;
51
52 /**
53 * @brief The currently selected index in the UI combo box (0-3).
54 * Maps to Gamepad::ONE through Gamepad::FOUR.
55 */
56 int selectedGamepadIndex_ = 0;
57
58 /**
59 * @brief Flag to control visibility of the settings panel.
60 */
61 bool showSettings_ = true;
62
63 /**
64 * @brief Helper to map an integer index (0-3) to a Gamepad enum ID.
65 *
66 * @param index The index selected in the UI.
67 * @return The corresponding Gamepad enum value.
68 */
69 [[nodiscard]] helios::input::types::Gamepad indexToId(int index) noexcept {
70 switch (index) {
76 }
77 }
78
79 /**
80 * @brief Helper method to draw a visual representation of an analog stick.
81 *
82 * Draws a circle representing the boundary and a dot representing the stick's position.
83 *
84 * @param label The label to display below the visualizer.
85 * @param value The normalized (x, y) vector of the stick axis.
86 * @param radius The radius of the visualization circle in pixels.
87 */
88 void drawStickVisualizer(const char* label, const helios::math::vec2f& value, float radius) {
89 ImVec2 p = ImGui::GetCursorScreenPos();
90 ImDrawList* draw_list = ImGui::GetWindowDrawList();
91
92 float center_x = p.x + radius;
93 float center_y = p.y + radius;
94
95 // Draw Background Circle (Dark Grey)
96 draw_list->AddCircleFilled(ImVec2(center_x, center_y), radius, IM_COL32(50, 50, 50, 255));
97 // Draw Boundary (White)
98 draw_list->AddCircle(ImVec2(center_x, center_y), radius, IM_COL32(255, 255, 255, 255));
99
100 // Calculate Dot Position
101 // ImGui coordinates: Y increases downwards.
102 // Standard Gamepad Y: Up is usually positive (or negative depending on normalization,
103 // but we visually want "Up" stick to be "Up" on screen).
104 // Assuming GamepadState provides standard Cartesian (Up=+Y), we subtract Y for screen coords.
105 float dot_x = center_x + (value[0] * radius);
106 float dot_y = center_y - (value[1] * radius);
107
108 // Draw Stick Position Dot (Green)
109 draw_list->AddCircleFilled(ImVec2(dot_x, dot_y), 4.0f, IM_COL32(0, 255, 0, 255));
110
111 // Advance cursor to reserve space for the drawing
112 ImGui::Dummy(ImVec2(radius * 2, radius * 2));
113 ImGui::Text("%s: (%.2f, %.2f)", label, value[0], value[1]);
114 }
115
116 /**
117 * @brief Draws the settings panel for the currently selected gamepad.
118 *
119 * @param gamepadId The gamepad to configure.
120 */
121 void drawSettingsPanel(helios::input::types::Gamepad gamepadId) {
122 auto& settings = inputManager_->inputAdapter().gamepadSettings(gamepadId);
123
124 ImGui::Spacing();
125
126 // --- Deadzone Configuration (Side by Side) ---
127 ImGui::Columns(2, "deadzoneColumns", false);
128
129 // Left Stick Column
130 ImGui::Text("Left Stick");
131 float leftDeadzone = settings.leftStickDeadzone();
132 if (ImGui::SliderFloat("##LeftDZ", &leftDeadzone, 0.0f, 0.9f, "%.2f")) {
133 settings.setLeftStickDeadzone(leftDeadzone);
134 }
135
136 bool invertLX = settings.invertLeftX();
137 bool invertLY = settings.invertLeftY();
138 if (ImGui::Checkbox("Invert X##L", &invertLX)) settings.setInvertLeftX(invertLX);
139 ImGui::SameLine();
140 if (ImGui::Checkbox("Invert Y##L", &invertLY)) settings.setInvertLeftY(invertLY);
141
142 ImGui::NextColumn();
143
144 // Right Stick Column
145 ImGui::Text("Right Stick");
146 float rightDeadzone = settings.rightStickDeadzone();
147 if (ImGui::SliderFloat("##RightDZ", &rightDeadzone, 0.0f, 0.9f, "%.2f")) {
148 settings.setRightStickDeadzone(rightDeadzone);
149 }
150
151 bool invertRX = settings.invertRightX();
152 bool invertRY = settings.invertRightY();
153 if (ImGui::Checkbox("Invert X##R", &invertRX)) settings.setInvertRightX(invertRX);
154 ImGui::SameLine();
155 if (ImGui::Checkbox("Invert Y##R", &invertRY)) settings.setInvertRightY(invertRY);
156
157 ImGui::Columns(1);
158
159 ImGui::Spacing();
160
161 // --- Reset ---
162 if (ImGui::Button("Reset Settings")) {
163 settings.setLeftStickDeadzone(0.0f);
164 settings.setRightStickDeadzone(0.0f);
165 settings.setInvertLeftX(false);
166 settings.setInvertLeftY(false);
167 settings.setInvertRightX(false);
168 settings.setInvertRightY(false);
169 }
170 }
171
172 public:
173
174 /**
175 * @brief Constructs the GamepadWidget.
176 *
177 * @param inputManager Pointer to the InputManager to query states and settings from.
178 * Pass nullptr to render an empty/disabled widget.
179 */
181 : inputManager_(inputManager) {}
182
183 /**
184 * @brief Renders the gamepad debug interface using ImGui.
185 */
186 void draw() override {
187 if (!inputManager_) return;
188
189 // Helper Lambda to draw read-only checkboxes.
190 // Solves the "address of r-value" error (C2102) by creating a temporary l-value.
191 auto DrawReadOnlyCheckbox = [](const char* label, bool isPressed) {
192 ImGui::BeginDisabled(); // Visually gray out to indicate read-only
193 bool tempValue = isPressed; // Create l-value copy
194 ImGui::Checkbox(label, &tempValue); // Pass address of l-value
195 ImGui::EndDisabled();
196 };
197
198 ImGui::SetNextWindowSize(ImVec2(450, 550), ImGuiCond_FirstUseEver);
199
200 if (ImGui::Begin("Gamepad Debugger", nullptr, ImGuiWindowFlags_NoCollapse)) {
201
202 // 1 Gamepad Selection
203 const char* items[] = { "Gamepad 1", "Gamepad 2", "Gamepad 3", "Gamepad 4" };
204 ImGui::Combo("Select ID", &selectedGamepadIndex_, items, IM_ARRAYSIZE(items));
205
206 auto gamepadId = indexToId(selectedGamepadIndex_);
207
208 // 2 Connection Status
209 bool connected = inputManager_->isConnected(gamepadId);
210 ImGui::SameLine();
211 if (connected) {
212 ImGui::TextColored(ImVec4(0, 1, 0, 1), "Connected");
213 } else {
214 ImGui::TextColored(ImVec4(1, 0, 0, 1), "Disconnected");
215 }
216
217 // Settings toggle button (below combobox)
218 if (ImGui::Button(showSettings_ ? "Hide Settings" : "Show Settings")) {
219 showSettings_ = !showSettings_;
220 }
221
222 ImGui::Separator();
223
224 // --- Settings Panel (collapsible) ---
225 if (showSettings_) {
226 if (ImGui::CollapsingHeader("Controller Settings", ImGuiTreeNodeFlags_DefaultOpen)) {
227 drawSettingsPanel(gamepadId);
228 }
229 ImGui::Separator();
230 }
231
232 if (connected) {
233 // Retrieve const reference to state.
234 const auto& state = inputManager_->gamepadState(gamepadId);
235
236 // Layout: 2 Columns for Sticks
237 ImGui::Columns(2, "sticks", false);
238
239 // --- Left Stick ---
240 drawStickVisualizer("L-Stick", state.left(), 40.0f);
241 DrawReadOnlyCheckbox("L-Thumb", state.buttonLeftThumb());
242
243 ImGui::NextColumn();
244
245 // --- Right Stick ---
246 drawStickVisualizer("R-Stick", state.right(), 40.0f);
247 DrawReadOnlyCheckbox("R-Thumb", state.buttonRightThumb());
248
249 ImGui::Columns(1);
250 ImGui::Separator();
251
252 // --- Triggers (Progress Bars) ---
253 ImGui::Text("Triggers");
254
255 ImGui::Text("L: %.2f", state.triggerLeft());
256 ImGui::SameLine();
257 // ProgressBar expects [0.0, 1.0]
258 ImGui::ProgressBar(state.triggerLeft(), ImVec2(100, 0), "");
259
260 ImGui::SameLine(220); // Spacing
261
262 ImGui::Text("R: %.2f", state.triggerRight());
263 ImGui::SameLine();
264 ImGui::ProgressBar(state.triggerRight(), ImVec2(100, 0), "");
265
266 ImGui::Separator();
267
268 // --- Buttons ---
269 // Layout: 2 Columns (D-Pad vs Face Buttons)
270 ImGui::Columns(2, "buttons", false);
271
272 ImGui::Text("D-Pad");
273 DrawReadOnlyCheckbox("Up", state.buttonDpadUp());
274 DrawReadOnlyCheckbox("Down", state.buttonDpadDown());
275 DrawReadOnlyCheckbox("Left", state.buttonDpadLeft());
276 DrawReadOnlyCheckbox("Right", state.buttonDpadRight());
277
278 ImGui::NextColumn();
279
280 ImGui::Text("Face Buttons");
281 DrawReadOnlyCheckbox("A", state.buttonA());
282 DrawReadOnlyCheckbox("B", state.buttonB());
283 DrawReadOnlyCheckbox("X", state.buttonX());
284 DrawReadOnlyCheckbox("Y", state.buttonY());
285
286 ImGui::Columns(1);
287 ImGui::Separator();
288
289 // --- Middle / Bumpers ---
290 ImGui::Text("Center & Bumpers");
291
292 // Use manual layout for horizontal arrangement
293 DrawReadOnlyCheckbox("LB", state.buttonLeftBumper());
294 ImGui::SameLine();
295 DrawReadOnlyCheckbox("Back", state.buttonBack());
296 ImGui::SameLine();
297 DrawReadOnlyCheckbox("Guide", state.buttonGuide());
298 ImGui::SameLine();
299 DrawReadOnlyCheckbox("Start", state.buttonStart());
300 ImGui::SameLine();
301 DrawReadOnlyCheckbox("RB", state.buttonRightBumper());
302 }
303 else {
304 ImGui::TextDisabled("No input data available. Please connect a controller.");
305 }
306 }
307 ImGui::End();
308 }
309 };
310}

Generated via doxygen2docusaurus 2.0.0 by Doxygen 1.15.0.