diff --git a/Source/stores.cpp b/Source/stores.cpp index 03a794067..29f12e3f7 100644 --- a/Source/stores.cpp +++ b/Source/stores.cpp @@ -99,6 +99,62 @@ const char *const TownerNames[] = { N_("Wirt"), }; +constexpr int PaddingTop = 32; + +// For most languages, line height is always 12. +// This includes blank lines and divider line. +constexpr int SmallLineHeight = 12; +constexpr int SmallTextHeight = 12; + +// For larger small fonts (Chinese and Japanese), text lines are +// taller and overflow. +// We space out blank lines a bit more to give space to 3-line store items. +constexpr int LargeLineHeight = SmallLineHeight + 1; +constexpr int LargeTextHeight = 18; + +/** + * The line index with the Back / Leave button. + * This is a special button that is always the last line. + * + * For lists with a scrollbar, it is not selectable (mouse-only). + */ +int BackButtonLine() +{ + if (IsSmallFontTall()) { + return stextscrl ? 21 : 20; + } + return 22; +} + +int LineHeight() +{ + return IsSmallFontTall() ? LargeLineHeight : SmallLineHeight; +} + +int TextHeight() +{ + return IsSmallFontTall() ? LargeTextHeight : SmallTextHeight; +} + +void CalculateLineHeights() +{ + stext[0].y = 0; + if (IsSmallFontTall()) { + for (int i = 1; i < STORE_LINES; ++i) { + // Space out consecutive text lines, unless they are both selectable (never the case currently). + if (stext[i].IsText() && stext[i - 1].IsText() && !(stext[i]._ssel && stext[i - 1]._ssel)) { + stext[i].y = stext[i - 1].y + LargeTextHeight; + } else { + stext[i].y = i * LargeLineHeight; + } + } + } else { + for (int i = 1; i < STORE_LINES; ++i) { + stext[i].y = i * SmallLineHeight; + } + } +} + void DrawSTextBack(const Surface &out) { CelDrawTo(out, { PANEL_X + 320 + 24, 327 + UI_OFFSET_Y }, *pSTextBoxCels, 1); @@ -122,7 +178,7 @@ void DrawSSlider(const Surface &out, int y1, int y2) for (; yd3 < yd2; yd3 += 12) { CelDrawTo(out, { PANEL_X + 601, yd3 }, *pSTextSlidCels, 14); } - if (stextsel == 22) + if (stextsel == BackButtonLine()) yd3 = stextlhold; else yd3 = stextsel; @@ -146,11 +202,6 @@ void AddSTextVal(int y, int val) stext[y]._sval = val; } -void OffsetSTextY(int y, int yo) -{ - stext[y]._syoff = yo; -} - void AddSText(int x, int y, const char *str, UiFlags flags, bool sel) { stext[y]._sx = x; @@ -161,6 +212,26 @@ void AddSText(int x, int y, const char *str, UiFlags flags, bool sel) stext[y]._ssel = sel; } +void AddOptionsBackButton() +{ + const int line = BackButtonLine(); + AddSText(0, line, _("Back"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); + stext[line]._syoff = IsSmallFontTall() ? 0 : 6; +} + +void AddItemListBackButton(bool selectable = false) +{ + const int line = BackButtonLine(); + const char *text = _("Back"); + if (!selectable && IsSmallFontTall()) { + AddSText(0, line, text, UiFlags::ColorWhite | UiFlags::AlignRight, selectable); + } else { + AddSLine(line - 1); + AddSText(0, line, text, UiFlags::ColorWhite | UiFlags::AlignCenter, selectable); + stext[line]._syoff = 6; + } +} + void PrintStoreItem(Item *x, int l, UiFlags flags) { char sstr[128]; @@ -275,7 +346,7 @@ void ScrollSmithBuy(int idx) } } - if (stextsel != -1 && !stext[stextsel]._ssel && stextsel != 22) + if (stextsel != -1 && !stext[stextsel]._ssel && stextsel != BackButtonLine()) stextsel = stextdown; } @@ -290,10 +361,8 @@ void StartSmithBuy() AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); - AddSLine(21); ScrollSmithBuy(stextsval); - AddSText(0, 22, _("Back"), UiFlags::ColorWhite | UiFlags::AlignCenter, false); - OffsetSTextY(22, 6); + AddItemListBackButton(); storenumh = 0; for (int i = 0; !smithitem[i].isEmpty(); i++) { @@ -326,7 +395,7 @@ void ScrollSmithPremiumBuy(int boughtitems) } idx++; } - if (stextsel != -1 && !stext[stextsel]._ssel && stextsel != 22) + if (stextsel != -1 && !stext[stextsel]._ssel && stextsel != BackButtonLine()) stextsel = stextdown; } @@ -352,9 +421,7 @@ bool StartSmithPremiumBuy() AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); - AddSLine(21); - AddSText(0, 22, _("Back"), UiFlags::ColorWhite | UiFlags::AlignCenter, false); - OffsetSTextY(22, 6); + AddItemListBackButton(); stextsmax = std::max(storenumh - 4, 0); @@ -475,9 +542,7 @@ void StartSmithSell() AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); - AddSLine(21); - AddSText(0, 22, _("Back"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - OffsetSTextY(22, 6); + AddItemListBackButton(/*selectable=*/true); return; } @@ -490,10 +555,8 @@ void StartSmithSell() AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); - AddSLine(21); ScrollSmithSell(stextsval); - AddSText(0, 22, _("Back"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - OffsetSTextY(22, 6); + AddItemListBackButton(); } bool SmithRepairOk(int i) @@ -565,9 +628,7 @@ void StartSmithRepair() AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); - AddSLine(21); - AddSText(0, 22, _("Back"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - OffsetSTextY(22, 6); + AddItemListBackButton(/*selectable=*/true); return; } @@ -580,10 +641,9 @@ void StartSmithRepair() AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); - AddSLine(21); + ScrollSmithSell(stextsval); - AddSText(0, 22, _("Back"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - OffsetSTextY(22, 6); + AddItemListBackButton(); } void FillManaPlayer() @@ -639,7 +699,7 @@ void ScrollWitchBuy(int idx) } } - if (stextsel != -1 && !stext[stextsel]._ssel && stextsel != 22) + if (stextsel != -1 && !stext[stextsel]._ssel && stextsel != BackButtonLine()) stextsel = stextdown; } @@ -655,10 +715,8 @@ void StartWitchBuy() AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); - AddSLine(21); ScrollWitchBuy(stextsval); - AddSText(0, 22, _("Back"), UiFlags::ColorWhite | UiFlags::AlignCenter, false); - OffsetSTextY(22, 6); + AddItemListBackButton(); storenumh = 0; for (int i = 0; !witchitem[i].isEmpty(); i++) { @@ -747,9 +805,7 @@ void StartWitchSell() AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); - AddSLine(21); - AddSText(0, 22, _("Back"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - OffsetSTextY(22, 6); + AddItemListBackButton(/*selectable=*/true); return; } @@ -762,10 +818,8 @@ void StartWitchSell() AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); - AddSLine(21); ScrollSmithSell(stextsval); - AddSText(0, 22, _("Back"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - OffsetSTextY(22, 6); + AddItemListBackButton(); } bool WitchRechargeOk(int i) @@ -828,9 +882,7 @@ void StartWitchRecharge() AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); - AddSLine(21); - AddSText(0, 22, _("Back"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - OffsetSTextY(22, 6); + AddItemListBackButton(/*selectable=*/true); return; } @@ -843,10 +895,8 @@ void StartWitchRecharge() AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); - AddSLine(21); ScrollSmithSell(stextsval); - AddSText(0, 22, _("Back"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - OffsetSTextY(22, 6); + AddItemListBackButton(); } void StoreNoMoney() @@ -958,7 +1008,7 @@ void SStartBoyBuy() AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); - AddSLine(21); + UiFlags itemColor = boyitem.getTextColorWithStatCheck(); if (boyitem._iMagical != ITEM_QUALITY_NORMAL) @@ -971,8 +1021,15 @@ void SStartBoyBuy() else AddSTextVal(10, boyitem._iIvalue + (boyitem._iIvalue / 2)); PrintStoreItem(&boyitem, 11, itemColor); - AddSText(0, 22, _("Leave"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - OffsetSTextY(22, 6); + + { + // Add a Leave button. Unlike the other item list back buttons, + // this one has different text and different layout in LargerSmallFont locales. + const int line = BackButtonLine(); + AddSLine(line - 1); + AddSText(0, line, _("Leave"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); + stext[line]._syoff = 6; + } } void HealPlayer() @@ -1018,7 +1075,7 @@ void ScrollHealerBuy(int idx) } } - if (stextsel != -1 && !stext[stextsel]._ssel && stextsel != 22) + if (stextsel != -1 && !stext[stextsel]._ssel && stextsel != BackButtonLine()) stextsel = stextdown; } @@ -1033,10 +1090,9 @@ void StartHealerBuy() AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); - AddSLine(21); + ScrollHealerBuy(stextsval); - AddSText(0, 22, _("Back"), UiFlags::ColorWhite | UiFlags::AlignCenter, false); - OffsetSTextY(22, 6); + AddItemListBackButton(); storenumh = 0; for (int i = 0; !healitem[i].isEmpty(); i++) { @@ -1150,9 +1206,7 @@ void StartStorytellerIdentify() AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); - AddSLine(21); - AddSText(0, 22, _("Back"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - OffsetSTextY(22, 6); + AddItemListBackButton(/*selectable=*/true); return; } @@ -1165,10 +1219,9 @@ void StartStorytellerIdentify() AddSText(0, 1, tempstr, UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(3); - AddSLine(21); + ScrollSmithSell(stextsval); - AddSText(0, 22, _("Back"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); - OffsetSTextY(22, 6); + AddItemListBackButton(); } void StartStorytellerIdentifyShow() @@ -1202,7 +1255,7 @@ void StartTalk() AddSText(0, 12, _("is not available"), UiFlags::ColorWhite | UiFlags::AlignCenter, false); AddSText(0, 14, _("in the shareware"), UiFlags::ColorWhite | UiFlags::AlignCenter, false); AddSText(0, 16, _("version"), UiFlags::ColorWhite | UiFlags::AlignCenter, false); - AddSText(0, 22, _("Back"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); + AddOptionsBackButton(); return; } @@ -1229,7 +1282,7 @@ void StartTalk() } } AddSText(0, sn2, _("Gossip"), UiFlags::ColorBlue | UiFlags::AlignCenter, true); - AddSText(0, 22, _("Back"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); + AddOptionsBackButton(); } void StartTavern() @@ -1324,7 +1377,7 @@ void SmithBuyItem() void SmithBuyEnter() { - if (stextsel == 22) { + if (stextsel == BackButtonLine()) { StartStore(STORE_SMITH); stextsel = 12; return; @@ -1384,7 +1437,7 @@ void SmithBuyPItem() void SmithPremiumBuyEnter() { - if (stextsel == 22) { + if (stextsel == BackButtonLine()) { StartStore(STORE_SMITH); stextsel = 14; return; @@ -1529,7 +1582,7 @@ void StoreSellItem() void SmithSellEnter() { - if (stextsel == 22) { + if (stextsel == BackButtonLine()) { StartStore(STORE_SMITH); stextsel = 16; return; @@ -1578,7 +1631,7 @@ void SmithRepairItem() void SmithRepairEnter() { - if (stextsel == 22) { + if (stextsel == BackButtonLine()) { StartStore(STORE_SMITH); stextsel = 18; return; @@ -1656,7 +1709,7 @@ void WitchBuyItem() void WitchBuyEnter() { - if (stextsel == 22) { + if (stextsel == BackButtonLine()) { StartStore(STORE_WITCH); stextsel = 14; return; @@ -1689,7 +1742,7 @@ void WitchBuyEnter() void WitchSellEnter() { - if (stextsel == 22) { + if (stextsel == BackButtonLine()) { StartStore(STORE_WITCH); stextsel = 16; return; @@ -1730,7 +1783,7 @@ void WitchRechargeItem() void WitchRechargeEnter() { - if (stextsel == 22) { + if (stextsel == BackButtonLine()) { StartStore(STORE_WITCH); stextsel = 18; return; @@ -1939,7 +1992,7 @@ void ConfirmEnter() StartStore(stextshold); - if (stextsel == 22) + if (stextsel == BackButtonLine()) return; stextsel = stextlhold; @@ -1972,7 +2025,7 @@ void HealerEnter() void HealerBuyEnter() { - if (stextsel == 22) { + if (stextsel == BackButtonLine()) { StartStore(STORE_HEALER); stextsel = 16; return; @@ -2026,7 +2079,7 @@ void StorytellerEnter() void StorytellerIdentifyEnter() { - if (stextsel == 22) { + if (stextsel == BackButtonLine()) { StartStore(STORE_STORY); stextsel = 14; return; @@ -2048,7 +2101,7 @@ void StorytellerIdentifyEnter() void TalkEnter() { - if (stextsel == 22) { + if (stextsel == BackButtonLine()) { StartStore(stextshold); stextsel = stextlhold; return; @@ -2252,7 +2305,7 @@ void PrintSString(const Surface &out, int margin, int line, const char *text, Ui sx += 320; } - int sy = UI_OFFSET_Y + 32 + line * 12 + stext[line]._syoff; + const int sy = UI_OFFSET_Y + PaddingTop + stext[line].y + stext[line]._syoff; int width = stextsize ? 575 : 255; if (stextscrl && line >= 4 && line <= 20) { @@ -2276,7 +2329,7 @@ void PrintSString(const Surface &out, int margin, int line, const char *text, Ui void DrawSLine(const Surface &out, int y) { int sx = 26; - int sy = y * 12; + const int sy = PaddingTop + stext[y].y + TextHeight() / 2; int width = 587; if (!stextsize) { @@ -2285,7 +2338,7 @@ void DrawSLine(const Surface &out, int y) } BYTE *src = out.at(PANEL_LEFT + sx, UI_OFFSET_Y + 25); - BYTE *dst = out.at(PANEL_X + sx, UI_OFFSET_Y + sy + 38); + BYTE *dst = out.at(PANEL_X + sx, UI_OFFSET_Y + sy); for (int i = 0; i < 3; i++, src += out.pitch(), dst += out.pitch()) memcpy(dst, src, width); @@ -2453,10 +2506,11 @@ void DrawSText(const Surface &out) } } + CalculateLineHeights(); for (int i = 0; i < STORE_LINES; i++) { - if (stext[i]._sline != 0) + if (stext[i].IsDivider()) DrawSLine(out, i); - if (stext[i]._sstr[0] != '\0') + if (stext[i].IsText()) PrintSString(out, stext[i]._sx, i, stext[i]._sstr, stext[i].flags, stext[i]._sval); } @@ -2745,7 +2799,7 @@ void CheckStoreBtn() qtextflag = false; if (leveltype == DTYPE_TOWN) stream_stop(); - } else if (stextsel != -1 && MousePosition.y >= (32 + UI_OFFSET_Y) && MousePosition.y <= (320 + UI_OFFSET_Y)) { + } else if (stextsel != -1 && MousePosition.y >= (PaddingTop + UI_OFFSET_Y) && MousePosition.y <= (320 + UI_OFFSET_Y)) { if (!stextsize) { if (MousePosition.x < 344 + PANEL_LEFT || MousePosition.x > 616 + PANEL_LEFT) return; @@ -2753,8 +2807,12 @@ void CheckStoreBtn() if (MousePosition.x < 24 + PANEL_LEFT || MousePosition.x > 616 + PANEL_LEFT) return; } - int y = (MousePosition.y - (32 + UI_OFFSET_Y)) / 12; + + const int relativeY = MousePosition.y - (UI_OFFSET_Y + PaddingTop); + if (stextscrl && MousePosition.x > 600 + PANEL_LEFT) { + // Scroll bar is always measured in terms of the small line height. + int y = relativeY / SmallLineHeight; if (y == 4) { if (stextscrlubtn <= 0) { StoreUp(); @@ -2771,17 +2829,29 @@ void CheckStoreBtn() stextscrldbtn--; } } - } else if (y >= 5) { - if (y >= 23) - y = 22; - if (stextscrl && y < 21 && !stext[y]._ssel) { + return; + } + + int y = relativeY / LineHeight(); + + // Large small fonts draw beyond LineHeight. Check if the click was on the overflow text. + if (IsSmallFontTall() && y > 0 && y < STORE_LINES + && stext[y - 1].IsText() && !stext[y].IsText() + && relativeY < stext[y - 1].y + LargeTextHeight) { + --y; + } + + if (y >= 5) { + if (y >= BackButtonLine() + 1) + y = BackButtonLine(); + if (stextscrl && y <= 20 && !stext[y]._ssel) { if (stext[y - 2]._ssel) { y -= 2; } else if (stext[y - 1]._ssel) { y--; } } - if (stext[y]._ssel || (stextscrl && y == 22)) { + if (stext[y]._ssel || (stextscrl && y == BackButtonLine())) { stextsel = y; StoreEnter(); } diff --git a/Source/stores.h b/Source/stores.h index 503c39fa6..b65297c0a 100644 --- a/Source/stores.h +++ b/Source/stores.h @@ -52,6 +52,17 @@ struct STextStruct { int _sline; bool _ssel; int _sval; + + int y; + + [[nodiscard]] bool IsDivider() const + { + return _sline != 0; + } + [[nodiscard]] bool IsText() const + { + return _sstr[0] != '\0'; + } }; /** Shop frame graphics */ diff --git a/Source/utils/language.cpp b/Source/utils/language.cpp index 2c8846aa0..899e362db 100644 --- a/Source/utils/language.cpp +++ b/Source/utils/language.cpp @@ -9,6 +9,7 @@ #include "engine/assets.hpp" #include "utils/file_util.h" #include "utils/paths.h" +#include "utils/stdcompat/string_view.hpp" using namespace devilution; #define MO_MAGIC 0x950412de @@ -279,6 +280,12 @@ bool HasTranslation(const std::string &locale) return false; } +bool IsSmallFontTall() +{ + string_view code(sgOptions.Language.szCode, 2); + return code == "zh" || code == "ja" || code == "ko"; +} + void LanguageInitialize() { const std::string lang = sgOptions.Language.szCode; diff --git a/Source/utils/language.h b/Source/utils/language.h index cbbe69361..f3866380f 100644 --- a/Source/utils/language.h +++ b/Source/utils/language.h @@ -14,3 +14,6 @@ const std::string &LanguageParticularTranslate(const char *context, const char * const std::string &LanguagePluralTranslate(const char *singular, const char *plural, int count); const std::string &LanguageTranslate(const char *key); const char *LanguageMetadata(const char *key); + +// Chinese and Japanese, and Korean small font is 16px instead of a 12px one for readability. +bool IsSmallFontTall();