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.)
This commit is contained in:
Peter Nelson 2023-11-11 15:17:12 +00:00 committed by Peter Nelson
parent 62e4d1e507
commit 49532914dd
6 changed files with 201 additions and 241 deletions

View File

@ -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 <SpriteID TSprite = SPR_SQUARE>
class DropDownListColourItem : public DropDownIcon<DropDownString<DropDownListItem>> {
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<DropDownString<DropDownListItem>>(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<DropDownListColourItem>(default_col, false));
list.push_back(std::make_unique<DropDownListColourItem<>>(default_col, false));
}
for (uint i = 0; i < lengthof(_colour_dropdown); i++) {
list.push_back(std::make_unique<DropDownListColourItem>(i, HasBit(used_colours, i)));
list.push_back(std::make_unique<DropDownListColourItem<>>(i, HasBit(used_colours, i)));
}
byte sel = (default_livery == nullptr || HasBit(livery->in_use, primary ? 0 : 1)) ? (primary ? livery->colour1 : livery->colour2) : default_col;

View File

@ -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<DropDownListItem>(-1, false)); // separator line
list.push_back(std::make_unique<DropDownListDividerItem>(-1, false)); // separator line
newgrf_size++;
}

View File

@ -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<DropDownListItem>(-1, false)); // separator line
list.push_back(std::make_unique<DropDownListDividerItem>(-1, false)); // separator line
list.push_back(std::make_unique<DropDownListStringItem>(STR_GAME_OPTIONS_CURRENCY_CUSTOM, CURRENCY_CUSTOM, HasBit(disabled, CURRENCY_CUSTOM)));
break;
}

View File

