From 13a3424ff04ad7cb1323dcf14713f535843db6b5 Mon Sep 17 00:00:00 2001 From: Andrew James Date: Sun, 19 Jun 2022 23:18:48 +1000 Subject: [PATCH] Add helpers for creating/using Rectangles in UI contexts (#4734) * Add MakeRectangle helper to convert from SDL_Rect * Add Rectangle::inset method for shrinking a rectangle Turns out some of the other use cases I though this could apply to were actually doing something based on a fixed region * Simplify initialisation of settings menu rects --- Source/DiabloUI/button.cpp | 2 +- Source/DiabloUI/diabloui.cpp | 17 +++++--------- Source/DiabloUI/settingsmenu.cpp | 4 ++-- Source/engine/rectangle.hpp | 13 +++++++++++ Source/panels/spell_book.cpp | 40 ++++++++++++++++++++++---------- Source/utils/sdl_geometry.h | 4 ++++ 6 files changed, 54 insertions(+), 26 deletions(-) diff --git a/Source/DiabloUI/button.cpp b/Source/DiabloUI/button.cpp index 23bf935e5..3ba3c2ddd 100644 --- a/Source/DiabloUI/button.cpp +++ b/Source/DiabloUI/button.cpp @@ -18,7 +18,7 @@ void RenderButton(UiButton *button) const Surface &out = Surface(DiabloUiSurface()); RenderPcxSprite(out, ButtonSprite(button->IsPressed()), { button->m_rect.x, button->m_rect.y }); - Rectangle textRect { { button->m_rect.x, button->m_rect.y }, { button->m_rect.w, button->m_rect.h } }; + Rectangle textRect = MakeRectangle(button->m_rect); if (!button->IsPressed()) { --textRect.position.y; } diff --git a/Source/DiabloUI/diabloui.cpp b/Source/DiabloUI/diabloui.cpp index da277396c..8788e30f4 100644 --- a/Source/DiabloUI/diabloui.cpp +++ b/Source/DiabloUI/diabloui.cpp @@ -802,18 +802,14 @@ namespace { void Render(const UiText *uiText) { - Rectangle rect { { uiText->m_rect.x, uiText->m_rect.y }, { uiText->m_rect.w, uiText->m_rect.h } }; - const Surface &out = Surface(DiabloUiSurface()); - DrawString(out, uiText->GetText(), rect, uiText->GetFlags() | UiFlags::FontSizeDialog); + DrawString(out, uiText->GetText(), MakeRectangle(uiText->m_rect), uiText->GetFlags() | UiFlags::FontSizeDialog); } void Render(const UiArtText *uiArtText) { - Rectangle rect { { uiArtText->m_rect.x, uiArtText->m_rect.y }, { uiArtText->m_rect.w, uiArtText->m_rect.h } }; - const Surface &out = Surface(DiabloUiSurface()); - DrawString(out, uiArtText->GetText(), rect, uiArtText->GetFlags(), uiArtText->GetSpacing(), uiArtText->GetLineHeight()); + DrawString(out, uiArtText->GetText(), MakeRectangle(uiArtText->m_rect), uiArtText->GetFlags(), uiArtText->GetSpacing(), uiArtText->GetLineHeight()); } void Render(const UiImage *uiImage) @@ -865,10 +861,8 @@ void Render(const UiImageAnimatedPcx *uiImage) void Render(const UiArtTextButton *uiButton) { - Rectangle rect { { uiButton->m_rect.x, uiButton->m_rect.y }, { uiButton->m_rect.w, uiButton->m_rect.h } }; - const Surface &out = Surface(DiabloUiSurface()); - DrawString(out, uiButton->GetText(), rect, uiButton->GetFlags()); + DrawString(out, uiButton->GetText(), MakeRectangle(uiButton->m_rect), uiButton->GetFlags()); } void Render(const UiList *uiList) @@ -881,7 +875,7 @@ void Render(const UiList *uiList) if (i == SelectedItem) DrawSelector(rect); - Rectangle rectangle { { rect.x, rect.y }, { rect.w, rect.h } }; + Rectangle rectangle = MakeRectangle(rect); if (item->args.empty()) DrawString(out, item->m_text, rectangle, uiList->GetFlags() | item->uiFlags, uiList->GetSpacing()); else @@ -928,7 +922,8 @@ void Render(const UiEdit *uiEdit) { DrawSelector(uiEdit->m_rect); - Rectangle rect { { uiEdit->m_rect.x + 43, uiEdit->m_rect.y + 1 }, { uiEdit->m_rect.w - 86, uiEdit->m_rect.h } }; + // To simulate padding we inset the region used to draw text in an edit control + Rectangle rect = MakeRectangle(uiEdit->m_rect).inset({ 43, 1 }); const Surface &out = Surface(DiabloUiSurface()); DrawString(out, uiEdit->m_value, rect, uiEdit->GetFlags() | UiFlags::TextCursor); diff --git a/Source/DiabloUI/settingsmenu.cpp b/Source/DiabloUI/settingsmenu.cpp index c452d8278..0f17f3b99 100644 --- a/Source/DiabloUI/settingsmenu.cpp +++ b/Source/DiabloUI/settingsmenu.cpp @@ -254,8 +254,8 @@ void UiSettingsMenu() const Rectangle &uiRectangle = GetUIRectangle(); - rectList = { { uiRectangle.position.x + 50, (uiRectangle.position.y + 204) }, { 540, 208 } }; - rectDescription = { { uiRectangle.position.x + 24, rectList.position.y + rectList.size.height + 16 }, { 590, 35 } }; + rectList = { uiRectangle.position + Displacement { 50, 204 }, Size { 540, 208 } }; + rectDescription = { rectList.position + Displacement { -26, rectList.size.height + 16 }, Size { 590, 35 } }; optionDescription[0] = '\0'; diff --git a/Source/engine/rectangle.hpp b/Source/engine/rectangle.hpp index e3ffab6fb..606303966 100644 --- a/Source/engine/rectangle.hpp +++ b/Source/engine/rectangle.hpp @@ -46,6 +46,19 @@ struct Rectangle { { return position + Displacement(size / 2); } + + /** + * @brief Returns a rectangle with all sides shrunk according to the given displacement + * + * Effectively moves the left/right edges in by deltaX, and the top/bottom edges in by deltaY + */ + constexpr Rectangle inset(Displacement factor) const + { + return { + position + factor, + Size { size.width - factor.deltaX * 2, size.height - factor.deltaY * 2 } + }; + } }; } // namespace devilution diff --git a/Source/panels/spell_book.cpp b/Source/panels/spell_book.cpp index 3709a2b14..41edda860 100644 --- a/Source/panels/spell_book.cpp +++ b/Source/panels/spell_book.cpp @@ -38,16 +38,15 @@ spell_id SpellPages[6][7] = { { SPL_INVALID, SPL_INVALID, SPL_INVALID, SPL_INVALID, SPL_INVALID, SPL_INVALID, SPL_INVALID } }; -constexpr int SpellBookDescriptionWidth = 250; -constexpr int SpellBookDescriptionHeight = 43; -constexpr int SpellBookDescriptionPaddingLeft = 2; -constexpr int SpellBookDescriptionPaddingRight = 2; +constexpr Size SpellBookDescription { 250, 43 }; +constexpr int SpellBookDescriptionPaddingHorizontal = 2; void PrintSBookStr(const Surface &out, Point position, string_view text, UiFlags flags = UiFlags::None) { DrawString(out, text, - { GetPanelPosition(UiPanels::Spell, { SPLICONLENGTH + SpellBookDescriptionPaddingLeft + position.x, position.y }), - { SpellBookDescriptionWidth - SpellBookDescriptionPaddingLeft - SpellBookDescriptionPaddingRight, 0 } }, + Rectangle(GetPanelPosition(UiPanels::Spell, position + Displacement { SPLICONLENGTH, 0 }), + SpellBookDescription) + .inset({ SpellBookDescriptionPaddingHorizontal, 0 }), UiFlags::ColorWhite | flags); } @@ -140,7 +139,7 @@ void DrawSpellBook(const Surface &out) if (sn != SPL_INVALID && (spl & GetSpellBitmask(sn)) != 0) { spell_type st = GetSBookTrans(sn, true); SetSpellTrans(st); - const Point spellCellPosition = GetPanelPosition(UiPanels::Spell, { 11, yp + SpellBookDescriptionHeight }); + const Point spellCellPosition = GetPanelPosition(UiPanels::Spell, { 11, yp + SpellBookDescription.height }); DrawSpellCel(out, spellCellPosition, *pSBkIconCels, SpellITbl[sn]); if (sn == player._pRSpell && st == player._pRSplType) { SetSpellTrans(RSPLTYPE_SKILL); @@ -184,16 +183,19 @@ void DrawSpellBook(const Surface &out) } break; } } - yp += SpellBookDescriptionHeight; + yp += SpellBookDescription.height; } } void CheckSBook() { - Rectangle iconArea = { GetPanelPosition(UiPanels::Spell, { 11, 18 }), { 48 - 11, 314 - 18 } }; - Rectangle tabArea = { GetPanelPosition(UiPanels::Spell, { 7, 320 }), { 311 - 7, 349 - 320 } }; + // Icons are drawn in a column near the left side of the panel and aligned with the spell book description entries + // Spell icons/buttons are 37x38 pixels, laid out from 11,18 with a 5 pixel margin between each icon. This is close + // enough to the height of the space given to spell descriptions that we can reuse that value and subtract the + // padding from the end of the area. + Rectangle iconArea = { GetPanelPosition(UiPanels::Spell, { 11, 18 }), Size { 37, SpellBookDescription.height * 7 - 5 } }; if (iconArea.Contains(MousePosition)) { - spell_id sn = SpellPages[sbooktab][(MousePosition.y - GetRightPanel().position.y - 18) / 43]; + spell_id sn = SpellPages[sbooktab][(MousePosition.y - iconArea.position.y) / SpellBookDescription.height]; Player &player = *MyPlayer; uint64_t spl = player._pMemSpells | player._pISpells | player._pAblSpells; if (sn != SPL_INVALID && (spl & GetSpellBitmask(sn)) != 0) { @@ -208,9 +210,23 @@ void CheckSBook() player._pRSplType = st; force_redraw = 255; } + return; } + + // The width of the panel excluding the border is 305 pixels. This does not cleanly divide by 4 meaning Diablo tabs + // end up with an extra pixel somewhere around the buttons. Vanilla Diablo had the buttons left-aligned, devilutionX + // instead justifies the buttons and puts the gap between buttons 2/3. See DrawSpellBook + const int TabWidth = gbIsHellfire ? 61 : 76; + // Tabs are drawn in a row near the bottom of the panel + Rectangle tabArea = { GetPanelPosition(UiPanels::Spell, { 7, 320 }), Size { 305, 29 } }; if (tabArea.Contains(MousePosition)) { - sbooktab = (MousePosition.x - (GetRightPanel().position.x + 7)) / (gbIsHellfire ? 61 : 76); + int hitColumn = MousePosition.x - tabArea.position.x; + // Clicking on the gutter currently activates tab 3. Could make it do nothing by checking for == here and return early. + if (!gbIsHellfire && hitColumn > TabWidth * 2) { + // Subtract 1 pixel to account for the gutter between buttons 2/3 + hitColumn--; + } + sbooktab = hitColumn / TabWidth; } } diff --git a/Source/utils/sdl_geometry.h b/Source/utils/sdl_geometry.h index bbe69ce60..07a9d64c0 100644 --- a/Source/utils/sdl_geometry.h +++ b/Source/utils/sdl_geometry.h @@ -31,4 +31,8 @@ inline SDL_Rect MakeSdlRect(Rectangle rect) return MakeSdlRect(rect.position.x, rect.position.y, rect.size.width, rect.size.height); } +constexpr Rectangle MakeRectangle(SDL_Rect sdlRect) +{ + return { Point { sdlRect.x, sdlRect.y }, Size { sdlRect.w, sdlRect.h } }; +} } // namespace devilution