From 258b569deb9e1aba495c2f88a2e7ccc2e4ad60b3 Mon Sep 17 00:00:00 2001 From: Josh Trzebiatowski Date: Thu, 26 Jan 2023 15:40:02 -0600 Subject: [PATCH] Allow filtering in Scenery window (#19272) * WIP: scenery searching * refactor snake case * actually start filtering scenery * prepare for merge from develop * use ObjectManager to get scenery info * clear selected scenery when it is filtered out * Clear tool when no scenery is selected * updating changelog * fix clang formatting issues --- distribution/changelog.txt | 1 + src/openrct2-ui/windows/Scenery.cpp | 133 ++++++++++++++++++++++++---- src/openrct2/interface/Window.h | 2 +- 3 files changed, 116 insertions(+), 20 deletions(-) diff --git a/distribution/changelog.txt b/distribution/changelog.txt index c5d55b1366..0956030c31 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -17,6 +17,7 @@ - Improved: [#19131] Track missing objects when selecting scenery groups in console. - Improved: [#19253] Queue junctions drawn properly when using regular paths as queue. - Improved: [#19067] New Ride window now allows filtering similarly to Object Selection. +- Improved: [#19272] Scenery window now allows filtering similarly to Object Selection. - Change: [#19018] Renamed actions to fit the naming scheme. - Change: [#19091] [Plugin] Add game action information to callback arguments of custom actions. - Change: [#19233] Reduce lift speed minimum and maximum values for “Classic Wooden Coaster”. diff --git a/src/openrct2-ui/windows/Scenery.cpp b/src/openrct2-ui/windows/Scenery.cpp index 0615cbbad9..fc35f6c0a5 100644 --- a/src/openrct2-ui/windows/Scenery.cpp +++ b/src/openrct2-ui/windows/Scenery.cpp @@ -24,6 +24,8 @@ #include #include #include +#include +#include #include #include #include @@ -32,6 +34,8 @@ #include #include +using namespace OpenRCT2; + static constexpr const StringId WINDOW_TITLE = STR_NONE; constexpr int32_t WINDOW_SCENERY_MIN_WIDTH = 634; constexpr int32_t WINDOW_SCENERY_MIN_HEIGHT = 180; @@ -56,6 +60,8 @@ enum WindowSceneryListWidgetIdx WIDX_SCENERY_TERTIARY_COLOUR_BUTTON, WIDX_SCENERY_EYEDROPPER_BUTTON, WIDX_SCENERY_BUILD_CLUSTER_BUTTON, + WIDX_FILTER_TEXT_BOX, + WIDX_FILTER_CLEAR_BUTTON, WIDX_SCENERY_TAB_1, }; @@ -67,7 +73,7 @@ validate_global_widx(WC_SCENERY, WIDX_SCENERY_EYEDROPPER_BUTTON); static Widget WindowSceneryBaseWidgets[] = { WINDOW_SHIM(WINDOW_TITLE, WINDOW_SCENERY_MIN_WIDTH, WINDOW_SCENERY_MIN_HEIGHT), MakeWidget ({ 0, 43}, {634, 99}, WindowWidgetType::Resize, WindowColour::Secondary ), // 8 0x009DE2C8 - MakeWidget ({ 2, 47}, {607, 80}, WindowWidgetType::Scroll, WindowColour::Secondary, SCROLL_VERTICAL ), // 1000000 0x009DE418 + MakeWidget ({ 2, 62}, {607, 80}, WindowWidgetType::Scroll, WindowColour::Secondary, SCROLL_VERTICAL ), // 1000000 0x009DE418 MakeWidget ({609, 44}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_ROTATE_ARROW), STR_ROTATE_OBJECTS_90 ), // 2000000 0x009DE428 MakeWidget ({609, 68}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_PAINTBRUSH), STR_SCENERY_PAINTBRUSH_TIP ), // 4000000 0x009DE438 MakeWidget ({615, 93}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_SELECT_COLOUR ), // 8000000 0x009DE448 @@ -75,6 +81,8 @@ static Widget WindowSceneryBaseWidgets[] = { MakeWidget ({615, 117}, { 12, 12}, WindowWidgetType::ColourBtn, WindowColour::Secondary, 0xFFFFFFFF, STR_SELECT_TERNARY_COLOUR ), // 20000000 0x009DE468 MakeWidget ({609, 130}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_G2_EYEDROPPER), STR_SCENERY_EYEDROPPER_TIP ), // 40000000 0x009DE478 MakeWidget ({609, 154}, { 24, 24}, WindowWidgetType::FlatBtn, WindowColour::Secondary, ImageId(SPR_SCENERY_CLUSTER), STR_SCENERY_CLUSTER_TIP ), // 40000000 0x009DE478 + MakeWidget ({ 4, 46}, {211, 14}, WindowWidgetType::TextBox, WindowColour::Secondary ), + MakeWidget ({218, 46}, { 70, 14}, WindowWidgetType::Button, WindowColour::Secondary, STR_OBJECT_SEARCH_CLEAR ), WIDGETS_END, }; // clang-format on @@ -134,12 +142,15 @@ private: int32_t _requiredWidth; ScenerySelection _selectedScenery; int16_t _hoverCounter; + u8string _filter; + std::vector _filteredScenery; public: void OnOpen() override { Init(); + _filter.clear(); InitScrollWidgets(); ContentUpdateScroll(); ShowGridlines(); @@ -227,6 +238,15 @@ public: } Invalidate(); break; + case WIDX_FILTER_TEXT_BOX: + WindowStartTextbox(*this, widgetIndex, STR_STRING, _filter.data(), MAX_PATH); + break; + case WIDX_FILTER_CLEAR_BUTTON: + _filter.clear(); + ContentUpdateScroll(); + scrolls->v_top = 0; + Invalidate(); + break; } } @@ -391,6 +411,12 @@ public: } } + if (gCurrentTextBox.window.classification == classification && gCurrentTextBox.window.number == number) + { + WindowUpdateTextboxCaret(); + WidgetInvalidate(*this, WIDX_FILTER_TEXT_BOX); + } + Invalidate(); if (!SceneryToolIsActive()) @@ -434,9 +460,28 @@ public: gCurrentToolId = static_cast(GetSmallSceneryEntry(tabSelectedScenery.EntryIndex)->tool_id); } } + else + { + gCurrentToolId = Tool::Arrow; + } } } + void OnTextInput(WidgetIndex widgetIndex, std::string_view text) override + { + if (widgetIndex != WIDX_FILTER_TEXT_BOX) + return; + + if (text == _filter) + return; + + _filter.assign(text); + ContentUpdateScroll(); + + scrolls->v_top = 0; + Invalidate(); + } + ScreenSize OnScrollGetSize(int32_t scrollIndex) override { if (scrollIndex == SceneryContentScrollIndex) @@ -504,6 +549,7 @@ public: } } widgets[WIDX_SCENERY_TITLE].text = titleStringId; + widgets[WIDX_FILTER_TEXT_BOX].string = _filter.data(); pressed_widgets = 0; pressed_widgets |= 1uLL << (tabIndex + WIDX_SCENERY_TAB_1); @@ -865,7 +911,7 @@ private: return 0; } - const auto totalItems = _tabEntries[tabIndex].Entries.size(); + const auto totalItems = _filteredScenery.size(); const auto numColumns = GetNumColumns(); const auto rows = CountRows(totalItems + numColumns - 1); return rows; @@ -884,6 +930,8 @@ private: return; } + SetFilteredScenery(tabIndex); + const int32_t listHeight = height - 14 - widgets[WIDX_SCENERY_LIST].top - 1; const auto sceneryItem = ContentCountRowsWithSelectedItem(tabIndex); @@ -893,11 +941,15 @@ private: auto rowSelected = CountRows(sceneryItem.selected_item); if (sceneryItem.scenerySelection.IsUndefined()) { + SetSelectedScenery(tabIndex, ScenerySelection()); rowSelected = 0; - const auto& scenery = _tabEntries[tabIndex].Entries[0]; - if (!scenery.IsUndefined()) + if (!_filteredScenery.empty()) { - SetSelectedScenery(tabIndex, scenery); + const auto& scenery = _filteredScenery[0]; + if (!scenery.IsUndefined()) + { + SetSelectedScenery(tabIndex, scenery); + } } } @@ -911,17 +963,16 @@ private: { SceneryItem sceneryItem = { 0, 0, ScenerySelection() }; const auto scenerySelection = GetSelectedScenery(tabIndex); - const auto& tabInfo = _tabEntries[tabIndex]; - for (size_t i = 0; i < tabInfo.Entries.size(); i++) + for (size_t i = 0; i < _filteredScenery.size(); i++) { - const auto& currentEntry = tabInfo.Entries[i]; + const auto& currentEntry = _filteredScenery[i]; if (currentEntry == scenerySelection) { sceneryItem.selected_item = static_cast(i); sceneryItem.scenerySelection = scenerySelection; } } - sceneryItem.allRows = static_cast(CountRows(tabInfo.Entries.size() + 8)); + sceneryItem.allRows = static_cast(CountRows(_filteredScenery.size() + 8)); return sceneryItem; } @@ -1004,6 +1055,56 @@ private: } } + void SetFilteredScenery(const size_t tabIndex) + { + auto currentTab = _tabEntries[tabIndex]; + + _filteredScenery.clear(); + for (auto selection : currentTab.Entries) + { + if (IsFiltered(selection)) + _filteredScenery.push_back(selection); + } + } + + bool IsFiltered(const ScenerySelection& selection) + { + if (_filter.empty()) + return true; + + auto& objManager = GetContext()->GetObjectManager(); + auto sceneryObjectType = GetObjectTypeFromSceneryType(selection.SceneryType); + auto sceneryObject = objManager.GetLoadedObject(sceneryObjectType, selection.EntryIndex); + + return IsFilterInName(*sceneryObject) || IsFilterInAuthors(*sceneryObject) || IsFilterInIdentifier(*sceneryObject) + || IsFilterInFilename(*sceneryObject); + } + + bool IsFilterInName(const Object& object) + { + return String::Contains(object.GetName(), _filter, true); + } + + bool IsFilterInAuthors(const Object& object) + { + for (auto author : object.GetAuthors()) + if (String::Contains(author, _filter, true)) + return true; + + return false; + } + + bool IsFilterInIdentifier(const Object& object) + { + return String::Contains(object.GetIdentifier(), _filter, true); + } + + bool IsFilterInFilename(const Object& object) + { + auto repoItem = ObjectRepositoryFindObjectByEntry(&(object.GetObjectEntry())); + return String::Contains(repoItem->Path, _filter, true); + } + void SortTabs() { std::sort(_tabEntries.begin(), _tabEntries.end(), [](const SceneryTabInfo& a, const SceneryTabInfo& b) { @@ -1064,14 +1165,9 @@ private: if (colIndex >= 0 && colIndex < numColumns && rowIndex >= 0) { const auto tabSceneryIndex = static_cast((rowIndex * numColumns) + colIndex); - const auto tabIndex = _activeTabIndex; - if (tabIndex < _tabEntries.size()) + if (tabSceneryIndex < _filteredScenery.size()) { - auto& tabInfo = _tabEntries[tabIndex]; - if (tabSceneryIndex < tabInfo.Entries.size()) - { - return tabInfo.Entries[tabSceneryIndex]; - } + return _filteredScenery[tabSceneryIndex]; } } return scenery; @@ -1307,10 +1403,9 @@ private: ScreenCoordsXY topLeft{ 0, 0 }; - const auto& tabInfo = _tabEntries[tabIndex]; - for (size_t sceneryTabItemIndex = 0; sceneryTabItemIndex < tabInfo.Entries.size(); sceneryTabItemIndex++) + for (size_t sceneryTabItemIndex = 0; sceneryTabItemIndex < _filteredScenery.size(); sceneryTabItemIndex++) { - const auto& currentSceneryGlobal = tabInfo.Entries[sceneryTabItemIndex]; + const auto& currentSceneryGlobal = _filteredScenery[sceneryTabItemIndex]; const auto tabSelectedScenery = GetSelectedScenery(tabIndex); if (gWindowSceneryPaintEnabled == 1 || gWindowSceneryEyedropperEnabled) { diff --git a/src/openrct2/interface/Window.h b/src/openrct2/interface/Window.h index a52bcf3749..a5849f50f8 100644 --- a/src/openrct2/interface/Window.h +++ b/src/openrct2/interface/Window.h @@ -419,7 +419,7 @@ enum WindowDetail #define WC_MAZE_CONSTRUCTION__WIDX_MAZE_DIRECTION_GROUPBOX WC_RIDE_CONSTRUCTION__WIDX_CONSTRUCT #define WC_MAZE_CONSTRUCTION__WIDX_MAZE_ENTRANCE WC_RIDE_CONSTRUCTION__WIDX_ENTRANCE #define WC_MAZE_CONSTRUCTION__WIDX_MAZE_EXIT WC_RIDE_CONSTRUCTION__WIDX_EXIT -#define WC_SCENERY__WIDX_SCENERY_TAB_1 12 +#define WC_SCENERY__WIDX_SCENERY_TAB_1 14 #define WC_SCENERY__WIDX_SCENERY_ROTATE_OBJECTS_BUTTON 5 #define WC_SCENERY__WIDX_SCENERY_EYEDROPPER_BUTTON 10 #define WC_PEEP__WIDX_PATROL 10