@@ -210,53 +210,53 @@ public:
}
Point OnInitialPosition([[maybe_unused]] int16_t sm_width, [[maybe_unused]] int16_t sm_height, [[maybe_unused]] int window_number) override
{
/* Position the window so hopefully the first bridge from the list is under the mouse pointer. */
NWidgetBase *list = this->GetWidget<NWidgetBase>(WID_BBS_BRIDGE_LIST);
Point corner; // point of the top left corner of the window.
corner.y = Clamp(_cursor.pos.y - list->pos_y - 5, GetMainViewTop(), GetMainViewBottom() - sm_height);
corner.x = Clamp(_cursor.pos.x - list->pos_x - 5, 0, _screen.width - sm_width);
return corner;
void DrawWidget(const Rect &r, WidgetID widget) const override
switch (widget) {
case WID_BBS_DROPDOWN_ORDER:
this->DrawSortButtonState(widget, this->bridges.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
break;
case WID_BBS_BRIDGE_LIST: {
Rect tr = r.WithHeight(this->resize.step_height).Shrink(WidgetDimensions::scaled.matrix);
bool rtl = _current_text_dir == TD_RTL;
for (int i = this->vscroll->GetPosition(); this->vscroll->IsVisible(i) && i < (int)this->bridges.size(); i++) {
const BuildBridgeData &bridge_data = this->bridges.at(i);
const BridgeSpec *b = bridge_data.spec;
auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->bridges);
for (auto it = first; it != last; ++it) {
const BridgeSpec *b = it->spec;
DrawSpriteIgnorePadding(b->sprite, b->pal, tr.WithWidth(this->icon_width, rtl), SA_HOR_CENTER | SA_BOTTOM);
DrawStringMultiLine(tr.Indent(this->icon_width + WidgetDimensions::scaled.hsep_normal, rtl), GetBridgeSelectString(bridge_data));
DrawStringMultiLine(tr.Indent(this->icon_width + WidgetDimensions::scaled.hsep_normal, rtl), GetBridgeSelectString(*it));
tr = tr.Translate(0, this->resize.step_height);
EventState OnKeyPress([[maybe_unused]] char32_t key, uint16_t keycode) override
const uint8_t i = keycode - '1';
if (i < 9 && i < this->bridges.size()) {
/* Build the requested bridge */
this->BuildBridge(this->bridges[i].index);
this->Close();
return ES_HANDLED;
return ES_NOT_HANDLED;
void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
default: break;
@@ -420,50 +420,50 @@ public:
case WID_SL_BACKGROUND: {
static std::string path;
static std::optional<uint64_t> free_space = std::nullopt;
if (_fios_path_changed) {
path = FiosGetCurrentPath();
free_space = FiosGetDiskFreeSpace(path);
_fios_path_changed = false;
Rect ir = r.Shrink(WidgetDimensions::scaled.framerect);
if (free_space.has_value()) SetDParam(0, free_space.value());
DrawString(ir.left, ir.right, ir.top + GetCharacterHeight(FS_NORMAL), free_space.has_value() ? STR_SAVELOAD_BYTES_FREE : STR_ERROR_UNABLE_TO_READ_DRIVE);
DrawString(ir.left, ir.right, ir.top, path, TC_BLACK);
case WID_SL_DRIVES_DIRECTORIES_LIST: {
const Rect br = r.Shrink(WidgetDimensions::scaled.bevel);
GfxFillRect(br, PC_BLACK);
Rect tr = r.Shrink(WidgetDimensions::scaled.inset).WithHeight(this->resize.step_height);
uint scroll_pos = this->vscroll->GetPosition();
for (auto it = this->display_list.begin() + scroll_pos; it != this->display_list.end() && tr.top < br.bottom; ++it) {
auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->display_list);
const FiosItem *item = *it;
if (item == this->selected) {
GfxFillRect(br.left, tr.top, br.right, tr.bottom, PC_DARK_BLUE);
} else if (item == this->highlighted) {
GfxFillRect(br.left, tr.top, br.right, tr.bottom, PC_VERY_DARK_BLUE);
DrawString(tr, item->title, _fios_colours[GetDetailedFileType(item->type)]);
case WID_SL_DETAILS:
this->DrawDetails(r);
void DrawDetails(const Rect &r) const
/* Header panel */
int HEADER_HEIGHT = GetCharacterHeight(FS_NORMAL) + WidgetDimensions::scaled.frametext.Vertical();
@@ -1002,55 +1002,53 @@ struct PaymentRatesGraphWindow : BaseGra
for (const CargoSpec *cs : _sorted_standard_cargo_specs) {
SetDParam(0, cs->name);
Dimension d = GetStringBoundingBox(STR_GRAPH_CARGO_PAYMENT_CARGO);
d.width += this->legend_width + WidgetDimensions::scaled.hsep_normal; // colour field
d.width += WidgetDimensions::scaled.framerect.Horizontal();
d.height += WidgetDimensions::scaled.framerect.Vertical();
*size = maxdim(d, *size);
this->line_height = size->height;
size->height = this->line_height * 11; /* Default number of cargo types in most climates. */
resize->width = 0;
resize->height = this->line_height;
if (widget != WID_CPR_MATRIX) {
BaseGraphWindow::DrawWidget(r, widget);
return;
int pos = this->vscroll->GetPosition();
int max = pos + this->vscroll->GetCapacity();
auto [first, last] = this->vscroll->GetVisibleRangeIterators(_sorted_standard_cargo_specs);
Rect line = r.WithHeight(this->line_height);
if (pos-- > 0) continue;
if (--max < 0) break;
const CargoSpec *cs = *it;
bool lowered = !HasBit(_legend_excluded_cargo, cs->Index());
/* Redraw frame if lowered */
if (lowered) DrawFrameRect(line, COLOUR_BROWN, FR_LOWERED);
const Rect text = line.Shrink(WidgetDimensions::scaled.framerect);
/* Cargo-colour box with outline */
const Rect cargo = text.WithWidth(this->legend_width, rtl);
GfxFillRect(cargo, PC_BLACK);
GfxFillRect(cargo.Shrink(WidgetDimensions::scaled.bevel), cs->legend_colour);
/* Cargo name */
DrawString(text.Indent(this->legend_width + WidgetDimensions::scaled.hsep_normal, rtl), STR_GRAPH_CARGO_PAYMENT_CARGO);
line = line.Translate(0, this->line_height);
@@ -605,51 +605,51 @@ public:
size_t max = std::min<size_t>(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];
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]));
y1 += this->tiny_step_height;
if ((uint)this->group_sb->GetPosition() + this->group_sb->GetCapacity() > this->groups.size()) {
DrawGroupInfo(y1, r.left, r.right, NEW_GROUP);
case WID_GL_SORT_BY_ORDER:
this->DrawSortButtonState(WID_GL_SORT_BY_ORDER, this->vehgroups.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
case WID_GL_LIST_VEHICLE:
if (this->vli.index != ALL_GROUP && this->grouping == GB_NONE) {
/* Mark vehicles which are in sub-groups (only if we are not using shared order coalescing) */
Rect mr = r.WithHeight(this->resize.step_height);
size_t max = std::min<size_t>(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->vehgroups.size());
for (size_t i = this->vscroll->GetPosition(); i < max; ++i) {
const Vehicle *v = this->vehgroups[i].GetSingleVehicle();
auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->vehgroups);
const Vehicle *v = it->GetSingleVehicle();
if (v->group_id != this->vli.index) {
GfxFillRect(mr.Shrink(WidgetDimensions::scaled.bevel), GetColourGradient(COLOUR_GREY, SHADE_DARK), FILLRECT_CHECKER);
mr = mr.Translate(0, this->resize.step_height);
this->DrawVehicleListItems(this->vehicle_sel, this->resize.step_height, r);
static void DeleteGroupCallback(Window *win, bool confirmed)
if (confirmed) {
VehicleGroupWindow *w = (VehicleGroupWindow*)win;
w->vli.index = ALL_GROUP;
Command<CMD_DELETE_GROUP>::Post(STR_ERROR_GROUP_CAN_T_DELETE, w->group_confirm);
@@ -507,50 +507,51 @@ public:
if (this->selected_type != INVALID_INDUSTRYTYPE) {
const IndustrySpec *indsp = GetIndustrySpec(this->selected_type);
SetDParam(0, (_settings_game.construction.raw_industry_construction == 2 && indsp->IsRawIndustry()) ? STR_FUND_INDUSTRY_PROSPECT_NEW_INDUSTRY : STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY);
} else {
SetDParam(0, STR_FUND_INDUSTRY_FUND_NEW_INDUSTRY);
case WID_DPI_MATRIX_WIDGET: {
Rect text = r.WithHeight(this->resize.step_height).Shrink(WidgetDimensions::scaled.matrix);
Rect icon = text.WithWidth(this->legend.width, rtl);
text = text.Indent(this->legend.width + WidgetDimensions::scaled.hsep_wide, rtl);
/* Vertical offset for legend icon. */
icon.top = r.top + (this->resize.step_height - this->legend.height + 1) / 2;
icon.bottom = icon.top + this->legend.height - 1;
for (uint16_t i = this->vscroll->GetPosition(); this->vscroll->IsVisible(i) && i < this->vscroll->GetCount(); i++) {
IndustryType type = this->list[i];
auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->list);
IndustryType type = *it;
bool selected = this->selected_type == type;
const IndustrySpec *indsp = GetIndustrySpec(type);
/* Draw the name of the industry in white is selected, otherwise, in orange */
DrawString(text, indsp->name, selected ? TC_WHITE : TC_ORANGE);
GfxFillRect(icon, selected ? PC_WHITE : PC_BLACK);
GfxFillRect(icon.Shrink(WidgetDimensions::scaled.bevel), indsp->map_colour);
SetDParam(0, Industry::GetIndustryTypeCount(type));
DrawString(text, STR_JUST_COMMA, TC_BLACK, SA_RIGHT, false, FS_SMALL);
text = text.Translate(0, this->resize.step_height);
icon = icon.Translate(0, this->resize.step_height);
case WID_DPI_INFOPANEL: {
if (this->selected_type == INVALID_INDUSTRYTYPE) {
DrawStringMultiLine(ir, STR_FUND_INDUSTRY_MANY_RANDOM_INDUSTRIES_TOOLTIP);
@@ -1678,62 +1679,61 @@ public:
case WID_ID_DROPDOWN_ORDER:
this->DrawSortButtonState(widget, this->industries.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
case WID_ID_INDUSTRY_LIST: {
/* Setup a clipping rectangle... */
DrawPixelInfo tmp_dpi;
if (!FillDrawPixelInfo(&tmp_dpi, ir)) return;
/* ...but keep coordinates relative to the window. */
tmp_dpi.left += ir.left;
tmp_dpi.top += ir.top;
AutoRestoreBackup dpi_backup(_cur_dpi, &tmp_dpi);
ir.left -= this->hscroll->GetPosition();
ir.right += this->hscroll->GetCapacity() - this->hscroll->GetPosition();
if (this->industries.empty()) {
DrawString(ir, STR_INDUSTRY_DIRECTORY_NONE);
int n = 0;
const CargoID acf_cid = this->accepted_cargo_filter_criteria;
for (uint i = this->vscroll->GetPosition(); i < this->industries.size(); i++) {
auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->industries);
TextColour tc = TC_FROMSTRING;
if (acf_cid != CargoFilterCriteria::CF_ANY && acf_cid != CargoFilterCriteria::CF_NONE) {
Industry *ind = const_cast<Industry *>(this->industries[i]);
Industry *ind = const_cast<Industry *>(*it);
if (IndustryTemporarilyRefusesCargo(ind, acf_cid)) {
tc = TC_GREY | TC_FORCED;
DrawString(ir, this->GetIndustryString(this->industries[i]), tc);
DrawString(ir, this->GetIndustryString(*it), tc);
ir.top += this->resize.step_height;
if (++n == this->vscroll->GetCapacity()) break; // max number of industries in 1 window
void UpdateWidgetSize(WidgetID widget, Dimension *size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension *fill, [[maybe_unused]] Dimension *resize) override
case WID_ID_DROPDOWN_ORDER: {
Dimension d = GetStringBoundingBox(this->GetWidget<NWidgetCore>(widget)->widget_data);
d.width += padding.width + Window::SortButtonWidth() * 2; // Doubled since the string is centred and it also looks better.
d.height += padding.height;
*size = maxdim(*size, d);
case WID_ID_DROPDOWN_CRITERIA: {
Dimension d = {0, 0};
for (uint i = 0; IndustryDirectoryWindow::sorter_names[i] != INVALID_STRING_ID; i++) {
d = maxdim(d, GetStringBoundingBox(IndustryDirectoryWindow::sorter_names[i]));
d.width += padding.width;
@@ -631,53 +631,50 @@ public:
this->DrawWidgets();
switch (this->content.SortType()) {
case WID_NCL_CHECKBOX - WID_NCL_CHECKBOX: this->DrawSortButtonState(WID_NCL_CHECKBOX, arrow); break;
case WID_NCL_TYPE - WID_NCL_CHECKBOX: this->DrawSortButtonState(WID_NCL_TYPE, arrow); break;
case WID_NCL_NAME - WID_NCL_CHECKBOX: this->DrawSortButtonState(WID_NCL_NAME, arrow); break;
/**
* Draw/fill the matrix with the list of content to download.
* @param r The boundaries of the matrix.
*/
void DrawMatrix(const Rect &r) const
Rect checkbox = this->GetWidget<NWidgetBase>(WID_NCL_CHECKBOX)->GetCurrentRect();
Rect name = this->GetWidget<NWidgetBase>(WID_NCL_NAME)->GetCurrentRect().Shrink(WidgetDimensions::scaled.framerect);
Rect type = this->GetWidget<NWidgetBase>(WID_NCL_TYPE)->GetCurrentRect();
/* Fill the matrix with the information */
int sprite_y_offset = (this->resize.step_height - this->checkbox_size.height) / 2;
int text_y_offset = (this->resize.step_height - GetCharacterHeight(FS_NORMAL)) / 2;
auto iter = this->content.begin() + this->vscroll->GetPosition();
size_t last = this->vscroll->GetPosition() + this->vscroll->GetCapacity();
auto end = (last < this->content.size()) ? this->content.begin() + last : this->content.end();
for (/**/; iter != end; iter++) {
auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->content);
for (auto iter = first; iter != last; iter++) {
const ContentInfo *ci = *iter;
if (ci == this->selected) GfxFillRect(mr.Shrink(WidgetDimensions::scaled.bevel), PC_GREY);
SpriteID sprite;
SpriteID pal = PAL_NONE;
switch (ci->state) {
case ContentInfo::UNSELECTED: sprite = SPR_BOX_EMPTY; break;
case ContentInfo::SELECTED: sprite = SPR_BOX_CHECKED; break;
case ContentInfo::AUTOSELECTED: sprite = SPR_BOX_CHECKED; break;
case ContentInfo::ALREADY_HERE: sprite = SPR_BLOT; pal = PALETTE_TO_GREEN; break;
case ContentInfo::DOES_NOT_EXIST: sprite = SPR_BLOT; pal = PALETTE_TO_RED; break;
default: NOT_REACHED();
DrawSprite(sprite, pal, checkbox.left + (sprite == SPR_BLOT ? 3 : 2), mr.top + sprite_y_offset + (sprite == SPR_BLOT ? 0 : 1));
StringID str = STR_CONTENT_TYPE_BASE_GRAPHICS + ci->type - CONTENT_TYPE_BASE_GRAPHICS;
DrawString(type.left, type.right, mr.top + text_y_offset, str, TC_BLACK, SA_HOR_CENTER);
DrawString(name.left, name.right, mr.top + text_y_offset, ci->name, TC_BLACK);
@@ -506,52 +506,51 @@ public:
case WID_NG_MAPSIZE:
size->width += 2 * Window::SortButtonWidth(); // Make space for the arrow
SetDParamMaxValue(0, MAX_MAP_SIZE);
SetDParamMaxValue(1, MAX_MAP_SIZE);
*size = maxdim(*size, GetStringBoundingBox(STR_NETWORK_SERVER_LIST_MAP_SIZE_SHORT));
case WID_NG_DATE:
case WID_NG_YEARS:
SetDParamMaxValue(0, 5);
*size = maxdim(*size, GetStringBoundingBox(STR_JUST_INT));
case WID_NG_MATRIX: {
uint16_t y = r.top;
const int max = std::min(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), (int)this->servers.size());
for (int i = this->vscroll->GetPosition(); i < max; ++i) {
const NetworkGameList *ngl = this->servers[i];
auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->servers);
const NetworkGameList *ngl = *it;
this->DrawServerLine(ngl, y, ngl == this->server);
y += this->resize.step_height;
case WID_NG_LASTJOINED:
/* Draw the last joined server, if any */
if (this->last_joined != nullptr) this->DrawServerLine(this->last_joined, r.top, this->last_joined == this->server);
case WID_NG_DETAILS:
case WID_NG_NAME:
case WID_NG_CLIENTS:
case WID_NG_INFO:
if (widget - WID_NG_NAME == this->servers.SortType()) this->DrawSortButtonState(widget, this->servers.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
@@ -912,55 +912,55 @@ struct SpriteAlignerWindow : Window {
y = ir.Height() / 2;
DrawPixelInfo new_dpi;
if (!FillDrawPixelInfo(&new_dpi, ir)) break;
AutoRestoreBackup dpi_backup(_cur_dpi, &new_dpi);
DrawSprite(this->current_sprite, PAL_NONE, x, y, nullptr, SpriteAlignerWindow::zoom);
Rect outline = {0, 0, UnScaleByZoom(spr->width, SpriteAlignerWindow::zoom) - 1, UnScaleByZoom(spr->height, SpriteAlignerWindow::zoom) - 1};
outline = outline.Translate(x + UnScaleByZoom(spr->x_offs, SpriteAlignerWindow::zoom), y + UnScaleByZoom(spr->y_offs, SpriteAlignerWindow::zoom));
DrawRectOutline(outline.Expand(1), PC_LIGHT_BLUE, 1, 1);
if (SpriteAlignerWindow::crosshair) {
GfxDrawLine(x, 0, x, ir.Height() - 1, PC_WHITE, 1, 1);
GfxDrawLine(0, y, ir.Width() - 1, y, PC_WHITE, 1, 1);
case WID_SA_LIST: {
const NWidgetBase *nwid = this->GetWidget<NWidgetBase>(widget);
int step_size = nwid->resize_y;
std::vector<SpriteID> &list = _newgrf_debug_sprite_picker.sprites;
int max = std::min<int>(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), (uint)list.size());
const std::vector<SpriteID> &list = _newgrf_debug_sprite_picker.sprites;
Rect ir = r.Shrink(WidgetDimensions::scaled.matrix);
for (int i = this->vscroll->GetPosition(); i < max; i++) {
SetDParam(0, list[i]);
DrawString(ir, STR_JUST_COMMA, list[i] == this->current_sprite ? TC_WHITE : TC_BLACK, SA_RIGHT | SA_FORCE);
auto [first, last] = this->vscroll->GetVisibleRangeIterators(list);
SetDParam(0, *it);
DrawString(ir, STR_JUST_COMMA, *it == this->current_sprite ? TC_WHITE : TC_BLACK, SA_RIGHT | SA_FORCE);
ir.top += step_size;
case WID_SA_PREVIOUS:
do {
this->current_sprite = (this->current_sprite == 0 ? GetMaxSpriteID() : this->current_sprite) - 1;
} while (GetSpriteType(this->current_sprite) != SpriteType::Normal);
this->SetDirty();
case WID_SA_GOTO:
ShowQueryString(STR_EMPTY, STR_SPRITE_ALIGNER_GOTO_CAPTION, 7, this, CS_NUMERAL, QSF_NONE);
case WID_SA_NEXT:
this->current_sprite = (this->current_sprite + 1) % GetMaxSpriteID();
@@ -888,53 +888,52 @@ struct NewGRFWindow : public Window, New
uint top = this->active_over < active_sel_pos ? tr.top + 1 : tr.top + step_height - 2;
GfxFillRect(tr.left, top - 1, tr.right, top + 1, PC_GREY);
DrawSprite(SPR_SQUARE, pal, square_left, tr.top + square_offset_y);
if (c->error.has_value()) DrawSprite(SPR_WARNING_SIGN, 0, warning_left, tr.top + warning_offset_y);
uint txtoffset = !c->error.has_value() ? 0 : warning.width;
DrawString(text_left + (rtl ? 0 : txtoffset), text_right - (rtl ? txtoffset : 0), tr.top + offset_y, text, h ? TC_WHITE : TC_ORANGE);
tr.top += step_height;
if (i == this->active_over && this->vscroll->IsVisible(i)) { // Highlight is after the last GRF entry.
GfxFillRect(tr.left, tr.top, tr.right, tr.top + 2, PC_GREY);
case WID_NS_AVAIL_LIST: {
GfxFillRect(br, this->active_over == -2 ? PC_DARK_GREY : PC_BLACK);
Rect tr = r.Shrink(WidgetDimensions::scaled.framerect);
uint step_height = this->GetWidget<NWidgetBase>(WID_NS_AVAIL_LIST)->resize_y;
int offset_y = (step_height - GetCharacterHeight(FS_NORMAL)) / 2;
uint min_index = this->vscroll2->GetPosition();
uint max_index = std::min(min_index + this->vscroll2->GetCapacity(), (uint)this->avails.size());
for (uint i = min_index; i < max_index; i++) {
const GRFConfig *c = this->avails[i];
auto [first, last] = this->vscroll2->GetVisibleRangeIterators(this->avails);
const GRFConfig *c = *it;
bool h = (c == this->avail_sel);
const char *text = c->GetName();
if (h) GfxFillRect(br.left, tr.top, br.right, tr.top + step_height - 1, PC_DARK_BLUE);
DrawString(tr.left, tr.right, tr.top + offset_y, text, h ? TC_WHITE : TC_SILVER);
case WID_NS_NEWGRF_INFO_TITLE: {
/* Create the nice grayish rectangle at the details top. */
GfxFillRect(r.Shrink(WidgetDimensions::scaled.bevel), PC_DARK_BLUE);
DrawString(r.left, r.right, CenterBounds(r.top, r.bottom, GetCharacterHeight(FS_NORMAL)), STR_NEWGRF_SETTINGS_INFO_TITLE, TC_FROMSTRING, SA_HOR_CENTER);
case WID_NS_NEWGRF_INFO: {
const GRFConfig *selected = this->active_sel;
if (selected == nullptr) selected = this->avail_sel;
if (selected != nullptr) {
ShowNewGRFInfo(selected, r, this->show_params);
@@ -2106,55 +2105,55 @@ struct SavePresetWindow : public Window
case WID_SVP_PRESET_LIST: {
resize->height = GetCharacterHeight(FS_NORMAL);
size->height = 0;
for (uint i = 0; i < this->presets.size(); i++) {
Dimension d = GetStringBoundingBox(this->presets[i]);
size->width = std::max(size->width, d.width + padding.width);
resize->height = std::max(resize->height, d.height);
size->height = ClampU((uint)this->presets.size(), 5, 20) * resize->height + padding.height;
uint step_height = this->GetWidget<NWidgetBase>(WID_SVP_PRESET_LIST)->resize_y;
uint min_index = this->vscroll->GetPosition();
uint max_index = std::min(min_index + this->vscroll->GetCapacity(), (uint)this->presets.size());
if ((int)i == this->selected) GfxFillRect(br.left, tr.top, br.right, tr.top + step_height - 1, PC_DARK_BLUE);
auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->presets);
int row = static_cast<int>(std::distance(std::begin(this->presets), it));
if (row == this->selected) GfxFillRect(br.left, tr.top, br.right, tr.top + step_height - 1, PC_DARK_BLUE);
DrawString(tr.left, tr.right, tr.top + offset_y, this->presets[i], ((int)i == this->selected) ? TC_WHITE : TC_SILVER);
DrawString(tr.left, tr.right, tr.top + offset_y, *it, (row == this->selected) ? TC_WHITE : TC_SILVER);
auto it = this->vscroll->GetScrolledItemFromWidget(this->presets, pt.y, this, WID_SVP_PRESET_LIST);
if (it != this->presets.end()) {
this->selected = it - this->presets.begin();
this->presetname_editbox.text.Assign(*it);
this->SetWidgetDirty(WID_SVP_PRESET_LIST);
this->SetWidgetDirty(WID_SVP_EDITBOX);
case WID_SVP_CANCEL:
@@ -906,79 +906,80 @@ struct ScriptDebugWindow : public Window
* Draw a company button icon.
* @param r Rect area to draw within.
* @param widget Widget index to start.
* @param start Widget index of first company button.
void DrawWidgetCompanyButton(const Rect &r, WidgetID widget, int start) const
if (this->IsWidgetDisabled(widget)) return;
CompanyID cid = (CompanyID)(widget - start);
Dimension sprite_size = GetSpriteSize(SPR_COMPANY_ICON);
DrawCompanyIcon(cid, CenterBounds(r.left, r.right, sprite_size.width), CenterBounds(r.top, r.bottom, sprite_size.height));
* Draw the AI/GS log.
void DrawWidgetLog(const Rect &r) const
if (this->filter.script_debug_company == INVALID_COMPANY) return;
ScriptLogTypes::LogData &log = this->GetLogData();
const ScriptLogTypes::LogData &log = this->GetLogData();
if (log.empty()) return;
Rect fr = r.Shrink(WidgetDimensions::scaled.framerect);
if (!FillDrawPixelInfo(&tmp_dpi, fr)) return;
tmp_dpi.left += fr.left;
tmp_dpi.top += fr.top;
fr.left -= this->hscroll->GetPosition();
for (int i = this->vscroll->GetPosition(); this->vscroll->IsVisible(i) && (size_t)i < log.size(); i++) {
const ScriptLogTypes::LogLine &line = log[i];
auto [first, last] = this->vscroll->GetVisibleRangeIterators(log);
const ScriptLogTypes::LogLine &line = *it;
TextColour colour;
switch (line.type) {
case ScriptLogTypes::LOG_SQ_INFO: colour = TC_BLACK; break;
case ScriptLogTypes::LOG_SQ_ERROR: colour = TC_WHITE; break;
case ScriptLogTypes::LOG_INFO: colour = TC_BLACK; break;
case ScriptLogTypes::LOG_WARNING: colour = TC_YELLOW; break;
case ScriptLogTypes::LOG_ERROR: colour = TC_RED; break;
default: colour = TC_BLACK; break;
/* Check if the current line should be highlighted */
if (i == this->highlight_row) {
if (std::distance(std::begin(log), it) == this->highlight_row) {
fr.bottom = fr.top + this->resize.step_height - 1;
GfxFillRect(fr, PC_BLACK);
if (colour == TC_BLACK) colour = TC_WHITE; // Make black text readable by inverting it to white.
DrawString(fr, line.text, colour, SA_LEFT | SA_FORCE);
fr.top += this->resize.step_height;
* Update the scrollbar and scroll position of the log panel.
void UpdateLogScroll()
this->SetWidgetsDisabledState(this->filter.script_debug_company == INVALID_COMPANY, WID_SCRD_VSCROLLBAR, WID_SCRD_HSCROLLBAR);
int scroll_count = (int)log.size();
if (this->vscroll->GetCount() != scroll_count) {
this->vscroll->SetCount(scroll_count);
@@ -190,51 +190,51 @@ struct SignListWindow : Window, SignList
if (!this->IsShaded() && this->signs.NeedRebuild()) this->BuildSortSignList();
case WID_SIL_LIST: {
uint text_offset_y = (this->resize.step_height - GetCharacterHeight(FS_NORMAL) + 1) / 2;
/* No signs? */
if (this->vscroll->GetCount() == 0) {
DrawString(tr.left, tr.right, tr.top + text_offset_y, STR_STATION_LIST_NONE);
Dimension d = GetSpriteSize(SPR_COMPANY_ICON);
int sprite_offset_y = (this->resize.step_height - d.height + 1) / 2;
uint icon_left = rtl ? tr.right - this->text_offset : tr.left;
tr = tr.Indent(this->text_offset, rtl);
/* At least one sign available. */
for (uint16_t i = this->vscroll->GetPosition(); this->vscroll->IsVisible(i) && i < this->vscroll->GetCount(); i++)
const Sign *si = this->signs[i];
auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->signs);
const Sign *si = *it;
if (si->owner != OWNER_NONE) DrawCompanyIcon(si->owner, icon_left, tr.top + sprite_offset_y);
SetDParam(0, si->index);
DrawString(tr.left, tr.right, tr.top + text_offset_y, STR_SIGN_NAME, TC_YELLOW);
tr.top += this->resize.step_height;
void SetStringParameters(WidgetID widget) const override
if (widget == WID_SIL_CAPTION) SetDParam(0, this->vscroll->GetCount());
auto it = this->vscroll->GetScrolledItemFromWidget(this->signs, pt.y, this, WID_SIL_LIST, WidgetDimensions::scaled.framerect.top);
if (it == this->signs.end()) return;
@@ -434,58 +434,58 @@ public:
/* Approximately match original 16 pixel wide rating bars by multiplying string width by 1.6 */
this->rating_width = this->rating_width * 16 / 10;
void OnPaint() override
this->BuildStationsList((Owner)this->window_number);
this->SortStationsList();
case WID_STL_SORTBY:
/* draw arrow pointing up/down for ascending/descending sorting */
this->DrawSortButtonState(WID_STL_SORTBY, this->stations.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
case WID_STL_LIST: {
size_t max = std::min<size_t>(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->stations.size());
uint line_height = this->GetWidget<NWidgetBase>(widget)->resize_y;
/* Spacing between station name and first rating graph. */
int text_spacing = WidgetDimensions::scaled.hsep_wide;
/* Spacing between additional rating graphs. */
int rating_spacing = WidgetDimensions::scaled.hsep_normal;
for (size_t i = this->vscroll->GetPosition(); i < max; ++i) { // do until max number of stations of owner
const Station *st = this->stations[i];
auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->stations);
const Station *st = *it;
assert(st->xy != INVALID_TILE);
/* Do not do the complex check HasStationInUse here, it may be even false
* when the order had been removed and the station list hasn't been removed yet */
assert(st->owner == owner || st->owner == OWNER_NONE);
SetDParam(0, st->index);
SetDParam(1, st->facilities);
int x = DrawString(tr.left, tr.right, tr.top + (line_height - GetCharacterHeight(FS_NORMAL)) / 2, STR_STATION_LIST_STATION);
x += rtl ? -text_spacing : text_spacing;
/* show cargo waiting and station ratings */
CargoID cid = cs->Index();
if (st->goods[cid].HasRating()) {
/* For RTL we work in exactly the opposite direction. So
* decrement the space needed first, then draw to the left
* instead of drawing to the left and then incrementing
* the space. */
if (rtl) {
x -= rating_width + rating_spacing;
if (x < tr.left) break;
StationsWndShowStationRating(x, x + rating_width, tr.top, cid, st->goods[cid].cargo.TotalCount(), st->goods[cid].rating);
@@ -2299,53 +2299,54 @@ struct SelectStationWindow : Window {
if (widget != WID_JS_PANEL) return;
/* Determine the widest string */
Dimension d = GetStringBoundingBox(T::EXPECTED_FACIL == FACIL_WAYPOINT ? STR_JOIN_WAYPOINT_CREATE_SPLITTED_WAYPOINT : STR_JOIN_STATION_CREATE_SPLITTED_STATION);
for (const auto &station : _stations_nearby_list) {
if (station == NEW_STATION) continue;
const T *st = T::Get(station);
d = maxdim(d, GetStringBoundingBox(T::EXPECTED_FACIL == FACIL_WAYPOINT ? STR_STATION_LIST_WAYPOINT : STR_STATION_LIST_STATION));
resize->height = d.height;
d.height *= 5;
*size = d;
for (uint i = this->vscroll->GetPosition(); i < _stations_nearby_list.size(); ++i, tr.top += this->resize.step_height) {
if (_stations_nearby_list[i] == NEW_STATION) {
auto [first, last] = this->vscroll->GetVisibleRangeIterators(_stations_nearby_list);
for (auto it = first; it != last; ++it, tr.top += this->resize.step_height) {
if (*it == NEW_STATION) {
DrawString(tr, T::EXPECTED_FACIL == FACIL_WAYPOINT ? STR_JOIN_WAYPOINT_CREATE_SPLITTED_WAYPOINT : STR_JOIN_STATION_CREATE_SPLITTED_STATION);
const T *st = T::Get(_stations_nearby_list[i]);
const T *st = T::Get(*it);
DrawString(tr, T::EXPECTED_FACIL == FACIL_WAYPOINT ? STR_STATION_LIST_WAYPOINT : STR_STATION_LIST_STATION);
auto it = this->vscroll->GetScrolledItemFromWidget(_stations_nearby_list, pt.y, this, WID_JS_PANEL, WidgetDimensions::scaled.framerect.top);
if (it == _stations_nearby_list.end()) return;
/* Execute stored Command */
this->select_station_proc(false, *it);
/* Close Window; this might cause double frees! */
CloseWindowById(WC_SELECT_STATION, 0);
void OnRealtimeTick([[maybe_unused]] uint delta_ms) override
@@ -819,81 +819,80 @@ public:
case WID_TD_SORT_CRITERIA:
SetDParam(0, TownDirectoryWindow::sorter_names[this->towns.SortType()]);
* Get the string to draw the town name.
* @param t Town to draw.
* @return The string to use.
static StringID GetTownString(const Town *t)
return t->larger_town ? STR_TOWN_DIRECTORY_CITY : STR_TOWN_DIRECTORY_TOWN;
case WID_TD_SORT_ORDER:
this->DrawSortButtonState(widget, this->towns.IsDescSortOrder() ? SBS_DOWN : SBS_UP);
case WID_TD_LIST: {
if (this->towns.empty()) { // No towns available.
DrawString(tr, STR_TOWN_DIRECTORY_NONE);
/* At least one town available. */
Dimension icon_size = GetSpriteSize(SPR_TOWN_RATING_GOOD);
int icon_x = tr.WithWidth(icon_size.width, rtl).left;
tr = tr.Indent(icon_size.width + WidgetDimensions::scaled.hsep_normal, rtl);
for (uint i = this->vscroll->GetPosition(); i < this->towns.size(); i++) {
const Town *t = this->towns[i];
auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->towns);
const Town *t = *it;
assert(t->xy != INVALID_TILE);
/* Draw rating icon. */
if (_game_mode == GM_EDITOR || !HasBit(t->have_ratings, _local_company)) {
DrawSprite(SPR_TOWN_RATING_NA, PAL_NONE, icon_x, tr.top + (this->resize.step_height - icon_size.height) / 2);
SpriteID icon = SPR_TOWN_RATING_APALLING;
if (t->ratings[_local_company] > RATING_VERYPOOR) icon = SPR_TOWN_RATING_MEDIOCRE;
if (t->ratings[_local_company] > RATING_GOOD) icon = SPR_TOWN_RATING_GOOD;
DrawSprite(icon, PAL_NONE, icon_x, tr.top + (this->resize.step_height - icon_size.height) / 2);
SetDParam(0, t->index);
SetDParam(1, t->cache.population);
DrawString(tr.left, tr.right, tr.top + (this->resize.step_height - GetCharacterHeight(FS_NORMAL)) / 2, GetTownString(t));
if (++n == this->vscroll->GetCapacity()) break; // max number of towns in 1 window
case WID_TD_SORT_ORDER: {
case WID_TD_SORT_CRITERIA: {
for (uint i = 0; TownDirectoryWindow::sorter_names[i] != INVALID_STRING_ID; i++) {
d = maxdim(d, GetStringBoundingBox(TownDirectoryWindow::sorter_names[i]));
@@ -1683,52 +1683,51 @@ uint GetVehicleListHeight(VehicleType ty
* Draw all the vehicle list items.
* @param selected_vehicle The vehicle that is to be highlighted.
* @param line_height Height of a single item line.
* @param r Rectangle with edge positions of the matrix widget.
void BaseVehicleListWindow::DrawVehicleListItems(VehicleID selected_vehicle, int line_height, const Rect &r) const
Rect ir = r.WithHeight(line_height).Shrink(WidgetDimensions::scaled.matrix, RectPadding::zero);
Dimension profit = GetSpriteSize(SPR_PROFIT_LOT);
int text_offset = std::max<int>(profit.width, GetDigitWidth() * this->unitnumber_digits) + WidgetDimensions::scaled.hsep_normal;
Rect tr = ir.Indent(text_offset, rtl);
bool show_orderlist = this->vli.vtype >= VEH_SHIP;
Rect olr = ir.Indent(std::max(ScaleGUITrad(100) + text_offset, ir.Width() / 2), rtl);
int image_left = (rtl && show_orderlist) ? olr.right : tr.left;
int image_right = (!rtl && show_orderlist) ? olr.left : tr.right;
int vehicle_button_x = rtl ? ir.right - profit.width : ir.left;
uint max = static_cast<uint>(std::min<size_t>(this->vscroll->GetPosition() + this->vscroll->GetCapacity(), this->vehgroups.size()));
for (uint i = this->vscroll->GetPosition(); i < max; ++i) {
const GUIVehicleGroup &vehgroup = this->vehgroups[i];
const GUIVehicleGroup &vehgroup = *it;
SetDParam(0, vehgroup.GetDisplayProfitThisYear());
SetDParam(1, vehgroup.GetDisplayProfitLastYear());
DrawString(tr.left, tr.right, ir.bottom - GetCharacterHeight(FS_SMALL) - WidgetDimensions::scaled.framerect.bottom,
TimerGameEconomy::UsingWallclockUnits() ? STR_VEHICLE_LIST_PROFIT_THIS_PERIOD_LAST_PERIOD : STR_VEHICLE_LIST_PROFIT_THIS_YEAR_LAST_YEAR);
DrawVehicleProfitButton(vehgroup.GetOldestVehicleAge(), vehgroup.GetDisplayProfitLastYear(), vehgroup.NumVehicles(), vehicle_button_x, ir.top + GetCharacterHeight(FS_NORMAL) + WidgetDimensions::scaled.vsep_normal);
switch (this->grouping) {
case GB_NONE: {
const Vehicle *v = vehgroup.GetSingleVehicle();
if (HasBit(v->vehicle_flags, VF_PATHFINDER_LOST)) {
DrawSprite(SPR_WARNING_SIGN, PAL_NONE, vehicle_button_x, ir.top + GetCharacterHeight(FS_NORMAL) + WidgetDimensions::scaled.vsep_normal + profit.height);
DrawVehicleImage(v, {image_left, ir.top, image_right, ir.bottom}, selected_vehicle, EIT_IN_LIST, 0);
if (_settings_client.gui.show_cargo_in_vehicle_lists) {
/* Get the cargoes the vehicle can carry */
CargoTypes vehicle_cargoes = 0;
for (auto u = v; u != nullptr; u = u->Next()) {
if (u->cargo_cap == 0) continue;
Status change: