// // GamepadPanel.cpp:手柄输入可视化回显(极简手柄视图)。 // // 一块画布 = 手柄俯视图:顶部扳机条 + 肩键,左上左摇杆、左下十字键、 // 右上 ABXY(Xbox 语义色)、右下右摇杆,中央 Back/Guide/Start 小键。 // 按下即亮蓝(面键亮语义色),无数值无卡片——位置即实体,一眼对上 // 手里的手柄。数据源 = Window::PollGamepad(GLFW 原始轴值/按键)。 // #include "GamepadPanel.h" #include #include #include namespace { // GLFW 标准布局下标 enum PadBtn { kA = 0, kB = 1, kX = 2, kY = 3, kLB = 4, kRB = 5, kBack = 6, kStart = 7, kGuide = 8, kLS = 9, kRS = 10, kUp = 11, kRight = 12, kDown = 13, kLeft = 14, }; constexpr ImU32 kLit = IM_COL32(90, 170, 250, 255); // 按下亮蓝 constexpr ImU32 kFill = IM_COL32(52, 52, 60, 255); // 暗态填充 constexpr ImU32 kEdge = IM_COL32(104, 104, 114, 255); // 暗态描边 constexpr ImU32 kTxt = IM_COL32(215, 215, 220, 255); // 标签文字 // 块状键(肩键 / Back / Start) void Block(ImDrawList* dl, const ImVec2& c, float w, float h, const char* label, bool pressed) { const ImVec2 mn(c.x - w * 0.5f, c.y - h * 0.5f); const ImVec2 mx(c.x + w * 0.5f, c.y + h * 0.5f); dl->AddRectFilled(mn, mx, pressed ? kLit : kFill, 5.0f); dl->AddRect(mn, mx, pressed ? IM_COL32(160, 210, 255, 255) : kEdge, 5.0f); const ImVec2 ts = ImGui::CalcTextSize(label); dl->AddText(ImVec2(c.x - ts.x * 0.5f, c.y - ts.y * 0.5f), kTxt, label); } // 圆键(Guide) void Circle(ImDrawList* dl, const ImVec2& c, float r, const char* label, bool pressed) { dl->AddCircleFilled(c, r, pressed ? kLit : kFill); dl->AddCircle(c, r, pressed ? IM_COL32(160, 210, 255, 255) : kEdge, 0, 1.5f); const ImVec2 ts = ImGui::CalcTextSize(label); dl->AddText(ImVec2(c.x - ts.x * 0.5f, c.y - ts.y * 0.5f), kTxt, label); } // Xbox 语义色面键 void Face(ImDrawList* dl, const ImVec2& c, float r, char label, bool pressed) { ImU32 col; switch (label) { case 'A': col = IM_COL32(32, 150, 40, 255); break; case 'B': col = IM_COL32(200, 45, 45, 255); break; case 'X': col = IM_COL32(50, 110, 220, 255); break; default: col = IM_COL32(225, 185, 40, 255); break; } dl->AddCircleFilled(c, r, pressed ? col : IM_COL32(col & 0xFF, (col >> 8) & 0xFF, (col >> 16) & 0xFF, 60)); dl->AddCircle(c, r, pressed ? col : kEdge, 0, 2.0f); const ImVec2 ts = ImGui::CalcTextSize(&label, &label + 1); dl->AddText(ImVec2(c.x - ts.x * 0.5f, c.y - ts.y * 0.5f), pressed ? IM_COL32(255, 255, 255, 255) : kTxt, &label); } // 摇杆画布(底座 + 死区圈 + 轴点) void Stick(ImDrawList* dl, const ImVec2& c, float r, float ax, float ay, float deadzone) { dl->AddCircleFilled(c, r, IM_COL32(32, 32, 37, 255)); dl->AddCircle(c, r, kEdge, 0, 1.5f); dl->AddCircle(c, r * deadzone, IM_COL32(90, 140, 210, 110), 0, 1.2f); dl->AddLine(ImVec2(c.x - r * 0.93f, c.y), ImVec2(c.x + r * 0.93f, c.y), IM_COL32(50, 50, 58, 255), 1.0f); dl->AddLine(ImVec2(c.x, c.y - r * 0.93f), ImVec2(c.x, c.y + r * 0.93f), IM_COL32(50, 50, 58, 255), 1.0f); const float mag = std::sqrt(ax * ax + ay * ay); const float cl = (mag > 1.0f) ? 1.0f / mag : 1.0f; const ImVec2 pt(c.x + ax * cl * (r - 8.0f), c.y + ay * cl * (r - 8.0f)); if (mag > deadzone) { dl->AddCircleFilled(pt, 10.0f, IM_COL32(90, 170, 250, 70)); } dl->AddCircleFilled(pt, 7.0f, mag > deadzone ? kLit : IM_COL32(140, 140, 148, 255)); } // 十字键(真十字形,按下臂亮) void Dpad(ImDrawList* dl, const ImVec2& c, float arm, float w, const bool* b) { const ImVec2 l0(c.x - arm, c.y - w * 0.5f); const ImVec2 l1(c.x, c.y + w * 0.5f); const ImVec2 r0(c.x, c.y - w * 0.5f); const ImVec2 r1(c.x + arm, c.y + w * 0.5f); const ImVec2 u0(c.x - w * 0.5f, c.y - arm); const ImVec2 u1(c.x + w * 0.5f, c.y); const ImVec2 d0(c.x - w * 0.5f, c.y); const ImVec2 d1(c.x + w * 0.5f, c.y + arm); dl->AddRectFilled(l0, l1, b[kLeft] ? kLit : kFill, 4.0f); dl->AddRectFilled(r0, r1, b[kRight] ? kLit : kFill, 4.0f); dl->AddRectFilled(u0, u1, b[kUp] ? kLit : kFill, 4.0f); dl->AddRectFilled(d0, d1, b[kDown] ? kLit : kFill, 4.0f); dl->AddRect(l0, l1, kEdge, 4.0f); dl->AddRect(r0, r1, kEdge, 4.0f); dl->AddRect(u0, u1, kEdge, 4.0f); dl->AddRect(d0, d1, kEdge, 4.0f); } // 线性扳机条(GLFW -1..1 → 0..1 填充) void Trigger(ImDrawList* dl, const ImVec2& mn, const ImVec2& mx, float raw) { dl->AddRectFilled(mn, mx, IM_COL32(32, 32, 37, 255), 4.0f); const float t = (raw + 1.0f) * 0.5f; if (t > 0.02f) { dl->AddRectFilled(mn, ImVec2(mn.x + (mx.x - mn.x) * t, mx.y), IM_COL32(235, 120, 55, 255), 4.0f); } dl->AddRect(mn, mx, kEdge, 4.0f); } } // namespace void GamepadPanel::Draw(const EditorContext& ctx) { if (!m_Open) { return; } if (!ImGui::Begin("Gamepad", &m_Open)) { ImGui::End(); return; } Engine::GamepadState gp; const bool connected = ctx.app && ctx.app->GetWindow().PollGamepad(gp); if (!connected) { ImGui::TextColored(ImVec4(0.60f, 0.60f, 0.60f, 1.0f), "○ Not connected (JOYSTICK_1)"); ImGui::End(); return; } ImGui::TextColored(ImVec4(0.40f, 0.85f, 0.40f, 1.0f), "● Connected"); ImGui::Spacing(); // ---- 手柄画布(固定尺寸,元素 = 手柄真实位置;无背景板)---- constexpr float kW = 440.0f, kH = 262.0f; constexpr float kDead = 0.15f; // = InputMap kRadialDeadZone ImGui::BeginChild("pad", ImVec2(kW, kH), false); ImDrawList* dl = ImGui::GetWindowDrawList(); const ImVec2 o = ImGui::GetWindowPos(); auto C = [&](float x, float y) { return ImVec2(o.x + x, o.y + y); }; // 顶部:线性扳机条 + 肩键 Trigger(dl, C(52, 8), C(122, 20), gp.axes[4]); // LT Trigger(dl, C(kW - 122, 8), C(kW - 52, 20), gp.axes[5]); // RT Block(dl, C(78, 40), 76.0f, 20.0f, "LB", gp.buttons[kLB]); Block(dl, C(kW - 78, 40), 76.0f, 20.0f, "RB", gp.buttons[kRB]); // 左上:左摇杆|左下:十字键(向右错开——真实手柄摇杆与十字不对齐) Stick(dl, C(76, 118), 46.0f, gp.axes[0], gp.axes[1], kDead); Dpad(dl, C(128, 210), 36.0f, 24.0f, gp.buttons); // 中央:Back / Guide / Start Block(dl, C(184, 74), 40.0f, 16.0f, "Back", gp.buttons[kBack]); Circle(dl, C(220, 58), 13.0f, "G", gp.buttons[kGuide]); Block(dl, C(256, 74), 40.0f, 16.0f, "Start", gp.buttons[kStart]); // 右上:ABXY 菱形|右下:右摇杆(对称错开,向中心收) const ImVec2 fc = C(360, 118); Face(dl, ImVec2(fc.x, fc.y - 24), 17.0f, 'Y', gp.buttons[kY]); Face(dl, ImVec2(fc.x - 24, fc.y), 17.0f, 'X', gp.buttons[kX]); Face(dl, ImVec2(fc.x + 24, fc.y), 17.0f, 'B', gp.buttons[kB]); Face(dl, ImVec2(fc.x, fc.y + 24), 17.0f, 'A', gp.buttons[kA]); Stick(dl, C(312, 210), 46.0f, gp.axes[2], gp.axes[3], kDead); ImGui::EndChild(); ImGui::End(); }