diff --git a/src/autoreplace_gui.cpp b/src/autoreplace_gui.cpp index 62e8b1e85e..b145e34696 100644 --- a/src/autoreplace_gui.cpp +++ b/src/autoreplace_gui.cpp @@ -34,8 +34,6 @@ #include "safeguards.h" -void DrawEngineList(VehicleType type, const Rect &r, const GUIEngineList &eng_list, const Scrollbar &sb, EngineID selected_id, bool show_count, GroupID selected_group); - static bool EngineNumberSorter(const GUIEngineListItem &a, const GUIEngineListItem &b) { return Engine::Get(a.engine_id)->list_position < Engine::Get(b.engine_id)->list_position; @@ -114,27 +112,6 @@ class ReplaceVehicleWindow : public Window { return true; } - void AddChildren(const GUIEngineList &source, GUIEngineList &target, EngineID parent, int indent, int side) - { - for (const auto &item : source) { - if (item.variant_id != parent || item.engine_id == parent) continue; - - const Engine *e = Engine::Get(item.engine_id); - EngineDisplayFlags flags = item.flags; - if (e->display_last_variant != INVALID_ENGINE) flags &= ~EngineDisplayFlags::Shaded; - target.emplace_back(e->display_last_variant == INVALID_ENGINE ? item.engine_id : e->display_last_variant, item.engine_id, flags, indent); - - /* Add variants if not folded */ - if ((item.flags & (EngineDisplayFlags::HasVariants | EngineDisplayFlags::IsFolded)) == EngineDisplayFlags::HasVariants) { - /* Add this engine again as a child */ - if ((item.flags & EngineDisplayFlags::Shaded) == EngineDisplayFlags::None) { - target.emplace_back(item.engine_id, item.engine_id, EngineDisplayFlags::None, indent + 1); - } - AddChildren(source, target, item.engine_id, indent + 1, side); - } - } - } - /** * Generate an engines list * @param draw_left true if generating the left list, otherwise false @@ -208,7 +185,7 @@ class ReplaceVehicleWindow : public Window { this->engines[side].clear(); if (side == 1) { - AddChildren(list, this->engines[side], INVALID_ENGINE, 0, side); + GUIEngineListAddChildren(this->engines[side], list); } else { this->engines[side].swap(list); } diff --git a/src/build_vehicle_gui.cpp b/src/build_vehicle_gui.cpp index db21d23c84..9d97603b3c 100644 --- a/src/build_vehicle_gui.cpp +++ b/src/build_vehicle_gui.cpp @@ -1045,6 +1045,9 @@ void DrawEngineList(VehicleType type, const Rect &r, const GUIEngineList &eng_li int small_text_y_offset = ir.Height() - GetCharacterHeight(FS_SMALL); int replace_icon_y_offset = (ir.Height() - replace_icon.height) / 2; + const int offset = (rtl ? -circle_width : circle_width) / 2; + const int level_width = rtl ? -WidgetDimensions::scaled.hsep_indent : WidgetDimensions::scaled.hsep_indent; + int y = ir.top; for (auto it = first; it != last; ++it) { const auto &item = *it; @@ -1052,6 +1055,21 @@ void DrawEngineList(VehicleType type, const Rect &r, const GUIEngineList &eng_li bool has_variants = (item.flags & EngineDisplayFlags::HasVariants) != EngineDisplayFlags::None; bool is_folded = (item.flags & EngineDisplayFlags::IsFolded) != EngineDisplayFlags::None; bool shaded = (item.flags & EngineDisplayFlags::Shaded) != EngineDisplayFlags::None; + + if (item.indent > 0) { + /* Draw tree continuation lines. */ + int tx = (rtl ? ir.right : ir.left) + offset; + int ty = y - WidgetDimensions::scaled.matrix.top; + for (uint lvl = 1; lvl <= item.indent; ++lvl) { + if (HasBit(item.level_mask, lvl)) GfxDrawLine(tx, ty, tx, ty + step_size - 1, linecolour, WidgetDimensions::scaled.fullbevel.top); + if (lvl < item.indent) tx += level_width; + } + /* Draw our node in the tree. */ + int ycentre = y + normal_text_y_offset + GetCharacterHeight(FS_NORMAL) / 2 - 1; + if (!HasBit(item.level_mask, item.indent)) GfxDrawLine(tx, ty, tx, ycentre, linecolour, WidgetDimensions::scaled.fullbevel.top); + GfxDrawLine(tx, ycentre, tx + offset - (rtl ? -1 : 1), ycentre, linecolour, WidgetDimensions::scaled.fullbevel.top); + } + /* Note: num_engines is only used in the autoreplace GUI, so it is correct to use _local_company here. */ const uint num_engines = GetGroupNumEngines(_local_company, selected_group, item.engine_id); @@ -1079,14 +1097,6 @@ void DrawEngineList(VehicleType type, const Rect &r, const GUIEngineList &eng_li Rect fr = ir.Indent(indent, rtl).WithWidth(circle_width, rtl); DrawSpriteIgnorePadding(is_folded ? SPR_CIRCLE_FOLDED : SPR_CIRCLE_UNFOLDED, PAL_NONE, {fr.left, y, fr.right, y + ir.Height() - 1}, SA_CENTER); } - if (indent > 0) { - /* Draw tree lines */ - Rect fr = ir.Indent(indent - WidgetDimensions::scaled.hsep_indent, rtl).WithWidth(circle_width, rtl); - int ycenter = y + normal_text_y_offset + GetCharacterHeight(FS_NORMAL) / 2; - bool continues = std::next(it) != std::end(eng_list) && std::next(it)->indent == item.indent; - GfxDrawLine(fr.left + circle_width / 2, y - WidgetDimensions::scaled.matrix.top, fr.left + circle_width / 2, continues ? y - WidgetDimensions::scaled.matrix.top + step_size - 1 : ycenter, linecolour, WidgetDimensions::scaled.fullbevel.top); - GfxDrawLine(fr.left + circle_width / 2, ycenter, fr.right, ycenter, linecolour, WidgetDimensions::scaled.fullbevel.top); - } y += step_size; } } @@ -1114,6 +1124,44 @@ void DisplayVehicleSortDropDown(Window *w, VehicleType vehicle_type, int selecte ShowDropDownMenu(w, _engine_sort_listing[vehicle_type], selected, button, 0, hidden_mask); } +/** + * Add children to GUI engine list to build a hierarchical tree. + * @param dst Destination list. + * @param src Source list. + * @param parent Current tree parent (set by self with recursion). + * @param indent Current tree indentation level (set by self with recursion). + */ +void GUIEngineListAddChildren(GUIEngineList &dst, const GUIEngineList &src, EngineID parent, uint8_t indent) +{ + for (const auto &item : src) { + if (item.variant_id != parent || item.engine_id == parent) continue; + + const Engine *e = Engine::Get(item.engine_id); + EngineDisplayFlags flags = item.flags; + if (e->display_last_variant != INVALID_ENGINE) flags &= ~EngineDisplayFlags::Shaded; + dst.emplace_back(e->display_last_variant == INVALID_ENGINE ? item.engine_id : e->display_last_variant, item.engine_id, flags, indent); + + /* Add variants if not folded */ + if ((item.flags & (EngineDisplayFlags::HasVariants | EngineDisplayFlags::IsFolded)) == EngineDisplayFlags::HasVariants) { + /* Add this engine again as a child */ + if ((item.flags & EngineDisplayFlags::Shaded) == EngineDisplayFlags::None) { + dst.emplace_back(item.engine_id, item.engine_id, EngineDisplayFlags::None, indent + 1); + } + GUIEngineListAddChildren(dst, src, item.engine_id, indent + 1); + } + } + + if (indent > 0 || dst.empty()) return; + + /* Hierarchy is complete, traverse in reverse to find where indentation levels continue. */ + uint16_t level_mask = 0; + for (auto it = std::rbegin(dst); std::next(it) != std::rend(dst); ++it) { + auto next_it = std::next(it); + SB(level_mask, it->indent, 1, it->indent <= next_it->indent); + next_it->level_mask = level_mask; + } +} + /** Enum referring to the Hotkeys in the build vehicle window */ enum BuildVehicleHotkeys { BVHK_FOCUS_FILTER_BOX, ///< Focus the edit box for editing the filter string @@ -1157,27 +1205,6 @@ struct BuildVehicleWindow : Window { } } - void AddChildren(const GUIEngineList &source, EngineID parent, int indent) - { - for (const auto &item : source) { - if (item.variant_id != parent || item.engine_id == parent) continue; - - const Engine *e = Engine::Get(item.engine_id); - EngineDisplayFlags flags = item.flags; - if (e->display_last_variant != INVALID_ENGINE) flags &= ~EngineDisplayFlags::Shaded; - this->eng_list.emplace_back(e->display_last_variant == INVALID_ENGINE ? item.engine_id : e->display_last_variant, item.engine_id, flags, indent); - - /* Add variants if not folded */ - if ((item.flags & (EngineDisplayFlags::HasVariants | EngineDisplayFlags::IsFolded)) == EngineDisplayFlags::HasVariants) { - /* Add this engine again as a child */ - if ((item.flags & EngineDisplayFlags::Shaded) == EngineDisplayFlags::None) { - this->eng_list.emplace_back(item.engine_id, item.engine_id, EngineDisplayFlags::None, indent + 1); - } - AddChildren(source, item.engine_id, indent + 1); - } - } - } - BuildVehicleWindow(WindowDesc *desc, TileIndex tile, VehicleType type) : Window(desc), vehicle_editbox(MAX_LENGTH_VEHICLE_NAME_CHARS * MAX_CHAR_LENGTH, MAX_LENGTH_VEHICLE_NAME_CHARS) { this->vehicle_type = type; @@ -1518,7 +1545,7 @@ struct BuildVehicleWindow : Window { default: NOT_REACHED(); case VEH_TRAIN: this->GenerateBuildTrainList(list); - AddChildren(list, INVALID_ENGINE, 0); + GUIEngineListAddChildren(this->eng_list, list); this->eng_list.shrink_to_fit(); this->eng_list.RebuildDone(); return; @@ -1556,7 +1583,7 @@ struct BuildVehicleWindow : Window { EngList_Sort(this->eng_list, _engine_sort_functions[this->vehicle_type][this->sort_criteria]); this->eng_list.swap(list); - AddChildren(list, INVALID_ENGINE, 0); + GUIEngineListAddChildren(this->eng_list, list, INVALID_ENGINE, 0); this->eng_list.shrink_to_fit(); this->eng_list.RebuildDone(); } diff --git a/src/engine_gui.h b/src/engine_gui.h index 5a43f1f610..ba7e41715f 100644 --- a/src/engine_gui.h +++ b/src/engine_gui.h @@ -11,6 +11,7 @@ #define ENGINE_GUI_H #include "engine_type.h" +#include "group_type.h" #include "sortlist_type.h" #include "gfx_type.h" #include "vehicle_type.h" @@ -20,9 +21,10 @@ struct GUIEngineListItem { EngineID engine_id; ///< Engine to display in build purchase list EngineID variant_id; ///< Variant group of the engine. EngineDisplayFlags flags; ///< Flags for toggling/drawing (un)folded status and controlling indentation. - int8_t indent; ///< Display indentation level. + uint8_t indent; ///< Display indentation level. + uint16_t level_mask; ///< Mask of level continuations. - GUIEngineListItem(EngineID engine_id, EngineID variant_id, EngineDisplayFlags flags, int indent) : engine_id(engine_id), variant_id(variant_id), flags(flags), indent(indent) {} + GUIEngineListItem(EngineID engine_id, EngineID variant_id, EngineDisplayFlags flags, uint8_t indent) : engine_id(engine_id), variant_id(variant_id), flags(flags), indent(indent), level_mask(0) {} /* Used when searching list only by engine_id. */ bool operator == (const EngineID &other) const { return this->engine_id == other; } @@ -50,7 +52,10 @@ extern bool _engine_sort_show_hidden_engines[]; extern const StringID _engine_sort_listing[][12]; extern EngList_SortTypeFunction * const _engine_sort_functions[][11]; +/* Functions in build_vehicle_gui.cpp */ uint GetEngineListHeight(VehicleType type); void DisplayVehicleSortDropDown(Window *w, VehicleType vehicle_type, int selected, WidgetID button); +void DrawEngineList(VehicleType type, const Rect &r, const GUIEngineList &eng_list, const Scrollbar &sb, EngineID selected_id, bool show_count, GroupID selected_group); +void GUIEngineListAddChildren(GUIEngineList &dst, const GUIEngineList &src, EngineID parent = INVALID_ENGINE, uint8_t indent = 0); #endif /* ENGINE_GUI_H */