browser DOM support
This commit is contained in:
parent
3859c43ce3
commit
5352ef69a2
37 changed files with 2637 additions and 59 deletions
|
|
@ -675,6 +675,161 @@ void Crafter::Gamepad::Rumble(Device& dev, float low, float high,
|
|||
}
|
||||
#endif
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────
|
||||
// DOM backend (browser Gamepad API via env.js polling helpers)
|
||||
// ────────────────────────────────────────────────────────────────────
|
||||
#ifdef CRAFTER_GRAPHICS_WINDOW_DOM
|
||||
namespace Crafter::DomEnv {
|
||||
// External linkage required for the import_module attribute to bind
|
||||
// against the env.js function. An anonymous namespace would drop the
|
||||
// attribute at link time.
|
||||
__attribute__((import_module("env"), import_name("gamepadPollConnected")))
|
||||
bool gamepadPollConnected();
|
||||
__attribute__((import_module("env"), import_name("gamepadPollDisconnected")))
|
||||
bool gamepadPollDisconnected();
|
||||
__attribute__((import_module("env"), import_name("gamepadCount")))
|
||||
std::int32_t gamepadCount();
|
||||
__attribute__((import_module("env"), import_name("gamepadGetButton")))
|
||||
std::int32_t gamepadGetButton(std::int32_t idx, std::int32_t buttonIdx);
|
||||
__attribute__((import_module("env"), import_name("gamepadGetAxis")))
|
||||
double gamepadGetAxis(std::int32_t idx, std::int32_t axisIdx);
|
||||
}
|
||||
namespace {
|
||||
using Crafter::DomEnv::gamepadPollConnected;
|
||||
using Crafter::DomEnv::gamepadPollDisconnected;
|
||||
using Crafter::DomEnv::gamepadCount;
|
||||
using Crafter::DomEnv::gamepadGetButton;
|
||||
using Crafter::DomEnv::gamepadGetAxis;
|
||||
|
||||
// Standard W3C Gamepad mapping: indices match the "standard" layout
|
||||
// every browser exposes for Xbox / DualShock / DualSense controllers.
|
||||
// Mismatched / non-standard pads fall through with zeroed buttons.
|
||||
constexpr int kStdBtnSouth = 0;
|
||||
constexpr int kStdBtnEast = 1;
|
||||
constexpr int kStdBtnWest = 2;
|
||||
constexpr int kStdBtnNorth = 3;
|
||||
constexpr int kStdBtnLB = 4;
|
||||
constexpr int kStdBtnRB = 5;
|
||||
constexpr int kStdBtnLT = 6;
|
||||
constexpr int kStdBtnRT = 7;
|
||||
constexpr int kStdBtnSelect = 8;
|
||||
constexpr int kStdBtnStart = 9;
|
||||
constexpr int kStdBtnLStickClick = 10;
|
||||
constexpr int kStdBtnRStickClick = 11;
|
||||
constexpr int kStdBtnDPadUp = 12;
|
||||
constexpr int kStdBtnDPadDown = 13;
|
||||
constexpr int kStdBtnDPadLeft = 14;
|
||||
constexpr int kStdBtnDPadRight = 15;
|
||||
constexpr int kStdBtnHome = 16;
|
||||
|
||||
constexpr int kStdAxisLX = 0, kStdAxisLY = 1, kStdAxisRX = 2, kStdAxisRY = 3;
|
||||
|
||||
// Map our `Gamepad::Button` ordinal to the W3C standard button index.
|
||||
int StdButtonIdx(Crafter::Gamepad::Button b) {
|
||||
using B = Crafter::Gamepad::Button;
|
||||
switch (b) {
|
||||
case B::South: return kStdBtnSouth;
|
||||
case B::East: return kStdBtnEast;
|
||||
case B::West: return kStdBtnWest;
|
||||
case B::North: return kStdBtnNorth;
|
||||
case B::Select: return kStdBtnSelect;
|
||||
case B::Start: return kStdBtnStart;
|
||||
case B::Home: return kStdBtnHome;
|
||||
case B::LeftStickClick: return kStdBtnLStickClick;
|
||||
case B::RightStickClick: return kStdBtnRStickClick;
|
||||
case B::LeftBumper: return kStdBtnLB;
|
||||
case B::RightBumper: return kStdBtnRB;
|
||||
case B::DPadUp: return kStdBtnDPadUp;
|
||||
case B::DPadDown: return kStdBtnDPadDown;
|
||||
case B::DPadLeft: return kStdBtnDPadLeft;
|
||||
case B::DPadRight: return kStdBtnDPadRight;
|
||||
case B::LeftTrigger: return kStdBtnLT;
|
||||
case B::RightTrigger: return kStdBtnRT;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Crafter::Gamepad::Tick() {
|
||||
// Handle connect/disconnect by rebuilding `connected` from scratch
|
||||
// when the JS flags say something changed. Cheap — only fires on
|
||||
// actual hot-plug, not every frame.
|
||||
if (gamepadPollConnected() || gamepadPollDisconnected() || connected.empty()) {
|
||||
// Snapshot previously-known ids so we can fire onDisconnected
|
||||
// for ones that went away in this rebuild.
|
||||
std::vector<std::uint32_t> previousIds;
|
||||
previousIds.reserve(connected.size());
|
||||
for (auto& d : connected) previousIds.push_back(d->id);
|
||||
|
||||
std::int32_t n = gamepadCount();
|
||||
connected.clear();
|
||||
for (std::int32_t i = 0; i < n; ++i) {
|
||||
auto dev = std::make_unique<Device>();
|
||||
dev->id = static_cast<std::uint32_t>(i); // index-based; stable for the page
|
||||
dev->name = "Gamepad";
|
||||
connected.push_back(std::move(dev));
|
||||
}
|
||||
// Fire connected events for ids that weren't in the previous set.
|
||||
for (auto& d : connected) {
|
||||
bool wasKnown = false;
|
||||
for (std::uint32_t prev : previousIds) if (prev == d->id) { wasKnown = true; break; }
|
||||
if (!wasKnown) onConnected.Invoke(d.get());
|
||||
}
|
||||
// No way to call onDisconnected with the right Device* once it's
|
||||
// gone — fire with nullptr so subscribers know SOMETHING changed.
|
||||
for (std::uint32_t prev : previousIds) {
|
||||
bool stillThere = false;
|
||||
for (auto& d : connected) if (d->id == prev) { stillThere = true; break; }
|
||||
if (!stillThere) onDisconnected.Invoke(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
// Poll buttons + axes on every Tick — same shape as the native paths.
|
||||
for (auto& devUp : connected) {
|
||||
Device& dev = *devUp;
|
||||
std::int32_t idx = static_cast<std::int32_t>(dev.id);
|
||||
|
||||
for (std::size_t b = 0; b < (std::size_t)Button::Max; ++b) {
|
||||
int stdIdx = StdButtonIdx(static_cast<Button>(b));
|
||||
bool newState = stdIdx >= 0 && gamepadGetButton(idx, stdIdx) != 0;
|
||||
if (newState != dev.buttons[b]) {
|
||||
dev.buttons[b] = newState;
|
||||
if (newState) dev.onButtonDown.Invoke(static_cast<Button>(b));
|
||||
else dev.onButtonUp .Invoke(static_cast<Button>(b));
|
||||
}
|
||||
}
|
||||
// Axes — sticks come straight through, triggers come from button
|
||||
// analog values (the standard mapping exposes those too, with
|
||||
// buttons[6/7].value, but the polling shim returns binary
|
||||
// pressed-state. For V1, triggers report 0 / 1 only).
|
||||
float lx = static_cast<float>(gamepadGetAxis(idx, kStdAxisLX));
|
||||
float ly = static_cast<float>(gamepadGetAxis(idx, kStdAxisLY));
|
||||
float rx = static_cast<float>(gamepadGetAxis(idx, kStdAxisRX));
|
||||
float ry = static_cast<float>(gamepadGetAxis(idx, kStdAxisRY));
|
||||
auto setAxis = [&](Axis a, float v) {
|
||||
std::size_t i = (std::size_t)a;
|
||||
if (dev.axes[i] != v) {
|
||||
dev.axes[i] = v;
|
||||
dev.onAxisChanged.Invoke(a);
|
||||
}
|
||||
};
|
||||
setAxis(Axis::LeftStickX, lx);
|
||||
setAxis(Axis::LeftStickY, ly);
|
||||
setAxis(Axis::RightStickX, rx);
|
||||
setAxis(Axis::RightStickY, ry);
|
||||
setAxis(Axis::LeftTrigger, gamepadGetButton(idx, kStdBtnLT) ? 1.0f : 0.0f);
|
||||
setAxis(Axis::RightTrigger, gamepadGetButton(idx, kStdBtnRT) ? 1.0f : 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void Crafter::Gamepad::Rumble(Device& /*dev*/, float /*low*/, float /*high*/,
|
||||
std::chrono::milliseconds /*duration*/) {
|
||||
// Browser Gamepad rumble (HapticActuator API) is patchy across
|
||||
// engines. V1: no-op. Pads that don't support it would silently
|
||||
// fall through anyway.
|
||||
}
|
||||
#endif
|
||||
|
||||
Crafter::Gamepad::Device* Crafter::Gamepad::FindById(std::uint32_t id) {
|
||||
for (auto& up : connected) {
|
||||
if (up->id == id) return up.get();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue