new input system

This commit is contained in:
Jorijn van der Graaf 2026-05-12 00:24:48 +02:00
commit ac2eb7fb0a
31 changed files with 3292 additions and 781 deletions

View file

@ -0,0 +1,99 @@
/*
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);
}