infinipaint/src/InputManager.cpp

1165 lines
44 KiB
C++
Raw Normal View History

2025-07-06 20:43:53 +03:00
#include "InputManager.hpp"
#include <SDL3/SDL_clipboard.h>
#include <SDL3/SDL_keyboard.h>
2025-07-06 20:43:53 +03:00
#include <SDL3/SDL_keycode.h>
2026-03-03 16:01:21 +03:00
#include <SDL3/SDL_pen.h>
#include <SDL3/SDL_timer.h>
2025-07-06 20:43:53 +03:00
#include <optional>
#include <Helpers/Logger.hpp>
2026-01-12 15:26:58 +03:00
#ifdef _WIN32
#include <include/core/SkStream.h>
#include <include/encode/SkPngEncoder.h>
#include "../deps/clip/clip.h"
#endif
2025-07-06 20:43:53 +03:00
#ifdef __EMSCRIPTEN__
#include <EmscriptenHelpers/emscripten_browser_clipboard.h>
extern "C" {
int isAcceptingInputEmscripten = 0;
EMSCRIPTEN_KEEPALIVE inline int is_text_input_happening() {
return isAcceptingInputEmscripten;
}
}
#endif
2026-03-01 17:28:43 +03:00
#include "MainProgram.hpp"
InputManager::InputManager(MainProgram& initMain):
main(initMain) {
2025-07-06 20:43:53 +03:00
frame_reset({0, 0});
defaultKeyAssignments[{0, SDLK_W}] = KEY_CAMERA_ROTATE_COUNTERCLOCKWISE;
defaultKeyAssignments[{0, SDLK_Q}] = KEY_CAMERA_ROTATE_CLOCKWISE;
defaultKeyAssignments[{0, SDLK_DELETE}] = KEY_DRAW_DELETE;
defaultKeyAssignments[{CTRL_MOD, SDLK_Z}] = KEY_UNDO;
defaultKeyAssignments[{CTRL_MOD, SDLK_R}] = KEY_REDO;
defaultKeyAssignments[{0, SDLK_TAB}] = KEY_NOGUI;
defaultKeyAssignments[{0, SDLK_F11}] = KEY_FULLSCREEN;
defaultKeyAssignments[{CTRL_MOD, SDLK_S}] = KEY_SAVE;
defaultKeyAssignments[{CTRL_MOD | SDL_KMOD_SHIFT, SDLK_S}] = KEY_SAVE_AS;
defaultKeyAssignments[{CTRL_MOD, SDLK_C}] = KEY_COPY;
defaultKeyAssignments[{CTRL_MOD, SDLK_X}] = KEY_CUT;
defaultKeyAssignments[{CTRL_MOD, SDLK_V}] = KEY_PASTE;
2026-01-06 19:06:44 +03:00
defaultKeyAssignments[{CTRL_MOD | SDL_KMOD_SHIFT, SDLK_V}] = KEY_PASTE_IMAGE;
2025-07-06 20:43:53 +03:00
defaultKeyAssignments[{0, SDLK_B}] = KEY_DRAW_TOOL_BRUSH;
defaultKeyAssignments[{0, SDLK_E}] = KEY_DRAW_TOOL_ERASER;
2026-01-07 10:34:51 +03:00
defaultKeyAssignments[{0, SDLK_U}] = KEY_DRAW_TOOL_ZOOM;
defaultKeyAssignments[{0, SDLK_L}] = KEY_DRAW_TOOL_LASSOSELECT;
defaultKeyAssignments[{0, SDLK_H}] = KEY_DRAW_TOOL_PAN;
2025-07-06 20:43:53 +03:00
defaultKeyAssignments[{0, SDLK_T}] = KEY_DRAW_TOOL_TEXTBOX;
defaultKeyAssignments[{0, SDLK_R}] = KEY_DRAW_TOOL_RECTANGLE;
defaultKeyAssignments[{0, SDLK_C}] = KEY_DRAW_TOOL_ELLIPSE;
defaultKeyAssignments[{0, SDLK_X}] = KEY_DRAW_TOOL_RECTSELECT;
2025-08-05 12:11:50 +03:00
defaultKeyAssignments[{0, SDLK_I}] = KEY_DRAW_TOOL_EYEDROPPER;
2025-07-06 20:43:53 +03:00
defaultKeyAssignments[{0, SDLK_P}] = KEY_DRAW_TOOL_SCREENSHOT;
2026-01-08 11:19:48 +03:00
defaultKeyAssignments[{0, SDLK_N}] = KEY_DRAW_TOOL_LINE;
2025-07-06 20:43:53 +03:00
defaultKeyAssignments[{0, SDLK_F1}] = KEY_OPEN_CHAT;
defaultKeyAssignments[{0, SDLK_F3}] = KEY_SHOW_METRICS;
defaultKeyAssignments[{0, SDLK_F2}] = KEY_SHOW_PLAYER_LIST;
defaultKeyAssignments[{0, SDLK_SPACE}] = KEY_HOLD_TO_PAN;
2026-01-07 10:34:51 +03:00
defaultKeyAssignments[{0, SDLK_Z}] = KEY_HOLD_TO_ZOOM;
defaultKeyAssignments[{0, SDLK_ESCAPE}] = KEY_DESELECT_AND_EDIT_TOOL;
2025-07-06 20:43:53 +03:00
2025-12-02 10:14:45 +03:00
#ifdef __EMSCRIPTEN__
// Without this, SDL eats the CTRL-V event that initiates the paste event
// https://github.com/pthom/hello_imgui/issues/3#issuecomment-1564536870
EM_ASM({
window.addEventListener('keydown', function(event) {
if((event.ctrlKey || event.metaKey) && (event.key == 'v' || event.code == 'KeyV')) {
if(Module["ccall"]('is_text_input_happening', 'number', [], []) === 1)
event.stopImmediatePropagation();
}
}, true);
});
emscripten_browser_clipboard::paste_event([](std::string&& pasteData, void* callbackData){
InputManager* inMan = (InputManager*)callbackData;
inMan->text.isNextPasteRich = true;
std::string pData = pasteData;
inMan->process_text_paste(pData);
}, this);
#endif
2025-07-06 20:43:53 +03:00
keyAssignments = defaultKeyAssignments;
}
void InputManager::set_rich_text_box_input_front(const std::shared_ptr<RichText::TextBox>& nTextBox, const std::shared_ptr<RichText::TextBox::Cursor>& nCursor, bool isRichTextBox, const std::optional<RichText::TextStyleModifier::ModifierMap>& nModMap) {
2026-03-01 17:28:43 +03:00
text.set_accepting_input(main.window.sdlWindow, true);
Text::TextBoxInfo textBoxInfo;
textBoxInfo.textBox = nTextBox;
textBoxInfo.cursor = nCursor;
textBoxInfo.modMap = nModMap;
textBoxInfo.isRichTextBox = isRichTextBox;
text.textBoxes.emplace_front(textBoxInfo);
}
void InputManager::set_rich_text_box_input_back(const std::shared_ptr<RichText::TextBox>& nTextBox, const std::shared_ptr<RichText::TextBox::Cursor>& nCursor, bool isRichTextBox, const std::optional<RichText::TextStyleModifier::ModifierMap>& nModMap) {
2026-03-01 17:28:43 +03:00
text.set_accepting_input(main.window.sdlWindow, true);
Text::TextBoxInfo textBoxInfo;
textBoxInfo.textBox = nTextBox;
textBoxInfo.cursor = nCursor;
textBoxInfo.modMap = nModMap;
textBoxInfo.isRichTextBox = isRichTextBox;
text.textBoxes.emplace_back(textBoxInfo);
}
void InputManager::remove_rich_text_box_input(const std::shared_ptr<RichText::TextBox>& nTextBox) {
std::erase_if(text.textBoxes, [&nTextBox](Text::TextBoxInfo& info) {
return (info.textBox == nTextBox);
});
if(text.textBoxes.empty())
2026-03-01 17:28:43 +03:00
text.set_accepting_input(main.window.sdlWindow, false);
2025-07-06 20:43:53 +03:00
}
void InputManager::Text::add_text_to_textbox(const std::string& inputText) {
if(!textBoxes.empty() && !inputText.empty()) {
auto& frontTextBox = textBoxes.front();
do_textbox_operation_with_undo([&]() {
frontTextBox.textBox->process_text_input(*frontTextBox.cursor, inputText, frontTextBox.modMap);
});
}
2025-07-06 20:43:53 +03:00
}
2025-11-12 11:36:14 +03:00
void InputManager::Text::add_textbox_undo(const RichText::TextBox::Cursor& prevCursor, const RichText::TextData& prevRichText) {
if(!textBoxes.empty()) {
auto& frontTextBox = textBoxes.front();
frontTextBox.textboxUndo.push({[textBox = frontTextBox.textBox, cursor = frontTextBox.cursor, prevCursor = prevCursor, prevRichText = prevRichText]() {
2025-11-12 11:36:14 +03:00
textBox->set_rich_text_data(prevRichText);
2025-11-12 14:54:46 +03:00
cursor->selectionBeginPos = cursor->selectionEndPos = cursor->pos = std::max(prevCursor.selectionEndPos, prevCursor.selectionBeginPos);
2025-11-12 11:36:14 +03:00
cursor->previousX = std::nullopt;
return true;
},
[textBox = frontTextBox.textBox, cursor = frontTextBox.cursor, currentCursor = *frontTextBox.cursor, currentRichText = frontTextBox.textBox->get_rich_text_data()]() {
2025-11-12 11:36:14 +03:00
textBox->set_rich_text_data(currentRichText);
2025-11-12 14:54:46 +03:00
cursor->selectionBeginPos = cursor->selectionEndPos = cursor->pos = std::max(currentCursor.selectionEndPos, currentCursor.selectionBeginPos);
2025-11-12 11:36:14 +03:00
cursor->previousX = std::nullopt;
return true;
}});
}
}
void InputManager::Text::do_textbox_operation_with_undo(const std::function<void()>& func) {
if(!textBoxes.empty()) {
auto& frontTextBox = textBoxes.front();
auto prevRichText = frontTextBox.textBox->get_rich_text_data();
auto prevCursor = *frontTextBox.cursor;
2025-11-12 11:36:14 +03:00
func();
add_textbox_undo(prevCursor, prevRichText);
}
}
bool InputManager::Text::is_accepting_input() {
return acceptingInput;
}
void InputManager::Text::set_accepting_input(SDL_Window* window, bool newAcceptingInputVal) {
if(!acceptingInput && newAcceptingInputVal) {
SDL_StartTextInput(window);
acceptingInput = true;
}
else if(acceptingInput && !newAcceptingInputVal) {
SDL_StopTextInput(window);
acceptingInput = false;
}
}
2025-12-02 09:53:03 +03:00
std::string InputManager::get_clipboard_str_SDL() {
2025-07-06 20:43:53 +03:00
char* data = SDL_GetClipboardText();
std::string toRet(data);
SDL_free(data);
return toRet;
2025-11-11 17:42:41 +03:00
}
2026-01-12 15:02:55 +03:00
#ifdef _WIN32
// From clip library example, useful for debugging
void print_clip_image_spec(const clip::image_spec& spec) {
std::cout << "Image in clipboard "
<< spec.width << "x" << spec.height
<< " (" << spec.bits_per_pixel << "bpp)\n"
<< "Format:" << "\n"
<< std::hex
<< " Red mask: " << spec.red_mask << "\n"
<< " Green mask: " << spec.green_mask << "\n"
<< " Blue mask: " << spec.blue_mask << "\n"
<< " Alpha mask: " << spec.alpha_mask << "\n"
<< std::dec
<< " Red shift: " << spec.red_shift << "\n"
<< " Green shift: " << spec.green_shift << "\n"
<< " Blue shift: " << spec.blue_shift << "\n"
<< " Alpha shift: " << spec.alpha_shift << "\n";
}
#endif
void InputManager::get_clipboard_image_data_SDL(const std::function<void(std::string_view data)>& callback) {
2026-01-05 19:15:42 +03:00
#ifndef __EMSCRIPTEN__
static std::unordered_set<std::string> validMimetypes;
if(validMimetypes.empty()) {
validMimetypes.emplace("image/png");
validMimetypes.emplace("image/gif");
validMimetypes.emplace("image/jpeg");
validMimetypes.emplace("image/svg+xml");
validMimetypes.emplace("image/webp");
#ifdef _WIN32
2026-01-11 16:16:48 +03:00
validMimetypes.emplace("image/bmp");
#endif
2026-01-05 19:15:42 +03:00
}
size_t mimeTypeSize;
char** mimeTypes = SDL_GetClipboardMimeTypes(&mimeTypeSize);
if(mimeTypes) {
for(size_t i = 0; i < mimeTypeSize; i++) {
if(validMimetypes.contains(mimeTypes[i])) {
if(std::string(mimeTypes[i]) == "image/bmp") {
2026-01-12 15:26:58 +03:00
#ifdef _WIN32
2026-01-12 15:02:55 +03:00
std::vector<uint8_t> result;
try {
if(!clip::has(clip::image_format()))
throw std::runtime_error("Clipboard doesn't contain an image");
clip::image img;
if(!clip::get_image(img))
throw std::runtime_error("Error getting image from clipboard");
clip::image_spec spec = img.spec();
SDL_PixelFormat pFormat = SDL_GetPixelFormatForMasks(spec.bits_per_pixel, spec.red_mask, spec.green_mask, spec.blue_mask, spec.alpha_mask);
if(pFormat != SDL_PIXELFORMAT_UNKNOWN) {
SDL_Surface* surfOriginal = SDL_CreateSurfaceFrom(spec.width, spec.height, pFormat, img.data(), spec.bytes_per_row);
if(surfOriginal) {
SDL_LockSurface(surfOriginal);
SDL_Surface* cSurf = SDL_ConvertSurface(surfOriginal, SDL_PIXELFORMAT_RGBA32);
SDL_UnlockSurface(surfOriginal);
SDL_DestroySurface(surfOriginal);
if(cSurf) {
SDL_LockSurface(cSurf);
SkDynamicMemoryWStream out;
2026-01-12 15:06:21 +03:00
if(SkPngEncoder::Encode(&out, SkPixmap(SkImageInfo::Make(cSurf->w, cSurf->h, kRGBA_8888_SkColorType, kUnpremul_SkAlphaType), cSurf->pixels, cSurf->pitch), {})) {
out.flush();
SDL_UnlockSurface(cSurf);
SDL_DestroySurface(cSurf);
result = out.detachAsVector();
}
else {
SDL_UnlockSurface(cSurf);
SDL_DestroySurface(cSurf);
2026-01-12 15:06:21 +03:00
throw std::runtime_error("Could not encode image data to PNG");
}
2026-01-12 15:02:55 +03:00
}
else
throw std::runtime_error("Bitmap could not be converted to new format");
}
else
2026-01-12 15:02:55 +03:00
throw std::runtime_error("Bitmap could not be read into surface");
}
2026-01-12 15:02:55 +03:00
else
throw std::runtime_error("Bitmap format is unsupported");
}
2026-01-12 15:02:55 +03:00
catch(const std::runtime_error& e) {
Logger::get().log("INFO", std::string("[InputManager::get_clipboard_image_data_SDL] Error thrown: ") + e.what());
}
catch(...) {
Logger::get().log("INFO", "[InputManager::get_clipboard_image_data_SDL] Unknown error thrown");
}
if(!result.empty())
callback(std::string_view((char*)(result.data()), result.size())); // This part of the code should throw errors
#endif
}
else {
size_t clipboardDataSize;
void* clipboardData = SDL_GetClipboardData(mimeTypes[i], &clipboardDataSize);
callback(std::string_view(static_cast<char*>(clipboardData), clipboardDataSize));
SDL_free(clipboardData);
}
2026-01-06 19:06:44 +03:00
return;
2026-01-05 19:15:42 +03:00
}
}
}
#endif
}
2025-07-06 20:43:53 +03:00
void InputManager::set_clipboard_str(std::string_view s) {
#ifdef __EMSCRIPTEN__
emscripten_browser_clipboard::copy(std::string(s));
lastCopiedRichText = std::nullopt;
2025-07-06 20:43:53 +03:00
#else
SDL_SetClipboardText(s.data());
#endif
}
void InputManager::set_clipboard_plain_and_richtext_pair(const std::pair<std::string, RichText::TextData>& plainAndRichtextPair) {
set_clipboard_str(plainAndRichtextPair.first);
lastCopiedRichText = plainAndRichtextPair.second;
2025-11-11 17:42:41 +03:00
}
2025-07-06 20:43:53 +03:00
std::string InputManager::key_assignment_to_str(const Vector2ui32& k) {
std::string toRet;
if(k.x() & SDL_KMOD_CTRL)
toRet += "CTRL+";
if(k.x() & SDL_KMOD_ALT)
toRet += "ALT+";
if(k.x() & SDL_KMOD_SHIFT)
toRet += "SHIFT+";
if(k.x() & SDL_KMOD_GUI)
toRet += "META+";
toRet += SDL_GetKeyName(k.y());
return toRet;
}
Vector2ui32 InputManager::key_assignment_from_str(const std::string& s) {
Vector2ui32 toRet = {0, 0};
if(s.find("CTRL+") != std::string::npos)
toRet.x() |= SDL_KMOD_CTRL;
if(s.find("ALT+") != std::string::npos)
toRet.x() |= SDL_KMOD_ALT;
if(s.find("SHIFT+") != std::string::npos)
toRet.x() |= SDL_KMOD_SHIFT;
if(s.find("META+") != std::string::npos)
toRet.x() |= SDL_KMOD_GUI;
size_t startInd = s.find_last_of("+");
if(startInd == std::string::npos)
startInd = 0;
else
startInd++;
toRet.y() = SDL_GetKeyFromName(s.substr(startInd).c_str());
if(toRet.y() == 0)
toRet.x() = 0;
return toRet;
}
const InputManager::KeyData& InputManager::key(KeyCode kCode) {
return keys[kCode];
}
void InputManager::set_key_down(const SDL_KeyboardEvent& e, KeyCode kCode) {
auto& k = keys[kCode];
if(!k.held || (std::chrono::steady_clock::now() - k.lastPressTime) > std::chrono::milliseconds(500)) // Timer works around some systems that emit a keypress twice (a repeat and a press event)
k.repeat = true;
if(!k.held) { // We use if statements instead of setting booleans so that values arent forced to false
k.pressed = true;
k.lastPressTime = std::chrono::steady_clock::now();
}
k.held = true;
}
void InputManager::set_key_up(const SDL_KeyboardEvent& e, KeyCode kCode) {
keys[kCode].held = false;
}
uint32_t InputManager::make_generic_key_mod(SDL_Keymod m) {
uint32_t toRet = 0;
if(m & SDL_KMOD_CTRL)
toRet |= SDL_KMOD_CTRL;
if(m & SDL_KMOD_ALT)
toRet |= SDL_KMOD_ALT;
if(m & SDL_KMOD_GUI)
toRet |= SDL_KMOD_GUI;
if(m & SDL_KMOD_SHIFT)
toRet |= SDL_KMOD_SHIFT;
return toRet;
}
void InputManager::backend_drop_file_event(const SDL_DropEvent& e) {
Vector2f mousePos = {e.x * main.window.density, e.y * main.window.density};
mouse.set_pos(mousePos);
main.input_drop_file_callback({
.pos = mousePos,
.source = e.source,
.data = e.data
});
}
void InputManager::backend_drop_text_event(const SDL_DropEvent& e) {
Vector2f mousePos = {e.x * main.window.density, e.y * main.window.density};
mouse.set_pos(mousePos);
main.input_drop_text_callback({
.pos = mousePos,
.source = e.source,
.data = e.data
});
}
2026-03-01 17:28:43 +03:00
void InputManager::backend_mouse_button_up_update(const SDL_MouseButtonEvent& e) {
Vector2f mousePos = {e.x * main.window.density, e.y * main.window.density};
2026-03-03 14:59:18 +03:00
mouse.set_pos(mousePos);
2026-03-01 17:28:43 +03:00
if(e.button == 1)
mouse.leftDown = false;
else if(e.button == 2)
mouse.middleDown = false;
else if(e.button == 3)
mouse.rightDown = false;
2026-03-03 14:59:18 +03:00
MouseButtonCallbackArgs args{
2026-03-02 23:09:48 +03:00
.button = static_cast<MouseButton>(e.button),
2026-03-01 17:28:43 +03:00
.down = e.down,
.clicks = e.clicks,
.pos = mousePos
2026-03-03 14:59:18 +03:00
};
main.input_mouse_button_callback(args);
main.input_pure_mouse_button_callback(args);
2026-03-01 17:28:43 +03:00
}
void InputManager::backend_mouse_button_down_update(const SDL_MouseButtonEvent& e) {
Vector2f mousePos = {e.x * main.window.density, e.y * main.window.density};
2026-03-03 14:59:18 +03:00
mouse.set_pos(mousePos);
2026-03-01 17:28:43 +03:00
if(e.button == 1) {
mouse.leftDown = true;
mouse.leftClicks = e.clicks;
}
else if(e.button == 2) {
mouse.middleDown = true;
mouse.middleClicks = e.clicks;
}
else if(e.button == 3) {
mouse.rightDown = true;
mouse.rightClicks = e.clicks;
}
2026-03-03 14:59:18 +03:00
MouseButtonCallbackArgs args{
2026-03-02 23:09:48 +03:00
.button = static_cast<MouseButton>(e.button),
2026-03-01 17:28:43 +03:00
.down = e.down,
.clicks = e.clicks,
.pos = mousePos
2026-03-03 14:59:18 +03:00
};
main.input_mouse_button_callback(args);
main.input_pure_mouse_button_callback(args);
2026-03-01 17:28:43 +03:00
}
void InputManager::backend_mouse_motion_update(const SDL_MouseMotionEvent& e) {
Vector2f mouseNewPos = {e.x * main.window.density, e.y * main.window.density};
mouse.set_pos(mouseNewPos);
2026-03-03 14:59:18 +03:00
Vector2f mouseRel = {e.xrel * main.window.density, e.yrel * main.window.density};
main.input_pure_mouse_motion_callback({
.pos = mouseNewPos,
.move = mouseRel
});
main.input_mouse_motion_callback({
2026-03-01 17:28:43 +03:00
.pos = mouseNewPos,
.move = mouseRel
});
}
void InputManager::backend_mouse_wheel_update(const SDL_MouseWheelEvent& e) {
2026-03-02 23:09:48 +03:00
Vector2f mouseNewPos = {e.mouse_x * main.window.density, e.mouse_y * main.window.density};
mouse.set_pos(mouseNewPos);
2026-03-03 14:59:18 +03:00
2026-03-01 17:28:43 +03:00
mouse.scrollAmount.x() += e.x;
mouse.scrollAmount.y() += e.y;
2026-03-02 23:09:48 +03:00
main.input_mouse_wheel_callback({
.mousePos = mouseNewPos,
.amount = {e.x, e.y},
.tickAmount = {e.integer_x, e.integer_y}
});
2026-03-01 17:28:43 +03:00
}
2026-03-03 14:59:18 +03:00
void InputManager::backend_pen_button_down_update(const SDL_PenButtonEvent& e) {
Vector2f mouseNewPos = {e.x * main.window.density, e.y * main.window.density};
mouse.set_pos(mouseNewPos);
pen.previousPos = mouseNewPos;
pen.buttons[e.button].held = true;
pen.buttons[e.button].pressed = true;
2026-03-03 16:01:21 +03:00
pen.isEraser = e.pen_state & SDL_PEN_INPUT_ERASER_TIP;
2026-03-03 14:59:18 +03:00
if(e.button == main.toolbar.tabletOptions.middleClickButton) {
main.input_mouse_button_callback({
.button = MouseButton::MIDDLE,
.down = e.down,
.clicks = 1,
.pos = mouseNewPos
});
}
else if(e.button == main.toolbar.tabletOptions.rightClickButton) {
main.input_mouse_button_callback({
.button = MouseButton::RIGHT,
.down = e.down,
.clicks = 1,
.pos = mouseNewPos
});
}
main.input_pen_button_callback({
.button = e.button,
.down = e.down,
.pos = mouseNewPos
});
}
void InputManager::backend_pen_button_up_update(const SDL_PenButtonEvent& e) {
Vector2f mouseNewPos = {e.x * main.window.density, e.y * main.window.density};
mouse.set_pos(mouseNewPos);
pen.previousPos = mouseNewPos;
pen.buttons[e.button].held = false;
2026-03-03 16:01:21 +03:00
pen.isEraser = e.pen_state & SDL_PEN_INPUT_ERASER_TIP;
2026-03-03 14:59:18 +03:00
if(e.button == main.toolbar.tabletOptions.middleClickButton) {
main.input_mouse_button_callback({
.button = MouseButton::MIDDLE,
.down = e.down,
.clicks = 1,
.pos = mouseNewPos
});
}
else if(e.button == main.toolbar.tabletOptions.rightClickButton) {
main.input_mouse_button_callback({
.button = MouseButton::RIGHT,
.down = e.down,
.clicks = 1,
.pos = mouseNewPos
});
}
main.input_pen_button_callback({
.button = e.button,
.down = e.down,
.pos = mouseNewPos
});
}
void InputManager::backend_pen_touch_down_update(const SDL_PenTouchEvent& e) {
Vector2f mouseNewPos = {e.x * main.window.density, e.y * main.window.density};
mouse.set_pos(mouseNewPos);
pen.previousPos = mouseNewPos;
pen.isDown = true;
mouse.leftDown = true;
if((std::chrono::steady_clock::now() - pen.lastPenLeftClickTime) > std::chrono::milliseconds(300))
pen.leftClicksSaved = 0;
pen.leftClicksSaved++;
mouse.leftClicks = pen.leftClicksSaved;
pen.lastPenLeftClickTime = std::chrono::steady_clock::now();
pen.isEraser = e.eraser;
main.input_mouse_button_callback({
.button = MouseButton::LEFT,
.down = e.down,
.clicks = mouse.leftClicks,
.pos = mouseNewPos
});
main.input_pen_touch_callback({
.down = e.down,
.eraser = e.eraser,
.pos = mouseNewPos
});
}
void InputManager::backend_pen_touch_up_update(const SDL_PenTouchEvent& e) {
Vector2f mouseNewPos = {e.x * main.window.density, e.y * main.window.density};
mouse.set_pos(mouseNewPos);
pen.previousPos = mouseNewPos;
pen.isDown = false;
mouse.leftDown = false;
pen.isEraser = e.eraser;
pen.pressure = 0.0;
main.input_mouse_button_callback({
.button = MouseButton::LEFT,
.down = e.down,
.clicks = 0,
.pos = mouseNewPos
});
main.input_pen_touch_callback({
.down = e.down,
.eraser = e.eraser,
.pos = mouseNewPos
});
}
void InputManager::backend_pen_motion_update(const SDL_PenMotionEvent& e) {
Vector2f mouseNewPos = {e.x * main.window.density, e.y * main.window.density};
mouse.set_pos(mouseNewPos);
Vector2f mouseRel = mouseNewPos - pen.previousPos;
pen.previousPos = mouseNewPos;
2026-03-03 16:01:21 +03:00
pen.isEraser = e.pen_state & SDL_PEN_INPUT_ERASER_TIP;
2026-03-03 14:59:18 +03:00
main.input_mouse_motion_callback({
.pos = mouseNewPos,
.move = mouseRel
});
main.input_pen_motion_callback({
.pos = mouseNewPos,
.move = mouseRel
});
}
2026-03-03 16:01:21 +03:00
void InputManager::backend_pen_axis_update(const SDL_PenAxisEvent& e) {
Vector2f mouseNewPos = {e.x * main.window.density, e.y * main.window.density};
mouse.set_pos(mouseNewPos);
pen.previousPos = mouseNewPos;
pen.isEraser = e.pen_state & SDL_PEN_INPUT_ERASER_TIP;
// For now, only pass pressure axis
if(e.axis == SDL_PEN_AXIS_PRESSURE) {
pen.pressure = e.value;
main.input_pen_axis_callback({
.pos = mouseNewPos,
.axis = e.axis,
.value = e.value
});
}
}
std::vector<Vector2f> InputManager::get_multiple_finger_positions() {
std::vector<Vector2f> pos;
for(const SDL_TouchFingerEvent& fingerEvent : touch.fingers)
pos.emplace_back(fingerEvent.x * main.window.size.x() * main.window.density, fingerEvent.y * main.window.size.y() * main.window.density);
return pos;
}
std::vector<Vector2f> InputManager::get_multiple_finger_motions() {
std::vector<Vector2f> pos;
for(const SDL_TouchFingerEvent& fingerEvent : touch.fingers)
pos.emplace_back(fingerEvent.dx * main.window.size.x() * main.window.density, fingerEvent.dy * main.window.size.y() * main.window.density);
return pos;
}
void InputManager::backend_touch_finger_down_update(const SDL_TouchFingerEvent& e) {
touch.fingers.emplace_back(e);
switch(touch.touchEventType) {
case Touch::NO_TOUCH_EVENT: {
if(touch.fingers.size() == 2) {
touch.isDown = true;
touch.touchEventType = Touch::TWO_FINGER_EVENT;
main.input_multi_finger_touch_callback({
.down = true,
.pos = get_multiple_finger_positions()
});
}
break;
}
case Touch::ONE_FINGER_EVENT: {
break;
}
case Touch::TWO_FINGER_EVENT: {
break;
}
case Touch::EVENT_DONE: {
if(touch.fingers.size() == 1)
touch.touchEventType = Touch::NO_TOUCH_EVENT;
break;
}
}
}
void InputManager::backend_touch_finger_up_update(const SDL_TouchFingerEvent& e) {
switch(touch.touchEventType) {
case Touch::NO_TOUCH_EVENT: {
Vector2f mouseNewPos = {e.x * main.window.size.x() * main.window.density, e.y * main.window.size.y() * main.window.density};
mouse.set_pos(mouseNewPos);
touch.isDown = false;
mouse.leftDown = false;
if((std::chrono::steady_clock::now() - touch.lastLeftClickTime) > std::chrono::milliseconds(300))
touch.leftClicksSaved = 0;
touch.leftClicksSaved++;
mouse.leftClicks = touch.leftClicksSaved;
touch.lastLeftClickTime = std::chrono::steady_clock::now();
main.input_mouse_button_callback({
.button = MouseButton::LEFT,
.down = true,
.clicks = mouse.leftClicks,
.pos = mouseNewPos
});
main.input_mouse_button_callback({
.button = MouseButton::LEFT,
.down = false,
.clicks = 0,
.pos = mouseNewPos
});
touch.touchEventType = Touch::EVENT_DONE;
break;
}
case Touch::ONE_FINGER_EVENT: {
Vector2f mouseNewPos = {e.x * main.window.size.x() * main.window.density, e.y * main.window.size.y() * main.window.density};
mouse.set_pos(mouseNewPos);
touch.isDown = false;
mouse.leftDown = false;
main.input_mouse_button_callback({
.button = MouseButton::LEFT,
.down = false,
.clicks = 0,
.pos = mouseNewPos
});
touch.touchEventType = Touch::EVENT_DONE;
break;
}
case Touch::TWO_FINGER_EVENT: {
touch.isDown = false;
main.input_multi_finger_touch_callback({
.down = false,
.pos = get_multiple_finger_positions()
});
touch.touchEventType = Touch::EVENT_DONE;
break;
}
case Touch::EVENT_DONE: {
break;
}
}
std::erase_if(touch.fingers, [&e](auto& tE) {
return e.fingerID == tE.fingerID;
});
}
void InputManager::backend_touch_finger_motion_update(const SDL_TouchFingerEvent& e) {
switch(touch.touchEventType) {
case Touch::NO_TOUCH_EVENT: {
{
auto& prevTouch = touch.fingers[0];
Vector2f mouseNewPos = {prevTouch.x * main.window.size.x() * main.window.density, prevTouch.y * main.window.size.y() * main.window.density};
mouse.set_pos(mouseNewPos);
mouse.leftDown = true;
if((std::chrono::steady_clock::now() - touch.lastLeftClickTime) > std::chrono::milliseconds(300))
touch.leftClicksSaved = 0;
touch.leftClicksSaved++;
mouse.leftClicks = touch.leftClicksSaved;
touch.lastLeftClickTime = std::chrono::steady_clock::now();
main.input_mouse_button_callback({
.button = MouseButton::LEFT,
.down = true,
.clicks = mouse.leftClicks,
.pos = mouseNewPos
});
}
{
Vector2f mouseNewPos = {e.x * main.window.size.x() * main.window.density, e.y * main.window.size.y() * main.window.density};
mouse.set_pos(mouseNewPos);
Vector2f mouseRel = {e.dx * main.window.size.x() * main.window.density, e.dy * main.window.size.y() * main.window.density};
main.input_mouse_motion_callback({
.pos = mouseNewPos,
.move = mouseRel
});
}
touch.touchEventType = Touch::ONE_FINGER_EVENT;
break;
}
case Touch::ONE_FINGER_EVENT: {
Vector2f mouseNewPos = {e.x * main.window.size.x() * main.window.density, e.y * main.window.size.y() * main.window.density};
mouse.set_pos(mouseNewPos);
Vector2f mouseRel = {e.dx * main.window.size.x() * main.window.density, e.dy * main.window.size.y() * main.window.density};
main.input_mouse_motion_callback({
.pos = mouseNewPos,
.move = mouseRel
});
break;
}
case Touch::TWO_FINGER_EVENT: {
main.input_multi_finger_motion_callback({
.pos = get_multiple_finger_positions(),
.move = get_multiple_finger_motions()
});
break;
}
case Touch::EVENT_DONE: {
break;
}
}
for(auto& tE : touch.fingers) {
if(tE.fingerID == e.fingerID) {
tE = e;
break;
}
}
}
2025-07-06 20:43:53 +03:00
void InputManager::backend_key_down_update(const SDL_KeyboardEvent& e) {
auto kPress = e.key;
auto kMod = e.mod;
if(kPress != SDLK_LSHIFT && kPress != SDLK_RSHIFT && kPress != SDLK_LCTRL && kPress != SDLK_RCTRL &&
kPress != SDLK_LGUI && kPress != SDLK_RGUI &&
kPress != SDLK_LALT && kPress != SDLK_RALT) {
lastPressedKeybind = {make_generic_key_mod(kMod), kPress};
}
// e.key != SDLK_LMETA && e.key != SDLK_RMETA &&
if(stopKeyInput)
2025-07-06 20:43:53 +03:00
return;
std::shared_ptr<RichText::TextBox> textBox = text.textBoxes.empty() ? nullptr : text.textBoxes.front().textBox;
std::shared_ptr<RichText::TextBox::Cursor> cursor = text.textBoxes.empty() ? nullptr : text.textBoxes.front().cursor;
std::optional<RichText::TextStyleModifier::ModifierMap> modMap = text.textBoxes.empty() ? std::nullopt : text.textBoxes.front().modMap;
2025-07-06 20:43:53 +03:00
switch(kPress) {
case SDLK_UP:
set_key_down(e, KEY_TEXT_UP);
if(textBox && key(KEY_TEXT_UP).repeat)
textBox->process_key_input(*cursor, RichText::TextBox::InputKey::UP, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, modMap);
2025-07-06 20:43:53 +03:00
break;
case SDLK_DOWN:
set_key_down(e, KEY_TEXT_DOWN);
if(textBox && key(KEY_TEXT_DOWN).repeat)
textBox->process_key_input(*cursor, RichText::TextBox::InputKey::DOWN, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, modMap);
2025-07-06 20:43:53 +03:00
break;
case SDLK_LEFT:
set_key_down(e, KEY_TEXT_LEFT);
if(textBox && key(KEY_TEXT_LEFT).repeat)
textBox->process_key_input(*cursor, RichText::TextBox::InputKey::LEFT, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, modMap);
2025-07-06 20:43:53 +03:00
break;
case SDLK_RIGHT:
set_key_down(e, KEY_TEXT_RIGHT);
if(textBox && key(KEY_TEXT_RIGHT).repeat)
textBox->process_key_input(*cursor, RichText::TextBox::InputKey::RIGHT, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, modMap);
2025-07-06 20:43:53 +03:00
break;
case SDLK_BACKSPACE:
set_key_down(e, KEY_TEXT_BACKSPACE);
if(textBox && key(KEY_TEXT_BACKSPACE).repeat)
2025-11-12 11:36:14 +03:00
text.do_textbox_operation_with_undo([&]() {
textBox->process_key_input(*cursor, RichText::TextBox::InputKey::BACKSPACE, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, modMap);
2025-11-12 11:36:14 +03:00
});
2025-07-06 20:43:53 +03:00
break;
case SDLK_DELETE:
set_key_down(e, KEY_TEXT_DELETE);
if(textBox && key(KEY_TEXT_DELETE).repeat)
2025-11-12 11:36:14 +03:00
text.do_textbox_operation_with_undo([&]() {
textBox->process_key_input(*cursor, RichText::TextBox::InputKey::DEL, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, modMap);
2025-11-12 11:36:14 +03:00
});
2025-07-06 20:43:53 +03:00
break;
case SDLK_HOME:
set_key_down(e, KEY_TEXT_HOME);
if(textBox && key(KEY_TEXT_HOME).repeat)
textBox->process_key_input(*cursor, RichText::TextBox::InputKey::HOME, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, modMap);
break;
case SDLK_END:
set_key_down(e, KEY_TEXT_END);
if(textBox && key(KEY_TEXT_END).repeat)
textBox->process_key_input(*cursor, RichText::TextBox::InputKey::END, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, modMap);
2025-07-06 20:43:53 +03:00
break;
case SDLK_LSHIFT:
set_key_down(e, KEY_TEXT_SHIFT);
set_key_down(e, KEY_GENERIC_LSHIFT);
break;
case SDLK_LALT:
set_key_down(e, KEY_GENERIC_LALT);
2025-07-06 20:43:53 +03:00
break;
case SDLK_LCTRL:
set_key_down(e, KEY_TEXT_CTRL);
set_key_down(e, KEY_GENERIC_LCTRL);
2025-07-06 20:43:53 +03:00
break;
case SDLK_LMETA:
set_key_down(e, KEY_TEXT_META);
set_key_down(e, KEY_GENERIC_LMETA);
break;
2025-07-06 20:43:53 +03:00
case SDLK_C:
// Use either Ctrl or Meta (command for Mac) keys. We do either instead of checking, since checking can get complicated on Emscripten
if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL))
set_key_down(e, KEY_TEXT_COPY);
if(textBox && key(KEY_TEXT_COPY).repeat)
set_clipboard_plain_and_richtext_pair(textBox->process_copy(*cursor));
2025-07-06 20:43:53 +03:00
break;
case SDLK_X:
if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL))
set_key_down(e, KEY_TEXT_CUT);
if(textBox && key(KEY_TEXT_CUT).repeat)
2025-11-12 11:36:14 +03:00
text.do_textbox_operation_with_undo([&]() {
set_clipboard_plain_and_richtext_pair(textBox->process_cut(*cursor));
2025-11-12 11:36:14 +03:00
});
2025-07-06 20:43:53 +03:00
break;
case SDLK_V:
if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL))
set_key_down(e, KEY_TEXT_PASTE);
if(textBox && key(KEY_TEXT_PASTE).repeat)
2025-11-12 11:36:14 +03:00
text.do_textbox_operation_with_undo([&]() {
2025-12-02 09:53:03 +03:00
call_text_paste(true);
2025-11-12 11:36:14 +03:00
});
2025-07-06 20:43:53 +03:00
break;
case SDLK_A:
if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL))
set_key_down(e, KEY_TEXT_SELECTALL);
if(textBox && key(KEY_TEXT_SELECTALL).repeat)
textBox->process_key_input(*cursor, RichText::TextBox::InputKey::SELECT_ALL, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, modMap);
2025-07-06 20:43:53 +03:00
break;
2025-11-12 11:36:14 +03:00
case SDLK_Z:
if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL))
set_key_down(e, KEY_TEXT_UNDO);
if(textBox && key(KEY_TEXT_UNDO).repeat)
text.textBoxes.front().textboxUndo.undo();
2025-11-12 11:36:14 +03:00
break;
case SDLK_R:
if((kMod & SDL_KMOD_GUI) || (kMod & SDL_KMOD_CTRL))
set_key_down(e, KEY_TEXT_REDO);
if(textBox && key(KEY_TEXT_REDO).repeat)
text.textBoxes.front().textboxUndo.redo();
2025-11-12 11:36:14 +03:00
break;
case SDLK_RETURN: {
2025-07-06 20:43:53 +03:00
set_key_down(e, KEY_TEXT_ENTER);
if(textBox && key(KEY_TEXT_ENTER).repeat)
2025-11-12 11:36:14 +03:00
text.do_textbox_operation_with_undo([&]() {
textBox->process_key_input(*cursor, RichText::TextBox::InputKey::ENTER, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, modMap);
2025-11-12 11:36:14 +03:00
});
2026-03-04 12:15:24 +03:00
main.input_key_callback(KeyCallbackArgs{.key = KEY_TEXT_ENTER, .down = e.down, .repeat = e.repeat});
2025-07-06 20:43:53 +03:00
break;
}
case SDLK_TAB: {
set_key_down(e, KEY_TEXT_TAB);
if(textBox && key(KEY_TEXT_TAB).repeat)
2025-11-12 11:36:14 +03:00
text.do_textbox_operation_with_undo([&]() {
textBox->process_key_input(*cursor, RichText::TextBox::InputKey::TAB, ctrl_or_meta_held(), key(KEY_GENERIC_LSHIFT).held, modMap);
2025-11-12 11:36:14 +03:00
});
break;
}
2025-07-06 20:43:53 +03:00
case SDLK_ESCAPE:
2026-03-04 12:15:24 +03:00
set_key_down(e, KEY_TEXT_ESCAPE);
main.input_key_callback(KeyCallbackArgs{.key = KEY_TEXT_ESCAPE, .down = e.down, .repeat = e.repeat});
2025-07-06 20:43:53 +03:00
break;
default:
break;
}
if(text.acceptingInput)
2025-07-06 20:43:53 +03:00
return;
auto f = keyAssignments.find({make_generic_key_mod(kMod), kPress});
if(f != keyAssignments.end()) {
2025-07-06 20:43:53 +03:00
set_key_down(e, f->second);
main.input_key_callback(KeyCallbackArgs{.key = f->second, .down = e.down, .repeat = e.repeat});
}
switch(kPress) {
case SDLK_UP:
2026-03-04 12:15:24 +03:00
set_key_down(e, KEY_GENERIC_UP);
main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_UP, .down = e.down, .repeat = e.repeat});
break;
case SDLK_DOWN:
2026-03-04 12:15:24 +03:00
set_key_down(e, KEY_GENERIC_DOWN);
main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_DOWN, .down = e.down, .repeat = e.repeat});
break;
case SDLK_LEFT:
2026-03-04 12:15:24 +03:00
set_key_down(e, KEY_GENERIC_LEFT);
main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_LEFT, .down = e.down, .repeat = e.repeat});
break;
case SDLK_RIGHT:
2026-03-04 12:15:24 +03:00
set_key_down(e, KEY_GENERIC_RIGHT);
main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_RIGHT, .down = e.down, .repeat = e.repeat});
break;
case SDLK_RETURN:
set_key_down(e, KEY_GENERIC_ENTER);
main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_ENTER, .down = e.down, .repeat = e.repeat});
break;
case SDLK_ESCAPE:
set_key_down(e, KEY_GENERIC_ESCAPE);
main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_ESCAPE, .down = e.down, .repeat = e.repeat});
break;
}
2025-07-06 20:43:53 +03:00
}
2025-12-02 09:53:03 +03:00
void InputManager::call_text_paste(bool isRichTextPaste) {
text.isNextPasteRich = isRichTextPaste;
#ifdef __EMSCRIPTEN__
2025-12-02 10:14:45 +03:00
emscripten_browser_clipboard::paste_async([](std::string&& pasteData, void* callbackData){
2025-12-02 09:53:03 +03:00
InputManager* inMan = (InputManager*)callbackData;
std::string pData = pasteData;
inMan->process_text_paste(pData);
}, this);
#else
process_text_paste(get_clipboard_str_SDL());
#endif
}
void InputManager::process_text_paste(const std::string& plainClipboardStr) {
if(!text.textBoxes.empty()) {
// Workaround for not being able to copy richtext to system clipboard, this should at least work within the application itself
if(text.textBoxes.front().isRichTextBox && text.isNextPasteRich && lastCopiedRichText.has_value()) {
if(lastCopiedRichText.value().get_plain_text() == remove_carriage_returns_from_str(plainClipboardStr))
text.textBoxes.front().textBox->process_rich_text_input(*text.textBoxes.front().cursor, lastCopiedRichText.value());
else
text.textBoxes.front().textBox->process_text_input(*text.textBoxes.front().cursor, plainClipboardStr, text.textBoxes.front().modMap);
}
else
text.textBoxes.front().textBox->process_text_input(*text.textBoxes.front().cursor, plainClipboardStr, text.textBoxes.front().modMap);
}
}
bool InputManager::ctrl_or_meta_held() {
return key(KEY_GENERIC_LCTRL).held || key(KEY_GENERIC_LMETA).held;
}
2025-07-06 20:43:53 +03:00
void InputManager::backend_key_up_update(const SDL_KeyboardEvent& e) {
auto kPress = e.key;
//auto kMod = e.mod;
switch(kPress) {
case SDLK_UP:
set_key_up(e, KEY_TEXT_UP);
break;
case SDLK_DOWN:
set_key_up(e, KEY_TEXT_DOWN);
break;
case SDLK_LEFT:
set_key_up(e, KEY_TEXT_LEFT);
break;
case SDLK_RIGHT:
set_key_up(e, KEY_TEXT_RIGHT);
break;
case SDLK_BACKSPACE:
set_key_up(e, KEY_TEXT_BACKSPACE);
break;
case SDLK_DELETE:
set_key_up(e, KEY_TEXT_DELETE);
break;
case SDLK_HOME:
set_key_up(e, KEY_TEXT_HOME);
break;
case SDLK_END:
set_key_up(e, KEY_TEXT_END);
break;
2025-07-06 20:43:53 +03:00
case SDLK_LSHIFT:
set_key_up(e, KEY_TEXT_SHIFT);
set_key_up(e, KEY_GENERIC_LSHIFT);
break;
case SDLK_LALT:
set_key_up(e, KEY_GENERIC_LALT);
2025-07-06 20:43:53 +03:00
break;
case SDLK_LCTRL:
set_key_up(e, KEY_TEXT_CTRL);
set_key_up(e, KEY_GENERIC_LCTRL);
2025-07-06 20:43:53 +03:00
break;
case SDLK_C:
set_key_up(e, KEY_TEXT_COPY);
break;
case SDLK_X:
set_key_up(e, KEY_TEXT_CUT);
break;
case SDLK_V:
set_key_up(e, KEY_TEXT_PASTE);
break;
case SDLK_A:
set_key_up(e, KEY_TEXT_SELECTALL);
break;
2025-11-12 11:36:14 +03:00
case SDLK_Z:
set_key_up(e, KEY_TEXT_UNDO);
break;
case SDLK_R:
set_key_up(e, KEY_TEXT_REDO);
break;
2025-07-06 20:43:53 +03:00
case SDLK_RETURN:
set_key_up(e, KEY_TEXT_ENTER);
2026-03-04 12:15:24 +03:00
main.input_key_callback(KeyCallbackArgs{.key = KEY_TEXT_ENTER, .down = e.down, .repeat = e.repeat});
2025-07-06 20:43:53 +03:00
break;
case SDLK_TAB:
set_key_up(e, KEY_TEXT_TAB);
break;
2025-07-06 20:43:53 +03:00
case SDLK_ESCAPE:
2026-03-04 12:15:24 +03:00
set_key_up(e, KEY_TEXT_ESCAPE);
main.input_key_callback(KeyCallbackArgs{.key = KEY_TEXT_ESCAPE, .down = e.down, .repeat = e.repeat});
2025-07-06 20:43:53 +03:00
break;
default:
break;
}
for(auto& p : keyAssignments) {
if(p.first.y() == kPress) { // Dont try matching modifiers when setting key to go up
2025-07-06 20:43:53 +03:00
set_key_up(e, p.second);
main.input_key_callback(KeyCallbackArgs{.key = p.second, .down = e.down, .repeat = e.repeat});
}
}
switch(kPress) {
case SDLK_UP:
2026-03-04 12:15:24 +03:00
set_key_up(e, KEY_GENERIC_UP);
main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_UP, .down = e.down, .repeat = e.repeat});
break;
case SDLK_DOWN:
2026-03-04 12:15:24 +03:00
set_key_up(e, KEY_GENERIC_DOWN);
main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_DOWN, .down = e.down, .repeat = e.repeat});
break;
case SDLK_LEFT:
2026-03-04 12:15:24 +03:00
set_key_up(e, KEY_GENERIC_LEFT);
main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_LEFT, .down = e.down, .repeat = e.repeat});
break;
case SDLK_RIGHT:
2026-03-04 12:15:24 +03:00
set_key_up(e, KEY_GENERIC_RIGHT);
main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_RIGHT, .down = e.down, .repeat = e.repeat});
break;
case SDLK_RETURN:
set_key_up(e, KEY_GENERIC_ENTER);
main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_ENTER, .down = e.down, .repeat = e.repeat});
break;
case SDLK_ESCAPE:
set_key_up(e, KEY_GENERIC_ESCAPE);
main.input_key_callback(KeyCallbackArgs{.key = KEY_GENERIC_ESCAPE, .down = e.down, .repeat = e.repeat});
break;
2025-07-06 20:43:53 +03:00
}
}
void InputManager::stop_key_input() {
stopKeyInput = 2;
}
void InputManager::Mouse::set_pos(const Vector2f& newPos) {
pos = newPos;
move = newPos - lastPos;
}
void InputManager::update() {
if(touch.touchEventType == Touch::NO_TOUCH_EVENT && (SDL_GetTicksNS() - touch.fingers[0].timestamp) > (100 * 1000000)) { // 100ms to determine whether touch is with a single finger
auto& prevTouch = touch.fingers[0];
Vector2f mouseNewPos = {prevTouch.x * main.window.size.x() * main.window.density, prevTouch.y * main.window.size.y() * main.window.density};
mouse.set_pos(mouseNewPos);
mouse.leftDown = true;
if((std::chrono::steady_clock::now() - touch.lastLeftClickTime) > std::chrono::milliseconds(300))
touch.leftClicksSaved = 0;
touch.leftClicksSaved++;
mouse.leftClicks = touch.leftClicksSaved;
touch.lastLeftClickTime = std::chrono::steady_clock::now();
main.input_mouse_button_callback({
.button = MouseButton::LEFT,
.down = true,
.clicks = mouse.leftClicks,
.pos = mouseNewPos
});
touch.touchEventType = Touch::ONE_FINGER_EVENT;
}
}
2025-07-06 20:43:53 +03:00
void InputManager::frame_reset(const Vector2i& windowSize) {
mouse.lastPos = mouse.pos;
mouse.move = Vector2f({0.0f, 0.0f});
mouse.scrollAmount = Vector2f({0.0f, 0.0f});
mouse.leftClicks = 0;
mouse.rightClicks = 0;
mouse.middleClicks = 0;
2025-07-06 20:43:53 +03:00
cursorIcon = SystemCursorType::DEFAULT;
for(auto& p : keys) {
p.pressed = false;
p.repeat = false;
}
for(auto& p : pen.buttons)
p.pressed = false;
lastPressedKeybind = std::nullopt;
if(stopKeyInput)
stopKeyInput--;
toggleFullscreen = false;
hideCursor = false;
#ifdef __EMSCRIPTEN__
isAcceptingInputEmscripten = text.acceptingInput ? 1 : 0;
#endif
}