From 4f64b873307c1d1cbe4b2d30106e2e033351a08c Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Wed, 10 Nov 2021 04:20:32 +0000 Subject: [PATCH] Fix talk/store text overlap for Chinese and Japanese We ensure that selectable lines are placed at the same vertical coordinates but space out unselectable text lines at the cost of reduced heigh of empty space between the store items. We also have to move the back button in scrollable lists to the lower right. This can definitely be improved further but at least it solves the problem for now. Refs #3162 --- Source/stores.cpp | 230 +++++++++++++++++++++++++------------- Source/stores.h | 11 ++ Source/utils/language.cpp | 7 ++ Source/utils/language.h | 3 + 4 files changed, 171 insertions(+), 80 deletions(-) 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();