From 107c208d87b1d094e636022013a8bd4e06edf23c Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Mon, 18 Mar 2024 17:49:51 +0000 Subject: [PATCH] Codechange: Use single list for hierarchical group lists. (#12330) Replace both group list implementations (vehicle group list and company colour group list) with a single implementation, using a struct to hold the group and indentation level instead of two separate lists. Parts that were previously duplicated are now shared. --- src/company_gui.cpp | 73 +++++--------------- src/group_gui.cpp | 162 ++++++++++++++++++++++++-------------------- src/group_gui.h | 11 +++ 3 files changed, 116 insertions(+), 130 deletions(-) diff --git a/src/company_gui.cpp b/src/company_gui.cpp index 7896f2bb9a..253bc187ed 100644 --- a/src/company_gui.cpp +++ b/src/company_gui.cpp @@ -40,6 +40,7 @@ #include "company_cmd.h" #include "economy_cmd.h" #include "group_cmd.h" +#include "group_gui.h" #include "misc_cmd.h" #include "object_cmd.h" #include "timer/timer.h" @@ -591,8 +592,6 @@ public: } }; -typedef GUIList GUIGroupList; - /** Company livery colour scheme window. */ struct SelectCompanyLiveryWindow : public Window { private: @@ -602,7 +601,6 @@ private: uint rows; uint line_height; GUIGroupList groups; - std::vector indents; Scrollbar *vscroll; void ShowColourDropDownMenu(uint32_t widget) @@ -660,57 +658,15 @@ private: ShowDropDownList(this, std::move(list), sel, widget); } - void AddChildren(GUIGroupList &source, GroupID parent, int indent) - { - for (const Group *g : source) { - if (g->parent != parent) continue; - this->groups.push_back(g); - this->indents.push_back(indent); - AddChildren(source, g->index, indent + 1); - } - } - void BuildGroupList(CompanyID owner) { if (!this->groups.NeedRebuild()) return; this->groups.clear(); - this->indents.clear(); if (this->livery_class >= LC_GROUP_RAIL) { - GUIGroupList list; VehicleType vtype = (VehicleType)(this->livery_class - LC_GROUP_RAIL); - - for (const Group *g : Group::Iterate()) { - if (g->owner == owner && g->vehicle_type == vtype) { - list.push_back(g); - } - } - - list.ForceResort(); - - /* Sort the groups by their name */ - const Group *last_group[2] = { nullptr, nullptr }; - std::string last_name[2] = { {}, {} }; - list.Sort([&](const Group * const &a, const Group * const &b) -> bool { - if (a != last_group[0]) { - last_group[0] = a; - SetDParam(0, a->index); - last_name[0] = GetString(STR_GROUP_NAME); - } - - if (b != last_group[1]) { - last_group[1] = b; - SetDParam(0, b->index); - last_name[1] = GetString(STR_GROUP_NAME); - } - - int r = StrNaturalCompare(last_name[0], last_name[1]); // Sort by name (natural sorting). - if (r == 0) return a->index < b->index; - return r < 0; - }); - - AddChildren(list, INVALID_GROUP, 0); + BuildGuiGroupList(this->groups, false, owner, vtype); } this->groups.shrink_to_fit(); @@ -774,7 +730,7 @@ public: /* Position scrollbar to selected group */ for (uint i = 0; i < this->rows; i++) { - if (this->groups[i]->index == sel) { + if (this->groups[i].group->index == sel) { this->vscroll->SetPosition(i - this->vscroll->GetCapacity() / 2); break; } @@ -944,11 +900,11 @@ public: } } } else { - uint max = static_cast(std::min(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->groups.size())); - for (uint i = this->vscroll->GetPosition(); i < max; ++i) { - const Group *g = this->groups[i]; + auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->groups); + for (auto it = first; it != last; ++it) { + const Group *g = it->group; SetDParam(0, g->index); - draw_livery(STR_GROUP_NAME, g->livery, this->sel == g->index, false, this->indents[i] * WidgetDimensions::scaled.hsep_indent); + draw_livery(STR_GROUP_NAME, g->livery, this->sel == g->index, false, it->indent * WidgetDimensions::scaled.hsep_indent); } if (this->vscroll->GetCount() == 0) { @@ -991,7 +947,7 @@ public: this->BuildGroupList((CompanyID)this->window_number); if (!this->groups.empty()) { - this->sel = this->groups[0]->index; + this->sel = this->groups[0].group->index; } } @@ -1008,10 +964,10 @@ public: break; case WID_SCL_MATRIX: { - uint row = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_SCL_MATRIX); - if (row >= this->rows) return; - if (this->livery_class < LC_GROUP_RAIL) { + uint row = this->vscroll->GetScrolledRowFromWidget(pt.y, this, widget); + if (row >= this->rows) return; + LiveryScheme j = (LiveryScheme)row; for (LiveryScheme scheme = LS_BEGIN; scheme <= j && scheme < LS_END; scheme++) { @@ -1025,7 +981,10 @@ public: this->sel = 1 << j; } } else { - this->sel = this->groups[row]->index; + auto it = this->vscroll->GetScrolledItemFromWidget(this->groups, pt.y, this, widget); + if (it == std::end(this->groups)) return; + + this->sel = it->group->index; } this->SetDirty(); break; @@ -1078,7 +1037,7 @@ public: if (!Group::IsValidID(this->sel)) { this->sel = INVALID_GROUP; - if (!this->groups.empty()) this->sel = this->groups[0]->index; + if (!this->groups.empty()) this->sel = this->groups[0].group->index; } this->SetDirty(); diff --git a/src/group_gui.cpp b/src/group_gui.cpp index 5083da047a..812f7a9e86 100644 --- a/src/group_gui.cpp +++ b/src/group_gui.cpp @@ -27,6 +27,7 @@ #include "company_gui.h" #include "gui.h" #include "group_cmd.h" +#include "group_gui.h" #include "vehicle_cmd.h" #include "gfx_func.h" @@ -36,8 +37,6 @@ #include "safeguards.h" -typedef GUIList GUIGroupList; - static constexpr NWidgetPart _nested_group_widgets[] = { NWidget(NWID_HORIZONTAL), // Window header NWidget(WWT_CLOSEBOX, COLOUR_GREY), @@ -111,6 +110,73 @@ static constexpr NWidgetPart _nested_group_widgets[] = { EndContainer(), }; +/** + * Add children to GUI group list to build a hierarchical tree. + * @param dst Destination list. + * @param src Source list. + * @param fold Whether to handle group folding/hiding. + * @param parent Current tree parent (set by self with recursion). + * @param indent Current tree indentation level (set by self with recursion). + */ +static void GuiGroupListAddChildren(GUIGroupList &dst, const GUIGroupList &src, bool fold, GroupID parent, int indent) +{ + for (const auto &item : src) { + if (item.group->parent != parent) continue; + + dst.emplace_back(item.group, indent); + + if (fold && item.group->folded) { + /* Test if this group has children at all. If not, the folded flag should be cleared to avoid lingering unfold buttons in the list. */ + GroupID groupid = item.group->index; + bool has_children = std::any_of(src.begin(), src.end(), [groupid](const auto &child) { return child.group->parent == groupid; }); + Group::Get(item.group->index)->folded = has_children; + } else { + GuiGroupListAddChildren(dst, src, fold, item.group->index, indent + 1); + } + } +} + +/** + * Build GUI group list, a sorted hierarchical list of groups for owner and vehicle type. + * @param dst Destination list, owned by the caller. + * @param fold Whether to handle group folding/hiding. + * @param owner Owner of groups. + * @param veh_type Vehicle type of groups. + */ +void BuildGuiGroupList(GUIGroupList &dst, bool fold, Owner owner, VehicleType veh_type) +{ + GUIGroupList list; + + for (const Group *g : Group::Iterate()) { + if (g->owner == owner && g->vehicle_type == veh_type) { + list.emplace_back(g, 0); + } + } + + list.ForceResort(); + + /* Sort the groups by their name */ + std::array, 2> last_group{}; + + list.Sort([&last_group](const GUIGroupListItem &a, const GUIGroupListItem &b) -> bool { + if (a.group != last_group[0].first) { + SetDParam(0, a.group->index); + last_group[0] = {a.group, GetString(STR_GROUP_NAME)}; + } + + if (b.group != last_group[1].first) { + SetDParam(0, b.group->index); + last_group[1] = {b.group, GetString(STR_GROUP_NAME)}; + } + + int r = StrNaturalCompare(last_group[0].second, last_group[1].second); // Sort by name (natural sorting). + if (r == 0) return a.group->index < b.group->index; + return r < 0; + }); + + GuiGroupListAddChildren(dst, list, fold, INVALID_GROUP, 0); +} + class VehicleGroupWindow : public BaseVehicleListWindow { private: /* Columns in the group list */ @@ -133,26 +199,8 @@ private: uint tiny_step_height; ///< Step height for the group list Scrollbar *group_sb; - std::vector indents; ///< Indentation levels - Dimension column_size[VGC_END]; ///< Size of the columns in the group list. - void AddChildren(GUIGroupList &source, GroupID parent, int indent) - { - for (const Group *g : source) { - if (g->parent != parent) continue; - this->groups.push_back(g); - this->indents.push_back(indent); - if (g->folded) { - /* Test if this group has children at all. If not, the folded flag should be cleared to avoid lingering unfold buttons in the list. */ - bool has_children = std::any_of(source.begin(), source.end(), [g](const Group *child){ return child->parent == g->index; }); - Group::Get(g->index)->folded = has_children; - } else { - AddChildren(source, g->index, indent + 1); - } - } - } - /** * (Re)Build the group list. * @@ -163,40 +211,8 @@ private: if (!this->groups.NeedRebuild()) return; this->groups.clear(); - this->indents.clear(); - GUIGroupList list; - - for (const Group *g : Group::Iterate()) { - if (g->owner == owner && g->vehicle_type == this->vli.vtype) { - list.push_back(g); - } - } - - list.ForceResort(); - - /* Sort the groups by their name */ - const Group *last_group[2] = { nullptr, nullptr }; - std::string last_name[2] = { {}, {} }; - list.Sort([&](const Group * const &a, const Group * const &b) { - if (a != last_group[0]) { - last_group[0] = a; - SetDParam(0, a->index); - last_name[0] = GetString(STR_GROUP_NAME); - } - - if (b != last_group[1]) { - last_group[1] = b; - SetDParam(0, b->index); - last_name[1] = GetString(STR_GROUP_NAME); - } - - int r = StrNaturalCompare(last_name[0], last_name[1]); // Sort by name (natural sorting). - if (r == 0) return a->index < b->index; - return r < 0; - }); - - AddChildren(list, INVALID_GROUP, 0); + BuildGuiGroupList(this->groups, true, owner, this->vli.vtype); this->groups.shrink_to_fit(); this->groups.RebuildDone(); @@ -602,13 +618,13 @@ public: case WID_GL_LIST_GROUP: { int y1 = r.top; - size_t max = std::min(this->group_sb->GetPosition() + this->group_sb->GetCapacity(), this->groups.size()); - for (size_t i = this->group_sb->GetPosition(); i < max; ++i) { - const Group *g = this->groups[i]; + auto [first, last] = this->group_sb->GetVisibleRangeIterators(this->groups); + for (auto it = first; it != last; ++it) { + const Group *g = it->group; assert(g->owner == this->owner); - DrawGroupInfo(y1, r.left, r.right, g->index, this->indents[i] * WidgetDimensions::scaled.hsep_indent, HasBit(g->flags, GroupFlags::GF_REPLACE_PROTECTION), g->folded || (i + 1 < this->groups.size() && indents[i + 1] > this->indents[i])); + DrawGroupInfo(y1, r.left, r.right, g->index, it->indent * WidgetDimensions::scaled.hsep_indent, HasBit(g->flags, GroupFlags::GF_REPLACE_PROTECTION), g->folded || (std::next(it) != std::end(this->groups) && std::next(it)->indent > it->indent)); y1 += this->tiny_step_height; } @@ -690,27 +706,26 @@ public: auto it = this->group_sb->GetScrolledItemFromWidget(this->groups, pt.y, this, WID_GL_LIST_GROUP); if (it == this->groups.end()) return; - size_t id_g = it - this->groups.begin(); - if ((*it)->folded || (id_g + 1 < this->groups.size() && this->indents[id_g + 1] > this->indents[id_g])) { + if (it->group->folded || (std::next(it) != std::end(this->groups) && std::next(it)->indent > it->indent)) { /* The group has children, check if the user clicked the fold / unfold button. */ NWidgetCore *group_display = this->GetWidget(widget); int x = _current_text_dir == TD_RTL ? - group_display->pos_x + group_display->current_x - WidgetDimensions::scaled.framerect.right - this->indents[id_g] * WidgetDimensions::scaled.hsep_indent - this->column_size[VGC_FOLD].width : - group_display->pos_x + WidgetDimensions::scaled.framerect.left + this->indents[id_g] * WidgetDimensions::scaled.hsep_indent; + group_display->pos_x + group_display->current_x - WidgetDimensions::scaled.framerect.right - it->indent * WidgetDimensions::scaled.hsep_indent - this->column_size[VGC_FOLD].width : + group_display->pos_x + WidgetDimensions::scaled.framerect.left + it->indent * WidgetDimensions::scaled.hsep_indent; if (click_count > 1 || (pt.x >= x && pt.x < (int)(x + this->column_size[VGC_FOLD].width))) { GroupID g = this->vli.index; if (!IsAllGroupID(g) && !IsDefaultGroupID(g)) { do { g = Group::Get(g)->parent; - if (g == groups[id_g]->index) { + if (g == it->group->index) { this->vli.index = g; break; } } while (g != INVALID_GROUP); } - Group::Get(groups[id_g]->index)->folded = !groups[id_g]->folded; + Group::Get(it->group->index)->folded = !it->group->folded; this->groups.ForceRebuild(); this->SetDirty(); @@ -718,7 +733,7 @@ public: } } - this->group_sel = this->vli.index = this->groups[id_g]->index; + this->group_sel = this->vli.index = it->group->index; SetObjectToPlaceWnd(SPR_CURSOR_MOUSE, PAL_NONE, HT_DRAG, this); @@ -843,7 +858,7 @@ public: case WID_GL_LIST_GROUP: { // Matrix group auto it = this->group_sb->GetScrolledItemFromWidget(this->groups, pt.y, this, WID_GL_LIST_GROUP); - GroupID new_g = it == this->groups.end() ? INVALID_GROUP : (*it)->index; + GroupID new_g = it == this->groups.end() ? INVALID_GROUP : it->group->index; if (this->group_sel != new_g && g->parent != new_g) { Command::Post(STR_ERROR_GROUP_CAN_T_SET_PARENT, AlterGroupMode::SetParent, this->group_sel, new_g, {}); @@ -876,7 +891,7 @@ public: this->SetDirty(); auto it = this->group_sb->GetScrolledItemFromWidget(this->groups, pt.y, this, WID_GL_LIST_GROUP); - GroupID new_g = it == this->groups.end() ? NEW_GROUP : (*it)->index; + GroupID new_g = it == this->groups.end() ? NEW_GROUP : it->group->index; Command::Post(STR_ERROR_GROUP_CAN_T_ADD_VEHICLE, new_g == NEW_GROUP ? CcAddVehicleNewGroup : nullptr, new_g, vindex, _ctrl_pressed || this->grouping == GB_SHARED_ORDERS, VehicleListIdentifier{}); break; @@ -1025,7 +1040,7 @@ public: case WID_GL_LIST_GROUP: { // ... the list of custom groups. auto it = this->group_sb->GetScrolledItemFromWidget(this->groups, pt.y, this, WID_GL_LIST_GROUP); - new_group_over = it == this->groups.end() ? NEW_GROUP : (*it)->index; + new_group_over = it == this->groups.end() ? NEW_GROUP : it->group->index; break; } } @@ -1080,18 +1095,19 @@ public: this->vli.index = g_id; if (g_id != ALL_GROUP && g_id != DEFAULT_GROUP) { const Group *g = Group::Get(g_id); - int id_g = find_index(this->groups, g); - // The group's branch is maybe collapsed, so try to expand it - if (id_g == -1) { + + auto found = std::find_if(std::begin(this->groups), std::end(this->groups), [g](const auto &item) { return item.group == g; }); + if (found == std::end(this->groups)) { + /* The group's branch is maybe collapsed, so try to expand it. */ for (auto pg = Group::GetIfValid(g->parent); pg != nullptr; pg = Group::GetIfValid(pg->parent)) { pg->folded = false; } this->groups.ForceRebuild(); this->BuildGroupList(this->owner); this->group_sb->SetCount(this->groups.size()); - id_g = find_index(this->groups, g); + found = std::find_if(std::begin(this->groups), std::end(this->groups), [g](const auto &item) { return item.group == g; }); } - this->group_sb->ScrollTowards(id_g); + if (found != std::end(this->groups)) this->group_sb->ScrollTowards(std::distance(std::begin(this->groups), found)); } this->vehgroups.ForceRebuild(); this->SetDirty(); @@ -1121,7 +1137,7 @@ static WindowDesc _train_group_desc( * @param group The group to be selected. Defaults to INVALID_GROUP. * @param need_existing_window Whether the existing window is needed. Defaults to false. */ -void ShowCompanyGroup(CompanyID company, VehicleType vehicle_type, GroupID group = INVALID_GROUP, bool need_existing_window = false) +void ShowCompanyGroup(CompanyID company, VehicleType vehicle_type, GroupID group, bool need_existing_window) { if (!Company::IsValidID(company)) return; diff --git a/src/group_gui.h b/src/group_gui.h index d42704663e..ec5f790d84 100644 --- a/src/group_gui.h +++ b/src/group_gui.h @@ -17,4 +17,15 @@ void ShowCompanyGroup(CompanyID company, VehicleType veh, GroupID group = INVALI void ShowCompanyGroupForVehicle(const Vehicle *v); void DeleteGroupHighlightOfVehicle(const Vehicle *v); +struct GUIGroupListItem { + const Group *group; + int8_t indent; ///< Display indentation level. + + constexpr GUIGroupListItem(const Group *group, int8_t indent) : group(group), indent(indent) {} +}; + +using GUIGroupList = GUIList; + +void BuildGuiGroupList(GUIGroupList &dst, bool fold, Owner owner, VehicleType veh_type); + #endif /* GROUP_GUI_H */