/***************************************************************************** * Copyright (c) 2014-2023 OpenRCT2 developers * * For a complete list of all authors, please refer to contributors.md * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2 * * OpenRCT2 is licensed under the GNU General Public License version 3. *****************************************************************************/ #include "../interface/Theme.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static constexpr const StringId WINDOW_TITLE = STR_SELECT_SCENARIO; static constexpr const int32_t WW = 734; static constexpr const int32_t WH = 384; static constexpr const int32_t SidebarWidth = 180; static constexpr const int32_t TabWidth = 92; static constexpr const int32_t TabHeight = 34; static constexpr const int32_t TrueFontSize = 24; static constexpr const int32_t WidgetsStart = 17; static constexpr const int32_t TabsStart = WidgetsStart; #define INITIAL_NUM_UNLOCKED_SCENARIOS 5 constexpr const uint8_t NumTabs = 10; enum class ListItemType : uint8_t { Heading, Scenario, }; struct ScenarioListItem { ListItemType type; union { struct { StringId string_id; } heading; struct { const ScenarioIndexEntry* scenario; bool is_locked; } scenario; }; }; enum { WIDX_BACKGROUND, WIDX_TITLEBAR, WIDX_CLOSE, WIDX_TABCONTENT, WIDX_TAB1, WIDX_TAB2, WIDX_TAB3, WIDX_TAB4, WIDX_TAB5, WIDX_TAB6, WIDX_TAB7, WIDX_TAB8, WIDX_TAB9, WIDX_TAB10, WIDX_SCENARIOLIST }; static constexpr const StringId ScenarioOriginStringIds[] = { STR_SCENARIO_CATEGORY_RCT1, STR_SCENARIO_CATEGORY_RCT1_AA, STR_SCENARIO_CATEGORY_RCT1_LL, STR_SCENARIO_CATEGORY_RCT2, STR_SCENARIO_CATEGORY_RCT2_WW, STR_SCENARIO_CATEGORY_RCT2_TT, STR_SCENARIO_CATEGORY_UCES, STR_SCENARIO_CATEGORY_REAL_PARKS, STR_SCENARIO_CATEGORY_EXTRAS_PARKS, STR_SCENARIO_CATEGORY_OTHER_PARKS, }; // clang-format off static Widget window_scenarioselect_widgets[] = { WINDOW_SHIM(WINDOW_TITLE, WW, WH), MakeWidget({ TabWidth + 1, WidgetsStart }, { WW, 284 }, WindowWidgetType::Resize, WindowColour::Secondary), // tab content panel MakeRemapWidget({ 3, TabsStart + (TabHeight * 0) }, { TabWidth, TabHeight}, WindowWidgetType::Tab, WindowColour::Secondary, SPR_G2_SIDEWAYS_TAB), // tab 01 MakeRemapWidget({ 3, TabsStart + (TabHeight * 1) }, { TabWidth, TabHeight}, WindowWidgetType::Tab, WindowColour::Secondary, SPR_G2_SIDEWAYS_TAB), // tab 02 MakeRemapWidget({ 3, TabsStart + (TabHeight * 2) }, { TabWidth, TabHeight}, WindowWidgetType::Tab, WindowColour::Secondary, SPR_G2_SIDEWAYS_TAB), // tab 03 MakeRemapWidget({ 3, TabsStart + (TabHeight * 3) }, { TabWidth, TabHeight}, WindowWidgetType::Tab, WindowColour::Secondary, SPR_G2_SIDEWAYS_TAB), // tab 04 MakeRemapWidget({ 3, TabsStart + (TabHeight * 4) }, { TabWidth, TabHeight}, WindowWidgetType::Tab, WindowColour::Secondary, SPR_G2_SIDEWAYS_TAB), // tab 05 MakeRemapWidget({ 3, TabsStart + (TabHeight * 5) }, { TabWidth, TabHeight}, WindowWidgetType::Tab, WindowColour::Secondary, SPR_G2_SIDEWAYS_TAB), // tab 06 MakeRemapWidget({ 3, TabsStart + (TabHeight * 6) }, { TabWidth, TabHeight}, WindowWidgetType::Tab, WindowColour::Secondary, SPR_G2_SIDEWAYS_TAB), // tab 07 MakeRemapWidget({ 3, TabsStart + (TabHeight * 7) }, { TabWidth, TabHeight}, WindowWidgetType::Tab, WindowColour::Secondary, SPR_G2_SIDEWAYS_TAB), // tab 08 MakeRemapWidget({ 3, TabsStart + (TabHeight * 8) }, { TabWidth, TabHeight}, WindowWidgetType::Tab, WindowColour::Secondary, SPR_G2_SIDEWAYS_TAB), // tab 09 MakeRemapWidget({ 3, TabsStart + (TabHeight * 8) }, { TabWidth, TabHeight}, WindowWidgetType::Tab, WindowColour::Secondary, SPR_G2_SIDEWAYS_TAB), // tab 10 MakeWidget({ TabWidth + 3, WidgetsStart + 1 }, { WW - SidebarWidth, 276 }, WindowWidgetType::Scroll, WindowColour::Secondary, SCROLL_VERTICAL), // level list WIDGETS_END, }; // clang-format on static void WindowScenarioselectInitTabs(WindowBase* w); static void DrawCategoryHeading(WindowBase* w, DrawPixelInfo* dpi, int32_t left, int32_t right, int32_t y, StringId stringId); static void InitialiseListItems(WindowBase* w); static bool IsScenarioVisible(WindowBase* w, const ScenarioIndexEntry* scenario); static bool IsLockingEnabled(WindowBase* w); static bool _titleEditor = false; static bool _disableLocking{}; static std::function _callback; static std::vector _listItems; static bool ScenarioSelectUseSmallFont() { return ThemeGetFlags() & UITHEME_FLAG_USE_ALTERNATIVE_SCENARIO_SELECT_FONT; } static int32_t GetScenarioListItemSize() { if (!LocalisationService_UseTrueTypeFont()) return TrueFontSize; // Scenario title int32_t lineHeight = FontGetLineHeight(FontStyle::Medium); // 'Completed by' line lineHeight += FontGetLineHeight(FontStyle::Small); return lineHeight; } WindowBase* WindowScenarioselectOpen(scenarioselect_callback callback, bool titleEditor) { if (_titleEditor != titleEditor) { _titleEditor = titleEditor; WindowCloseByClass(WindowClass::ScenarioSelect); } auto window = WindowBringToFrontByClass(WindowClass::ScenarioSelect); if (window != nullptr) return window; return WindowScenarioselectOpen( [callback](std::string_view scenario) { callback(std::string(scenario).c_str()); }, titleEditor, titleEditor); } static void WindowScenarioselectInitTabs(WindowBase* w) { int32_t showPages = 0; size_t numScenarios = ScenarioRepositoryGetCount(); for (size_t i = 0; i < numScenarios; i++) { const ScenarioIndexEntry* scenario = ScenarioRepositoryGetByIndex(i); if (gConfigGeneral.ScenarioSelectMode == SCENARIO_SELECT_MODE_ORIGIN || _titleEditor) { if (_titleEditor && scenario->SourceGame == ScenarioSource::Other) continue; showPages |= 1 << static_cast(scenario->SourceGame); } else { int32_t category = scenario->Category; if (category > SCENARIO_CATEGORY_OTHER) { category = SCENARIO_CATEGORY_OTHER; } showPages |= 1 << category; } } if (showPages & (1 << gConfigInterface.ScenarioselectLastTab)) { w->selected_tab = gConfigInterface.ScenarioselectLastTab; } else { int32_t firstPage = UtilBitScanForward(showPages); if (firstPage != -1) { w->selected_tab = firstPage; } } int32_t y = TabsStart; for (int32_t i = 0; i < NumTabs; i++) { auto& widget = w->widgets[i + WIDX_TAB1]; if (!(showPages & (1 << i))) { widget.type = WindowWidgetType::Empty; continue; } widget.type = WindowWidgetType::Tab; widget.top = y; widget.bottom = y + (TabHeight - 1); y += TabHeight; } } class ScenarioSelectWindow final : public Window { private: bool _showLockedInformation = false; public: void OnOpen() override { // Load scenario list ScenarioRepositoryScan(); widgets = window_scenarioselect_widgets; WindowScenarioselectInitTabs(this); InitialiseListItems(this); WindowInitScrollWidgets(*this); } void OnMouseUp(WidgetIndex widgetIndex) override { if (widgetIndex == WIDX_CLOSE) { Close(); } } void OnMouseDown(WidgetIndex widgetIndex) override { if (widgetIndex >= WIDX_TAB1 && widgetIndex <= WIDX_TAB10) { selected_tab = widgetIndex - 4; highlighted_scenario = nullptr; gConfigInterface.ScenarioselectLastTab = selected_tab; ConfigSaveDefault(); InitialiseListItems(this); Invalidate(); WindowEventResizeCall(this); WindowEventInvalidateCall(this); WindowInitScrollWidgets(*this); Invalidate(); } } void OnDraw(DrawPixelInfo& dpi) override { int32_t format; const ScenarioIndexEntry* scenario; DrawWidgets(dpi); format = ScenarioSelectUseSmallFont() ? STR_SMALL_WINDOW_COLOUR_2_STRINGID : STR_WINDOW_COLOUR_2_STRINGID; FontStyle fontStyle = ScenarioSelectUseSmallFont() ? FontStyle::Small : FontStyle::Medium; // Text for each tab for (uint32_t i = 0; i < std::size(ScenarioOriginStringIds); i++) { Widget* widget = &window_scenarioselect_widgets[WIDX_TAB1 + i]; if (widget->type == WindowWidgetType::Empty) continue; auto ft = Formatter(); if (gConfigGeneral.ScenarioSelectMode == SCENARIO_SELECT_MODE_ORIGIN || _titleEditor) { ft.Add(ScenarioOriginStringIds[i]); } else { // old-style ft.Add(ScenarioCategoryStringIds[i]); } auto stringCoords = windowPos + ScreenCoordsXY{ widget->midX(), widget->midY() - 3 }; DrawTextWrapped(dpi, stringCoords, 87, format, ft, { COLOUR_AQUAMARINE, fontStyle, TextAlignment::CENTRE }); } // Return if no scenario highlighted scenario = highlighted_scenario; if (scenario == nullptr) { if (_showLockedInformation) { // Show locked information auto screenPos = windowPos + ScreenCoordsXY{ window_scenarioselect_widgets[WIDX_SCENARIOLIST].right + 4, window_scenarioselect_widgets[WIDX_TABCONTENT].top + 5 }; DrawTextEllipsised( dpi, screenPos + ScreenCoordsXY{ 85, 0 }, 170, STR_SCENARIO_LOCKED, {}, { TextAlignment::CENTRE }); DrawTextWrapped(dpi, screenPos + ScreenCoordsXY{ 0, 15 }, 170, STR_SCENARIO_LOCKED_DESC); } else { // Show general information about how to start. auto screenPos = windowPos + ScreenCoordsXY{ window_scenarioselect_widgets[WIDX_SCENARIOLIST].right + 4, window_scenarioselect_widgets[WIDX_TABCONTENT].top + 5 }; DrawTextWrapped(dpi, screenPos + ScreenCoordsXY{ 0, 15 }, 170, STR_SCENARIO_HOVER_HINT); } return; } // Scenario path if (gConfigGeneral.DebuggingTools) { utf8 path[MAX_PATH]; ShortenPath(path, sizeof(path), scenario->Path, width - 6 - TabWidth, FontStyle::Medium); const utf8* pathPtr = path; auto ft = Formatter(); ft.Add(pathPtr); DrawTextBasic(dpi, windowPos + ScreenCoordsXY{ TabWidth + 3, height - 3 - 11 }, STR_STRING, ft, { colours[1] }); } // Scenario name auto screenPos = windowPos + ScreenCoordsXY{ window_scenarioselect_widgets[WIDX_SCENARIOLIST].right + 4, window_scenarioselect_widgets[WIDX_TABCONTENT].top + 5 }; auto ft = Formatter(); ft.Add(STR_STRING); ft.Add(scenario->Name); DrawTextEllipsised( dpi, screenPos + ScreenCoordsXY{ 85, 0 }, 170, STR_WINDOW_COLOUR_2_STRINGID, ft, { TextAlignment::CENTRE }); screenPos.y += 15; // Scenario details ft = Formatter(); ft.Add(STR_STRING); ft.Add(scenario->Details); screenPos.y += DrawTextWrapped(dpi, screenPos, 170, STR_BLACK_STRING, ft) + 5; // Scenario objective ft = Formatter(); ft.Add(ObjectiveNames[scenario->ObjectiveType]); if (scenario->ObjectiveType == OBJECTIVE_BUILD_THE_BEST) { StringId rideTypeString = STR_NONE; auto rideTypeId = scenario->ObjectiveArg3; if (rideTypeId != RIDE_TYPE_NULL && rideTypeId < RIDE_TYPE_COUNT) { rideTypeString = GetRideTypeDescriptor(rideTypeId).Naming.Name; } ft.Add(rideTypeString); } else { ft.Add(scenario->ObjectiveArg3); ft.Add(DateGetTotalMonths(MONTH_OCTOBER, scenario->ObjectiveArg1)); if (scenario->ObjectiveType == OBJECTIVE_FINISH_5_ROLLERCOASTERS) ft.Add(scenario->ObjectiveArg2); else ft.Add(scenario->ObjectiveArg2); } screenPos.y += DrawTextWrapped(dpi, screenPos, 170, STR_OBJECTIVE, ft) + 5; // Scenario score if (scenario->Highscore != nullptr) { // TODO: Should probably be translatable u8string completedByName = "???"; if (!scenario->Highscore->name.empty()) { completedByName = scenario->Highscore->name; } ft = Formatter(); ft.Add(STR_STRING); ft.Add(completedByName.c_str()); ft.Add(scenario->Highscore->company_value); screenPos.y += DrawTextWrapped(dpi, screenPos, 170, STR_COMPLETED_BY_WITH_COMPANY_VALUE, ft); } } void OnClose() override { _listItems.clear(); _listItems.shrink_to_fit(); } void OnPrepareDraw() override { pressed_widgets &= ~( (1uLL << WIDX_CLOSE) | (1uLL << WIDX_TAB1) | (1uLL << WIDX_TAB2) | (1uLL << WIDX_TAB3) | (1uLL << WIDX_TAB4) | (1uLL << WIDX_TAB5) | (1uLL << WIDX_TAB6) | (1uLL << WIDX_TAB7) | (1uLL << WIDX_TAB8) | (1uLL << WIDX_TAB9) | (1uLL << WIDX_TAB10)); pressed_widgets |= 1LL << (selected_tab + WIDX_TAB1); ResizeFrameWithPage(); const int32_t bottomMargin = gConfigGeneral.DebuggingTools ? 17 : 5; window_scenarioselect_widgets[WIDX_SCENARIOLIST].right = width - 179; window_scenarioselect_widgets[WIDX_SCENARIOLIST].bottom = height - bottomMargin; } ScreenSize OnScrollGetSize(int32_t scrollIndex) override { const int32_t scenarioItemHeight = GetScenarioListItemSize(); int32_t y = 0; for (const auto& listItem : _listItems) { switch (listItem.type) { case ListItemType::Heading: y += 18; break; case ListItemType::Scenario: y += scenarioItemHeight; break; } } return { WW, y }; } void OnScrollMouseOver(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) override { const int32_t scenarioItemHeight = GetScenarioListItemSize(); bool originalShowLockedInformation = _showLockedInformation; _showLockedInformation = false; const ScenarioIndexEntry* selected = nullptr; auto mutableScreenCoords = screenCoords; for (const auto& listItem : _listItems) { switch (listItem.type) { case ListItemType::Heading: mutableScreenCoords.y -= 18; break; case ListItemType::Scenario: mutableScreenCoords.y -= scenarioItemHeight; if (mutableScreenCoords.y < 0) { if (listItem.scenario.is_locked) { _showLockedInformation = true; } else { selected = listItem.scenario.scenario; } } break; } if (mutableScreenCoords.y < 0) { break; } } if (highlighted_scenario != selected) { highlighted_scenario = selected; Invalidate(); } else if (_showLockedInformation != originalShowLockedInformation) { Invalidate(); } } void OnScrollMouseDown(int32_t scrollIndex, const ScreenCoordsXY& screenCoords) override { const int32_t scenarioItemHeight = GetScenarioListItemSize(); auto mutableScreenCoords = screenCoords; for (const auto& listItem : _listItems) { switch (listItem.type) { case ListItemType::Heading: mutableScreenCoords.y -= 18; break; case ListItemType::Scenario: mutableScreenCoords.y -= scenarioItemHeight; if (mutableScreenCoords.y < 0 && !listItem.scenario.is_locked) { OpenRCT2::Audio::Play(OpenRCT2::Audio::SoundId::Click1, 0, windowPos.x + (width / 2)); gFirstTimeSaving = true; _callback(listItem.scenario.scenario->Path); if (_titleEditor) { Close(); return; } } break; } if (mutableScreenCoords.y < 0) { break; } } } void OnScrollDraw(int32_t scrollIndex, DrawPixelInfo& dpi) override { uint8_t paletteIndex = ColourMapA[colours[1]].mid_light; GfxClear(&dpi, paletteIndex); StringId highlighted_format = ScenarioSelectUseSmallFont() ? STR_WHITE_STRING : STR_WINDOW_COLOUR_2_STRINGID; StringId unhighlighted_format = ScenarioSelectUseSmallFont() ? STR_WHITE_STRING : STR_BLACK_STRING; const auto& listWidget = widgets[WIDX_SCENARIOLIST]; int32_t listWidth = listWidget.width() - 12; const int32_t scenarioItemHeight = GetScenarioListItemSize(); // Scenario title int32_t scenarioTitleHeight = FontGetLineHeight(FontStyle::Medium); int32_t y = 0; for (const auto& listItem : _listItems) { if (y > dpi.y + dpi.height) { continue; } switch (listItem.type) { case ListItemType::Heading: { const int32_t horizontalRuleMargin = 4; DrawCategoryHeading( this, &dpi, horizontalRuleMargin, listWidth - horizontalRuleMargin, y + 2, listItem.heading.string_id); y += 18; break; } case ListItemType::Scenario: { // Draw hover highlight const ScenarioIndexEntry* scenario = listItem.scenario.scenario; bool isHighlighted = highlighted_scenario == scenario; if (isHighlighted) { GfxFilterRect(&dpi, { 0, y, width, y + scenarioItemHeight - 1 }, FilterPaletteID::PaletteDarken1); } bool isCompleted = scenario->Highscore != nullptr; bool isDisabled = listItem.scenario.is_locked; // Draw scenario name char buffer[64]; SafeStrCpy(buffer, scenario->Name, sizeof(buffer)); StringId format = isDisabled ? static_cast(STR_STRINGID) : (isHighlighted ? highlighted_format : unhighlighted_format); auto ft = Formatter(); ft.Add(STR_STRING); ft.Add(buffer); colour_t colour = isDisabled ? colours[1] | COLOUR_FLAG_INSET : COLOUR_BLACK; auto darkness = isDisabled ? TextDarkness::Dark : TextDarkness::Regular; const auto scrollCentre = window_scenarioselect_widgets[WIDX_SCENARIOLIST].width() / 2; DrawTextBasic( dpi, { scrollCentre, y + 1 }, format, ft, { colour, FontStyle::Medium, TextAlignment::CENTRE, darkness }); // Check if scenario is completed if (isCompleted) { // Draw completion tick GfxDrawSprite( &dpi, ImageId(SPR_MENU_CHECKMARK), { window_scenarioselect_widgets[WIDX_SCENARIOLIST].width() - 45, y + 1 }); // Draw completion score u8string completedByName = "???"; if (!scenario->Highscore->name.empty()) { completedByName = scenario->Highscore->name; } ft = Formatter(); ft.Add(STR_COMPLETED_BY); ft.Add(STR_STRING); ft.Add(completedByName.c_str()); DrawTextBasic( dpi, { scrollCentre, y + scenarioTitleHeight + 1 }, format, ft, { FontStyle::Small, TextAlignment::CENTRE }); } y += scenarioItemHeight; break; } } } } }; static bool IsScenarioVisible(WindowBase* w, const ScenarioIndexEntry* scenario) { if (gConfigGeneral.ScenarioSelectMode == SCENARIO_SELECT_MODE_ORIGIN || _titleEditor) { if (static_cast(scenario->SourceGame) != w->selected_tab) { return false; } } else { int32_t category = scenario->Category; if (category > SCENARIO_CATEGORY_OTHER) { category = SCENARIO_CATEGORY_OTHER; } if (category != w->selected_tab) { return false; } } return true; } static void InitialiseListItems(WindowBase* w) { size_t numScenarios = ScenarioRepositoryGetCount(); _listItems.clear(); // Mega park unlock const uint32_t rct1RequiredCompletedScenarios = (1 << SC_MEGA_PARK) - 1; uint32_t rct1CompletedScenarios = 0; size_t megaParkListItemIndex = SIZE_MAX; int32_t numUnlocks = INITIAL_NUM_UNLOCKED_SCENARIOS; uint8_t currentHeading = UINT8_MAX; for (size_t i = 0; i < numScenarios; i++) { const ScenarioIndexEntry* scenario = ScenarioRepositoryGetByIndex(i); if (!IsScenarioVisible(w, scenario)) continue; if (_titleEditor && scenario->SourceGame == ScenarioSource::Other) continue; // Category heading StringId headingStringId = STR_NONE; if (gConfigGeneral.ScenarioSelectMode == SCENARIO_SELECT_MODE_ORIGIN || _titleEditor) { if (w->selected_tab != static_cast(ScenarioSource::Real) && currentHeading != scenario->Category) { currentHeading = scenario->Category; headingStringId = ScenarioCategoryStringIds[currentHeading]; } } else { if (w->selected_tab <= SCENARIO_CATEGORY_EXPERT) { if (currentHeading != static_cast(scenario->SourceGame)) { currentHeading = static_cast(scenario->SourceGame); headingStringId = ScenarioOriginStringIds[currentHeading]; } } else if (w->selected_tab == SCENARIO_CATEGORY_OTHER) { int32_t category = scenario->Category; if (category <= SCENARIO_CATEGORY_REAL) { category = SCENARIO_CATEGORY_OTHER; } if (currentHeading != category) { currentHeading = category; headingStringId = ScenarioCategoryStringIds[category]; } } } if (headingStringId != STR_NONE) { ScenarioListItem headerItem; headerItem.type = ListItemType::Heading; headerItem.heading.string_id = headingStringId; _listItems.push_back(std::move(headerItem)); } // Scenario ScenarioListItem scenarioItem; scenarioItem.type = ListItemType::Scenario; scenarioItem.scenario.scenario = scenario; if (IsLockingEnabled(w)) { scenarioItem.scenario.is_locked = numUnlocks <= 0; if (scenario->Highscore == nullptr) { numUnlocks--; } else { // Mark RCT1 scenario as completed if (scenario->ScenarioId < SC_MEGA_PARK) { rct1CompletedScenarios |= 1 << scenario->ScenarioId; } } // If scenario is Mega Park, keep a reference to it if (scenario->ScenarioId == SC_MEGA_PARK) { megaParkListItemIndex = _listItems.size() - 1; } } else { scenarioItem.scenario.is_locked = false; } _listItems.push_back(std::move(scenarioItem)); } // Mega park handling if (megaParkListItemIndex != SIZE_MAX) { bool megaParkLocked = (rct1CompletedScenarios & rct1RequiredCompletedScenarios) != rct1RequiredCompletedScenarios; _listItems[megaParkListItemIndex].scenario.is_locked = megaParkLocked; if (megaParkLocked && gConfigGeneral.ScenarioHideMegaPark) { // Remove mega park _listItems.pop_back(); // Remove empty headings for (auto it = _listItems.begin(); it != _listItems.end();) { const auto& listItem = *it; if (listItem.type == ListItemType::Heading) { auto nextIt = std::next(it); if (nextIt == _listItems.end() || nextIt->type == ListItemType::Heading) { it = _listItems.erase(it); continue; } } ++it; } } } } static void DrawCategoryHeading(WindowBase* w, DrawPixelInfo* dpi, int32_t left, int32_t right, int32_t y, StringId stringId) { colour_t baseColour = w->colours[1]; colour_t lightColour = ColourMapA[baseColour].lighter; colour_t darkColour = ColourMapA[baseColour].mid_dark; // Draw string int32_t centreX = (left + right) / 2; DrawTextBasic(*dpi, { centreX, y }, stringId, {}, { baseColour, TextAlignment::CENTRE }); // Get string dimensions utf8 buffer[CommonTextBufferSize]; auto bufferPtr = buffer; OpenRCT2::FormatStringLegacy(bufferPtr, sizeof(buffer), stringId, nullptr); int32_t categoryStringHalfWidth = (GfxGetStringWidth(bufferPtr, FontStyle::Medium) / 2) + 4; int32_t strLeft = centreX - categoryStringHalfWidth; int32_t strRight = centreX + categoryStringHalfWidth; // Draw light horizontal rule int32_t lineY = y + 4; auto lightLineLeftTop1 = ScreenCoordsXY{ left, lineY }; auto lightLineRightBottom1 = ScreenCoordsXY{ strLeft, lineY }; GfxDrawLine(dpi, { lightLineLeftTop1, lightLineRightBottom1 }, lightColour); auto lightLineLeftTop2 = ScreenCoordsXY{ strRight, lineY }; auto lightLineRightBottom2 = ScreenCoordsXY{ right, lineY }; GfxDrawLine(dpi, { lightLineLeftTop2, lightLineRightBottom2 }, lightColour); // Draw dark horizontal rule lineY++; auto darkLineLeftTop1 = ScreenCoordsXY{ left, lineY }; auto darkLineRightBottom1 = ScreenCoordsXY{ strLeft, lineY }; GfxDrawLine(dpi, { darkLineLeftTop1, darkLineRightBottom1 }, darkColour); auto darkLineLeftTop2 = ScreenCoordsXY{ strRight, lineY }; auto darkLineRightBottom2 = ScreenCoordsXY{ right, lineY }; GfxDrawLine(dpi, { darkLineLeftTop2, darkLineRightBottom2 }, darkColour); } static bool IsLockingEnabled(WindowBase* w) { if (gConfigGeneral.ScenarioSelectMode != SCENARIO_SELECT_MODE_ORIGIN) return false; if (!gConfigGeneral.ScenarioUnlockingEnabled) return false; if (w->selected_tab >= 6) return false; if (_titleEditor) return false; return true; } WindowBase* WindowScenarioselectOpen(std::function callback, bool titleEditor, bool disableLocking) { WindowBase* window; _callback = callback; _disableLocking = disableLocking; int32_t screenWidth = ContextGetWidth(); int32_t screenHeight = ContextGetHeight(); ScreenCoordsXY screenPos = { (screenWidth - WW) / 2, std::max(TOP_TOOLBAR_HEIGHT + 1, (screenHeight - WH) / 2) }; window = WindowCreate(WindowClass::ScenarioSelect, screenPos, WW, WH, 0); window->widgets = window_scenarioselect_widgets; window->highlighted_scenario = nullptr; return window; }