1
0
Fork 0

Change: Add badge dropdown filters and configuration.

pull/14305/head
Peter Nelson 2025-05-25 09:51:48 +01:00 committed by Peter Nelson
parent 308ce39747
commit 25f1c97bea
9 changed files with 206 additions and 15 deletions

View File

@ -5919,5 +5919,9 @@ STR_TOOLBAR_RAILTYPE_VELOCITY :{STRING} ({VELO
STR_BADGE_NAME_LIST :{STRING}: {GOLD}{RAW_STRING}
STR_BADGE_CONFIG_MENU_TOOLTIP :Open badge configuration
STR_BADGE_CONFIG_RESET :Reset
STR_BADGE_CONFIG_ICONS :{WHITE}Badge Icons
STR_BADGE_CONFIG_FILTERS :{WHITE}Badge Filters
STR_BADGE_CONFIG_PREVIEW :Preview Image
STR_BADGE_CONFIG_NAME :Name
STR_BADGE_FILTER_ANY_LABEL :Any {STRING}
STR_BADGE_FILTER_IS_LABEL :{STRING} is {STRING}

View File

@ -10,7 +10,6 @@
#include "stdafx.h"
#include "newgrf.h"
#include "newgrf_badge.h"
#include "newgrf_badge_config.h"
#include "newgrf_badge_type.h"
#include "newgrf_spritegroup.h"
#include "stringfilter_type.h"
@ -271,6 +270,7 @@ void ApplyBadgeFeaturesToClassBadges()
Badge *class_badge = GetClassBadge(badge.class_index);
assert(class_badge != nullptr);
class_badge->features.Set(badge.features);
if (badge.name != STR_NULL) class_badge->flags.Set(BadgeFlag::HasText);
}
}
@ -297,7 +297,7 @@ PalSpriteID GetBadgeSprite(const Badge &badge, GrfSpecFeature feature, std::opti
* Create a list of used badge classes for a feature.
* @param feature GRF feature being used.
*/
UsedBadgeClasses::UsedBadgeClasses(GrfSpecFeature feature)
UsedBadgeClasses::UsedBadgeClasses(GrfSpecFeature feature) : feature(feature)
{
for (auto index : _badges.classes) {
Badge *class_badge = GetBadge(index);
@ -341,3 +341,16 @@ bool BadgeTextFilter::Filter(std::span<const BadgeID> badges) const
{
return std::ranges::any_of(badges, [this](const BadgeID &badge) { return std::ranges::binary_search(this->badges, badge); });
}
/**
* Test if the given badges matches the filtered badge list.
* @param badges List of badges.
* @return true iff all required badges are present in the provided list.
*/
bool BadgeDropdownFilter::Filter(std::span<const BadgeID> badges) const
{
if (this->badges.empty()) return true;
/* We want all filtered badges to match. */
return std::ranges::all_of(this->badges, [&badges](const auto &badge) { return std::ranges::find(badges, badge.second) != std::end(badges); });
}

View File

@ -33,14 +33,21 @@ public:
/** Utility class to create a list of badge classes used by a feature. */
class UsedBadgeClasses {
public:
UsedBadgeClasses() = default;
explicit UsedBadgeClasses(GrfSpecFeature feature);
inline GrfSpecFeature GetFeature() const
{
return this->feature;
}
inline std::span<const BadgeClassID> Classes() const
{
return this->classes;
}
private:
GrfSpecFeature feature;
std::vector<BadgeClassID> classes; ///< List of badge classes.
};
@ -70,4 +77,13 @@ private:
FlatSet<BadgeID> badges{};
};
class BadgeDropdownFilter {
public:
BadgeDropdownFilter(const BadgeFilterChoices &conf) : badges(conf) {}
bool Filter(std::span<const BadgeID> badges) const;
private:
const BadgeFilterChoices &badges;
};
#endif /* NEWGRF_BADGE_H */

View File

@ -87,7 +87,7 @@ void AddBadgeClassesToConfiguration()
if (found != std::end(config)) continue;
/* Not found, insert it. */
config.emplace_back(badge.label, 0, true);
config.emplace_back(badge.label, 0, true, false);
}
}
}
@ -106,7 +106,7 @@ void ResetBadgeClassConfiguration(GrfSpecFeature feature)
for (const BadgeID &index : GetClassBadges()) {
const Badge &badge = *GetBadge(index);
if (badge.name == STR_NULL) continue;
config.emplace_back(badge.label, 0, true);
config.emplace_back(badge.label, 0, true, false);
}
}
@ -147,14 +147,16 @@ static void BadgeClassLoadConfigFeature(const IniFile &ini, GrfSpecFeature featu
for (const IniItem &item : group->items) {
int column = 0;
bool show_icon = true;
bool show_filter = false;
if (item.value.has_value() && !item.value.value().empty()) {
StringConsumer consumer(item.value.value());
if (consumer.ReadCharIf('?')) show_filter = true;
if (consumer.ReadCharIf('!')) show_icon = false;
if (auto value = consumer.TryReadIntegerBase<int>(10); value.has_value()) column = *value;
}
config.emplace_back(item.name, column, show_icon);
config.emplace_back(item.name, column, show_icon, show_filter);
}
}
@ -183,7 +185,7 @@ static void BadgeClassSaveConfigFeature(IniFile &ini, GrfSpecFeature feature)
group.Clear();
for (const auto &item : _badge_config.features[to_underlying(feature)]) {
group.CreateItem(item.label).SetValue(fmt::format("{}{}", item.show_icon ? "" : "!", item.column));
group.CreateItem(item.label).SetValue(fmt::format("{}{}{}", item.show_filter ? "?" : "", item.show_icon ? "" : "!", item.column));
}
}

