From 49532914dd44e4876a1185bf4a1fbd68f65b96d6 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Sat, 11 Nov 2023 15:17:12 +0000 Subject: [PATCH] Change: Use CRTP-mixins to compose dropdown list items. This allows list items to built from component parts as required, and additional functionality is added: * Icons and text can be positioned at the start or end of the space (templated.) * Font size of text can be changed (templated.) * Palette of sprites can be set (runtime.) --- src/company_gui.cpp | 39 ++----- src/genworld_gui.cpp | 2 +- src/settings_gui.cpp | 2 +- src/toolbar_gui.cpp | 113 +++----------------- src/widgets/dropdown.cpp | 87 +--------------- src/widgets/dropdown_type.h | 199 ++++++++++++++++++++++++++++++------ 6 files changed, 201 insertions(+), 241 deletions(-) diff --git a/src/company_gui.cpp b/src/company_gui.cpp index 6e7e41b871..439e2257dc 100644 --- a/src/company_gui.cpp +++ b/src/company_gui.cpp @@ -575,36 +575,15 @@ static const LiveryClass _livery_class[LS_END] = { LC_ROAD, LC_ROAD, }; -class DropDownListColourItem : public DropDownListStringItem { +/** + * Colour selection list item, with icon and string components. + * @tparam TSprite Recolourable sprite to draw as icon. + */ +template +class DropDownListColourItem : public DropDownIcon> { public: - DropDownListColourItem(int result, bool masked) : DropDownListStringItem(result >= COLOUR_END ? STR_COLOUR_DEFAULT : _colour_dropdown[result], result, masked) {} - - uint Width() const override + DropDownListColourItem(int colour, bool masked) : DropDownIcon>(TSprite, PALETTE_RECOLOUR_START + (colour % COLOUR_END), colour < COLOUR_END ? _colour_dropdown[colour] : STR_COLOUR_DEFAULT, colour, masked) { - return ScaleGUITrad(28) + WidgetDimensions::scaled.hsep_normal + GetStringBoundingBox(this->String()).width + WidgetDimensions::scaled.dropdowntext.Horizontal(); - } - - uint Height() const override - { - return std::max(GetCharacterHeight(FS_NORMAL), ScaleGUITrad(12) + WidgetDimensions::scaled.vsep_normal); - } - - bool Selectable() const override - { - return true; - } - - void Draw(const Rect &r, bool sel, Colours) const override - { - bool rtl = _current_text_dir == TD_RTL; - int icon_y = CenterBounds(r.top, r.bottom, 0); - int text_y = CenterBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)); - Rect tr = r.Shrink(WidgetDimensions::scaled.dropdowntext); - DrawSprite(SPR_VEH_BUS_SIDE_VIEW, PALETTE_RECOLOUR_START + (this->result % COLOUR_END), - rtl ? tr.right - ScaleGUITrad(14) : tr.left + ScaleGUITrad(14), - icon_y); - tr = tr.Indent(ScaleGUITrad(28) + WidgetDimensions::scaled.hsep_normal, rtl); - DrawString(tr.left, tr.right, text_y, this->String(), sel ? TC_WHITE : TC_BLACK); } }; @@ -662,10 +641,10 @@ private: if (default_livery != nullptr) { /* Add COLOUR_END to put the colour out of range, but also allow us to show what the default is */ default_col = (primary ? default_livery->colour1 : default_livery->colour2) + COLOUR_END; - list.push_back(std::make_unique(default_col, false)); + list.push_back(std::make_unique>(default_col, false)); } for (uint i = 0; i < lengthof(_colour_dropdown); i++) { - list.push_back(std::make_unique(i, HasBit(used_colours, i))); + list.push_back(std::make_unique>(i, HasBit(used_colours, i))); } byte sel = (default_livery == nullptr || HasBit(livery->in_use, primary ? 0 : 1)) ? (primary ? livery->colour1 : livery->colour2) : default_col; diff --git a/src/genworld_gui.cpp b/src/genworld_gui.cpp index f529e5d77c..5fa8a0a289 100644 --- a/src/genworld_gui.cpp +++ b/src/genworld_gui.cpp @@ -358,7 +358,7 @@ static DropDownList BuildTownNameDropDown() size_t newgrf_size = list.size(); /* Insert newgrf_names at the top of the list */ if (newgrf_size > 0) { - list.push_back(std::make_unique(-1, false)); // separator line + list.push_back(std::make_unique(-1, false)); // separator line newgrf_size++; } diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 97a3066dae..5484d15acd 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -224,7 +224,7 @@ struct GameOptionsWindow : Window { std::sort(list.begin(), list.end(), DropDownListStringItem::NatSortFunc); /* Append custom currency at the end */ - list.push_back(std::make_unique(-1, false)); // separator line + list.push_back(std::make_unique(-1, false)); // separator line list.push_back(std::make_unique(STR_GAME_OPTIONS_CURRENCY_CUSTOM, CURRENCY_CUSTOM, HasBit(disabled, CURRENCY_CUSTOM))); break; } diff --git a/src/toolbar_gui.cpp b/src/toolbar_gui.cpp index 74b623d862..70b2fb5ec8 100644 --- a/src/toolbar_gui.cpp +++ b/src/toolbar_gui.cpp @@ -89,97 +89,16 @@ enum CallBackFunction { static CallBackFunction _last_started_action = CBF_NONE; ///< Last started user action. - /** - * Drop down list entry for showing a checked/unchecked toggle item. + * Company name list item, with company-colour icon, name, and lock components. */ -class DropDownListCheckedItem : public DropDownListStringItem { - uint checkmark_width; +class DropDownListCompanyItem : public DropDownIcon, true>> { public: - bool checked; - - DropDownListCheckedItem(StringID string, int result, bool masked, bool checked) : DropDownListStringItem(string, result, masked), checked(checked) + DropDownListCompanyItem(CompanyID company, bool shaded) : DropDownIcon, true>>(SPR_COMPANY_ICON, COMPANY_SPRITE_COLOUR(company), NetworkCompanyIsPassworded(company) ? SPR_LOCK : SPR_EMPTY, PAL_NONE, STR_NULL, company, false, shaded) { - this->checkmark_width = GetStringBoundingBox(STR_JUST_CHECKMARK).width + WidgetDimensions::scaled.hsep_wide; - } - - uint Width() const override - { - return DropDownListStringItem::Width() + this->checkmark_width; - } - - void Draw(const Rect &r, bool sel, Colours) const override - { - bool rtl = _current_text_dir == TD_RTL; - Rect tr = r.Shrink(WidgetDimensions::scaled.dropdowntext, RectPadding::zero); - if (this->checked) { - DrawString(tr, STR_JUST_CHECKMARK, sel ? TC_WHITE : TC_BLACK); - } - DrawString(tr.Indent(this->checkmark_width, rtl), this->String(), sel ? TC_WHITE : TC_BLACK); - } -}; - -/** - * Drop down list entry for showing a company entry, with companies 'blob'. - */ -class DropDownListCompanyItem : public DropDownListItem { - Dimension icon_size; - Dimension lock_size; -public: - bool greyed; - - DropDownListCompanyItem(int result, bool masked, bool greyed) : DropDownListItem(result, masked), greyed(greyed) - { - this->icon_size = GetSpriteSize(SPR_COMPANY_ICON); - this->lock_size = GetSpriteSize(SPR_LOCK); - } - - bool Selectable() const override - { - return true; - } - - uint Width() const override - { - CompanyID company = (CompanyID)this->result; SetDParam(0, company); SetDParam(1, company); - return GetStringBoundingBox(STR_COMPANY_NAME_COMPANY_NUM).width + this->icon_size.width + this->lock_size.width + WidgetDimensions::scaled.dropdowntext.Horizontal() + WidgetDimensions::scaled.hsep_wide; - } - - uint Height() const override - { - return std::max(std::max(this->icon_size.height, this->lock_size.height) + WidgetDimensions::scaled.imgbtn.Vertical(), (uint)GetCharacterHeight(FS_NORMAL)); - } - - void Draw(const Rect &r, bool sel, Colours) const override - { - CompanyID company = (CompanyID)this->result; - bool rtl = _current_text_dir == TD_RTL; - - /* It's possible the company is deleted while the dropdown is open */ - if (!Company::IsValidID(company)) return; - - Rect tr = r.Shrink(WidgetDimensions::scaled.dropdowntext, RectPadding::zero); - int icon_y = CenterBounds(r.top, r.bottom, icon_size.height); - int text_y = CenterBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)); - int lock_y = CenterBounds(r.top, r.bottom, lock_size.height); - - DrawCompanyIcon(company, tr.WithWidth(this->icon_size.width, rtl).left, icon_y); - if (NetworkCompanyIsPassworded(company)) { - DrawSprite(SPR_LOCK, PAL_NONE, tr.WithWidth(this->lock_size.width, !rtl).left, lock_y); - } - - SetDParam(0, company); - SetDParam(1, company); - TextColour col; - if (this->greyed) { - col = (sel ? TC_SILVER : TC_GREY) | TC_NO_SHADE; - } else { - col = sel ? TC_WHITE : TC_BLACK; - } - tr = tr.Indent(this->icon_size.width + WidgetDimensions::scaled.hsep_normal, rtl).Indent(this->lock_size.width + WidgetDimensions::scaled.hsep_normal, !rtl); - DrawString(tr.left, tr.right, text_y, STR_COMPANY_NAME_COMPANY_NUM, col); + this->SetString(GetString(STR_COMPANY_NAME_COMPANY_NUM)); } }; @@ -208,7 +127,7 @@ static void PopupMainToolbarMenu(Window *w, int widget, const std::initializer_l int i = 0; for (StringID string : strings) { if (string == STR_NULL) { - list.push_back(std::make_unique(-1, false)); + list.push_back(std::make_unique(-1, false)); } else { list.push_back(std::make_unique(string, i, false)); i++; @@ -254,7 +173,7 @@ static void PopupMainCompanyToolbMenu(Window *w, int widget, int grey = 0) for (CompanyID c = COMPANY_FIRST; c < MAX_COMPANIES; c++) { if (!Company::IsValidID(c)) continue; - list.push_back(std::make_unique(c, false, HasBit(grey, c))); + list.push_back(std::make_unique(c, HasBit(grey, c))); } PopupMainToolbarMenu(w, widget, std::move(list), _local_company == COMPANY_SPECTATOR ? (widget == WID_TN_COMPANIES ? CTMN_CLIENT_LIST : CTMN_SPECTATOR) : (int)_local_company); @@ -341,16 +260,16 @@ static CallBackFunction ToolbarOptionsClick(Window *w) } list.push_back(std::make_unique(STR_SETTINGS_MENU_NEWGRF_SETTINGS, OME_NEWGRFSETTINGS, false)); list.push_back(std::make_unique(STR_SETTINGS_MENU_TRANSPARENCY_OPTIONS, OME_TRANSPARENCIES, false)); - list.push_back(std::make_unique(-1, false)); - list.push_back(std::make_unique(STR_SETTINGS_MENU_TOWN_NAMES_DISPLAYED, OME_SHOW_TOWNNAMES, false, HasBit(_display_opt, DO_SHOW_TOWN_NAMES))); - list.push_back(std::make_unique(STR_SETTINGS_MENU_STATION_NAMES_DISPLAYED, OME_SHOW_STATIONNAMES, false, HasBit(_display_opt, DO_SHOW_STATION_NAMES))); - list.push_back(std::make_unique(STR_SETTINGS_MENU_WAYPOINTS_DISPLAYED, OME_SHOW_WAYPOINTNAMES, false, HasBit(_display_opt, DO_SHOW_WAYPOINT_NAMES))); - list.push_back(std::make_unique(STR_SETTINGS_MENU_SIGNS_DISPLAYED, OME_SHOW_SIGNS, false, HasBit(_display_opt, DO_SHOW_SIGNS))); - list.push_back(std::make_unique(STR_SETTINGS_MENU_SHOW_COMPETITOR_SIGNS, OME_SHOW_COMPETITOR_SIGNS, false, HasBit(_display_opt, DO_SHOW_COMPETITOR_SIGNS))); - list.push_back(std::make_unique(STR_SETTINGS_MENU_FULL_ANIMATION, OME_FULL_ANIMATION, false, HasBit(_display_opt, DO_FULL_ANIMATION))); - list.push_back(std::make_unique(STR_SETTINGS_MENU_FULL_DETAIL, OME_FULL_DETAILS, false, HasBit(_display_opt, DO_FULL_DETAIL))); - list.push_back(std::make_unique(STR_SETTINGS_MENU_TRANSPARENT_BUILDINGS, OME_TRANSPARENTBUILDINGS, false, IsTransparencySet(TO_HOUSES))); - list.push_back(std::make_unique(STR_SETTINGS_MENU_TRANSPARENT_SIGNS, OME_SHOW_STATIONSIGNS, false, IsTransparencySet(TO_SIGNS))); + list.push_back(std::make_unique(-1, false)); + list.push_back(std::make_unique(HasBit(_display_opt, DO_SHOW_TOWN_NAMES), STR_SETTINGS_MENU_TOWN_NAMES_DISPLAYED, OME_SHOW_TOWNNAMES, false)); + list.push_back(std::make_unique(HasBit(_display_opt, DO_SHOW_STATION_NAMES), STR_SETTINGS_MENU_STATION_NAMES_DISPLAYED, OME_SHOW_STATIONNAMES, false)); + list.push_back(std::make_unique(HasBit(_display_opt, DO_SHOW_WAYPOINT_NAMES), STR_SETTINGS_MENU_WAYPOINTS_DISPLAYED, OME_SHOW_WAYPOINTNAMES, false)); + list.push_back(std::make_unique(HasBit(_display_opt, DO_SHOW_SIGNS), STR_SETTINGS_MENU_SIGNS_DISPLAYED, OME_SHOW_SIGNS, false)); + list.push_back(std::make_unique(HasBit(_display_opt, DO_SHOW_COMPETITOR_SIGNS), STR_SETTINGS_MENU_SHOW_COMPETITOR_SIGNS, OME_SHOW_COMPETITOR_SIGNS, false)); + list.push_back(std::make_unique(HasBit(_display_opt, DO_FULL_ANIMATION), STR_SETTINGS_MENU_FULL_ANIMATION, OME_FULL_ANIMATION, false)); + list.push_back(std::make_unique(HasBit(_display_opt, DO_FULL_DETAIL), STR_SETTINGS_MENU_FULL_DETAIL, OME_FULL_DETAILS, false)); + list.push_back(std::make_unique(IsTransparencySet(TO_HOUSES), STR_SETTINGS_MENU_TRANSPARENT_BUILDINGS, OME_TRANSPARENTBUILDINGS, false)); + list.push_back(std::make_unique(IsTransparencySet(TO_SIGNS), STR_SETTINGS_MENU_TRANSPARENT_SIGNS, OME_SHOW_STATIONSIGNS, false)); ShowDropDownList(w, std::move(list), 0, WID_TN_SETTINGS, 140, true); if (_settings_client.sound.click_beep) SndPlayFx(SND_15_BEEP); diff --git a/src/widgets/dropdown.cpp b/src/widgets/dropdown.cpp index ebf40fe909..9ad87c0a84 100644 --- a/src/widgets/dropdown.cpp +++ b/src/widgets/dropdown.cpp @@ -22,82 +22,6 @@ #include "../safeguards.h" -void DropDownListItem::Draw(const Rect &r, bool, Colours bg_colour) const -{ - int c1 = _colour_gradient[bg_colour][3]; - int c2 = _colour_gradient[bg_colour][7]; - - int mid = CenterBounds(r.top, r.bottom, 0); - GfxFillRect(r.left, mid - WidgetDimensions::scaled.bevel.bottom, r.right, mid - 1, c1); - GfxFillRect(r.left, mid, r.right, mid + WidgetDimensions::scaled.bevel.top - 1, c2); -} - -DropDownListStringItem::DropDownListStringItem(StringID string, int result, bool masked) : DropDownListItem(result, masked), string(GetString(string)) -{ -} - -DropDownListStringItem::DropDownListStringItem(const std::string &string, int result, bool masked) : DropDownListItem(result, masked) -{ - /* A raw string may contain parsable tokens, so it needs to be passed through GetString. */ - SetDParamStr(0, string); - this->string = GetString(STR_JUST_RAW_STRING); -} - -uint DropDownListStringItem::Width() const -{ - return GetStringBoundingBox(this->String()).width + WidgetDimensions::scaled.dropdowntext.Horizontal(); -} - -void DropDownListStringItem::Draw(const Rect &r, bool sel, Colours) const -{ - Rect ir = r.Shrink(WidgetDimensions::scaled.dropdowntext); - DrawString(ir.left, ir.right, r.top, this->String(), sel ? TC_WHITE : TC_BLACK); -} - -/** - * Natural sorting comparator function for DropDownList::sort(). - * @param first Left side of comparison. - * @param second Right side of comparison. - * @return true if \a first precedes \a second. - * @warning All items in the list need to be derivates of DropDownListStringItem. - */ -/* static */ bool DropDownListStringItem::NatSortFunc(std::unique_ptr const &first, std::unique_ptr const &second) -{ - std::string str1 = static_cast(first.get())->String(); - std::string str2 = static_cast(second.get())->String(); - return StrNaturalCompare(str1, str2) < 0; -} - -DropDownListIconItem::DropDownListIconItem(SpriteID sprite, PaletteID pal, StringID string, int result, bool masked) : DropDownListStringItem(string, result, masked), sprite(sprite), pal(pal) -{ - this->dim = GetSpriteSize(sprite); - this->sprite_y = dim.height; -} - -uint DropDownListIconItem::Height() const -{ - return std::max(this->dim.height, (uint)GetCharacterHeight(FS_NORMAL)); -} - -uint DropDownListIconItem::Width() const -{ - return DropDownListStringItem::Width() + this->dim.width + WidgetDimensions::scaled.hsep_wide; -} - -void DropDownListIconItem::Draw(const Rect &r, bool sel, Colours) const -{ - bool rtl = _current_text_dir == TD_RTL; - Rect ir = r.Shrink(WidgetDimensions::scaled.dropdowntext); - Rect tr = ir.Indent(this->dim.width + WidgetDimensions::scaled.hsep_normal, rtl); - DrawSprite(this->sprite, this->pal, ir.WithWidth(this->dim.width, rtl).left, CenterBounds(r.top, r.bottom, this->sprite_y)); - DrawString(tr.left, tr.right, CenterBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)), this->String(), sel ? TC_WHITE : TC_BLACK); -} - -void DropDownListIconItem::SetDimension(Dimension d) -{ - this->dim = d; -} - static const NWidgetPart _nested_dropdown_menu_widgets[] = { NWidget(NWID_HORIZONTAL), NWidget(WWT_PANEL, COLOUR_END, WID_DM_ITEMS), SetMinimalSize(1, 1), SetScrollbar(WID_DM_SCROLL), EndContainer(), @@ -256,14 +180,12 @@ struct DropdownWindow : Window { if (--pos >= 0) continue; if (y + item_height - 1 <= ir.bottom) { + Rect full{ir.left, y, ir.right, y + item_height - 1}; + bool selected = (this->selected_index == item->result); - if (selected) GfxFillRect(ir.left, y, ir.right, y + item_height - 1, PC_BLACK); + if (selected) GfxFillRect(full, PC_BLACK); - item->Draw({ir.left, y, ir.right, y + item_height - 1}, selected, colour); - - if (item->masked) { - GfxFillRect(ir.left, y, ir.right, y + item_height - 1, _colour_gradient[colour][5], FILLRECT_CHECKER); - } + item->Draw(full, full.Shrink(WidgetDimensions::scaled.dropdowntext, RectPadding::zero), selected, colour); } y += item_height; } @@ -343,6 +265,7 @@ Dimension GetDropDownListDimension(const DropDownList &list) dim.height += item->Height(); dim.width = std::max(dim.width, item->Width()); } + dim.width += WidgetDimensions::scaled.dropdowntext.Horizontal(); return dim; } diff --git a/src/widgets/dropdown_type.h b/src/widgets/dropdown_type.h index 62a6c30e2b..5f8de71ea0 100644 --- a/src/widgets/dropdown_type.h +++ b/src/widgets/dropdown_type.h @@ -12,61 +12,200 @@ #include "../window_type.h" #include "../gfx_func.h" -#include "table/strings.h" +#include "../gfx_type.h" +#include "../string_func.h" +#include "../strings_func.h" +#include "../table/strings.h" +#include "../window_gui.h" /** - * Base list item class from which others are derived. If placed in a list it - * will appear as a horizontal line in the menu. + * Base list item class from which others are derived. */ class DropDownListItem { public: - int result; ///< Result code to return to window on selection - bool masked; ///< Masked and unselectable item + int result; ///< Result value to return to window on selection. + bool masked; ///< Masked and unselectable item. + bool shaded; ///< Shaded item, affects text colour. - DropDownListItem(int result, bool masked) : result(result), masked(masked) {} + explicit DropDownListItem(int result, bool masked = false, bool shaded = false) : result(result), masked(masked), shaded(shaded) {} virtual ~DropDownListItem() = default; - virtual bool Selectable() const { return false; } - virtual uint Height() const { return GetCharacterHeight(FS_NORMAL); } + virtual bool Selectable() const { return true; } + virtual uint Height() const { return 0; } virtual uint Width() const { return 0; } - virtual void Draw(const Rect &r, bool sel, Colours bg_colour) const; + + virtual void Draw(const Rect &full, const Rect &, bool, Colours bg_colour) const + { + if (this->masked) GfxFillRect(full, _colour_gradient[bg_colour][5], FILLRECT_CHECKER); + } + + TextColour GetColour(bool sel) const + { + if (this->shaded) return (sel ? TC_SILVER : TC_GREY) | TC_NO_SHADE; + return sel ? TC_WHITE : TC_BLACK; + } }; /** - * Common string list item. + * Drop down divider component. + * @tparam TBase Base component. + * @tparam TFs Font size -- used to determine height. */ -class DropDownListStringItem : public DropDownListItem { +template +class DropDownDivider : public TBase { public: - std::string string; ///< String of item + template + explicit DropDownDivider(Args&&... args) : TBase(std::forward(args)...) {} - DropDownListStringItem(StringID string, int result, bool masked); - DropDownListStringItem(const std::string &string, int result, bool masked); + bool Selectable() const override { return false; } + uint Height() const override { return std::max(GetCharacterHeight(TFs), this->TBase::Height()); } - bool Selectable() const override { return true; } - uint Width() const override; - void Draw(const Rect &r, bool sel, Colours bg_colour) const override; - virtual const std::string &String() const { return this->string; } + void Draw(const Rect &full, const Rect &, bool, Colours bg_colour) const override + { + uint8_t c1 = _colour_gradient[bg_colour][3]; + uint8_t c2 = _colour_gradient[bg_colour][7]; - static bool NatSortFunc(std::unique_ptr const &first, std::unique_ptr const &second); + int mid = CenterBounds(full.top, full.bottom, 0); + GfxFillRect(full.left, mid - WidgetDimensions::scaled.bevel.bottom, full.right, mid - 1, c1); + GfxFillRect(full.left, mid, full.right, mid + WidgetDimensions::scaled.bevel.top - 1, c2); + } }; /** - * List item with icon and string. + * Drop down string component. + * @tparam TBase Base component. + * @tparam TFs Font size. + * @tparam TEnd Position string at end if true, or start if false. */ -class DropDownListIconItem : public DropDownListStringItem { - SpriteID sprite; - PaletteID pal; - Dimension dim; - uint sprite_y; +template +class DropDownString : public TBase { + std::string string; ///< String to be drawn. + Dimension dim; ///< Dimensions of string. public: - DropDownListIconItem(SpriteID sprite, PaletteID pal, StringID string, int result, bool masked); + template + explicit DropDownString(StringID string, Args&&... args) : TBase(std::forward(args)...) + { + this->SetString(GetString(string)); + } - uint Height() const override; - uint Width() const override; - void Draw(const Rect &r, bool sel, Colours bg_colour) const override; - void SetDimension(Dimension d); + template + explicit DropDownString(const std::string &string, Args&&... args) : TBase(std::forward(args)...) + { + SetDParamStr(0, string); + this->SetString(GetString(STR_JUST_RAW_STRING)); + } + + void SetString(std::string &&string) + { + this->string = std::move(string); + this->dim = GetStringBoundingBox(this->string, TFs); + } + + uint Height() const override + { + return std::max(this->dim.height, this->TBase::Height()); + } + + uint Width() const override { return this->dim.width + this->TBase::Width(); } + + void Draw(const Rect &full, const Rect &r, bool sel, Colours bg_colour) const override + { + bool rtl = TEnd ^ (_current_text_dir == TD_RTL); + DrawStringMultiLine(r.WithWidth(this->dim.width, rtl), this->string, this->GetColour(sel), SA_CENTER, false, TFs); + this->TBase::Draw(full, r.Indent(this->dim.width, rtl), sel, bg_colour); + } + + /** + * Natural sorting comparator function for DropDownList::sort(). + * @param first Left side of comparison. + * @param second Right side of comparison. + * @return true if \a first precedes \a second. + * @warning All items in the list need to be derivates of DropDownListStringItem. + */ + static bool NatSortFunc(std::unique_ptr const &first, std::unique_ptr const &second) + { + const std::string &str1 = static_cast(first.get())->string; + const std::string &str2 = static_cast(second.get())->string; + return StrNaturalCompare(str1, str2) < 0; + } }; +/** + * Drop down icon component. + * @tparam TBase Base component. + * @tparam TEnd Position icon at end if true, or start if false. + */ +template +class DropDownIcon : public TBase { + SpriteID sprite; ///< Sprite ID to be drawn. + PaletteID palette; ///< Palette ID to use. + Dimension dsprite; ///< Bounding box dimensions of sprite. + Dimension dbounds; ///< Bounding box dimensions of bounds. +public: + template + explicit DropDownIcon(SpriteID sprite, PaletteID palette, Args&&... args) : TBase(std::forward(args)...), sprite(sprite), palette(palette) + { + this->dsprite = GetSpriteSize(this->sprite); + this->dbounds = this->dsprite; + } + + uint Height() const override { return std::max(this->dbounds.height, this->TBase::Height()); } + uint Width() const override { return this->dbounds.width + WidgetDimensions::scaled.hsep_normal + this->TBase::Width(); } + + void Draw(const Rect &full, const Rect &r, bool sel, Colours bg_colour) const override + { + bool rtl = TEnd ^ (_current_text_dir == TD_RTL); + Rect ir = r.WithWidth(this->dbounds.width, rtl); + DrawSprite(this->sprite, this->palette, CenterBounds(ir.left, ir.right, this->dsprite.width), CenterBounds(r.top, r.bottom, this->dsprite.height)); + this->TBase::Draw(full, r.Indent(this->dbounds.width + WidgetDimensions::scaled.hsep_normal, rtl), sel, bg_colour); + } + + /** + * Override bounding box dimensions of sprite, to allow multiple options to have consistent spacing. + * @param dim New bounding box to assign. + */ + void SetDimension(const Dimension &dim) + { + this->dbounds = dim; + } +}; + +/** + * Drop down checkmark component. + * @tparam TBase Base component. + * @tparam TFs Font size. + * @tparam TEnd Position checkmark at end if true, or start if false. + */ +template +class DropDownCheck : public TBase { + bool checked; ///< Is item checked. + Dimension dim; ///< Dimension of checkmark. +public: + template + explicit DropDownCheck(bool checked, Args&&... args) : TBase(std::forward(args)...), checked(checked) + { + this->dim = GetStringBoundingBox(STR_JUST_CHECKMARK, TFs); + } + + uint Height() const override { return std::max(this->dim.height, this->TBase::Height()); } + uint Width() const override { return this->dim.width + WidgetDimensions::scaled.hsep_wide + this->TBase::Width(); } + + void Draw(const Rect &full, const Rect &r, bool sel, Colours bg_colour) const override + { + bool rtl = TEnd ^ (_current_text_dir == TD_RTL); + if (this->checked) { + DrawStringMultiLine(r.WithWidth(this->dim.width, rtl), STR_JUST_CHECKMARK, this->GetColour(sel), SA_CENTER, false, TFs); + } + this->TBase::Draw(full, r.Indent(this->dim.width + WidgetDimensions::scaled.hsep_wide, rtl), sel, bg_colour); + } +}; + +/* Commonly used drop down list items. */ +using DropDownListDividerItem = DropDownDivider; +using DropDownListStringItem = DropDownString; +using DropDownListIconItem = DropDownIcon>; +using DropDownListCheckedItem = DropDownCheck>; + /** * A drop down list is a collection of drop down list items. */