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.
563 lines
20 KiB
563 lines
20 KiB
#include "selstart.h" |
|
|
|
#include <cstdint> |
|
#include <optional> |
|
#include <vector> |
|
|
|
#include <function_ref.hpp> |
|
|
|
#include "DiabloUI/diabloui.h" |
|
#include "DiabloUI/scrollbar.h" |
|
#include "control.h" |
|
#include "controls/controller_motion.h" |
|
#include "controls/plrctrls.h" |
|
#include "controls/remap_keyboard.h" |
|
#include "engine/assets.hpp" |
|
#include "engine/render/text_render.hpp" |
|
#include "hwcursor.hpp" |
|
#include "options.h" |
|
#include "utils/display.h" |
|
#include "utils/is_of.hpp" |
|
#include "utils/language.h" |
|
#include "utils/utf8.hpp" |
|
|
|
namespace devilution { |
|
namespace { |
|
|
|
constexpr size_t IndexKeyOrPadInput = 1; |
|
constexpr size_t IndexPadTimerText = 2; |
|
|
|
bool endMenu = false; |
|
bool backToMain = false; |
|
|
|
std::vector<std::unique_ptr<UiListItem>> vecDialogItems; |
|
std::vector<std::unique_ptr<UiItemBase>> vecDialog; |
|
std::vector<OptionEntryBase *> vecOptions; |
|
OptionCategoryBase *selectedCategory = nullptr; |
|
OptionEntryBase *selectedOption = nullptr; |
|
|
|
enum class ShownMenuType : uint8_t { |
|
Categories, |
|
Settings, |
|
ListOption, |
|
KeyInput, |
|
PadInput, |
|
}; |
|
|
|
ShownMenuType shownMenu; |
|
|
|
char optionDescription[512]; |
|
|
|
Rectangle rectList; |
|
Rectangle rectDescription; |
|
|
|
enum class SpecialMenuEntry : int8_t { |
|
None = -1, |
|
PreviousMenu = -2, |
|
UnbindKey = -3, |
|
BindPadButton = -4, |
|
UnbindPadButton = -5, |
|
}; |
|
|
|
ControllerButtonCombo padEntryCombo {}; |
|
Uint32 padEntryStartTime = 0; |
|
std::string padEntryTimerText; |
|
|
|
bool IsValidEntry(OptionEntryBase *pOptionEntry) |
|
{ |
|
auto flags = pOptionEntry->GetFlags(); |
|
if (HasAnyOf(flags, OptionEntryFlags::NeedDiabloMpq) && !HaveDiabdat()) |
|
return false; |
|
if (HasAnyOf(flags, OptionEntryFlags::NeedHellfireMpq) && !HaveHellfire()) |
|
return false; |
|
return HasNoneOf(flags, OptionEntryFlags::Invisible | (gbIsHellfire ? OptionEntryFlags::OnlyDiablo : OptionEntryFlags::OnlyHellfire)); |
|
} |
|
|
|
std::vector<DrawStringFormatArg> CreateDrawStringFormatArgForEntry(OptionEntryBase *pEntry) |
|
{ |
|
return std::vector<DrawStringFormatArg> { |
|
{ pEntry->GetName(), UiFlags::ColorUiGold }, |
|
{ pEntry->GetValueDescription(), UiFlags::ColorUiSilver } |
|
}; |
|
} |
|
|
|
/** @brief Check if the option text can't fit in one list line (list width minus drawn selector) */ |
|
bool NeedsTwoLinesToDisplayOption(std::vector<DrawStringFormatArg> &formatArgs) |
|
{ |
|
return GetLineWidth("{}: {}", formatArgs.data(), formatArgs.size(), 0, GameFontTables::GameFont24, 1) >= (rectList.size.width - 90); |
|
} |
|
|
|
void CleanUpSettingsUI() |
|
{ |
|
UiInitList_clear(); |
|
|
|
vecDialogItems.clear(); |
|
vecDialog.clear(); |
|
vecOptions.clear(); |
|
|
|
ArtBackground = std::nullopt; |
|
ArtBackgroundWidescreen = std::nullopt; |
|
UnloadScrollBar(); |
|
} |
|
|
|
void GoBackOneMenuLevel() |
|
{ |
|
endMenu = true; |
|
switch (shownMenu) { |
|
case ShownMenuType::Categories: |
|
backToMain = true; |
|
break; |
|
case ShownMenuType::Settings: |
|
shownMenu = ShownMenuType::Categories; |
|
break; |
|
default: |
|
shownMenu = ShownMenuType::Settings; |
|
break; |
|
} |
|
} |
|
|
|
void StartPadEntryTimer() |
|
{ |
|
padEntryCombo = ControllerButton_NONE; |
|
padEntryStartTime = SDL_GetTicks(); |
|
if (padEntryStartTime == 0) |
|
padEntryStartTime++; |
|
// Removes access to these dialog items while entering bindings |
|
for (size_t i = IndexPadTimerText + 1; i < vecDialogItems.size(); i++) |
|
vecDialogItems[i]->uiFlags |= UiFlags::ElementHidden; |
|
} |
|
|
|
void StopPadEntryTimer() |
|
{ |
|
padEntryCombo = ControllerButton_NONE; |
|
padEntryStartTime = 0; |
|
padEntryTimerText = ""; |
|
vecDialogItems[IndexPadTimerText]->m_text = padEntryTimerText; |
|
// Restores access to these dialog items after binding is complete |
|
for (size_t i = IndexPadTimerText + 1; i < vecDialogItems.size(); i++) |
|
vecDialogItems[i]->uiFlags &= ~UiFlags::ElementHidden; |
|
} |
|
|
|
void UpdatePadEntryTimerText() |
|
{ |
|
if (shownMenu != ShownMenuType::PadInput) |
|
return; |
|
Uint32 elapsed = SDL_GetTicks() - padEntryStartTime; |
|
if (padEntryStartTime == 0 || elapsed > 10000) { |
|
StopPadEntryTimer(); |
|
return; |
|
} |
|
padEntryTimerText = StrCat(_("Press gamepad buttons to change."), " ", 10 - elapsed / 1000); |
|
vecDialogItems[IndexPadTimerText]->m_text = padEntryTimerText; |
|
} |
|
|
|
void UpdateDescription(const OptionEntryBase &option) |
|
{ |
|
auto paragraphs = WordWrapString(option.GetDescription(), rectDescription.size.width, GameFont12, 1); |
|
CopyUtf8(optionDescription, paragraphs, sizeof(optionDescription)); |
|
} |
|
|
|
void UpdateDescription(const OptionCategoryBase &category) |
|
{ |
|
auto paragraphs = WordWrapString(category.GetDescription(), rectDescription.size.width, GameFont12, 1); |
|
CopyUtf8(optionDescription, paragraphs, sizeof(optionDescription)); |
|
} |
|
|
|
void ItemFocused(size_t value) |
|
{ |
|
switch (shownMenu) { |
|
case ShownMenuType::Categories: { |
|
auto &vecItem = vecDialogItems[value]; |
|
optionDescription[0] = '\0'; |
|
if (vecItem->m_value < 0) |
|
return; |
|
auto *pCategory = GetOptions().GetCategories()[vecItem->m_value]; |
|
UpdateDescription(*pCategory); |
|
} break; |
|
case ShownMenuType::Settings: { |
|
auto &vecItem = vecDialogItems[value]; |
|
optionDescription[0] = '\0'; |
|
if (vecItem->m_value < 0) |
|
return; |
|
auto *pOption = vecOptions[vecItem->m_value]; |
|
UpdateDescription(*pOption); |
|
} break; |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
bool ChangeOptionValue(OptionEntryBase *pOption, size_t listIndex) |
|
{ |
|
if (HasAnyOf(pOption->GetFlags(), OptionEntryFlags::RecreateUI)) { |
|
endMenu = true; |
|
// Clean up all UI related Data |
|
CleanUpSettingsUI(); |
|
UnloadUiGFX(); |
|
FreeItemGFX(); |
|
selectedOption = pOption; |
|
} |
|
|
|
switch (pOption->GetType()) { |
|
case OptionEntryType::Boolean: { |
|
auto *pOptionBoolean = static_cast<OptionEntryBoolean *>(pOption); |
|
pOptionBoolean->SetValue(!**pOptionBoolean); |
|
} break; |
|
case OptionEntryType::List: { |
|
auto *pOptionList = static_cast<OptionEntryListBase *>(pOption); |
|
pOptionList->SetActiveListIndex(listIndex); |
|
} break; |
|
case OptionEntryType::Key: |
|
case OptionEntryType::PadButton: |
|
break; |
|
} |
|
|
|
if (HasAnyOf(pOption->GetFlags(), OptionEntryFlags::RecreateUI)) { |
|
// Reinitialize UI with changed settings (for example game mode, language or resolution) |
|
UiInitialize(); |
|
InitItemGFX(); |
|
SetHardwareCursor(CursorInfo::UnknownCursor()); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
void ItemSelected(size_t value) |
|
{ |
|
auto &vecItem = vecDialogItems[value]; |
|
int vecItemValue = vecItem->m_value; |
|
if (vecItemValue < 0) { |
|
auto specialMenuEntry = static_cast<SpecialMenuEntry>(vecItemValue); |
|
switch (specialMenuEntry) { |
|
case SpecialMenuEntry::None: |
|
break; |
|
case SpecialMenuEntry::PreviousMenu: |
|
GoBackOneMenuLevel(); |
|
break; |
|
case SpecialMenuEntry::UnbindKey: { |
|
auto *pOptionKey = static_cast<KeymapperOptions::Action *>(selectedOption); |
|
pOptionKey->SetValue(SDLK_UNKNOWN); |
|
vecDialogItems[IndexKeyOrPadInput]->m_text = selectedOption->GetValueDescription(); |
|
break; |
|
} |
|
case SpecialMenuEntry::BindPadButton: |
|
StartPadEntryTimer(); |
|
break; |
|
case SpecialMenuEntry::UnbindPadButton: |
|
auto *pOptionPad = static_cast<PadmapperOptions::Action *>(selectedOption); |
|
pOptionPad->SetValue(ControllerButton_NONE); |
|
vecDialogItems[IndexKeyOrPadInput]->m_text = selectedOption->GetValueDescription(); |
|
break; |
|
} |
|
return; |
|
} |
|
|
|
switch (shownMenu) { |
|
case ShownMenuType::Categories: { |
|
selectedCategory = GetOptions().GetCategories()[vecItemValue]; |
|
endMenu = true; |
|
shownMenu = ShownMenuType::Settings; |
|
} break; |
|
case ShownMenuType::Settings: { |
|
auto *pOption = vecOptions[vecItemValue]; |
|
bool updateValueDescription = false; |
|
if (pOption->GetType() == OptionEntryType::List) { |
|
auto *pOptionList = static_cast<OptionEntryListBase *>(pOption); |
|
if (pOptionList->GetListSize() > 2) { |
|
selectedOption = pOption; |
|
endMenu = true; |
|
shownMenu = ShownMenuType::ListOption; |
|
} else { |
|
// If the list contains only two items, we don't show a submenu and instead change the option value instantly |
|
size_t nextIndex = pOptionList->GetActiveListIndex() + 1; |
|
if (nextIndex >= pOptionList->GetListSize()) |
|
nextIndex = 0; |
|
updateValueDescription = ChangeOptionValue(pOption, nextIndex); |
|
} |
|
} else if (pOption->GetType() == OptionEntryType::Key) { |
|
selectedOption = pOption; |
|
endMenu = true; |
|
shownMenu = ShownMenuType::KeyInput; |
|
} else if (pOption->GetType() == OptionEntryType::PadButton) { |
|
selectedOption = pOption; |
|
endMenu = true; |
|
shownMenu = ShownMenuType::PadInput; |
|
} else { |
|
updateValueDescription = ChangeOptionValue(pOption, 0); |
|
} |
|
if (updateValueDescription) { |
|
auto args = CreateDrawStringFormatArgForEntry(pOption); |
|
bool optionUsesTwoLines = ((value + 1) < vecDialogItems.size() && vecDialogItems[value]->m_value == vecDialogItems[value + 1]->m_value); |
|
if (NeedsTwoLinesToDisplayOption(args) != optionUsesTwoLines) { |
|
selectedOption = pOption; |
|
endMenu = true; |
|
} else { |
|
vecItem->args.clear(); |
|
for (auto &arg : args) |
|
vecItem->args.push_back(arg); |
|
if (optionUsesTwoLines) { |
|
vecDialogItems[value + 1]->m_text = std::string(pOption->GetValueDescription()); |
|
} |
|
} |
|
} |
|
} break; |
|
case ShownMenuType::ListOption: { |
|
ChangeOptionValue(selectedOption, vecItemValue); |
|
GoBackOneMenuLevel(); |
|
} break; |
|
case ShownMenuType::KeyInput: |
|
case ShownMenuType::PadInput: |
|
break; |
|
} |
|
} |
|
|
|
void EscPressed() |
|
{ |
|
GoBackOneMenuLevel(); |
|
} |
|
|
|
void FullscreenChanged() |
|
{ |
|
auto *fullscreenOption = &GetOptions().Graphics.fullscreen; |
|
|
|
for (auto &vecItem : vecDialogItems) { |
|
int vecItemValue = vecItem->m_value; |
|
if (vecItemValue < 0 || static_cast<size_t>(vecItemValue) >= vecOptions.size()) |
|
continue; |
|
|
|
auto *pOption = vecOptions[vecItemValue]; |
|
if (pOption != fullscreenOption) |
|
continue; |
|
|
|
vecItem->args.clear(); |
|
for (auto &arg : CreateDrawStringFormatArgForEntry(pOption)) |
|
vecItem->args.push_back(arg); |
|
break; |
|
} |
|
} |
|
|
|
} // namespace |
|
|
|
void UiSettingsMenu() |
|
{ |
|
backToMain = false; |
|
shownMenu = ShownMenuType::Categories; |
|
selectedCategory = nullptr; |
|
selectedOption = nullptr; |
|
|
|
do { |
|
endMenu = false; |
|
|
|
// For the settings menu, we use the full height and allow some more width. |
|
const int uiWidth = std::clamp<int>(gnScreenWidth, 640, 720); |
|
const Rectangle uiRectangle = { |
|
{ (gnScreenWidth - uiWidth) / 2, 0 }, |
|
{ uiWidth, gnScreenHeight } |
|
}; |
|
|
|
UiLoadBlackBackground(); |
|
LoadScrollBar(); |
|
UiAddBackground(&vecDialog); |
|
UiAddLogo(&vecDialog, uiRectangle.position.y); |
|
|
|
const int descriptionLineHeight = IsSmallFontTall() ? 20 : 18; |
|
const int descriptionMarginTop = IsSmallFontTall() ? 10 : 16; |
|
|
|
optionDescription[0] = '\0'; |
|
|
|
std::string_view titleText; |
|
switch (shownMenu) { |
|
case ShownMenuType::Categories: |
|
titleText = _("Settings"); |
|
break; |
|
case ShownMenuType::Settings: |
|
titleText = selectedCategory->GetName(); |
|
break; |
|
default: |
|
titleText = selectedOption->GetName(); |
|
break; |
|
} |
|
vecDialog.push_back(std::make_unique<UiArtText>(titleText.data(), MakeSdlRect(uiRectangle.position.x, uiRectangle.position.y + 161, uiRectangle.size.width, 35), UiFlags::FontSize30 | UiFlags::ColorUiSilver | UiFlags::AlignCenter, 8)); |
|
|
|
size_t itemToSelect = 0; |
|
std::optional<tl::function_ref<bool(SDL_Event &)>> eventHandler; |
|
|
|
switch (shownMenu) { |
|
case ShownMenuType::Categories: { |
|
size_t catIndex = 0; |
|
for (OptionCategoryBase *pCategory : GetOptions().GetCategories()) { |
|
for (OptionEntryBase *pEntry : pCategory->GetEntries()) { |
|
if (!IsValidEntry(pEntry)) |
|
continue; |
|
if (selectedCategory == pCategory) |
|
itemToSelect = vecDialogItems.size(); |
|
vecDialogItems.push_back(std::make_unique<UiListItem>(pCategory->GetName(), static_cast<int>(catIndex), UiFlags::ColorUiGold)); |
|
break; |
|
} |
|
catIndex++; |
|
} |
|
} break; |
|
case ShownMenuType::Settings: { |
|
for (OptionEntryBase *pEntry : selectedCategory->GetEntries()) { |
|
if (!IsValidEntry(pEntry)) |
|
continue; |
|
if (selectedOption == pEntry) |
|
itemToSelect = vecDialogItems.size(); |
|
auto formatArgs = CreateDrawStringFormatArgForEntry(pEntry); |
|
int optionId = static_cast<int>(vecOptions.size()); |
|
if (NeedsTwoLinesToDisplayOption(formatArgs)) { |
|
vecDialogItems.push_back(std::make_unique<UiListItem>(std::string_view("{}:"), formatArgs, optionId, UiFlags::ColorUiGold | UiFlags::NeedsNextElement)); |
|
vecDialogItems.push_back(std::make_unique<UiListItem>(std::string(pEntry->GetValueDescription()), optionId, UiFlags::ColorUiSilver | UiFlags::ElementDisabled)); |
|
} else { |
|
vecDialogItems.push_back(std::make_unique<UiListItem>(std::string_view("{}: {}"), formatArgs, optionId, UiFlags::ColorUiGold)); |
|
} |
|
vecOptions.push_back(pEntry); |
|
} |
|
} break; |
|
case ShownMenuType::ListOption: { |
|
auto *pOptionList = static_cast<OptionEntryListBase *>(selectedOption); |
|
for (size_t i = 0; i < pOptionList->GetListSize(); i++) { |
|
vecDialogItems.push_back(std::make_unique<UiListItem>(pOptionList->GetListDescription(i), static_cast<int>(i), UiFlags::ColorUiGold)); |
|
} |
|
itemToSelect = pOptionList->GetActiveListIndex(); |
|
UpdateDescription(*pOptionList); |
|
} break; |
|
case ShownMenuType::KeyInput: { |
|
vecDialogItems.push_back(std::make_unique<UiListItem>(_("Bound key:"), static_cast<int>(SpecialMenuEntry::None), UiFlags::ColorWhitegold | UiFlags::ElementDisabled)); |
|
vecDialogItems.push_back(std::make_unique<UiListItem>(std::string(selectedOption->GetValueDescription()), static_cast<int>(SpecialMenuEntry::None), UiFlags::ColorUiGold)); |
|
assert(IndexKeyOrPadInput == vecDialogItems.size() - 1); |
|
itemToSelect = IndexKeyOrPadInput; |
|
eventHandler = [](SDL_Event &event) { |
|
if (SelectedItem != IndexKeyOrPadInput) |
|
return false; |
|
uint32_t key = SDLK_UNKNOWN; |
|
switch (event.type) { |
|
case SDL_KEYDOWN: { |
|
SDL_Keycode keycode = event.key.keysym.sym; |
|
remap_keyboard_key(&keycode); |
|
key = static_cast<uint32_t>(keycode); |
|
if (key >= SDLK_a && key <= SDLK_z) { |
|
key -= 'a' - 'A'; |
|
} |
|
} break; |
|
case SDL_MOUSEBUTTONDOWN: |
|
switch (event.button.button) { |
|
case SDL_BUTTON_MIDDLE: |
|
case SDL_BUTTON_X1: |
|
case SDL_BUTTON_X2: |
|
key = event.button.button | KeymapperMouseButtonMask; |
|
break; |
|
} |
|
break; |
|
#if SDL_VERSION_ATLEAST(2, 0, 0) |
|
case SDL_MOUSEWHEEL: |
|
if (event.wheel.y > 0) { |
|
key = MouseScrollUpButton; |
|
} else if (event.wheel.y < 0) { |
|
key = MouseScrollDownButton; |
|
} else if (event.wheel.x > 0) { |
|
key = MouseScrollLeftButton; |
|
} else if (event.wheel.x < 0) { |
|
key = MouseScrollRightButton; |
|
} |
|
break; |
|
#endif |
|
} |
|
// Ignore unknown keys |
|
if (key == SDLK_UNKNOWN) |
|
return false; |
|
auto *pOptionKey = static_cast<KeymapperOptions::Action *>(selectedOption); |
|
if (!pOptionKey->SetValue(key)) |
|
return false; |
|
vecDialogItems[IndexKeyOrPadInput]->m_text = selectedOption->GetValueDescription(); |
|
return true; |
|
}; |
|
vecDialogItems.push_back(std::make_unique<UiListItem>(_("Press any key to change."), static_cast<int>(SpecialMenuEntry::None), UiFlags::ColorUiSilver | UiFlags::ElementDisabled)); |
|
vecDialogItems.push_back(std::make_unique<UiListItem>(std::string_view {}, static_cast<int>(SpecialMenuEntry::None), UiFlags::ElementDisabled)); |
|
vecDialogItems.push_back(std::make_unique<UiListItem>(_("Unbind key"), static_cast<int>(SpecialMenuEntry::UnbindKey), UiFlags::ColorUiGold)); |
|
UpdateDescription(*selectedOption); |
|
} break; |
|
case ShownMenuType::PadInput: { |
|
vecDialogItems.push_back(std::make_unique<UiListItem>(_("Bound button combo:"), static_cast<int>(SpecialMenuEntry::None), UiFlags::ColorWhitegold | UiFlags::ElementDisabled)); |
|
vecDialogItems.push_back(std::make_unique<UiListItem>(selectedOption->GetValueDescription(), static_cast<int>(SpecialMenuEntry::BindPadButton), UiFlags::ColorUiGold)); |
|
assert(IndexKeyOrPadInput == vecDialogItems.size() - 1); |
|
itemToSelect = IndexKeyOrPadInput; |
|
|
|
vecDialogItems.push_back(std::make_unique<UiListItem>(std::string_view(padEntryTimerText), static_cast<int>(SpecialMenuEntry::None), UiFlags::ColorUiSilver | UiFlags::ElementDisabled)); |
|
assert(IndexPadTimerText == vecDialogItems.size() - 1); |
|
|
|
vecDialogItems.push_back(std::make_unique<UiListItem>(std::string_view {}, static_cast<int>(SpecialMenuEntry::None), UiFlags::ElementDisabled)); |
|
vecDialogItems.push_back(std::make_unique<UiListItem>(_("Unbind button combo"), static_cast<int>(SpecialMenuEntry::UnbindPadButton), UiFlags::ColorUiGold)); |
|
|
|
padEntryStartTime = 0; |
|
eventHandler = [](SDL_Event &event) { |
|
if (padEntryStartTime == 0) |
|
return false; |
|
|
|
StaticVector<ControllerButtonEvent, 4> ctrlEvents = ToControllerButtonEvents(event); |
|
for (ControllerButtonEvent ctrlEvent : ctrlEvents) { |
|
bool isGamepadMotion = IsControllerMotion(event); |
|
DetectInputMethod(event, ctrlEvent); |
|
if (event.type == SDL_KEYUP && event.key.keysym.sym == SDLK_ESCAPE) { |
|
StopPadEntryTimer(); |
|
return true; |
|
} |
|
if (isGamepadMotion || IsAnyOf(ctrlEvent.button, ControllerButton_NONE, ControllerButton_IGNORE)) { |
|
continue; |
|
} |
|
|
|
bool modifierPressed = padEntryCombo.modifier != ControllerButton_NONE && IsControllerButtonPressed(padEntryCombo.modifier); |
|
bool buttonPressed = padEntryCombo.button != ControllerButton_NONE && IsControllerButtonPressed(padEntryCombo.button); |
|
if (ctrlEvent.up) { |
|
// When the player has released all relevant inputs, assume the binding is finished and stop the timer |
|
if (padEntryCombo.button != ControllerButton_NONE && !modifierPressed && !buttonPressed) { |
|
StopPadEntryTimer(); |
|
return true; |
|
} |
|
continue; |
|
} |
|
|
|
auto *pOptionPad = static_cast<PadmapperOptions::Action *>(selectedOption); |
|
if (!modifierPressed && buttonPressed) |
|
padEntryCombo.modifier = padEntryCombo.button; |
|
padEntryCombo.button = ctrlEvent.button; |
|
if (pOptionPad->SetValue(padEntryCombo)) |
|
vecDialogItems[IndexKeyOrPadInput]->m_text = selectedOption->GetValueDescription(); |
|
} |
|
return true; |
|
}; |
|
UpdateDescription(*selectedOption); |
|
} break; |
|
} |
|
|
|
vecDialogItems.push_back(std::make_unique<UiListItem>(std::string_view {}, static_cast<int>(SpecialMenuEntry::None), UiFlags::ElementDisabled)); |
|
vecDialogItems.push_back(std::make_unique<UiListItem>(_("Previous Menu"), static_cast<int>(SpecialMenuEntry::PreviousMenu), UiFlags::ColorUiGold)); |
|
|
|
constexpr int ListItemHeight = 26; |
|
rectList = { uiRectangle.position + Displacement { 50, 204 }, |
|
Size { uiRectangle.size.width - 100, std::min<int>(static_cast<int>(vecDialogItems.size()) * ListItemHeight, uiRectangle.size.height - 272) } }; |
|
rectDescription = { rectList.position + Displacement { -26, rectList.size.height + descriptionMarginTop }, |
|
Size { uiRectangle.size.width - 50, 80 - descriptionMarginTop } }; |
|
vecDialog.push_back(std::make_unique<UiScrollbar>((*ArtScrollBarBackground)[0], (*ArtScrollBarThumb)[0], |
|
*ArtScrollBarArrow, MakeSdlRect(rectList.position.x + rectList.size.width + 5, rectList.position.y, 25, rectList.size.height))); |
|
vecDialog.push_back(std::make_unique<UiArtText>(optionDescription, MakeSdlRect(rectDescription), |
|
UiFlags::FontSize12 | UiFlags::ColorUiSilverDark | UiFlags::AlignCenter, 1, descriptionLineHeight)); |
|
vecDialog.push_back(std::make_unique<UiList>(vecDialogItems, rectList.size.height / ListItemHeight, |
|
rectList.position.x, rectList.position.y, rectList.size.width, ListItemHeight, UiFlags::FontSize24 | UiFlags::AlignCenter)); |
|
|
|
UiInitList(ItemFocused, ItemSelected, EscPressed, vecDialog, true, FullscreenChanged, nullptr, itemToSelect); |
|
|
|
while (!endMenu) { |
|
UiClearScreen(); |
|
UpdatePadEntryTimerText(); |
|
UiPollAndRender(eventHandler); |
|
} |
|
|
|
CleanUpSettingsUI(); |
|
} while (!backToMain); |
|
|
|
SaveOptions(); |
|
} |
|
|
|
} // namespace devilution
|
|
|