Files
ReSkate/Engine/Game/Input/controller_bindings.h
T
2026-10-02 14:40:41 -05:00

90 lines
3.7 KiB
C++

#pragma once
#include <bit>
#include <cstdint>
#include <optional>
#include <string>
namespace dingosdk {
// XInput button bits, plus two synthetic trigger bits (above the dead zone).
inline constexpr std::uint32_t controller_button_mask = 0x3f3ff;
inline constexpr bool valid_controller_combo(std::uint32_t value) noexcept {
return (value & ~controller_button_mask) == 0;
}
// Button names shown to the player. Bindings are always stored as XInput bits.
enum class ControllerStyle : std::uint8_t { xbox, dualshock4, dualsense };
struct ControllerInput {
bool available{};
std::uint32_t buttons{};
// Identifies the connected source set; zero means unavailable. Any change
// (a pad connecting, disconnecting or switching source) resets latches.
unsigned device{};
ControllerStyle style{};
};
struct ControllerBindingsModel {
bool available{};
std::uint32_t noclip_combo{};
std::uint32_t forward_velocity_combo{};
std::uint32_t up_velocity_combo{};
std::string status;
};
inline std::string controller_combo_label(std::uint32_t value, ControllerStyle style = ControllerStyle::xbox) {
if (!value) return "Not bound";
const bool playstation = style != ControllerStyle::xbox;
struct Button { std::uint32_t bit; const char* xbox; const char* playstation; };
constexpr Button buttons[]{
{0x100,"LB","L1"}, {0x200,"RB","R1"}, {0x10000,"LT","L2"}, {0x20000,"RT","R2"},
{0x40,"L3","L3"}, {0x80,"R3","R3"},
{0x1000,"A","Cross"}, {0x2000,"B","Circle"}, {0x4000,"X","Square"}, {0x8000,"Y","Triangle"},
{1,"D-pad Up","D-pad Up"}, {2,"D-pad Down","D-pad Down"}, {4,"D-pad Left","D-pad Left"}, {8,"D-pad Right","D-pad Right"},
{0x10,"Start","Options"}, {0x20,"Back",nullptr}};
std::string label;
for (const auto& button : buttons) if (value & button.bit) {
if (!label.empty()) label += " + ";
label += !playstation ? button.xbox : button.playstation ? button.playstation :
style == ControllerStyle::dualsense ? "Create" : "Share";
}
return label;
}
// A held combo never repeats. Returning from menus, reconnecting, changing a
// binding, and changing level all require releasing every bound button first.
struct ControllerComboLatch {
std::uint32_t combo{};
unsigned device{};
bool armed{};
bool update(std::uint32_t binding, ControllerInput input, bool inhibited) noexcept {
if (binding != combo || input.device != device) {
combo = binding; device = input.device; armed = false;
}
if (inhibited || !input.available || !combo || !valid_controller_combo(combo)) {
armed = false; return false;
}
const auto held = input.buttons & combo;
if (!held) armed = true;
if (!armed || held != combo) return false;
armed = false;
return true;
}
};
// Record the largest simultaneous chord, not a union of sequential presses.
// Begin neutral so the input that opened Record cannot become the binding.
struct ControllerComboCapture {
std::uint32_t chord{};
unsigned device{};
bool ready{};
std::optional<std::uint32_t> update(ControllerInput input) noexcept {
if (!input.available || input.device != device) {
chord = 0; ready = false; device = input.device;
}
if (!input.available) return {};
const auto buttons = input.buttons & controller_button_mask;
if (!ready) { ready = buttons == 0; return {}; }
if (std::popcount(buttons) > std::popcount(chord)) chord = buttons;
if (!buttons && chord) {
const auto result = chord; chord = 0; ready = false;
return result;
}
return {};
}
};
}