diff --git a/Source/DiabloUI/text_input.cpp b/Source/DiabloUI/text_input.cpp index 9ae84f2f4..d0e870829 100644 --- a/Source/DiabloUI/text_input.cpp +++ b/Source/DiabloUI/text_input.cpp @@ -1,20 +1,29 @@ #include "DiabloUI/text_input.hpp" #include +#include +#include #include +#include #ifdef USE_SDL1 #include "utils/sdl2_to_1_2_backports.h" #endif #include "utils/log.hpp" +#include "utils/parse_int.hpp" #include "utils/sdl_ptrs.h" +#include "utils/str_cat.hpp" #include "utils/utf8.hpp" namespace devilution { -bool HandleTextInputEvent(const SDL_Event &event, TextInputState &state) +namespace { + +bool HandleInputEvent(const SDL_Event &event, TextInputState &state, + tl::function_ref typeFn, + [[maybe_unused]] tl::function_ref assignFn) { switch (event.type) { case SDL_KEYDOWN: { @@ -27,7 +36,7 @@ bool HandleTextInputEvent(const SDL_Event &event, TextInputState &state) if (clipboard == nullptr || *clipboard == '\0') { Log("{}", SDL_GetError()); } else { - state.type(clipboard.get()); + typeFn(clipboard.get()); } } } @@ -58,7 +67,7 @@ bool HandleTextInputEvent(const SDL_Event &event, TextInputState &state) if ((event.key.keysym.mod & KMOD_CTRL) == 0 && event.key.keysym.unicode >= ' ') { std::string utf8; AppendUtf8(event.key.keysym.unicode, utf8); - state.type(utf8); + typeFn(utf8); return true; } #endif @@ -67,15 +76,98 @@ bool HandleTextInputEvent(const SDL_Event &event, TextInputState &state) #ifndef USE_SDL1 case SDL_TEXTINPUT: #ifdef __vita__ - state.assign(event.text.text); + assignFn(event.text.text); #else - state.type(event.text.text); + typeFn(event.text.text); #endif return true; + case SDL_TEXTEDITING: + return true; #endif default: 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 diff --git a/Source/DiabloUI/text_input.hpp b/Source/DiabloUI/text_input.hpp index 4073a1516..09548f239 100644 --- a/Source/DiabloUI/text_input.hpp +++ b/Source/DiabloUI/text_input.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -65,6 +66,12 @@ class TextInputState { buf_[len_] = '\0'; } + void clear() + { + len_ = 0; + buf_[0] = '\0'; + } + explicit operator std::string_view() const { return { buf_, len_ }; @@ -112,6 +119,11 @@ public: return value_.empty(); } + [[nodiscard]] size_t cursorPosition() const + { + return *cursorPosition_; + } + /** * @brief Overwrites the value with the given text and moves cursor to the end. */ @@ -121,6 +133,12 @@ public: *cursorPosition_ = value_.size(); } + void clear() + { + value_.clear(); + *cursorPosition_ = 0; + } + /** * @brief Truncate to precisely `length` bytes. */ @@ -165,7 +183,7 @@ public: void setCursorToEnd() { - *cursorPosition_ = std::string_view(value_).size(); + *cursorPosition_ = value_.size(); } void moveCursorLeft() @@ -185,18 +203,76 @@ public: private: [[nodiscard]] std::string_view beforeCursor() const { - return std::string_view(value_).substr(0, *cursorPosition_); + return value().substr(0, *cursorPosition_); } [[nodiscard]] std::string_view afterCursor() const { - return std::string_view(value_).substr(*cursorPosition_); + return value().substr(*cursorPosition_); } Buffer value_; size_t *cursorPosition_; // unowned }; +/** + * @brief Manages state for a number input with a cursor. + */ +class NumberInputState { +public: + struct Options { + TextInputState::Options textOptions; + int min; + int max; + }; + NumberInputState(const Options &options) + : textInput_(options.textOptions) + , min_(options.min) + , max_(options.max) + { + } + + [[nodiscard]] bool empty() const + { + return textInput_.empty(); + } + + [[nodiscard]] int value(int defaultValue = 0) const; + + [[nodiscard]] int max() const + { + return max_; + } + + /** + * @brief Inserts the text at the current cursor position. + * + * Ignores non-numeric characters. + */ + void type(std::string_view str); + + /** + * @brief Sets the text of the input. + * + * Ignores non-numeric characters. + */ + void assign(std::string_view str); + + TextInputState &textInput() + { + return textInput_; + } + +private: + void enforceRange(); + std::string filterStr(std::string_view str, bool allowMinus); + + TextInputState textInput_; + int min_; + int max_; +}; + bool HandleTextInputEvent(const SDL_Event &event, TextInputState &state); +bool HandleNumberInputEvent(const SDL_Event &event, NumberInputState &state); } // namespace devilution diff --git a/Source/control.cpp b/Source/control.cpp index 6f055b308..c72a10a40 100644 --- a/Source/control.cpp +++ b/Source/control.cpp @@ -67,19 +67,23 @@ namespace devilution { bool dropGoldFlag; +size_t GoldDropCursorPosition; +char GoldDropText[21]; +namespace { +int8_t GoldDropInvIndex; +std::optional GoldDropInputState; +} // namespace + bool chrbtn[4]; bool lvlbtndown; -int dropGoldValue; bool chrbtnactive; UiFlags InfoColor; int sbooktab; -int8_t initialDropGoldIndex; bool talkflag; bool sbookflag; bool chrflag; StringOrView InfoString; bool panelflag; -int initialDropGoldValue; bool panbtndown; bool spselflag; Rectangle MainPanel; @@ -625,10 +629,10 @@ void ControlUpDown(int v) } } -void RemoveGold(Player &player, int goldIndex) +void RemoveGold(Player &player, int goldIndex, int amount) { - int gi = goldIndex - INVITEM_INV_FIRST; - player.InvList[gi]._ivalue -= dropGoldValue; + const int gi = goldIndex - INVITEM_INV_FIRST; + player.InvList[gi]._ivalue -= amount; if (player.InvList[gi]._ivalue > 0) { SetPlrHandGoldCurs(player.InvList[gi]); NetSyncInvItem(player, gi); @@ -636,11 +640,10 @@ void RemoveGold(Player &player, int goldIndex) player.RemoveInvItem(gi); } - MakeGoldStack(player.HoldItem, dropGoldValue); + MakeGoldStack(player.HoldItem, amount); NewCursor(player.HoldItem); player._pGold = CalculateGold(player); - dropGoldValue = 0; } bool IsLevelUpButtonVisible() @@ -914,10 +917,6 @@ void InitControlPan() pGBoxBuff = LoadCel("ctrlpan\\golddrop", 261); } CloseGoldDrop(); - dropGoldValue = 0; - initialDropGoldValue = 0; - initialDropGoldIndex = 0; - CalculatePanelAreas(); if (!HeadlessMode) @@ -1143,17 +1142,11 @@ void CheckBtnUp() CloseGoldWithdraw(); CloseStash(); invflag = !invflag; - if (dropGoldFlag) { - CloseGoldDrop(); - dropGoldValue = 0; - } + CloseGoldDrop(); break; case PanelButtonSpellbook: CloseInventory(); - if (dropGoldFlag) { - CloseGoldDrop(); - dropGoldValue = 0; - } + CloseGoldDrop(); sbookflag = !sbookflag; break; case PanelButtonSendmsg: @@ -1367,19 +1360,23 @@ void RedBack(const Surface &out) } } -void DrawGoldSplit(const Surface &out, int amount) +void DrawGoldSplit(const Surface &out) { const int dialogX = 30; ClxDraw(out, GetPanelPosition(UiPanels::Inventory, { dialogX, 178 }), (*pGBoxBuff)[0]); + const std::string_view amountText = GoldDropText; + const size_t cursorPosition = GoldDropCursorPosition; + const int max = GetGoldDropMax(); + const std::string description = fmt::format( fmt::runtime(ngettext( /* TRANSLATORS: {:s} is a number with separators. Dialog is shown when splitting a stash of Gold.*/ "You have {:s} gold piece. How many do you want to remove?", "You have {:s} gold pieces. How many do you want to remove?", - initialDropGoldValue)), - FormatInteger(initialDropGoldValue)); + max)), + FormatInteger(max)); // Pre-wrap the string at spaces, otherwise DrawString would hard wrap in the middle of words const std::string wrapped = WordWrapString(description, 200); @@ -1389,13 +1386,10 @@ void DrawGoldSplit(const Surface &out, int amount) // for the text entered by the player. DrawString(out, wrapped, { GetPanelPosition(UiPanels::Inventory, { dialogX + 31, 75 }), { 200, 50 } }, UiFlags::ColorWhitegold | UiFlags::AlignCenter, 1, 17); - std::string value; - if (amount > 0) { - value = StrCat(amount); - } // Even a ten digit amount of gold only takes up about half a line. There's no need to wrap or clip text here so we // use the Point form of DrawString. - DrawString(out, value, GetPanelPosition(UiPanels::Inventory, { dialogX + 37, 128 }), UiFlags::ColorWhite | UiFlags::PentaCursor); + DrawString(out, amountText, GetPanelPosition(UiPanels::Inventory, { dialogX + 37, 128 }), + UiFlags::ColorWhite | UiFlags::PentaCursor, /*spacing=*/1, /*lineHeight=*/-1, cursorPosition); } void control_drop_gold(SDL_Keycode vkey) @@ -1404,19 +1398,22 @@ void control_drop_gold(SDL_Keycode vkey) if (myPlayer._pHitPoints >> 6 <= 0) { CloseGoldDrop(); - dropGoldValue = 0; return; } - if (vkey == SDLK_RETURN || vkey == SDLK_KP_ENTER) { - if (dropGoldValue > 0) - RemoveGold(myPlayer, initialDropGoldIndex); + switch (vkey) { + case SDLK_RETURN: + case SDLK_KP_ENTER: + if (const int value = GoldDropInputState->value(); value != 0) { + RemoveGold(myPlayer, GoldDropInvIndex, value); + } CloseGoldDrop(); - } else if (vkey == SDLK_ESCAPE) { + break; + case SDLK_ESCAPE: CloseGoldDrop(); - dropGoldValue = 0; - } else if (vkey == SDLK_BACKSPACE) { - dropGoldValue = dropGoldValue / 10; + break; + default: + break; } } @@ -1626,26 +1623,41 @@ void DiabloHotkeyMsg(uint32_t dwMsg) } } +void OpenGoldDrop(int8_t invIndex, int max) +{ + dropGoldFlag = true; + GoldDropInvIndex = invIndex; + GoldDropText[0] = '\0'; + GoldDropInputState.emplace(NumberInputState::Options { + .textOptions { + .value = GoldDropText, + .cursorPosition = &GoldDropCursorPosition, + .maxLength = sizeof(GoldDropText) - 1, + }, + .min = 0, + .max = max, + }); + SDL_StartTextInput(); +} + void CloseGoldDrop() { if (!dropGoldFlag) return; - dropGoldFlag = false; SDL_StopTextInput(); + dropGoldFlag = false; + GoldDropInputState = std::nullopt; + GoldDropInvIndex = 0; } -void GoldDropNewText(std::string_view text) +int GetGoldDropMax() { - for (char vkey : text) { - int digit = vkey - '0'; - if (digit >= 0 && digit <= 9) { - int newGoldValue = dropGoldValue * 10; - newGoldValue += digit; - if (newGoldValue <= initialDropGoldValue) { - dropGoldValue = newGoldValue; - } - } - } + return GoldDropInputState->max(); +} + +bool HandleGoldDropTextInputEvent(const SDL_Event &event) +{ + return HandleNumberInputEvent(event, *GoldDropInputState); } } // namespace devilution diff --git a/Source/control.h b/Source/control.h index 1ff7f8609..a49b9f30e 100644 --- a/Source/control.h +++ b/Source/control.h @@ -33,19 +33,19 @@ namespace devilution { constexpr Size SidePanelSize { 320, 352 }; extern bool dropGoldFlag; +extern size_t GoldDropCursorPosition; +extern char GoldDropText[21]; + extern bool chrbtn[4]; extern bool lvlbtndown; -extern int dropGoldValue; extern bool chrbtnactive; extern UiFlags InfoColor; extern int sbooktab; -extern int8_t initialDropGoldIndex; extern bool talkflag; extern bool sbookflag; extern bool chrflag; extern StringOrView InfoString; extern bool panelflag; -extern int initialDropGoldValue; extern bool panbtndown; extern bool spselflag; const Rectangle &GetMainPanel(); @@ -180,7 +180,7 @@ void ReleaseChrBtns(bool addAllStatPoints); void DrawDurIcon(const Surface &out); void RedBack(const Surface &out); void DrawSpellBook(const Surface &out); -void DrawGoldSplit(const Surface &out, int amount); +void DrawGoldSplit(const Surface &out); void control_drop_gold(SDL_Keycode vkey); void DrawTalkPan(const Surface &out); bool control_check_talk_btn(); @@ -191,8 +191,10 @@ bool IsTalkActive(); bool HandleTalkTextInputEvent(const SDL_Event &event); bool control_presskeys(SDL_Keycode vkey); void DiabloHotkeyMsg(uint32_t dwMsg); +void OpenGoldDrop(int8_t invIndex, int max); void CloseGoldDrop(); -void GoldDropNewText(std::string_view text); +int GetGoldDropMax(); +bool HandleGoldDropTextInputEvent(const SDL_Event &event); extern Rectangle ChrBtnsRect[4]; } // namespace devilution diff --git a/Source/diablo.cpp b/Source/diablo.cpp index 3c7c5ae4f..de9baf1e1 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -677,19 +677,6 @@ void HandleMouseButtonUp(Uint8 button, uint16_t modState) } } -bool HandleTextInput(std::string_view text) -{ - if (dropGoldFlag) { - GoldDropNewText(text); - return true; - } - if (IsWithdrawGoldOpen) { - GoldWithdrawNewText(text); - return true; - } - return false; -} - [[maybe_unused]] void LogUnhandledEvent(const char *name, int value) { LogVerbose("Unhandled SDL event: {} {}", name, value); @@ -722,35 +709,20 @@ void GameEventHandler(const SDL_Event &event, uint16_t modState) if (IsTalkActive() && HandleTalkTextInputEvent(event)) { return; } + if (dropGoldFlag && HandleGoldDropTextInputEvent(event)) { + return; + } + if (IsWithdrawGoldOpen && HandleGoldWithdrawTextInputEvent(event)) { + return; + } switch (event.type) { - case SDL_KEYDOWN: { -#ifdef USE_SDL1 - // SDL1 does not support TEXTINPUT events, so we emulate them here. - const Uint16 bmpCodePoint = event.key.keysym.unicode; - if (bmpCodePoint >= ' ') { - std::string utf8; - AppendUtf8(bmpCodePoint, utf8); - if (HandleTextInput(utf8)) { - return; - } - } -#endif + case SDL_KEYDOWN: PressKey(event.key.keysym.sym, modState); return; - } case SDL_KEYUP: ReleaseKey(event.key.keysym.sym); return; -#if SDL_VERSION_ATLEAST(2, 0, 0) - case SDL_TEXTEDITING: - return; - case SDL_TEXTINPUT: - if (!HandleTextInput(event.text.text)) { - LogUnhandledEvent("SDL_TEXTINPUT", event.text.windowID); - } - return; -#endif case SDL_MOUSEMOTION: if (ControlMode == ControlTypes::KeyboardAndMouse && invflag) InvalidateInventorySlot(); diff --git a/Source/engine/render/scrollrt.cpp b/Source/engine/render/scrollrt.cpp index 280fe5190..d26e4cf01 100644 --- a/Source/engine/render/scrollrt.cpp +++ b/Source/engine/render/scrollrt.cpp @@ -1185,9 +1185,9 @@ void DrawView(const Surface &out, Point startPosition) DrawSpellList(out); } if (dropGoldFlag) { - DrawGoldSplit(out, dropGoldValue); + DrawGoldSplit(out); } - DrawGoldWithdraw(out, WithdrawGoldValue); + DrawGoldWithdraw(out); if (HelpFlag) { DrawHelp(out); } diff --git a/Source/engine/render/text_render.hpp b/Source/engine/render/text_render.hpp index 284c2722e..07bd58d49 100644 --- a/Source/engine/render/text_render.hpp +++ b/Source/engine/render/text_render.hpp @@ -188,11 +188,12 @@ uint32_t DrawString(const Surface &out, std::string_view text, const Rectangle & * @param flags A combination of UiFlags to describe font size, color, alignment, etc. See ui_items.h for available options * @param spacing Additional space to add between characters. * This value may be adjusted if the flag UIS_FIT_SPACING is passed in the flags parameter. + * @param cursorPosition If non-negative, draws a blinking cursor after the given byte index. * @param lineHeight Allows overriding the default line height, useful for multi-line strings. */ -inline void DrawString(const Surface &out, std::string_view text, const Point &position, UiFlags flags = UiFlags::None, int spacing = 1, int lineHeight = -1) +inline void DrawString(const Surface &out, std::string_view text, const Point &position, UiFlags flags = UiFlags::None, int spacing = 1, int lineHeight = -1, int cursorPosition = -1) { - DrawString(out, text, { position, { out.w() - position.x, 0 } }, flags, spacing, lineHeight); + DrawString(out, text, { position, { out.w() - position.x, 0 } }, flags, spacing, lineHeight, cursorPosition); } /** diff --git a/Source/inv.cpp b/Source/inv.cpp index 4c5044723..df169dd9f 100644 --- a/Source/inv.cpp +++ b/Source/inv.cpp @@ -566,10 +566,7 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool return; } - if (dropGoldFlag) { - CloseGoldDrop(); - dropGoldValue = 0; - } + CloseGoldDrop(); uint32_t r = 0; for (; r < NUM_XY_SLOTS; r++) { @@ -956,14 +953,13 @@ void StartGoldDrop() { CloseGoldWithdraw(); - initialDropGoldIndex = pcursinvitem; + const int8_t invIndex = pcursinvitem; Player &myPlayer = *MyPlayer; - if (pcursinvitem <= INVITEM_INV_LAST) - initialDropGoldValue = myPlayer.InvList[pcursinvitem - INVITEM_INV_FIRST]._ivalue; - else - initialDropGoldValue = myPlayer.SpdList[pcursinvitem - INVITEM_BELT_FIRST]._ivalue; + const size_t max = (invIndex <= INVITEM_INV_LAST) + ? myPlayer.InvList[invIndex - INVITEM_INV_FIRST]._ivalue + : myPlayer.SpdList[invIndex - INVITEM_BELT_FIRST]._ivalue; if (talkflag) control_reset_talk(); @@ -972,9 +968,7 @@ void StartGoldDrop() SDL_Rect rect = MakeSdlRect(start.x, start.y, 180, 20); SDL_SetTextInputRect(&rect); - dropGoldFlag = true; - dropGoldValue = 0; - SDL_StartTextInput(); + OpenGoldDrop(invIndex, max); } int CreateGoldItemInInventorySlot(Player &player, int slotIndex, int value) @@ -1555,10 +1549,7 @@ void CheckInvScrn(bool isShiftHeld, bool isCtrlHeld) void InvGetItem(Player &player, int ii) { auto &item = Items[ii]; - if (dropGoldFlag) { - CloseGoldDrop(); - dropGoldValue = 0; - } + CloseGoldDrop(); if (dItem[item.position.x][item.position.y] == 0) return; @@ -1629,10 +1620,7 @@ void AutoGetItem(Player &player, Item *itemPointer, int ii) { Item &item = *itemPointer; - if (dropGoldFlag) { - CloseGoldDrop(); - dropGoldValue = 0; - } + CloseGoldDrop(); if (dItem[item.position.x][item.position.y] == 0) return; @@ -2057,10 +2045,7 @@ bool UseInvItem(int cii) return true; } - if (dropGoldFlag) { - CloseGoldDrop(); - dropGoldValue = 0; - } + CloseGoldDrop(); if (item->isScroll() && leveltype == DTYPE_TOWN && !GetSpellData(item->_iSpell).isAllowedInTown()) { return true; diff --git a/Source/objects.cpp b/Source/objects.cpp index fb82c7fee..3d030219c 100644 --- a/Source/objects.cpp +++ b/Source/objects.cpp @@ -2978,10 +2978,7 @@ void OperateShrine(Player &player, Object &shrine, _sfx_id sType) if (shrine._oSelFlag == 0) return; - if (dropGoldFlag) { - CloseGoldDrop(); - dropGoldValue = 0; - } + CloseGoldDrop(); SetRndSeed(shrine._oRndSeed); shrine._oSelFlag = 0; diff --git a/Source/qol/stash.cpp b/Source/qol/stash.cpp index 906c27ca2..f79335b36 100644 --- a/Source/qol/stash.cpp +++ b/Source/qol/stash.cpp @@ -6,6 +6,7 @@ #include +#include "DiabloUI/text_input.hpp" #include "control.h" #include "controls/plrctrls.h" #include "cursor.h" @@ -17,6 +18,7 @@ #include "engine/render/text_render.hpp" #include "engine/size.hpp" #include "hwcursor.hpp" +#include "inv.h" #include "minitext.h" #include "stores.h" #include "utils/format_int.hpp" @@ -29,14 +31,15 @@ namespace devilution { bool IsStashOpen; StashStruct Stash; bool IsWithdrawGoldOpen; -int WithdrawGoldValue; namespace { constexpr unsigned CountStashPages = 100; constexpr unsigned LastStashPage = CountStashPages - 1; -int InitialWithdrawGoldValue; +char GoldWithdrawText[21]; +size_t GoldWithdrawCursorPosition; +std::optional GoldWithdrawInputState; constexpr Size ButtonSize { 27, 16 }; /** Contains mappings for the buttons in the stash (2 navigation buttons, withdraw gold buttons, 2 navigation buttons) */ @@ -181,10 +184,7 @@ void CheckStashCut(Point cursorPosition, bool automaticMove) { Player &player = *MyPlayer; - if (IsWithdrawGoldOpen) { - IsWithdrawGoldOpen = false; - WithdrawGoldValue = 0; - } + CloseGoldWithdraw(); Point slot = InvalidStashPoint; @@ -276,8 +276,6 @@ void FreeStashGFX() void InitStash() { - InitialWithdrawGoldValue = 0; - if (!HeadlessMode) { StashPanelArt = LoadClx("data\\stash.clx"); StashNavButtonArt = LoadClx("data\\stashnavbtns.clx"); @@ -486,10 +484,7 @@ bool UseStashItem(uint16_t c) return true; } - if (IsWithdrawGoldOpen) { - IsWithdrawGoldOpen = false; - WithdrawGoldValue = 0; - } + CloseGoldWithdraw(); if (item->isScroll()) { return true; @@ -589,8 +584,6 @@ void StartGoldWithdraw() { CloseGoldDrop(); - InitialWithdrawGoldValue = std::min(RoomForGold(), Stash.gold); - if (talkflag) control_reset_talk(); @@ -599,7 +592,16 @@ void StartGoldWithdraw() SDL_SetTextInputRect(&rect); IsWithdrawGoldOpen = true; - WithdrawGoldValue = 0; + GoldWithdrawText[0] = '\0'; + GoldWithdrawInputState.emplace(NumberInputState::Options { + .textOptions { + .value = GoldWithdrawText, + .cursorPosition = &GoldWithdrawCursorPosition, + .maxLength = sizeof(GoldWithdrawText) - 1, + }, + .min = 0, + .max = std::min(RoomForGold(), Stash.gold), + }); SDL_StartTextInput(); } @@ -612,25 +614,32 @@ void WithdrawGoldKeyPress(SDL_Keycode vkey) return; } - if ((vkey == SDLK_RETURN) || (vkey == SDLK_KP_ENTER)) { - if (WithdrawGoldValue > 0) { - WithdrawGold(myPlayer, WithdrawGoldValue); + switch (vkey) { + case SDLK_RETURN: + case SDLK_KP_ENTER: + if (const int value = GoldWithdrawInputState->value(); value != 0) { + WithdrawGold(myPlayer, value); PlaySFX(IS_GOLD); } CloseGoldWithdraw(); - } else if (vkey == SDLK_ESCAPE) { + break; + case SDLK_ESCAPE: CloseGoldWithdraw(); - } else if (vkey == SDLK_BACKSPACE) { - WithdrawGoldValue /= 10; + break; + default: + break; } } -void DrawGoldWithdraw(const Surface &out, int amount) +void DrawGoldWithdraw(const Surface &out) { if (!IsWithdrawGoldOpen) { return; } + const std::string_view amountText = GoldWithdrawText; + const size_t cursorPosition = GoldWithdrawCursorPosition; + const int dialogX = 30; ClxDraw(out, GetPanelPosition(UiPanels::Stash, { dialogX, 178 }), (*pGBoxBuff)[0]); @@ -643,36 +652,24 @@ void DrawGoldWithdraw(const Surface &out, int amount) // for the text entered by the player. DrawString(out, wrapped, { GetPanelPosition(UiPanels::Stash, { dialogX + 31, 75 }), { 200, 50 } }, UiFlags::ColorWhitegold | UiFlags::AlignCenter, 1, 17); - std::string value = ""; - if (amount > 0) { - value = StrCat(amount); - } // Even a ten digit amount of gold only takes up about half a line. There's no need to wrap or clip text here so we // use the Point form of DrawString. - DrawString(out, value, GetPanelPosition(UiPanels::Stash, { dialogX + 37, 128 }), UiFlags::ColorWhite | UiFlags::PentaCursor); + DrawString(out, amountText, GetPanelPosition(UiPanels::Stash, { dialogX + 37, 128 }), + UiFlags::ColorWhite | UiFlags::PentaCursor, /*spacing=*/1, /*lineHeight=*/-1, cursorPosition); } void CloseGoldWithdraw() { if (!IsWithdrawGoldOpen) return; - IsWithdrawGoldOpen = false; - WithdrawGoldValue = 0; SDL_StopTextInput(); + IsWithdrawGoldOpen = false; + GoldWithdrawInputState = std::nullopt; } -void GoldWithdrawNewText(std::string_view text) +bool HandleGoldWithdrawTextInputEvent(const SDL_Event &event) { - for (char vkey : text) { - int digit = vkey - '0'; - if (digit >= 0 && digit <= 9) { - int newGoldValue = WithdrawGoldValue * 10; - newGoldValue += digit; - if (newGoldValue <= InitialWithdrawGoldValue) { - WithdrawGoldValue = newGoldValue; - } - } - } + return HandleNumberInputEvent(event, *GoldWithdrawInputState); } bool AutoPlaceItemInStash(Player &player, const Item &item, bool persistItem) diff --git a/Source/qol/stash.h b/Source/qol/stash.h index 2a11c4ac2..241a570d9 100644 --- a/Source/qol/stash.h +++ b/Source/qol/stash.h @@ -88,9 +88,9 @@ void CheckStashButtonPress(Point mousePosition); void StartGoldWithdraw(); void WithdrawGoldKeyPress(SDL_Keycode vkey); -void DrawGoldWithdraw(const Surface &out, int amount); +void DrawGoldWithdraw(const Surface &out); void CloseGoldWithdraw(); -void GoldWithdrawNewText(std::string_view text); +bool HandleGoldWithdrawTextInputEvent(const SDL_Event &event); /** * @brief Checks whether the given item can be placed on the specified player's stash.