diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp index 9704f8ccf5..4783bbe5aa 100644 --- a/src/build_vehicle_gui.cpp +++ b/src/build_vehicle_gui.cpp @@ -1436,6 +1436,8 @@ struct BuildVehicleWindow : Window { list.clear(); + BadgeTextFilter btf(this->string_filter, GSF_TRAINS); + /* Make list of all available train engines and wagons. * Also check to see if the previously selected engine is still available, * and if not, reset selection to EngineID::Invalid(). This could be the case @@ -1452,7 +1454,7 @@ struct BuildVehicleWindow : Window { if (!FilterSingleEngine(eid)) continue; /* Filter by name or NewGRF extra text */ - if (!FilterByText(e)) continue; + if (!FilterByText(e) && !btf.Filter(e->badges)) continue; list.emplace_back(eid, e->info.variant_id, e->display_flags, 0); @@ -1501,6 +1503,8 @@ struct BuildVehicleWindow : Window { this->eng_list.clear(); + BadgeTextFilter btf(this->string_filter, GSF_ROADVEHICLES); + for (const Engine *e : Engine::IterateType(VEH_ROAD)) { if (!this->show_hidden_engines && e->IsVariantHidden(_local_company)) continue; EngineID eid = e->index; @@ -1508,7 +1512,7 @@ struct BuildVehicleWindow : Window { if (this->filter.roadtype != INVALID_ROADTYPE && !HasPowerOnRoad(e->u.road.roadtype, this->filter.roadtype)) continue; /* Filter by name or NewGRF extra text */ - if (!FilterByText(e)) continue; + if (!FilterByText(e) && !btf.Filter(e->badges)) continue; this->eng_list.emplace_back(eid, e->info.variant_id, e->display_flags, 0); @@ -1523,13 +1527,15 @@ struct BuildVehicleWindow : Window { EngineID sel_id = EngineID::Invalid(); this->eng_list.clear(); + BadgeTextFilter btf(this->string_filter, GSF_SHIPS); + for (const Engine *e : Engine::IterateType(VEH_SHIP)) { if (!this->show_hidden_engines && e->IsVariantHidden(_local_company)) continue; EngineID eid = e->index; if (!IsEngineBuildable(eid, VEH_SHIP, _local_company)) continue; /* Filter by name or NewGRF extra text */ - if (!FilterByText(e)) continue; + if (!FilterByText(e) && !btf.Filter(e->badges)) continue; this->eng_list.emplace_back(eid, e->info.variant_id, e->display_flags, 0); @@ -1547,6 +1553,8 @@ struct BuildVehicleWindow : Window { const Station *st = this->listview_mode ? nullptr : Station::GetByTile(TileIndex(this->window_number)); + BadgeTextFilter btf(this->string_filter, GSF_AIRCRAFT); + /* Make list of all available planes. * Also check to see if the previously selected plane is still available, * and if not, reset selection to EngineID::Invalid(). This could be the case @@ -1559,7 +1567,7 @@ struct BuildVehicleWindow : Window { if (!this->listview_mode && !CanVehicleUseStation(eid, st)) continue; /* Filter by name or NewGRF extra text */ - if (!FilterByText(e)) continue; + if (!FilterByText(e) && !btf.Filter(e->badges)) continue; this->eng_list.emplace_back(eid, e->info.variant_id, e->display_flags, 0); diff --git a/src/newgrf_badge.cpp b/src/newgrf_badge.cpp index 4b444d05b0..6c22f96a8c 100644 --- a/src/newgrf_badge.cpp +++ b/src/newgrf_badge.cpp @@ -14,6 +14,7 @@ #include "newgrf_badge.h" #include "newgrf_badge_type.h" #include "newgrf_spritegroup.h" +#include "stringfilter_type.h" #include "strings_func.h" #include "timer/timer_game_calendar.h" #include "window_gui.h" @@ -389,6 +390,42 @@ uint GUIBadgeClasses::GetTotalColumnsWidth() const return std::accumulate(std::begin(this->column_widths), std::end(this->column_widths), 0U); } +/** + * Construct a badge text filter. + * @param filter string filter. + * @param feature feature being used. + */ +BadgeTextFilter::BadgeTextFilter(StringFilter &filter, GrfSpecFeature feature) +{ + /* Do not filter if the filter text box is empty */ + if (filter.IsEmpty()) return; + + /* Pre-build list of badges that match by string. */ + for (const auto &badge : _badges.specs) { + if (badge.name == STR_NULL) continue; + if (!badge.features.Test(feature)) continue; + + filter.ResetState(); + filter.AddLine(GetString(badge.name)); + if (!filter.GetState()) continue; + + auto it = std::ranges::lower_bound(this->badges, badge.index); + if (it != std::end(this->badges) && *it == badge.index) continue; + + this->badges.insert(it, badge.index); + } +} + +/** + * Test if any of the given badges matches the filtered badge list. + * @param badges List of badges. + * @returns true iff at least one badge in badges is present. + */ +bool BadgeTextFilter::Filter(std::span badges) const +{ + return std::ranges::any_of(badges, [this](const BadgeID &badge) { return std::ranges::binary_search(this->badges, badge); }); +} + /** * Draw names for a list of badge labels. * @param r Rect to draw in. diff --git a/src/newgrf_badge.h b/src/newgrf_badge.h index d0328169de..51ecac81a3 100644 --- a/src/newgrf_badge.h +++ b/src/newgrf_badge.h @@ -74,6 +74,15 @@ void DrawBadgeColumn(Rect r, int column_group, const GUIBadgeClasses &badge_clas uint32_t GetBadgeVariableResult(const struct GRFFile &grffile, std::span badges, uint32_t parameter); +class BadgeTextFilter { +public: + BadgeTextFilter(struct StringFilter &filter, GrfSpecFeature feature); + bool Filter(std::span badges) const; + +private: + std::vector badges{}; +}; + std::unique_ptr MakeDropDownListBadgeItem(const std::shared_ptr &gui_classes, std::span badges, GrfSpecFeature feature, std::optional introduction_date, StringID str, int value, bool masked = false, bool shaded = false); std::unique_ptr MakeDropDownListBadgeIconItem(const std::shared_ptr &gui_classes, std::span badges, GrfSpecFeature feature, std::optional introduction_date, const Dimension &dim, SpriteID sprite, PaletteID palette, StringID str, int value, bool masked = false, bool shaded = false); diff --git a/src/picker_gui.cpp b/src/picker_gui.cpp index 0940d7e107..67eeafb899 100644 --- a/src/picker_gui.cpp +++ b/src/picker_gui.cpp @@ -147,6 +147,8 @@ static bool TypeIDSorter(PickerItem const &a, PickerItem const &b) /** Filter types by class name. */ static bool TypeTagNameFilter(PickerItem const *item, PickerFilterData &filter) { + if (filter.btf.has_value() && filter.btf->Filter(filter.callbacks->GetTypeBadges(item->class_index, item->index))) return true; + filter.ResetState(); filter.AddLine(GetString(filter.callbacks->GetTypeName(item->class_index, item->index))); return filter.GetState(); @@ -449,6 +451,11 @@ void PickerWindow::OnEditboxChanged(WidgetID wid) case WID_PW_TYPE_FILTER: this->type_string_filter.SetFilterTerm(this->type_editbox.text.GetText()); + if (!type_string_filter.IsEmpty()) { + this->type_string_filter.btf.emplace(this->type_string_filter, this->callbacks.GetFeature()); + } else { + this->type_string_filter.btf.reset(); + } this->types.SetFilterState(!type_string_filter.IsEmpty()); this->InvalidateData(PickerInvalidation::Type); break; diff --git a/src/picker_gui.h b/src/picker_gui.h index 4f0060deff..4dabc59a50 100644 --- a/src/picker_gui.h +++ b/src/picker_gui.h @@ -144,6 +144,7 @@ public: struct PickerFilterData : StringFilter { const PickerCallbacks *callbacks; ///< Callbacks for filter functions to access to callbacks. + std::optional btf; }; using PickerClassList = GUIList; ///< GUIList holding classes to display.