From fde3b35a24cd2556564356ed3f5ce71fd849711f Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Tue, 7 May 2024 12:13:49 +0100 Subject: [PATCH] Feature: New filter to show only used types in build-pickers. This filters the build-picker type lists to only show types that have already been placed in the current game, making it simpler to get to build matching features. --- src/lang/english.txt | 2 ++ src/object_gui.cpp | 12 ++++++++++ src/picker_gui.cpp | 42 +++++++++++++++++++++++++++++---- src/picker_gui.h | 13 ++++++++++ src/rail_gui.cpp | 47 +++++++++++++++++++++++++++++++++++++ src/road_gui.cpp | 17 ++++++++++++++ src/widgets/picker_widget.h | 1 + 7 files changed, 130 insertions(+), 4 deletions(-) diff --git a/src/lang/english.txt b/src/lang/english.txt index 62298a8c07..d45870a205 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2799,6 +2799,8 @@ STR_STATION_BUILD_DRAG_DROP_TOOLTIP :{BLACK}Build a STR_PICKER_MODE_ALL :All STR_PICKER_MODE_ALL_TOOLTIP :Toggle showing items from all classes +STR_PICKER_MODE_USED :Used +STR_PICKER_MODE_USED_TOOLTIP :Toggle showing only existing items STR_PICKER_STATION_CLASS_TOOLTIP :Select a station class to display STR_PICKER_STATION_TYPE_TOOLTIP :Select a station type to build. Ctrl+Click to add or remove in saved items diff --git a/src/object_gui.cpp b/src/object_gui.cpp index 42f4c743b9..ff4d86824f 100644 --- a/src/object_gui.cpp +++ b/src/object_gui.cpp @@ -9,11 +9,13 @@ #include "stdafx.h" #include "command_func.h" +#include "company_func.h" #include "hotkeys.h" #include "newgrf.h" #include "newgrf_object.h" #include "newgrf_text.h" #include "object.h" +#include "object_base.h" #include "picker_gui.h" #include "sound_func.h" #include "strings_func.h" @@ -91,6 +93,16 @@ public: } } + void FillUsedItems(std::set &items) override + { + for (const Object *o : Object::Iterate()) { + if (GetTileOwner(o->location.tile) != _current_company) continue; + const ObjectSpec *spec = ObjectSpec::Get(o->type); + if (spec == nullptr || spec->class_index == INVALID_OBJECT_CLASS || !spec->IsEverAvailable()) continue; + items.insert(GetPickerItem(spec)); + } + } + static ObjectPickerCallbacks instance; }; /* static */ ObjectPickerCallbacks ObjectPickerCallbacks::instance; diff --git a/src/picker_gui.cpp b/src/picker_gui.cpp index 291532a5c5..94841519bf 100644 --- a/src/picker_gui.cpp +++ b/src/picker_gui.cpp @@ -27,6 +27,8 @@ #include "widgets/picker_widget.h" +#include "table/sprites.h" + #include "safeguards.h" /** Sort classes by id. */ @@ -107,6 +109,10 @@ void PickerWindow::ConstructWindow() this->classes.SetFilterFuncs(_class_filter_funcs); if (this->has_type_picker) { + /* Update used type information. */ + this->callbacks.used.clear(); + this->callbacks.FillUsedItems(this->callbacks.used); + SetWidgetDisabledState(WID_PW_MODE_ALL, !this->callbacks.HasClassChoice()); this->GetWidget(WID_PW_TYPE_ITEM)->tool_tip = this->callbacks.GetTypeTooltip(); @@ -200,6 +206,9 @@ void PickerWindow::DrawWidget(const Rect &r, WidgetID widget) const int y = (ir.Height() + ScaleSpriteTrad(PREVIEW_HEIGHT)) / 2 - ScaleSpriteTrad(PREVIEW_BOTTOM); this->callbacks.DrawType(x, y, item.class_index, item.index); + if (this->callbacks.used.contains(item)) { + DrawSprite(SPR_BLOT, PALETTE_TO_GREEN, ir.Width() - GetSpriteSize(SPR_BLOT).width, 0); + } } if (!this->callbacks.IsTypeAvailable(item.class_index, item.index)) { @@ -232,7 +241,6 @@ void PickerWindow::OnClick(Point pt, WidgetID widget, int) if (this->callbacks.GetSelectedClass() != *it || HasBit(this->callbacks.mode, PFM_ALL)) { ClrBit(this->callbacks.mode, PFM_ALL); // Disable showing all. - SetWidgetLoweredState(WID_PW_MODE_ALL, false); this->callbacks.SetSelectedClass(*it); this->InvalidateData(PFI_TYPE | PFI_POSITION | PFI_VALIDATE); } @@ -242,9 +250,13 @@ void PickerWindow::OnClick(Point pt, WidgetID widget, int) } case WID_PW_MODE_ALL: + case WID_PW_MODE_USED: ToggleBit(this->callbacks.mode, widget - WID_PW_MODE_ALL); - SetWidgetLoweredState(widget, HasBit(this->callbacks.mode, widget - WID_PW_MODE_ALL)); - this->InvalidateData(PFI_TYPE | PFI_POSITION); + if (!this->IsWidgetDisabled(WID_PW_MODE_ALL) && HasBit(this->callbacks.mode, widget - WID_PW_MODE_ALL)) { + /* Enabling used or saved filters automatically enables all. */ + SetBit(this->callbacks.mode, PFM_ALL); + } + this->InvalidateData(PFI_CLASS | PFI_TYPE | PFI_POSITION); break; /* Type Picker */ @@ -281,6 +293,7 @@ void PickerWindow::OnInvalidateData(int data, bool gui_scope) if (this->has_type_picker) { SetWidgetLoweredState(WID_PW_MODE_ALL, HasBit(this->callbacks.mode, PFM_ALL)); + SetWidgetLoweredState(WID_PW_MODE_USED, HasBit(this->callbacks.mode, PFM_USED)); } } @@ -332,8 +345,10 @@ void PickerWindow::BuildPickerClassList() this->classes.clear(); this->classes.reserve(count); + bool filter_used = HasBit(this->callbacks.mode, PFM_USED); for (int i = 0; i < count; i++) { if (this->callbacks.GetClassName(i) == INVALID_STRING_ID) continue; + if (filter_used && std::none_of(std::begin(this->callbacks.used), std::end(this->callbacks.used), [i](const PickerItem &item) { return item.class_index == i; })) continue; this->classes.emplace_back(i); } @@ -366,6 +381,15 @@ void PickerWindow::EnsureSelectedClassIsVisible() this->GetScrollbar(WID_PW_CLASS_SCROLL)->ScrollTowards(pos); } +void PickerWindow::RefreshUsedTypeList() +{ + if (!this->has_type_picker) return; + + this->callbacks.used.clear(); + this->callbacks.FillUsedItems(this->callbacks.used); + this->InvalidateData(PFI_TYPE); +} + /** Builds the filter list of types. */ void PickerWindow::BuildPickerTypeList() { @@ -373,9 +397,18 @@ void PickerWindow::BuildPickerTypeList() this->types.clear(); bool show_all = HasBit(this->callbacks.mode, PFM_ALL); + bool filter_used = HasBit(this->callbacks.mode, PFM_USED); int cls_id = this->callbacks.GetSelectedClass(); - if (show_all) { + if (filter_used) { + /* Showing used items. */ + this->types.reserve(this->callbacks.used.size()); + for (const PickerItem &item : this->callbacks.used) { + if (!show_all && item.class_index != cls_id) continue; + if (this->callbacks.GetTypeName(item.class_index, item.index) == INVALID_STRING_ID) continue; + this->types.emplace_back(item); + } + } else if (show_all) { /* Reserve enough space for everything. */ int total = 0; for (int class_index : this->classes) total += this->callbacks.GetTypeCount(class_index); @@ -472,6 +505,7 @@ std::unique_ptr MakePickerTypeWidgets() EndContainer(), NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, WID_PW_MODE_ALL), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_PICKER_MODE_ALL, STR_PICKER_MODE_ALL_TOOLTIP), + NWidget(WWT_TEXTBTN, COLOUR_DARK_GREEN, WID_PW_MODE_USED), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_PICKER_MODE_USED, STR_PICKER_MODE_USED_TOOLTIP), EndContainer(), NWidget(NWID_HORIZONTAL), NWidget(WWT_PANEL, COLOUR_DARK_GREEN), SetScrollbar(WID_PW_TYPE_SCROLL), diff --git a/src/picker_gui.h b/src/picker_gui.h index e48cc5f269..6f323372ee 100644 --- a/src/picker_gui.h +++ b/src/picker_gui.h @@ -16,6 +16,7 @@ #include "strings_type.h" #include "timer/timer.h" #include "timer/timer_game_calendar.h" +#include "timer/timer_window.h" #include "window_gui.h" #include "window_type.h" @@ -74,6 +75,9 @@ public: /** Draw preview image of an item. */ virtual void DrawType(int x, int y, int cls_id, int id) const = 0; + /** Fill a set with all items that are used by the current player. */ + virtual void FillUsedItems(std::set &items) = 0; + Listing class_last_sorting = { false, 0 }; ///< Default sorting of #PickerClassList. Filtering class_last_filtering = { false, 0 }; ///< Default filtering of #PickerClassList. @@ -81,6 +85,8 @@ public: Filtering type_last_filtering = { false, 0 }; ///< Default filtering of #PickerTypeList. uint8_t mode = 0; ///< Bitmask of \c PickerFilterModes. + + std::set used; ///< Set of items used in the current game by the current company. }; /** Helper for PickerCallbacks when the class system is based on NewGRFClass. */ @@ -108,6 +114,7 @@ public: } }; + struct PickerFilterData : StringFilter { const PickerCallbacks *callbacks; ///< Callbacks for filter functions to access to callbacks. }; @@ -119,6 +126,7 @@ class PickerWindow : public PickerWindowBase { public: enum PickerFilterModes { PFM_ALL = 0, ///< Show all classes. + PFM_USED = 1, ///< Show used types. }; enum PickerFilterInvalidation { @@ -171,6 +179,7 @@ private: PickerFilterData type_string_filter; QueryString type_editbox; ///< Filter editbox + void RefreshUsedTypeList(); void BuildPickerTypeList(); void EnsureSelectedTypeIsValid(); void EnsureSelectedTypeIsVisible(); @@ -178,6 +187,10 @@ private: IntervalTimer yearly_interval = {{TimerGameCalendar::YEAR, TimerGameCalendar::Priority::NONE}, [this](auto) { this->SetDirty(); }}; + + IntervalTimer refresh_interval = {std::chrono::seconds(3), [this](auto) { + RefreshUsedTypeList(); + }}; }; class NWidgetBase; diff --git a/src/rail_gui.cpp b/src/rail_gui.cpp index eea214f670..6e2e87ab93 100644 --- a/src/rail_gui.cpp +++ b/src/rail_gui.cpp @@ -9,6 +9,8 @@ #include "stdafx.h" #include "gui.h" +#include "station_base.h" +#include "waypoint_base.h" #include "window_gui.h" #include "station_gui.h" #include "terraform_gui.h" @@ -944,6 +946,19 @@ static void HandleStationPlacement(TileIndex start, TileIndex end) ShowSelectStationIfNeeded(ta, proc); } +/** + * Test if a station/waypoint uses the default graphics. + * @param bst Station to test. + * @return true if at least one of its rail station tiles uses the default graphics. + */ +static bool StationUsesDefaultType(const BaseStation *bst) +{ + for (TileIndex t : bst->train_station) { + if (bst->TileBelongsToRailStation(t) && IsRailStation(t) && GetCustomStationSpecIndex(t) == 0) return true; + } + return false; +} + class StationPickerCallbacks : public PickerCallbacksNewGRFClass { public: StringID GetClassTooltip() const override { return STR_PICKER_STATION_CLASS_TOOLTIP; } @@ -996,6 +1011,22 @@ public: } } + void FillUsedItems(std::set &items) override + { + bool default_added = false; + for (const Station *st : Station::Iterate()) { + if (st->owner != _local_company) continue; + if (!default_added && StationUsesDefaultType(st)) { + items.insert({0, 0, STAT_CLASS_DFLT, 0}); + default_added = true; + } + for (const auto &sm : st->speclist) { + if (sm.spec == nullptr) continue; + items.insert({sm.grfid, sm.localidx, sm.spec->class_index, sm.spec->index}); + } + } + } + static StationPickerCallbacks instance; }; /* static */ StationPickerCallbacks StationPickerCallbacks::instance; @@ -1788,6 +1819,22 @@ public: DrawWaypointSprite(x, y, this->GetClassIndex(cls_id), id, _cur_railtype); } + void FillUsedItems(std::set &items) override + { + bool default_added = false; + for (const Waypoint *wp : Waypoint::Iterate()) { + if (wp->owner != _local_company) continue; + if (!default_added && StationUsesDefaultType(wp)) { + items.insert({0, 0, STAT_CLASS_WAYP, 0}); + default_added = true; + } + for (const auto &sm : wp->speclist) { + if (sm.spec == nullptr) continue; + items.insert({0, 0, sm.spec->class_index, sm.spec->index}); + } + } + } + static WaypointPickerCallbacks instance; }; /* static */ WaypointPickerCallbacks WaypointPickerCallbacks::instance; diff --git a/src/road_gui.cpp b/src/road_gui.cpp index 18ed083687..dc21a4fadb 100644 --- a/src/road_gui.cpp +++ b/src/road_gui.cpp @@ -30,6 +30,7 @@ #include "dropdown_type.h" #include "dropdown_func.h" #include "engine_base.h" +#include "station_base.h" #include "strings_func.h" #include "core/geometry_func.hpp" #include "station_cmd.h" @@ -1160,6 +1161,22 @@ public: DrawRoadStopTile(x, y, _cur_roadtype, spec, roadstoptype == ROADSTOP_BUS ? STATION_BUS : STATION_TRUCK, (uint8_t)orientation); } } + + void FillUsedItems(std::set &items) override + { + for (const Station *st : Station::Iterate()) { + if (st->owner != _local_company) continue; + if (roadstoptype == ROADSTOP_TRUCK && !(st->facilities & FACIL_TRUCK_STOP)) continue; + if (roadstoptype == ROADSTOP_BUS && !(st->facilities & FACIL_BUS_STOP)) continue; + items.insert({0, 0, ROADSTOP_CLASS_DFLT, 0}); // We would need to scan the map to find out if default is used. + for (const auto &sm : st->roadstop_speclist) { + if (sm.spec == nullptr) continue; + if (roadstoptype == ROADSTOP_TRUCK && sm.spec->stop_type != ROADSTOPTYPE_FREIGHT && sm.spec->stop_type != ROADSTOPTYPE_ALL) continue; + if (roadstoptype == ROADSTOP_BUS && sm.spec->stop_type != ROADSTOPTYPE_PASSENGER && sm.spec->stop_type != ROADSTOPTYPE_ALL) continue; + items.insert({sm.grfid, sm.localidx, sm.spec->class_index, sm.spec->index}); + } + } + } }; template <> StringID RoadStopPickerCallbacks::GetClassTooltip() const { return STR_PICKER_ROADSTOP_BUS_CLASS_TOOLTIP; } diff --git a/src/widgets/picker_widget.h b/src/widgets/picker_widget.h index fe45a2a427..ee026db0e0 100644 --- a/src/widgets/picker_widget.h +++ b/src/widgets/picker_widget.h @@ -22,6 +22,7 @@ enum PickerClassWindowWidgets : WidgetID { WID_PW_TYPE_SEL, ///< Stack to hide the type picker. WID_PW_TYPE_FILTER, ///< Text filter. WID_PW_MODE_ALL, ///< Toggle "Show all" filter mode. + WID_PW_MODE_USED, ///< Toggle showing only used types. WID_PW_TYPE_MATRIX, ///< Matrix with items. WID_PW_TYPE_ITEM, ///< A single item. WID_PW_TYPE_SCROLL, ///< Scrollbar for the matrix.