Start switching to callbacks on SDL input events

This commit is contained in:
Yousef Khadadeh 2026-02-28 22:47:35 +03:00
parent ae88521b32
commit 8c11141ac5
11 changed files with 235 additions and 95 deletions

View file

@ -0,0 +1,27 @@
#pragma once
#include <functional>
#include <vector>
template <typename... Args> class CallbackManager {
public:
typedef std::function<void(const Args&...)> Callback;
Callback* register_callback(const Callback& func) {
Callback* toRet = new Callback(func);
funcs.emplace_back(toRet);
return toRet;
}
void deregister_callback(Callback* callbackToRemove) {
if(std::erase(funcs, callbackToRemove) > 0)
delete callbackToRemove;
}
void run_callbacks(const Args&... a) {
for(auto& f : funcs)
(*f)(a...);
}
~CallbackManager() {
for(Callback* c : funcs)
delete c;
}
private:
std::vector<Callback*> funcs;
};

View file

@ -45,6 +45,87 @@ DrawingProgram::DrawingProgram(World& initWorld):
drawTool = DrawingProgramToolBase::allocate_tool_type(*this, DrawingProgramToolType::BRUSH);
}
void DrawingProgram::register_callbacks() {
keyCallbacks[InputManager::KEY_DRAW_TOOL_BRUSH] = world.main.input.keyCallbacks[InputManager::KEY_DRAW_TOOL_BRUSH].register_callback([&](auto& key) {
if(key.down && !key.repeat)
switch_to_tool(DrawingProgramToolType::BRUSH);
});
keyCallbacks[InputManager::KEY_DRAW_TOOL_ERASER] = world.main.input.keyCallbacks[InputManager::KEY_DRAW_TOOL_ERASER].register_callback([&](auto& key) {
if(key.down && !key.repeat)
switch_to_tool(DrawingProgramToolType::ERASER);
});
keyCallbacks[InputManager::KEY_DRAW_TOOL_RECTSELECT] = world.main.input.keyCallbacks[InputManager::KEY_DRAW_TOOL_RECTSELECT].register_callback([&](auto& key) {
if(key.down && !key.repeat)
switch_to_tool(DrawingProgramToolType::RECTSELECT);
});
keyCallbacks[InputManager::KEY_DRAW_TOOL_RECTANGLE] = world.main.input.keyCallbacks[InputManager::KEY_DRAW_TOOL_RECTANGLE].register_callback([&](auto& key) {
if(key.down && !key.repeat)
switch_to_tool(DrawingProgramToolType::RECTANGLE);
});
keyCallbacks[InputManager::KEY_DRAW_TOOL_ELLIPSE] = world.main.input.keyCallbacks[InputManager::KEY_DRAW_TOOL_ELLIPSE].register_callback([&](auto& key) {
if(key.down && !key.repeat)
switch_to_tool(DrawingProgramToolType::ELLIPSE);
});
keyCallbacks[InputManager::KEY_DRAW_TOOL_TEXTBOX] = world.main.input.keyCallbacks[InputManager::KEY_DRAW_TOOL_TEXTBOX].register_callback([&](auto& key) {
if(key.down && !key.repeat)
switch_to_tool(DrawingProgramToolType::TEXTBOX);
});
keyCallbacks[InputManager::KEY_DRAW_TOOL_EYEDROPPER] = world.main.input.keyCallbacks[InputManager::KEY_DRAW_TOOL_EYEDROPPER].register_callback([&](auto& key) {
if(key.down && !key.repeat)
switch_to_tool(DrawingProgramToolType::EYEDROPPER);
});
keyCallbacks[InputManager::KEY_DRAW_TOOL_SCREENSHOT] = world.main.input.keyCallbacks[InputManager::KEY_DRAW_TOOL_SCREENSHOT].register_callback([&](auto& key) {
if(key.down && !key.repeat)
switch_to_tool(DrawingProgramToolType::SCREENSHOT);
});
keyCallbacks[InputManager::KEY_DRAW_TOOL_ZOOM] = world.main.input.keyCallbacks[InputManager::KEY_DRAW_TOOL_ZOOM].register_callback([&](auto& key) {
if(key.down && !key.repeat)
switch_to_tool(DrawingProgramToolType::ZOOM);
});
keyCallbacks[InputManager::KEY_DRAW_TOOL_LASSOSELECT] = world.main.input.keyCallbacks[InputManager::KEY_DRAW_TOOL_LASSOSELECT].register_callback([&](auto& key) {
if(key.down && !key.repeat)
switch_to_tool(DrawingProgramToolType::LASSOSELECT);
});
keyCallbacks[InputManager::KEY_DRAW_TOOL_PAN] = world.main.input.keyCallbacks[InputManager::KEY_DRAW_TOOL_PAN].register_callback([&](auto& key) {
if(key.down && !key.repeat)
switch_to_tool(DrawingProgramToolType::PAN);
});
keyCallbacks[InputManager::KEY_DRAW_TOOL_LINE] = world.main.input.keyCallbacks[InputManager::KEY_DRAW_TOOL_LINE].register_callback([&](auto& key) {
if(key.down && !key.repeat)
switch_to_tool(DrawingProgramToolType::LINE);
});
keyCallbacks[InputManager::KEY_HOLD_TO_PAN] = world.main.input.keyCallbacks[InputManager::KEY_HOLD_TO_PAN].register_callback([&](auto& key) {
if(key.down && !key.repeat && tempMoveToolSwitch == TemporaryMoveToolSwitch::NONE) {
toolTypeAfterTempMove = drawTool->get_type();
switch_to_tool(DrawingProgramToolType::PAN);
tempMoveToolSwitch = TemporaryMoveToolSwitch::PAN;
}
else if(!key.down && tempMoveToolSwitch == TemporaryMoveToolSwitch::PAN) {
switch_to_tool(toolTypeAfterTempMove);
tempMoveToolSwitch = TemporaryMoveToolSwitch::NONE;
}
});
keyCallbacks[InputManager::KEY_HOLD_TO_ZOOM] = world.main.input.keyCallbacks[InputManager::KEY_HOLD_TO_ZOOM].register_callback([&](auto& key) {
if(key.down && !key.repeat && tempMoveToolSwitch == TemporaryMoveToolSwitch::NONE) {
toolTypeAfterTempMove = drawTool->get_type();
switch_to_tool(DrawingProgramToolType::ZOOM);
tempMoveToolSwitch = TemporaryMoveToolSwitch::ZOOM;
}
else if(!key.down && tempMoveToolSwitch == TemporaryMoveToolSwitch::ZOOM) {
switch_to_tool(toolTypeAfterTempMove);
tempMoveToolSwitch = TemporaryMoveToolSwitch::NONE;
}
});
selection.register_key_callbacks();
}
void DrawingProgram::deregister_callbacks() {
for(auto& [key, callback] : keyCallbacks)
world.main.input.keyCallbacks[key].deregister_callback(callback);
keyCallbacks.clear();
tempMoveToolSwitch = TemporaryMoveToolSwitch::NONE;
}
void DrawingProgram::server_init_no_file() {
layerMan.server_init_no_file();
}
@ -291,30 +372,6 @@ void DrawingProgram::update() {
drag_drop_update();
if(world.main.input.key(InputManager::KEY_DRAW_TOOL_BRUSH).pressed)
switch_to_tool(DrawingProgramToolType::BRUSH);
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_ERASER).pressed)
switch_to_tool(DrawingProgramToolType::ERASER);
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_RECTSELECT).pressed)
switch_to_tool(DrawingProgramToolType::RECTSELECT);
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_RECTANGLE).pressed)
switch_to_tool(DrawingProgramToolType::RECTANGLE);
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_ELLIPSE).pressed)
switch_to_tool(DrawingProgramToolType::ELLIPSE);
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_TEXTBOX).pressed)
switch_to_tool(DrawingProgramToolType::TEXTBOX);
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_EYEDROPPER).pressed)
switch_to_tool(DrawingProgramToolType::EYEDROPPER);
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_SCREENSHOT).pressed)
switch_to_tool(DrawingProgramToolType::SCREENSHOT);
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_ZOOM).pressed)
switch_to_tool(DrawingProgramToolType::ZOOM);
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_LASSOSELECT).pressed)
switch_to_tool(DrawingProgramToolType::LASSOSELECT);
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_PAN).pressed)
switch_to_tool(DrawingProgramToolType::PAN);
else if(world.main.input.key(InputManager::KEY_DRAW_TOOL_LINE).pressed)
switch_to_tool(DrawingProgramToolType::LINE);
selection.update();
drawTool->tool_update();
@ -336,35 +393,6 @@ void DrawingProgram::update() {
}
void DrawingProgram::tool_temporary_switch_update() {
switch(tempMoveToolSwitch) {
case TemporaryMoveToolSwitch::NONE:
if(world.main.input.key(InputManager::KEY_HOLD_TO_PAN).held) {
toolTypeAfterTempMove = drawTool->get_type();
switch_to_tool(DrawingProgramToolType::PAN);
tempMoveToolSwitch = TemporaryMoveToolSwitch::PAN;
}
if(world.main.input.key(InputManager::KEY_HOLD_TO_ZOOM).held) {
toolTypeAfterTempMove = drawTool->get_type();
switch_to_tool(DrawingProgramToolType::ZOOM);
tempMoveToolSwitch = TemporaryMoveToolSwitch::ZOOM;
}
break;
case TemporaryMoveToolSwitch::PAN: {
if(!world.main.input.key(InputManager::KEY_HOLD_TO_PAN).held) {
switch_to_tool(toolTypeAfterTempMove);
tempMoveToolSwitch = TemporaryMoveToolSwitch::NONE;
}
break;
}
case TemporaryMoveToolSwitch::ZOOM: {
if(!world.main.input.key(InputManager::KEY_HOLD_TO_ZOOM).held) {
switch_to_tool(toolTypeAfterTempMove);
tempMoveToolSwitch = TemporaryMoveToolSwitch::NONE;
}
break;
}
}
if(world.main.input.pen.isEraser && !temporaryEraser && tempMoveToolSwitch == TemporaryMoveToolSwitch::NONE) {
if(drawTool->get_type() == DrawingProgramToolType::BRUSH)
switch_to_tool(DrawingProgramToolType::ERASER);

View file

@ -56,6 +56,10 @@ class DrawingProgram {
void invalidate_cache_at_component(CanvasComponentContainer::ObjInfo* objToCheck);
void preupdate_component(CanvasComponentContainer::ObjInfo* objToCheck);
void send_transforms_for(const std::vector<CanvasComponentContainer::ObjInfo*>& objsToSendTransformsFor);
void register_callbacks();
void deregister_callbacks();
private:
void process_transform_message(const std::vector<std::pair<NetworkingObjects::NetObjID, CoordSpaceHelper>>& transforms);
@ -121,6 +125,8 @@ class DrawingProgram {
uint32_t nextID = 0;
std::unordered_map<unsigned, InputManager::KeyCallbackManager::Callback*> keyCallbacks;
friend class EyeDropperTool;
friend class RectDrawTool;
friend class EllipseDrawTool;

View file

@ -494,32 +494,44 @@ void DrawingProgramSelection::update() {
commit_transform_selection();
return;
}
}
}
if(drawP.world.main.input.key(InputManager::KEY_GENERIC_ESCAPE).pressed)
deselect_all();
else if(drawP.world.main.input.key(InputManager::KEY_DRAW_DELETE).pressed)
void DrawingProgramSelection::register_key_callbacks() {
drawP.keyCallbacks[InputManager::KEY_DRAW_DELETE] = drawP.world.main.input.keyCallbacks[InputManager::KEY_DRAW_DELETE].register_callback([&](auto& key) {
if(key.down && !key.repeat)
delete_all();
else if(drawP.world.main.input.key(InputManager::KEY_COPY).pressed && !is_being_transformed())
});
drawP.keyCallbacks[InputManager::KEY_COPY] = drawP.world.main.input.keyCallbacks[InputManager::KEY_COPY].register_callback([&](auto& key) {
if(key.down && !key.repeat)
selection_to_clipboard();
else if(drawP.world.main.input.key(InputManager::KEY_CUT).pressed && !is_being_transformed()) {
});
drawP.keyCallbacks[InputManager::KEY_CUT] = drawP.world.main.input.keyCallbacks[InputManager::KEY_CUT].register_callback([&](auto& key) {
if(key.down && !key.repeat) {
selection_to_clipboard();
delete_all();
}
else if(drawP.world.main.input.key(InputManager::KEY_PASTE).pressed) {
});
drawP.keyCallbacks[InputManager::KEY_PASTE] = drawP.world.main.input.keyCallbacks[InputManager::KEY_PASTE].register_callback([&](auto& key) {
if(key.down && !key.repeat) {
deselect_all();
paste_clipboard(drawP.world.main.input.mouse.pos);
}
else if(drawP.world.main.input.key(InputManager::KEY_PASTE_IMAGE).pressed) {
});
drawP.keyCallbacks[InputManager::KEY_PASTE_IMAGE] = drawP.world.main.input.keyCallbacks[InputManager::KEY_PASTE_IMAGE].register_callback([&](auto& key) {
if(key.down && !key.repeat) {
deselect_all();
paste_image(drawP.world.main.input.mouse.pos);
}
}
else if(drawP.world.main.input.key(InputManager::KEY_PASTE).pressed && !drawP.prevent_undo_or_redo())
paste_clipboard(drawP.world.main.input.mouse.pos);
else if(drawP.world.main.input.key(InputManager::KEY_PASTE_IMAGE).pressed && !drawP.prevent_undo_or_redo())
paste_image(drawP.world.main.input.mouse.pos);
else if(drawP.world.main.input.key(InputManager::KEY_GENERIC_ESCAPE).pressed)
drawP.switch_to_tool(DrawingProgramToolType::EDIT, true);
});
drawP.keyCallbacks[InputManager::KEY_DESELECT_AND_EDIT_TOOL] = drawP.world.main.input.keyCallbacks[InputManager::KEY_DESELECT_AND_EDIT_TOOL].register_callback([&](auto& key) {
if(key.down && !key.repeat) {
if(is_something_selected())
deselect_all();
else
drawP.switch_to_tool(DrawingProgramToolType::EDIT, true);
}
});
}
void DrawingProgramSelection::deselect_all() {
@ -580,16 +592,18 @@ CanvasComponentContainer::ObjInfo* DrawingProgramSelection::get_front_object_col
void DrawingProgramSelection::selection_to_clipboard() {
check_add_stroke_color_change_undo();
auto& clipboard = drawP.world.main.clipboard;
std::unordered_set<NetworkingObjects::NetObjID> resourceSet;
for(auto& c : selectedSet)
c->obj->get_comp().get_used_resources(resourceSet);
clipboard.components.clear();
for(auto& c : selectedSet)
clipboard.components.emplace_back(c->obj->get_data_copy());
clipboard.pos = initialSelectionAABB.center();
clipboard.inverseScale = drawP.world.drawData.cam.c.inverseScale;
clipboard.resources = drawP.world.rMan.copy_resource_set_to_map(resourceSet);
if(is_something_selected()) {
auto& clipboard = drawP.world.main.clipboard;
std::unordered_set<NetworkingObjects::NetObjID> resourceSet;
for(auto& c : selectedSet)
c->obj->get_comp().get_used_resources(resourceSet);
clipboard.components.clear();
for(auto& c : selectedSet)
clipboard.components.emplace_back(c->obj->get_data_copy());
clipboard.pos = initialSelectionAABB.center();
clipboard.inverseScale = drawP.world.drawData.cam.c.inverseScale;
clipboard.resources = drawP.world.rMan.copy_resource_set_to_map(resourceSet);
}
}
void DrawingProgramSelection::paste_clipboard(Vector2f pasteScreenPos) {

View file

@ -28,6 +28,7 @@ class DrawingProgramSelection {
void delete_all();
void selection_to_clipboard();
CanvasComponentContainer::ObjInfo* get_front_object_colliding_with_in_editing_layer(const SCollision::ColliderCollection<float>& cC);
void register_key_callbacks();
private:
bool mouse_collided_with_selection_aabb();
bool mouse_collided_with_scale_point();

View file

@ -58,6 +58,7 @@ InputManager::InputManager() {
defaultKeyAssignments[{0, SDLK_F2}] = KEY_SHOW_PLAYER_LIST;
defaultKeyAssignments[{0, SDLK_SPACE}] = KEY_HOLD_TO_PAN;
defaultKeyAssignments[{0, SDLK_Z}] = KEY_HOLD_TO_ZOOM;
defaultKeyAssignments[{0, SDLK_ESCAPE}] = KEY_DESELECT_AND_EDIT_TOOL;
#ifdef __EMSCRIPTEN__
// Without this, SDL eats the CTRL-V event that initiates the paste event
@ -355,9 +356,11 @@ void InputManager::backend_key_down_update(const SDL_KeyboardEvent& e) {
// e.key != SDLK_LMETA && e.key != SDLK_RMETA &&
if(stopKeyInput)
if(stopKeyInput && !e.down)
return;
sdlKeyCallbacks.run_callbacks(e);
switch(kPress) {
case SDLK_UP:
set_key_down(e, KEY_TEXT_UP);
@ -482,12 +485,14 @@ void InputManager::backend_key_down_update(const SDL_KeyboardEvent& e) {
break;
}
if(text.get_accepting_input())
if(text.get_accepting_input() && e.down)
return;
auto f = keyAssignments.find({make_generic_key_mod(kMod), kPress});
if(f != keyAssignments.end())
if(f != keyAssignments.end()) {
set_key_down(e, f->second);
keyCallbacks[f->second].run_callbacks(KeyCallbackArgs{.down = e.down, .repeat = e.repeat});
}
}
void InputManager::call_text_paste(bool isRichTextPaste) {

View file

@ -8,6 +8,7 @@
#include <unordered_map>
#include <nlohmann/json.hpp>
#include <queue>
#include <Helpers/CallbackManager.hpp>
#include "UndoManager.hpp"
#include <SDL3/SDL.h>
@ -173,6 +174,7 @@ struct InputManager {
KEY_SHOW_PLAYER_LIST,
KEY_HOLD_TO_PAN,
KEY_HOLD_TO_ZOOM,
KEY_DESELECT_AND_EDIT_TOOL,
KEY_ASSIGNABLE_COUNT, // Not a real key
@ -231,6 +233,15 @@ struct InputManager {
const KeyData& key(KeyCode kCode);
CallbackManager<SDL_KeyboardEvent> sdlKeyCallbacks;
struct KeyCallbackArgs {
bool down;
bool repeat;
};
typedef CallbackManager<KeyCallbackArgs> KeyCallbackManager;
std::array<KeyCallbackManager, KEY_COUNT> keyCallbacks;
std::unordered_map<Vector2ui32, KeyCode> keyAssignments;
std::unordered_map<Vector2ui32, KeyCode> defaultKeyAssignments;
std::array<KeyData, KEY_COUNT> keys;
@ -266,5 +277,6 @@ NLOHMANN_JSON_SERIALIZE_ENUM(InputManager::KeyCodeEnum, {
{InputManager::KEY_OPEN_CHAT, "Open Chat"},
{InputManager::KEY_SHOW_PLAYER_LIST, "Show Player List"},
{InputManager::KEY_HOLD_TO_PAN, "Hold to Pan"},
{InputManager::KEY_HOLD_TO_ZOOM, "Hold to Zoom"}
{InputManager::KEY_HOLD_TO_ZOOM, "Hold to Zoom"},
{InputManager::KEY_DESELECT_AND_EDIT_TOOL, "Deselect selection / Edit Tool"}
})

View file

@ -61,6 +61,19 @@ MainProgram::MainProgram():
*logFile << "[CHAT] " << text << std::endl;
std::cout << "[CHAT] " << text << std::endl;
});
register_callbacks();
}
void MainProgram::register_callbacks() {
input.keyCallbacks[InputManager::KEY_NOGUI].register_callback([&](auto& keyData) {
if(keyData.down && !keyData.repeat)
drawGui = !drawGui;
});
input.keyCallbacks[InputManager::KEY_FULLSCREEN].register_callback([&](auto& keyData) {
if(keyData.down && !keyData.repeat)
drawGui = !drawGui;
});
}
void MainProgram::update() {
@ -97,8 +110,12 @@ void MainProgram::update() {
std::shared_ptr<World> oldWorld = world;
world = worlds[worldIndex];
if(oldWorld != world && oldWorld)
oldWorld->on_switch_out();
if(oldWorld != world) {
if(oldWorld)
oldWorld->on_tab_out();
if(world)
world->on_tab_in();
}
deltaTime.update_time_since();
deltaTime.update_time_point();
@ -114,11 +131,6 @@ void MainProgram::update() {
toolbar.update(); // GUI should be setup after the world data has been fully updated for this frame, so that the GUI reflects the current state of the world data
if(input.key(InputManager::KEY_NOGUI).pressed)
drawGui = !drawGui;
if(input.key(InputManager::KEY_FULLSCREEN).pressed)
input.toggleFullscreen = true;
NetLibrary::update();

View file

@ -122,6 +122,7 @@ class MainProgram {
~MainProgram();
private:
void register_callbacks();
void new_tab_open();
std::atomic<bool> tabSetToOpen = false;
World::OpenWorldInfo newTabToOpenInfo;

View file

@ -151,6 +151,9 @@ void World::init_client(const std::string& serverFullID) {
#endif
clientStillConnecting = false;
if(is_focus())
register_callbacks();
});
netClient->add_recv_callback(CLIENT_UPDATE_NETWORK_OBJECT, [&](cereal::PortableBinaryInputArchive& message) {
netObjMan.read_update_message(message, nullptr);
@ -191,11 +194,6 @@ void World::focus_update() {
drawData.cam.update_main(*this);
rMan.update();
if(main.input.key(InputManager::KEY_UNDO).repeat)
undo_with_checks();
else if(main.input.key(InputManager::KEY_REDO).repeat)
redo_with_checks();
}
void World::connection_update() {
@ -228,6 +226,10 @@ void World::connection_update() {
}
}
bool World::is_focus() {
return main.world.get() == this;
}
void World::undo_with_checks() {
if(!clientStillConnecting && !drawProg.prevent_undo_or_redo())
undo.undo();
@ -244,8 +246,33 @@ void World::unfocus_update() {
return;
}
void World::on_switch_out() {
void World::on_tab_out() {
rMan.clear_display_cache();
if(!clientStillConnecting)
deregister_callbacks();
}
void World::on_tab_in() {
if(!clientStillConnecting)
register_callbacks();
}
void World::register_callbacks() {
drawProg.register_callbacks();
redoKeyCallback = main.input.keyCallbacks[InputManager::KEY_REDO].register_callback([&](auto& key) {
if(key.down && !key.repeat)
redo_with_checks();
});
undoKeyCallback = main.input.keyCallbacks[InputManager::KEY_UNDO].register_callback([&](auto& key) {
if(key.down && !key.repeat)
undo_with_checks();
});
}
void World::deregister_callbacks() {
drawProg.deregister_callbacks();
main.input.keyCallbacks[InputManager::KEY_REDO].deregister_callback(redoKeyCallback);
main.input.keyCallbacks[InputManager::KEY_UNDO].deregister_callback(undoKeyCallback);
}
void World::send_chat_message(const std::string& message) {

View file

@ -66,7 +66,8 @@ class World {
void focus_update();
void unfocus_update();
void on_switch_out();
void on_tab_out();
void on_tab_in();
void draw(SkCanvas* canvas, const DrawData& calledDrawData);
void early_destroy();
@ -92,6 +93,7 @@ class World {
void scale_up_step();
bool should_ask_before_closing();
bool is_focus();
bool hasUnsavedLocalChanges = false;
bool setToDestroy = false;
@ -100,6 +102,11 @@ class World {
std::shared_ptr<NetServer> netServer;
std::shared_ptr<NetClient> netClient;
private:
InputManager::KeyCallbackManager::Callback* undoKeyCallback;
InputManager::KeyCallbackManager::Callback* redoKeyCallback;
void register_callbacks();
void deregister_callbacks();
void load_empty_canvas();
Vector3f get_random_cursor_color();