@ -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<DropDownIcon<DropDownString<DropDownListItem>, 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<DropDownIcon<DropDownString<DropDownListItem>, 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<DropDownListItem>(-1, false));
list.push_back(std::make_unique<DropDownListDividerItem>(-1, false));
} else {
list.push_back(std::make_unique<DropDownListStringItem>(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<DropDownListCompanyItem>(c, false, HasBit(grey, c)));
list.push_back(std::make_unique<DropDownListCompanyItem>(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<DropDownListStringItem>(STR_SETTINGS_MENU_NEWGRF_SETTINGS, OME_NEWGRFSETTINGS, false));
list.push_back(std::make_unique<DropDownListStringItem>(STR_SETTINGS_MENU_TRANSPARENCY_OPTIONS, OME_TRANSPARENCIES, false));
list.push_back(std::make_unique<DropDownListItem>(-1, false));
list.push_back(std::make_unique<DropDownListCheckedItem>(STR_SETTINGS_MENU_TOWN_NAMES_DISPLAYED, OME_SHOW_TOWNNAMES, false, HasBit(_display_opt, DO_SHOW_TOWN_NAMES)));
list.push_back(std::make_unique<DropDownListCheckedItem>(STR_SETTINGS_MENU_STATION_NAMES_DISPLAYED, OME_SHOW_STATIONNAMES, false, HasBit(_display_opt, DO_SHOW_STATION_NAMES)));
list.push_back(std::make_unique<DropDownListCheckedItem>(STR_SETTINGS_MENU_WAYPOINTS_DISPLAYED, OME_SHOW_WAYPOINTNAMES, false, HasBit(_display_opt, DO_SHOW_WAYPOINT_NAMES)));
list.push_back(std::make_unique<DropDownListCheckedItem>(STR_SETTINGS_MENU_SIGNS_DISPLAYED, OME_SHOW_SIGNS, false, HasBit(_display_opt, DO_SHOW_SIGNS)));
list.push_back(std::make_unique<DropDownListCheckedItem>(STR_SETTINGS_MENU_SHOW_COMPETITOR_SIGNS, OME_SHOW_COMPETITOR_SIGNS, false, HasBit(_display_opt, DO_SHOW_COMPETITOR_SIGNS)));
list.push_back(std::make_unique<DropDownListCheckedItem>(STR_SETTINGS_MENU_FULL_ANIMATION, OME_FULL_ANIMATION, false, HasBit(_display_opt, DO_FULL_ANIMATION)));
list.push_back(std::make_unique<DropDownListCheckedItem>(STR_SETTINGS_MENU_FULL_DETAIL, OME_FULL_DETAILS, false, HasBit(_display_opt, DO_FULL_DETAIL)));
list.push_back(std::make_unique<DropDownListCheckedItem>(STR_SETTINGS_MENU_TRANSPARENT_BUILDINGS, OME_TRANSPARENTBUILDINGS, false, IsTransparencySet(TO_HOUSES)));
list.push_back(std::make_unique<DropDownListCheckedItem>(STR_SETTINGS_MENU_TRANSPARENT_SIGNS, OME_SHOW_STATIONSIGNS, false, IsTransparencySet(TO_SIGNS)));
list.push_back(std::make_unique<DropDownListDividerItem>(-1, false));
list.push_back(std::make_unique<DropDownListCheckedItem>(HasBit(_display_opt, DO_SHOW_TOWN_NAMES), STR_SETTINGS_MENU_TOWN_NAMES_DISPLAYED, OME_SHOW_TOWNNAMES, false));
list.push_back(std::make_unique<DropDownListCheckedItem>(HasBit(_display_opt, DO_SHOW_STATION_NAMES), STR_SETTINGS_MENU_STATION_NAMES_DISPLAYED, OME_SHOW_STATIONNAMES, false));
list.push_back(std::make_unique<DropDownListCheckedItem>(HasBit(_display_opt, DO_SHOW_WAYPOINT_NAMES), STR_SETTINGS_MENU_WAYPOINTS_DISPLAYED, OME_SHOW_WAYPOINTNAMES, false));
list.push_back(std::make_unique<DropDownListCheckedItem>(HasBit(_display_opt, DO_SHOW_SIGNS), STR_SETTINGS_MENU_SIGNS_DISPLAYED, OME_SHOW_SIGNS, false));
list.push_back(std::make_unique<DropDownListCheckedItem>(HasBit(_display_opt, DO_SHOW_COMPETITOR_SIGNS), STR_SETTINGS_MENU_SHOW_COMPETITOR_SIGNS, OME_SHOW_COMPETITOR_SIGNS, false));
list.push_back(std::make_unique<DropDownListCheckedItem>(HasBit(_display_opt, DO_FULL_ANIMATION), STR_SETTINGS_MENU_FULL_ANIMATION, OME_FULL_ANIMATION, false));
list.push_back(std::make_unique<DropDownListCheckedItem>(HasBit(_display_opt, DO_FULL_DETAIL), STR_SETTINGS_MENU_FULL_DETAIL, OME_FULL_DETAILS, false));
list.push_back(std::make_unique<DropDownListCheckedItem>(IsTransparencySet(TO_HOUSES), STR_SETTINGS_MENU_TRANSPARENT_BUILDINGS, OME_TRANSPARENTBUILDINGS, false));
list.push_back(std::make_unique<DropDownListCheckedItem>(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);

View File

@ -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 DropDownListItem> const &first, std::unique_ptr<const DropDownListItem> const &second)
{
std::string str1 = static_cast<const DropDownListStringItem*>(first.get())->String();
std::string str2 = static_cast<const DropDownListStringItem*>(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;
}

View File

@ -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 TBase, FontSize TFs = FS_NORMAL>
class DropDownDivider : public TBase {
public:
std::string string; ///< String of item
template <typename... Args>
explicit DropDownDivider(Args&&... args) : TBase(std::forward<Args>(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<uint>(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 DropDownListItem> const &first, std::unique_ptr<const DropDownListItem> 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 TBase, FontSize TFs = FS_NORMAL, bool TEnd = false>
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 <typename... Args>
explicit DropDownString(StringID string, Args&&... args) : TBase(std::forward<Args>(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 <typename... Args>
explicit DropDownString(const std::string &string, Args&&... args) : TBase(std::forward<Args>(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<uint>(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 DropDownListItem> const &first, std::unique_ptr<const DropDownListItem> const &second)
{
const std::string &str1 = static_cast<const DropDownString*>(first.get())->string;
const std::string &str2 = static_cast<const DropDownString*>(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 TBase, bool TEnd = false>
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 <typename... Args>
explicit DropDownIcon(SpriteID sprite, PaletteID palette, Args&&... args) : TBase(std::forward<Args>(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 TBase, bool TEnd = false, FontSize TFs = FS_NORMAL>
class DropDownCheck : public TBase {
bool checked; ///< Is item checked.
Dimension dim; ///< Dimension of checkmark.
public:
template <typename... Args>
explicit DropDownCheck(bool checked, Args&&... args) : TBase(std::forward<Args>(args)...), checked(checked)
{
this->dim = GetStringBoundingBox(STR_JUST_CHECKMARK, TFs);
}
uint Height() const override { return std::max<uint>(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<DropDownListItem>;
using DropDownListStringItem = DropDownString<DropDownListItem>;
using DropDownListIconItem = DropDownIcon<DropDownString<DropDownListItem>>;
using DropDownListCheckedItem = DropDownCheck<DropDownString<DropDownListItem>>;
/**
* A drop down list is a collection of drop down list items.
*/