99 lines
4.2 KiB
Text
99 lines
4.2 KiB
Text
|
|
/*
|
||
|
|
Crafter®.Graphics
|
||
|
|
Copyright (C) 2026 Catcrafts®
|
||
|
|
Catcrafts.net
|
||
|
|
|
||
|
|
This library is free software; you can redistribute it and/or
|
||
|
|
modify it under the terms of the GNU Lesser General Public
|
||
|
|
License as published by the Free Software Foundation; either
|
||
|
|
version 3.0 of the License, or (at your option) any later version.
|
||
|
|
|
||
|
|
This library is distributed in the hope that it will be useful,
|
||
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
|
Lesser General Public License for more details.
|
||
|
|
|
||
|
|
You should have received a copy of the GNU Lesser General Public
|
||
|
|
License along with this library; if not, write to the Free Software
|
||
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||
|
|
*/
|
||
|
|
export module Crafter.Graphics:Gamepad;
|
||
|
|
import std;
|
||
|
|
import Crafter.Event;
|
||
|
|
|
||
|
|
// Raw gamepad device API. Platform-split implementation:
|
||
|
|
// - Linux: libudev (enumerate + hot-plug) + libevdev (read events,
|
||
|
|
// calibrate axes). Open every /dev/input/event* node the kernel
|
||
|
|
// classifies as a gamepad; ignore the rest.
|
||
|
|
// - Windows: Windows.Gaming.Input via the C ABI (COBJMACROS pattern,
|
||
|
|
// same as SDL). Added/Removed events fire on a thread pool and are
|
||
|
|
// marshaled into a queue drained from Tick() on the main thread.
|
||
|
|
//
|
||
|
|
// Button names follow the standardized kernel BTN_SOUTH / BTN_EAST /
|
||
|
|
// BTN_WEST / BTN_NORTH convention — physical position, not label. An
|
||
|
|
// Xbox "A" and a PlayStation "Cross" both arrive as `South`.
|
||
|
|
//
|
||
|
|
// Stick axes are -1..1 (post-deadzone calibration, raw float). Trigger
|
||
|
|
// axes are 0..1. The `LeftTrigger` / `RightTrigger` entries in `Button`
|
||
|
|
// and `Axis` are both populated — pick whichever fits your use case
|
||
|
|
// (digital threshold vs. analog reading).
|
||
|
|
|
||
|
|
export namespace Crafter::Gamepad {
|
||
|
|
enum class Button : std::uint8_t {
|
||
|
|
South, East, West, North, // BTN_SOUTH/EAST/WEST/NORTH
|
||
|
|
Select, Start, Home,
|
||
|
|
LeftStickClick, RightStickClick,
|
||
|
|
LeftBumper, RightBumper,
|
||
|
|
DPadUp, DPadDown, DPadLeft, DPadRight,
|
||
|
|
LeftTrigger, RightTrigger, // digital threshold; analog also on Axis
|
||
|
|
Max
|
||
|
|
};
|
||
|
|
|
||
|
|
enum class Axis : std::uint8_t {
|
||
|
|
LeftStickX, LeftStickY,
|
||
|
|
RightStickX, RightStickY,
|
||
|
|
LeftTrigger, RightTrigger,
|
||
|
|
Max
|
||
|
|
};
|
||
|
|
|
||
|
|
enum class Stick : std::uint8_t { Left, Right };
|
||
|
|
|
||
|
|
struct Device {
|
||
|
|
std::uint32_t id; // stable across the device's lifetime
|
||
|
|
std::string name;
|
||
|
|
bool buttons[(std::size_t)Button::Max] = {};
|
||
|
|
float axes[(std::size_t)Axis::Max] = {}; // sticks: -1..1, triggers: 0..1
|
||
|
|
Event<Button> onButtonDown;
|
||
|
|
Event<Button> onButtonUp;
|
||
|
|
Event<Axis> onAxisChanged;
|
||
|
|
};
|
||
|
|
|
||
|
|
// Process-level state — one input subsystem per app. Indices into
|
||
|
|
// `connected` are NOT stable across hot-plug; use `Device::id` when
|
||
|
|
// serializing bindings. The list is unsorted; iterate to find a
|
||
|
|
// specific id.
|
||
|
|
inline std::vector<std::unique_ptr<Device>> connected;
|
||
|
|
inline Event<Device*> onConnected;
|
||
|
|
inline Event<Device*> onDisconnected;
|
||
|
|
|
||
|
|
// Drains pending events from the OS, fires the relevant onButton* /
|
||
|
|
// onAxisChanged / onConnected / onDisconnected events synchronously,
|
||
|
|
// and updates the polled `buttons[]` / `axes[]` state on each Device.
|
||
|
|
// Must be called from the main thread once per frame — Window's
|
||
|
|
// event loop does this automatically. Safe to call when no gamepads
|
||
|
|
// are connected (early-out path).
|
||
|
|
void Tick();
|
||
|
|
|
||
|
|
// Optional rumble. `low` drives the heavy/low-frequency motor,
|
||
|
|
// `high` drives the light/high-frequency motor (Xbox naming). Both
|
||
|
|
// are clamped to 0..1. `duration` clamps to the backend's max; pass
|
||
|
|
// 0ms to stop any active rumble. No-op if the device doesn't
|
||
|
|
// advertise force-feedback.
|
||
|
|
void Rumble(Device& dev, float low, float high,
|
||
|
|
std::chrono::milliseconds duration);
|
||
|
|
|
||
|
|
// Find a connected device by its stable id. Returns nullptr if the
|
||
|
|
// device isn't currently connected — typical use after deserializing
|
||
|
|
// a saved binding.
|
||
|
|
Device* FindById(std::uint32_t id);
|
||
|
|
}
|