You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
221 lines
5.5 KiB
221 lines
5.5 KiB
#include "DiabloUI/text_input.hpp" |
|
|
|
#include <memory> |
|
#include <string> |
|
#include <string_view> |
|
|
|
#include <function_ref.hpp> |
|
|
|
#ifdef USE_SDL3 |
|
#include <SDL3/SDL_clipboard.h> |
|
#include <SDL3/SDL_events.h> |
|
#include <SDL3/SDL_keyboard.h> |
|
#include <SDL3/SDL_keycode.h> |
|
#else |
|
#include <SDL.h> |
|
|
|
#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<bool(std::string_view)> typeFn, |
|
[[maybe_unused]] tl::function_ref<bool(std::string_view)> 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<char, SDLFreeDeleter<char>> 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<int>(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<int> parsed = ParseInt<int>(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
|
|
|