View File

@ -18,6 +18,7 @@ public:
std::string label; ///< Class label.
int column = -1; ///< UI column, feature-dependent.
bool show_icon = false; ///< Set if the badge icons should be displayed for this class.
bool show_filter = false; ///< Set if a drop down filter should be added for this class.
};
void BadgeClassLoadConfig(const struct IniFile &ini);

View File

@ -67,23 +67,21 @@ static bool operator<(const GUIBadgeClasses::Element &a, const GUIBadgeClasses::
* Construct of list of badge classes and column groups to display.
* @param feature feature being used.
*/
GUIBadgeClasses::GUIBadgeClasses(GrfSpecFeature feature)
GUIBadgeClasses::GUIBadgeClasses(GrfSpecFeature feature) : UsedBadgeClasses(feature)
{
/* Get list of classes used by feature. */
UsedBadgeClasses used(feature);
uint max_column = 0;
for (BadgeClassID class_index : used.Classes()) {
for (BadgeClassID class_index : this->Classes()) {
const Badge *class_badge = GetClassBadge(class_index);
if (class_badge->name == STR_NULL) continue;
Dimension size = GetBadgeMaximalDimension(class_index, feature);
if (size.width == 0) continue;
auto [config, sort_order] = GetBadgeClassConfigItem(feature, class_badge->label);
const auto [config, sort_order] = GetBadgeClassConfigItem(feature, class_badge->label);
this->gui_classes.emplace_back(class_index, config.column, config.show_icon, sort_order, size, class_badge->label);
if (config.show_icon) max_column = std::max<uint>(max_column, config.column);
if (size.width != 0 && config.show_icon) max_column = std::max<uint>(max_column, config.column);
}
std::sort(std::begin(this->gui_classes), std::end(this->gui_classes));
@ -91,7 +89,7 @@ GUIBadgeClasses::GUIBadgeClasses(GrfSpecFeature feature)
/* Determine total width of visible badge columns. */
this->column_widths.resize(max_column + 1);
for (const auto &el : this->gui_classes) {
if (!el.visible) continue;
if (!el.visible || el.size.width == 0) continue;
this->column_widths[el.column_group] += ScaleGUITrad(el.size.width) + WidgetDimensions::scaled.hsep_normal;
}
@ -306,12 +304,14 @@ private:
};
using DropDownListToggleMoverItem = DropDownMover<DropDownToggle<DropDownString<DropDownListItem>>>;
using DropDownListToggleItem = DropDownToggle<DropDownString<DropDownListItem>>;
enum BadgeClick : int {
BADGE_CLICK_NONE,
BADGE_CLICK_MOVE_UP,
BADGE_CLICK_MOVE_DOWN,
BADGE_CLICK_TOGGLE_ICON,
BADGE_CLICK_TOGGLE_FILTER,
};
DropDownList BuildBadgeClassConfigurationList(const GUIBadgeClasses &gui_classes, uint columns, std::span<const StringID> column_separators)
@ -321,6 +321,7 @@ DropDownList BuildBadgeClassConfigurationList(const GUIBadgeClasses &gui_classes
list.push_back(MakeDropDownListStringItem(STR_BADGE_CONFIG_RESET, INT_MAX));
if (gui_classes.GetClasses().empty()) return list;
list.push_back(MakeDropDownListDividerItem());
list.push_back(std::make_unique<DropDownUnselectable<DropDownListStringItem>>(GetString(STR_BADGE_CONFIG_ICONS), -1));
const BadgeClassID front = gui_classes.GetClasses().front().class_index;
const BadgeClassID back = gui_classes.GetClasses().back().class_index;
@ -328,6 +329,7 @@ DropDownList BuildBadgeClassConfigurationList(const GUIBadgeClasses &gui_classes
for (uint i = 0; i < columns; ++i) {
for (const auto &gc : gui_classes.GetClasses()) {
if (gc.column_group != i) continue;
if (gc.size.width == 0) continue;
bool first = (i == 0 && gc.class_index == front);
bool last = (i == columns - 1 && gc.class_index == back);
@ -343,6 +345,17 @@ DropDownList BuildBadgeClassConfigurationList(const GUIBadgeClasses &gui_classes
}
}
list.push_back(MakeDropDownListDividerItem());
list.push_back(std::make_unique<DropDownUnselectable<DropDownListStringItem>>(GetString(STR_BADGE_CONFIG_FILTERS), -1));
for (const BadgeClassID &badge_class_index : gui_classes.Classes()) {
const Badge *badge = GetClassBadge(badge_class_index);
if (!badge->flags.Test(BadgeFlag::HasText)) continue;
const auto [config, _] = GetBadgeClassConfigItem(gui_classes.GetFeature(), badge->label);
list.push_back(std::make_unique<DropDownListToggleItem>(config.show_filter, BADGE_CLICK_TOGGLE_FILTER, COLOUR_YELLOW, COLOUR_GREY, GetString(badge->name), (1U << 16) | badge_class_index.base()));
}
return list;
}
@ -359,6 +372,7 @@ static void BadgeClassToggleVisibility(GrfSpecFeature feature, Badge &class_badg
if (it == std::end(config)) return;
if (click_result == BADGE_CLICK_TOGGLE_ICON) it->show_icon = !it->show_icon;
if (click_result == BADGE_CLICK_TOGGLE_FILTER) it->show_filter = !it->show_filter;
}
/**
@ -446,6 +460,7 @@ bool HandleBadgeConfigurationDropDownClick(GrfSpecFeature feature, uint columns,
BadgeClassMovePrevious(feature, *class_badge);
break;
case BADGE_CLICK_TOGGLE_ICON:
case BADGE_CLICK_TOGGLE_FILTER:
BadgeClassToggleVisibility(feature, *class_badge, click_result);
break;
default:
@ -454,3 +469,112 @@ bool HandleBadgeConfigurationDropDownClick(GrfSpecFeature feature, uint columns,
return true;
}
NWidgetBadgeFilter::NWidgetBadgeFilter(Colours colour, WidgetID index, GrfSpecFeature feature, BadgeClassID badge_class)
: NWidgetLeaf(WWT_DROPDOWN, colour, index, WidgetData{ .string = STR_JUST_STRING }, STR_NULL)
, feature(feature), badge_class(badge_class)
{
this->SetFill(1, 0);
this->SetResize(1, 0);
}
std::string NWidgetBadgeFilter::GetStringParameter(const BadgeFilterChoices &choices) const
{
auto it = choices.find(this->badge_class);
if (it == std::end(choices)) {
return ::GetString(STR_BADGE_FILTER_ANY_LABEL, GetClassBadge(this->badge_class)->name);
}
return ::GetString(STR_BADGE_FILTER_IS_LABEL, GetClassBadge(it->first)->name, GetBadge(it->second)->name);
}
/**
* Get the drop down list of badges for this filter.
* @return Drop down list for filter.
*/
DropDownList NWidgetBadgeFilter::GetDropDownList() const
{
DropDownList list;
/* Add item for disabling filtering. */
list.push_back(MakeDropDownListStringItem(::GetString(STR_BADGE_FILTER_ANY_LABEL, GetClassBadge(this->badge_class)->name), -1));
list.push_back(MakeDropDownListDividerItem());
/* Add badges */
Dimension d = GetBadgeMaximalDimension(this->badge_class, this->feature);
d.width = ScaleGUITrad(d.width);
d.height = ScaleGUITrad(d.height);
auto start = list.size();
const auto *bc = GetClassBadge(this->badge_class);
for (const Badge &badge : GetBadges()) {
if (badge.class_index != this->badge_class) continue;
if (badge.index == bc->index) continue;
if (badge.name == STR_NULL) continue;
if (!badge.features.Test(this->feature)) continue;
PalSpriteID ps = GetBadgeSprite(badge, this->feature, std::nullopt, PAL_NONE);
if (ps.sprite == 0) {
list.push_back(MakeDropDownListStringItem(badge.name, badge.index.base()));
} else {
list.push_back(MakeDropDownListIconItem(d, ps.sprite, ps.pal, badge.name, badge.index.base()));
}
}
std::sort(std::begin(list) + start, std::end(list), DropDownListStringItem::NatSortFunc);
return list;
}
/**
* Add badge drop down filter widgets.
* @param container Container widget to hold filter widgets.
* @param widget Widget index to apply to first filter.
* @param colour Background colour of widgets.
* @param feature GRF feature for filters.
* @return First and last widget indexes of filter widgets.
*/
std::pair<WidgetID, WidgetID> AddBadgeDropdownFilters(NWidgetContainer &container, WidgetID widget, Colours colour, GrfSpecFeature feature)
{
container.Clear();
WidgetID first = ++widget;
/* Get list of classes used by feature. */
UsedBadgeClasses used(feature);
for (BadgeClassID class_index : used.Classes()) {
const auto [config, _] = GetBadgeClassConfigItem(feature, GetClassBadge(class_index)->label);
if (!config.show_filter) continue;
container.Add(std::make_unique<NWidgetBadgeFilter>(colour, widget, feature, class_index));
++widget;
}
return {first, widget};
}
/**
* Reset badge filter choice for a class.
* @param choices Badge filter choices.
* @param badge_class_index Badge class to reset.
*/
void ResetBadgeFilter(BadgeFilterChoices &choices, BadgeClassID badge_class_index)
{
choices.erase(badge_class_index);
}
/**
* Set badge filter choice for a class.
* @param choides Badge filter choides.
* @param badge_index Badge to set. The badge class is inferred from the badge.
* @note if the badge_index is invalid, the filter will be reset instead.
*/
void SetBadgeFilter(BadgeFilterChoices &choices, BadgeID badge_index)
{
const Badge *badge = GetBadge(badge_index);
assert(badge != nullptr);
choices[badge->class_index] = badge_index;
}

View File

@ -12,15 +12,16 @@
#include "dropdown_type.h"
#include "newgrf.h"
#include "newgrf_badge.h"
#include "newgrf_badge_type.h"
#include "timer/timer_game_calendar.h"
class GUIBadgeClasses {
class GUIBadgeClasses : public UsedBadgeClasses {
public:
struct Element {
BadgeClassID class_index; ///< Badge class index.
uint8_t column_group; ///< Column group in UI. 0 = left, 1 = centre, 2 = right.
bool visible; ///< Whether this element is visible.
bool visible; ///< Whether the icon is visible.
uint sort_order; ///< Order of element.
Dimension size; ///< Maximal size of this element.
std::string_view label; ///< Class label (string owned by the class badge)
@ -55,4 +56,22 @@ std::unique_ptr<DropDownListItem> MakeDropDownListBadgeIconItem(const std::share
DropDownList BuildBadgeClassConfigurationList(const class GUIBadgeClasses &badge_class, uint columns, std::span<const StringID> column_separators);
bool HandleBadgeConfigurationDropDownClick(GrfSpecFeature feature, uint columns, int result, int click_result);
std::pair<WidgetID, WidgetID> AddBadgeDropdownFilters(NWidgetContainer &container, WidgetID widget, Colours colour, GrfSpecFeature feature);
class NWidgetBadgeFilter : public NWidgetLeaf {
public:
NWidgetBadgeFilter(Colours colour, WidgetID index, GrfSpecFeature feature, BadgeClassID badge_class);
BadgeClassID GetBadgeClassID() const { return this->badge_class; }
std::string GetStringParameter(const BadgeFilterChoices &choices) const;
DropDownList GetDropDownList() const;
private:
GrfSpecFeature feature; ///< Feature of this dropdown.
BadgeClassID badge_class; ///< Badge class of this dropdown.
};
void ResetBadgeFilter(BadgeFilterChoices &choices, BadgeClassID badge_class_index);
void SetBadgeFilter(BadgeFilterChoices &choices, BadgeID badge_index);
#endif /* NEWGRF_BADGE_GUI_H */

View File

@ -16,12 +16,23 @@
using BadgeID = StrongType::Typedef<uint16_t, struct BadgeIDTag, StrongType::Compare>;
using BadgeClassID = StrongType::Typedef<uint16_t, struct BadgeClassIDTag, StrongType::Compare>;
template <> struct std::hash<BadgeClassID> {
std::size_t operator()(const BadgeClassID &badge_class_index) const noexcept
{
return std::hash<BadgeClassID::BaseType>{}(badge_class_index.base());
}
};
enum class BadgeFlag : uint8_t {
Copy = 0, ///< Copy badge to related things.
NameListStop = 1, ///< Stop adding names to the name list after this badge.
NameListFirstOnly = 2, ///< Don't add this name to the name list if not first.
UseCompanyColour = 3, ///< Apply company colour palette to this badge.
HasText, ///< Internal flag set if the badge has text.
};
using BadgeFlags = EnumBitSet<BadgeFlag, uint8_t>;
using BadgeFilterChoices = std::unordered_map<BadgeClassID, BadgeID>;
#endif /* NEWGRF_BADGE_TYPE_H */

View File

@ -484,6 +484,7 @@ public:
inline bool IsEmpty() { return this->children.empty(); }
NWidgetBase *GetWidgetOfType(WidgetType tp) override;
void Clear() { this->children.clear(); }
protected:
std::vector<std::unique_ptr<NWidgetBase>> children{}; ///< Child widgets in container.