#include "DiabloUI/text_input.hpp" #include #include #include #include #ifdef USE_SDL3 #include #include #include #include #else #include #ifdef USE_SDL1 #include "utils/sdl2_to_1_2_backports.h" #endif #endif #include "utils/log.hpp" #include "utils/parse_int.hpp" #include "utils/sdl_compat.h" #include "utils/sdl_ptrs.h" #include "utils/str_cat.hpp" namespace devilution { namespace { bool HandleInputEvent(const SDL_Event &event, TextInputState &state, tl::function_ref typeFn, [[maybe_unused]] tl::function_ref assignFn) { const auto modState = SDL_GetModState(); const bool isCtrl = (modState & SDL_KMOD_CTRL) != 0; const bool isAlt = (modState & SDL_KMOD_ALT) != 0; const bool isShift = (modState & SDL_KMOD_SHIFT) != 0; switch (event.type) { case SDL_EVENT_KEY_DOWN: { switch (SDLC_EventKey(event)) { #ifndef USE_SDL1 case SDLK_A: if (isCtrl) { state.setCursorToStart(); state.setSelectCursorToEnd(); } return true; case SDLK_C: if (isCtrl) { const std::string selectedText { state.selectedText() }; if (SDLC_SetClipboardText(selectedText.c_str())) { LogError("Failed to set clipboard text: {}", SDL_GetError()); SDL_ClearError(); } } return true; case SDLK_X: if (isCtrl) { const std::string selectedText { state.selectedText() }; if (SDLC_SetClipboardText(selectedText.c_str())) { LogError("Failed to set clipboard text: {}", SDL_GetError()); SDL_ClearError(); } else { state.eraseSelection(); } } return true; case SDLK_V: if (isCtrl) { if (SDLC_HasClipboardText()) { const std::unique_ptr> clipboard { SDL_GetClipboardText() }; if (clipboard == nullptr) { LogError("Failed to get clipboard text: {}", SDL_GetError()); SDL_ClearError(); } else if (*clipboard != '\0') { typeFn(clipboard.get()); } } } return true; #endif case SDLK_BACKSPACE: state.backspace(/*word=*/isCtrl || isAlt); return true; case SDLK_DELETE: state.del(/*word=*/isCtrl || isAlt); return true; case SDLK_LEFT: isShift ? state.moveSelectCursorLeft(/*word=*/isCtrl || isAlt) : state.moveCursorLeft(/*word=*/isCtrl || isAlt); return true; case SDLK_RIGHT: isShift ? state.moveSelectCursorRight(/*word=*/isCtrl || isAlt) : state.moveCursorRight(/*word=*/isCtrl || isAlt); return true; case SDLK_HOME: isShift ? state.setSelectCursorToStart() : state.setCursorToStart(); return true; case SDLK_END: isShift ? state.setSelectCursorToEnd() : state.setCursorToEnd(); return true; default: break; } #ifdef USE_SDL1 if ((event.key.keysym.mod & KMOD_CTRL) == 0 && event.key.keysym.unicode >= ' ') { std::string utf8; AppendUtf8(event.key.keysym.unicode, utf8); typeFn(utf8); return true; } #else // Mark events that will also trigger SDL_TEXTINPUT as handled. return !isCtrl && !isAlt && SDLC_EventKey(event) >= SDLK_SPACE && SDLC_EventKey(event) <= SDLK_Z; #endif } break; #ifndef USE_SDL1 case SDL_EVENT_TEXT_INPUT: #ifdef __vita__ assignFn(event.text.text); #else typeFn(event.text.text); #endif return true; #ifdef USE_SDL3 case SDL_EVENT_TEXT_EDITING: case SDL_EVENT_TEXT_EDITING_CANDIDATES: #else case SDL_TEXTEDITING: #endif return true; #endif default: return false; } return false; } } // namespace bool HandleTextInputEvent(const SDL_Event &event, TextInputState &state) { return HandleInputEvent( event, state, [&](std::string_view str) { state.type(str); return true; }, [&](std::string_view str) { state.assign(str); return true; }); } [[nodiscard]] int NumberInputState::value(int defaultValue) const { return ParseInt(textInput_.value()).value_or(defaultValue); } std::string NumberInputState::filterStr(std::string_view str, bool allowMinus) { std::string result; if (allowMinus && !str.empty() && str[0] == '-') { str.remove_prefix(1); result += '-'; } for (const char c : str) { if (c >= '0' && c <= '9') { result += c; } } return result; } void NumberInputState::type(std::string_view str) { const std::string filtered = filterStr( str, /*allowMinus=*/min_ < 0 && textInput_.cursorPosition() == 0); if (filtered.empty()) return; textInput_.type(filtered); enforceRange(); } void NumberInputState::assign(std::string_view str) { const std::string filtered = filterStr(str, /*allowMinus=*/min_ < 0); if (filtered.empty()) { textInput_.clear(); return; } textInput_.assign(filtered); enforceRange(); } void NumberInputState::enforceRange() { if (textInput_.empty()) return; ParseIntResult parsed = ParseInt(textInput_.value()); if (parsed.has_value()) { if (*parsed > max_) { textInput_.assign(StrCat(max_)); } else if (*parsed < min_) { textInput_.assign(StrCat(min_)); } } } bool HandleNumberInputEvent(const SDL_Event &event, NumberInputState &state) { return HandleInputEvent( event, state.textInput(), [&](std::string_view str) { state.type(str); return true; }, [&](std::string_view str) { state.assign(str); return true; }); } } // namespace devilution