Adds support for customizing keybinds in the editor and while playing.
You can use Custom Keybinds as a dependency by declaring it in your mod.json
:
{
"dependencies": [
{
"id": "geode.custom-keybinds",
"version": "v1.1.0"
}
]
}
Adding a new keybind requires two things: registering it when your mod starts up, and handling callbacks when it's fired.
Registering can be done easily using $execute
:
#include <geode.custom-keybinds/include/Keybinds.hpp>
using namespace keybinds;
$execute {
BindManager::get()->registerBindable({
// ID, should be prefixed with mod ID
"backlip"_spr,
// Name
"Do a Backflip!",
// Description, leave empty for none
"Throw a backflip",
// Default binds
{ Keybind::create(KEY_Q, Modifier::None) },
// Category; use slashes for specifying subcategories. See the
// Category class for default categories
"My Mod/Awesome Tricks"
});
}
Now your bind shows up in the UI, and the user can assign their own binds to it. To make the bind also do stuff, you need to add an event listener for InvokeBindEvent
. If your bind is global (can be used from anywhere in-game), you should add the event listener in the same $execute
block. If the bind is layer-specific, add it in the layer's init
:
#include <geode.custom-keybinds/include/Keybinds.hpp>
using namespace keybinds;
bool MyLayer::init() {
...
this->template addEventListener<InvokeBindFilter>([=](InvokeBindEvent* event) {
if (event->isDown()) {
// do a backflip!
}
// Return Propagate if you want other actions with the same bind to
// also be fired, or Stop if you want to halt propagation
return ListenerResult::Propagate;
}, "backflip"_spr);
...
}
Adding keybinds to the editor / PlayLayer
is the same - just add the keybind to the Editor / Play categories, and hook EditorUI::init
or UILayer::init
to handle the callback. You can also pass the ID of an existing action to BindManager::registerBindable
to place the bind after, if you for example add a new build category and would like it after the Delete Mode
bind.
You can listen for global keybinds via an $execute
block:
#include <geode.custom-keybinds/include/Keybinds.hpp>
using namespace keybinds;
$execute {
new EventListener([=](InvokeBindEvent* event) {
// do stuff
return ListenerResult::Propagate;
}, InvokeBindFilter(nullptr, "event-id"));
}
You can invoke a bind by creating and posting an InvokeBindEvent
:
InvokeBindEvent("backflip"_spr, true).post();
You can also trigger a specific key combination with PressBindEvent
:
PressBindEvent(Keybind::create(KEY_X, Modifier::None)).post();
Custom keybinds has been written to support more input devices, in case you want to add first-class support for a gaming toaster to GD.
To add a new input device, first thing you should do is create a new bind class:
#include <geode.custom-keybinds/include/Keybinds.hpp>
using namespace keybinds;
class ToasterBind : public Bind {
public:
BreadType m_bread;
float m_temperature;
public:
static ToasterBind* create(BreadType type, float temperature) {
auto ret = new ToasterBind();
ret->autorelease();
ret->m_bread = type;
ret->m_temperature = temperature;
return ret;
}
// Parse from JSON
static ToasterBind* parse(matjson::Value const& json) {
return ToasterBind::create(
static_cast<BreadType>(json["bread"].as_int()),
static_cast<float>(json["temperature"].as_double())
);
}
// Save to JSON
matjson::Value save() const override {
return matjson::Object {
{ "bread", static_cast<int>(m_bread) },
{ "temperature", m_temperature },
};
}
// Getters for members
BreadType getThisBread() const;
float getTemperature() const;
// Get the hash for this bind
size_t getHash() const override {
return std::hash<float>()(m_temperature) ^ static_cast<size_t>(m_bread);
}
// Check if this bind is equal to another
bool isEqual(Bind* other) const override {
if (auto o = typeinfo_cast<ToasterBind*>(other)) {
return m_temperature == o->m_temperature && m_bread == o->m_bread;
}
return false;
}
// Convert to string
// By default, the bind is displayed in the UI as just the string in a
// label. If you want to show something else, override createLabel()
std::string toString() const override {
return fmt::format("Temp {} & Bread {}", m_temperature, m_bread);
}
// The device this bind is related to
DeviceID getDeviceID() const override {
return "toaster"_spr;
}
};
Registering a new input device is as simple as calling BindManager::attachDevice
:
// Device ID should be prefixed with mod ID as usual
BindManager::get()->attachDevice("toaster"_spr, &MyParser::parse);
If the device is detached while the game is running, you can unregister it similarly with BindManager::detachDevice
.
Once you have figured out how to get inputs from your awesome toaster, you need to post PressBindEvent
s:
void XInput_onGamingToasterCallback(double temp, int bread) {
// Make sure all bind events are only ever posted in the GD thread !!!!
Loader::get()->queueInMainThread([=] {
PressBindEvent(ToasterBind::create(static_cast<float>(temp), bread), true).post();
});
}