1
0
mirror of https://github.com/OpenTTD/OpenTTD synced 2026-01-30 07:34:37 +01:00

Codechange: Rework network client list buttons.

Instead of calculating and storing which buttons to display for each
line and then further calculating what each line should be when
displaying it, store everything on one pass.

This simplifies/deduplicates logic.
This commit is contained in:
Peter Nelson
2025-09-25 08:45:24 +01:00
committed by Peter Nelson
parent 37f737417c
commit cb1c2409e6

View File

@@ -1381,6 +1381,159 @@ public:
using CompanyButton = Button<CompanyID>;
using ClientButton = Button<ClientID>;
/**
* Base interface for a network client list line.
*/
class ButtonLine {
public:
std::vector<std::unique_ptr<ButtonCommon>> buttons{}; ///< Buttons for this line.
virtual ~ButtonLine() = default;
/**
* Draw the button line.
* @param r Rect to draw within.
*/
virtual void Draw(Rect r) const = 0;
template <typename T, typename...TArgs>
T &AddButton(TArgs &&... args)
{
auto &button = this->buttons.emplace_back(std::make_unique<T>(std::forward<TArgs &&>(args)...));
return static_cast<T &>(*button);
}
/**
* Get the button at a given point on the line.
* @param r Rect of line.
* @param pt Point of interest.
* @return Button at point, or \c nullptr if button isn't pressed.
*/
ButtonCommon *GetButton(Rect r, const Point &pt) const
{
bool rtl = _current_text_dir == TD_RTL;
for (auto &button : this->buttons) {
if (r.WithWidth(button->width, !rtl).Contains(pt)) return button.get();
r = r.Indent(button->width + WidgetDimensions::scaled.hsep_normal, !rtl);
}
return nullptr;
}
/**
* Get tooptip for a given point on the line.
* @param r Rect of line.
* @param pt Point of interest.
* @return EncodedString of tooltip, or \c std::nullopt if none.
*/
virtual std::optional<EncodedString> GetTooltip(Rect r, const Point &pt) const
{
ButtonCommon *button = this->GetButton(r, pt);
if (button == nullptr) return {};
return GetEncodedString(button->tooltip);
}
protected:
/**
* Draw the buttons for this line.
* @param r Rect to draw within.
* @return Rect of remaining space.
*/
Rect DrawButtons(Rect r) const
{
bool rtl = _current_text_dir == TD_RTL;
for (auto &button : buttons) {
Rect br = r.CentreTo(r.Width(), button->height).WithWidth(button->width, !rtl);
DrawFrameRect(br, button->colour, {});
DrawSpriteIgnorePadding(button->sprite, PAL_NONE, br, SA_CENTER);
if (button->disabled) {
GfxFillRect(br.Shrink(WidgetDimensions::scaled.bevel), GetColourGradient(button->colour, SHADE_DARKER), FILLRECT_CHECKER);
}
r = r.Indent(button->width + WidgetDimensions::scaled.hsep_normal, !rtl);
}
return r;
}
};
class CompanyButtonLine : public ButtonLine {
public:
CompanyButtonLine(CompanyID company_id) : company_id(company_id) {}
void Draw(Rect r) const override
{
bool rtl = _current_text_dir == TD_RTL;
r = this->DrawButtons(r);
Dimension d = GetSpriteSize(SPR_COMPANY_ICON);
PaletteID pal = Company::IsValidID(this->company_id) ? GetCompanyPalette(this->company_id) : PALETTE_TO_GREY;
DrawSpriteIgnorePadding(SPR_COMPANY_ICON, pal, r.WithWidth(d.width, rtl), SA_CENTER);
Rect tr = r.CentreTo(r.Width(), GetCharacterHeight(FS_NORMAL)).Indent(d.width + WidgetDimensions::scaled.hsep_normal, rtl);
if (this->company_id == COMPANY_SPECTATOR) {
DrawString(tr, STR_NETWORK_CLIENT_LIST_SPECTATORS, TC_SILVER);
} else if (this->company_id == COMPANY_NEW_COMPANY) {
DrawString(tr, STR_NETWORK_CLIENT_LIST_NEW_COMPANY, TC_WHITE);
} else {
DrawString(tr, GetString(STR_COMPANY_NAME, this->company_id, this->company_id), TC_SILVER);
}
};
private:
CompanyID company_id;
};
class ClientButtonLine : public ButtonLine {
public:
ClientButtonLine(ClientPoolID client_pool_id) : client_pool_id(client_pool_id) {}
void Draw(Rect r) const override
{
const NetworkClientInfo *ci = NetworkClientInfo::GetIfValid(this->client_pool_id);
if (ci == nullptr) return;
bool rtl = _current_text_dir == TD_RTL;
r = this->DrawButtons(r);
Rect tr = r.CentreTo(r.Width(), GetCharacterHeight(FS_NORMAL));
SpriteID player_icon = 0;
if (ci->client_id == _network_own_client_id) {
player_icon = SPR_PLAYER_SELF;
} else if (ci->client_id == CLIENT_ID_SERVER) {
player_icon = SPR_PLAYER_HOST;
}
if (player_icon != 0) {
Dimension d = GetSpriteSize(player_icon);
DrawSpriteIgnorePadding(player_icon, PALETTE_TO_GREY, r.WithWidth(d.width, rtl), SA_CENTER);
tr = tr.Indent(d.width + WidgetDimensions::scaled.hsep_normal, rtl);
}
DrawString(tr, GetString(STR_JUST_RAW_STRING, ci->client_name), TC_BLACK);
}
std::optional<EncodedString> GetTooltip(Rect r, const Point &pt) const override
{
bool rtl = _current_text_dir == TD_RTL;
Dimension d = GetSpriteSize(SPR_PLAYER_SELF);
if (r.WithWidth(d.width, rtl).Contains(pt)) {
const NetworkClientInfo *ci = NetworkClientInfo::GetIfValid(this->client_pool_id);
if (ci != nullptr) {
if (ci->client_id == _network_own_client_id) {
return GetEncodedString(STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP);
} else if (ci->client_id == CLIENT_ID_SERVER) {
return GetEncodedString(STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP);
}
}
}
return this->ButtonLine::GetTooltip(r, pt);
}
private:
ClientPoolID client_pool_id;
};
/**
* Main handle for clientlist
*/
@@ -1393,12 +1546,9 @@ private:
Scrollbar *vscroll = nullptr; ///< Vertical scrollbar of this window.
uint line_height = 0; ///< Current lineheight of each entry in the matrix.
uint line_count = 0; ///< Amount of lines in the matrix.
int hover_index = -1; ///< Index of the current line we are hovering over, or -1 if none.
int player_self_index = -1; ///< The line the current player is on.
int player_host_index = -1; ///< The line the host is on.
std::map<uint, std::vector<std::unique_ptr<ButtonCommon>>> buttons{}; ///< Per line which buttons are available.
std::vector<std::unique_ptr<ButtonLine>> buttons{}; ///< Per line which buttons are available.
/**
* Chat button on a Company is clicked.
@@ -1504,34 +1654,24 @@ private:
*/
void RebuildListCompany(CompanyID company_id, CompanyID client_playas, bool can_join_company)
{
ButtonCommon *chat_button = new CompanyButton(SPR_CHAT, company_id == COMPANY_SPECTATOR ? STR_NETWORK_CLIENT_LIST_CHAT_SPECTATOR_TOOLTIP : STR_NETWORK_CLIENT_LIST_CHAT_COMPANY_TOOLTIP, COLOUR_ORANGE, company_id, &NetworkClientListWindow::OnClickCompanyChat);
if (_network_server) this->buttons[line_count].push_back(std::make_unique<CompanyButton>(SPR_ADMIN, STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_TOOLTIP, COLOUR_RED, company_id, &NetworkClientListWindow::OnClickCompanyAdmin, company_id == COMPANY_SPECTATOR));
this->buttons[line_count].emplace_back(chat_button);
if (can_join_company) this->buttons[line_count].push_back(std::make_unique<CompanyButton>(SPR_JOIN, STR_NETWORK_CLIENT_LIST_JOIN_TOOLTIP, COLOUR_ORANGE, company_id, &NetworkClientListWindow::OnClickCompanyJoin, company_id != COMPANY_SPECTATOR && Company::Get(company_id)->is_ai));
this->line_count += 1;
ButtonLine &company_line = *this->buttons.emplace_back(std::make_unique<CompanyButtonLine>(company_id));
if (_network_server) company_line.AddButton<CompanyButton>(SPR_ADMIN, STR_NETWORK_CLIENT_LIST_ADMIN_COMPANY_TOOLTIP, COLOUR_RED, company_id, &NetworkClientListWindow::OnClickCompanyAdmin, company_id == COMPANY_SPECTATOR);
ButtonCommon &chat_button = company_line.AddButton<CompanyButton>(SPR_CHAT, company_id == COMPANY_SPECTATOR ? STR_NETWORK_CLIENT_LIST_CHAT_SPECTATOR_TOOLTIP : STR_NETWORK_CLIENT_LIST_CHAT_COMPANY_TOOLTIP, COLOUR_ORANGE, company_id, &NetworkClientListWindow::OnClickCompanyChat);
if (can_join_company) company_line.AddButton<CompanyButton>(SPR_JOIN, STR_NETWORK_CLIENT_LIST_JOIN_TOOLTIP, COLOUR_ORANGE, company_id, &NetworkClientListWindow::OnClickCompanyJoin, company_id != COMPANY_SPECTATOR && Company::Get(company_id)->is_ai);
bool has_players = false;
for (const NetworkClientInfo *ci : NetworkClientInfo::Iterate()) {
if (ci->client_playas != company_id) continue;
has_players = true;
if (_network_server) this->buttons[line_count].push_back(std::make_unique<ClientButton>(SPR_ADMIN, STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_TOOLTIP, COLOUR_RED, ci->client_id, &NetworkClientListWindow::OnClickClientAdmin, _network_own_client_id == ci->client_id));
if (_network_own_client_id != ci->client_id) this->buttons[line_count].push_back(std::make_unique<ClientButton>(SPR_CHAT, STR_NETWORK_CLIENT_LIST_CHAT_CLIENT_TOOLTIP, COLOUR_ORANGE, ci->client_id, &NetworkClientListWindow::OnClickClientChat));
if (_network_own_client_id != ci->client_id && client_playas != COMPANY_SPECTATOR && !ci->CanJoinCompany(client_playas)) this->buttons[line_count].push_back(std::make_unique<ClientButton>(SPR_JOIN, STR_NETWORK_CLIENT_LIST_COMPANY_AUTHORIZE_TOOLTIP, COLOUR_GREEN, ci->client_id, &NetworkClientListWindow::OnClickClientAuthorize));
if (ci->client_id == _network_own_client_id) {
this->player_self_index = this->line_count;
} else if (ci->client_id == CLIENT_ID_SERVER) {
this->player_host_index = this->line_count;
}
this->line_count += 1;
ButtonLine &line = *this->buttons.emplace_back(std::make_unique<ClientButtonLine>(ci->index));
if (_network_server) line.AddButton<ClientButton>(SPR_ADMIN, STR_NETWORK_CLIENT_LIST_ADMIN_CLIENT_TOOLTIP, COLOUR_RED, ci->client_id, &NetworkClientListWindow::OnClickClientAdmin, _network_own_client_id == ci->client_id);
if (_network_own_client_id != ci->client_id) line.AddButton<ClientButton>(SPR_CHAT, STR_NETWORK_CLIENT_LIST_CHAT_CLIENT_TOOLTIP, COLOUR_ORANGE, ci->client_id, &NetworkClientListWindow::OnClickClientChat);
if (_network_own_client_id != ci->client_id && client_playas != COMPANY_SPECTATOR && !ci->CanJoinCompany(client_playas)) line.AddButton<ClientButton>(SPR_JOIN, STR_NETWORK_CLIENT_LIST_COMPANY_AUTHORIZE_TOOLTIP, COLOUR_GREEN, ci->client_id, &NetworkClientListWindow::OnClickClientAuthorize);
}
/* Disable the chat button when there are players in this company. */
chat_button->disabled = !has_players;
chat_button.disabled = !has_players;
}
/**
@@ -1543,14 +1683,11 @@ private:
CompanyID client_playas = own_ci == nullptr ? COMPANY_SPECTATOR : own_ci->client_playas;
this->buttons.clear();
this->line_count = 0;
this->player_host_index = -1;
this->player_self_index = -1;
/* As spectator, show a line to create a new company. */
if (client_playas == COMPANY_SPECTATOR && !NetworkMaxCompaniesReached()) {
this->buttons[line_count].push_back(std::make_unique<CompanyButton>(SPR_JOIN, STR_NETWORK_CLIENT_LIST_NEW_COMPANY_TOOLTIP, COLOUR_ORANGE, COMPANY_SPECTATOR, &NetworkClientListWindow::OnClickCompanyNew));
this->line_count += 1;
ButtonLine &line = *this->buttons.emplace_back(std::make_unique<CompanyButtonLine>(COMPANY_NEW_COMPANY));
line.AddButton<CompanyButton>(SPR_JOIN, STR_NETWORK_CLIENT_LIST_NEW_COMPANY_TOOLTIP, COLOUR_ORANGE, COMPANY_SPECTATOR, &NetworkClientListWindow::OnClickCompanyNew);
}
if (client_playas != COMPANY_SPECTATOR) {
@@ -1567,40 +1704,7 @@ private:
/* Spectators */
this->RebuildListCompany(COMPANY_SPECTATOR, client_playas, client_playas != COMPANY_SPECTATOR);
this->vscroll->SetCount(this->line_count);
}
/**
* Get the button at a specific point on the WID_CL_MATRIX.
* @param pt The point to look for a button.
* @return The button or a nullptr if there was none.
*/
ButtonCommon *GetButtonAtPoint(Point pt)
{
uint index = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_CL_MATRIX);
Rect matrix = this->GetWidget<NWidgetBase>(WID_CL_MATRIX)->GetCurrentRect().Shrink(WidgetDimensions::scaled.framerect);
bool rtl = _current_text_dir == TD_RTL;
uint x = rtl ? matrix.left : matrix.right;
/* Find the buttons for this row. */
auto button_find = this->buttons.find(index);
if (button_find == this->buttons.end()) return nullptr;
/* Check if we want to display a tooltip for any of the buttons. */
for (auto &button : button_find->second) {
uint left = rtl ? x : x - button->width;
uint right = rtl ? x + button->width : x;
if (IsInsideMM(pt.x, left, right)) {
return button.get();
}
int width = button->width + WidgetDimensions::scaled.framerect.Horizontal();
x += rtl ? width : -width;
}
return nullptr;
this->vscroll->SetCount(this->buttons.size());
}
public:
@@ -1614,7 +1718,7 @@ public:
void OnInit() override
{
RebuildList();
this->RebuildList();
}
void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
@@ -1718,7 +1822,11 @@ public:
break;
case WID_CL_MATRIX: {
ButtonCommon *button = this->GetButtonAtPoint(pt);
auto it = this->vscroll->GetScrolledItemFromWidget(this->buttons, pt.y, this, WID_CL_MATRIX);
if (it == std::end(this->buttons)) break;
Rect r = this->GetWidget<NWidgetBase>(WID_CL_MATRIX)->GetCurrentRect().Shrink(WidgetDimensions::scaled.framerect);
ButtonCommon *button = (*it)->GetButton(r, pt);
if (button == nullptr) break;
button->OnClick(this, pt);
@@ -1731,34 +1839,14 @@ public:
{
switch (widget) {
case WID_CL_MATRIX: {
int index = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_CL_MATRIX);
auto it = this->vscroll->GetScrolledItemFromWidget(this->buttons, pt.y, this, WID_CL_MATRIX);
if (it == std::end(this->buttons)) break;
bool rtl = _current_text_dir == TD_RTL;
Rect matrix = this->GetWidget<NWidgetBase>(WID_CL_MATRIX)->GetCurrentRect().Shrink(WidgetDimensions::scaled.framerect);
Rect r = this->GetWidget<NWidgetBase>(WID_CL_MATRIX)->GetCurrentRect().Shrink(WidgetDimensions::scaled.framerect);
auto tooltip = (*it)->GetTooltip(r, pt);
if (!tooltip.has_value()) break;
Dimension d = GetSpriteSize(SPR_COMPANY_ICON);
uint text_left = matrix.left + (rtl ? 0 : d.width + WidgetDimensions::scaled.hsep_wide);
uint text_right = matrix.right - (rtl ? d.width + WidgetDimensions::scaled.hsep_wide : 0);
Dimension d2 = GetSpriteSize(SPR_PLAYER_SELF);
uint offset_x = WidgetDimensions::scaled.hsep_indent - d2.width - ScaleGUITrad(3);
uint player_icon_x = rtl ? text_right - offset_x - d2.width : text_left + offset_x;
if (IsInsideMM(pt.x, player_icon_x, player_icon_x + d2.width)) {
if (index == this->player_self_index) {
GuiShowTooltips(this, GetEncodedString(STR_NETWORK_CLIENT_LIST_PLAYER_ICON_SELF_TOOLTIP), close_cond);
return true;
} else if (index == this->player_host_index) {
GuiShowTooltips(this, GetEncodedString(STR_NETWORK_CLIENT_LIST_PLAYER_ICON_HOST_TOOLTIP), close_cond);
return true;
}
}
ButtonCommon *button = this->GetButtonAtPoint(pt);
if (button == nullptr) return false;
GuiShowTooltips(this, GetEncodedString(button->tooltip), close_cond);
GuiShowTooltips(this, std::move(*tooltip), close_cond);
return true;
};
}
@@ -1852,154 +1940,23 @@ public:
}
}
/**
* Draw the buttons for a single line in the matrix.
*
* The x-position in RTL is the most left or otherwise the most right pixel
* we can draw the buttons from.
*
* @param x The x-position to start with the buttons. Updated during this function.
* @param y The y-position to start with the buttons.
* @param buttons The buttons to draw.
*/
void DrawButtons(int &x, uint y, const std::vector<std::unique_ptr<ButtonCommon>> &buttons) const
{
Rect r;
for (auto &button : buttons) {
bool rtl = _current_text_dir == TD_RTL;
int offset = (this->line_height - button->height) / 2;
r.left = rtl ? x : x - button->width + 1;
r.right = rtl ? x + button->width - 1 : x;
r.top = y + offset;
r.bottom = r.top + button->height - 1;
DrawFrameRect(r, button->colour, {});
DrawSprite(button->sprite, PAL_NONE, r.left + WidgetDimensions::scaled.framerect.left, r.top + WidgetDimensions::scaled.framerect.top);
if (button->disabled) {
GfxFillRect(r.Shrink(WidgetDimensions::scaled.bevel), GetColourGradient(button->colour, SHADE_DARKER), FILLRECT_CHECKER);
}
int width = button->width + WidgetDimensions::scaled.hsep_normal;
x += rtl ? width : -width;
}
}
/**
* Draw a company and its clients on the matrix.
* @param company_id The company to draw.
* @param r The rect to draw within.
* @param line The Nth line we are drawing. Updated during this function.
*/
void DrawCompany(CompanyID company_id, const Rect &r, uint &line) const
{
bool rtl = _current_text_dir == TD_RTL;
int text_y_offset = CentreBounds(0, this->line_height, GetCharacterHeight(FS_NORMAL));
Dimension d = GetSpriteSize(SPR_COMPANY_ICON);
int offset = CentreBounds(0, this->line_height, d.height);
uint line_start = this->vscroll->GetPosition();
uint line_end = line_start + this->vscroll->GetCapacity();
uint y = r.top + (this->line_height * (line - line_start));
/* Draw the company line (if in range of scrollbar). */
if (IsInsideMM(line, line_start, line_end)) {
int icon_left = r.WithWidth(d.width, rtl).left;
Rect tr = r.Indent(d.width + WidgetDimensions::scaled.hsep_normal, rtl);
int &x = rtl ? tr.left : tr.right;
/* If there are buttons for this company, draw them. */
auto button_find = this->buttons.find(line);
if (button_find != this->buttons.end()) {
this->DrawButtons(x, y, button_find->second);
}
if (company_id == COMPANY_SPECTATOR) {
DrawSprite(SPR_COMPANY_ICON, PALETTE_TO_GREY, icon_left, y + offset);
DrawString(tr.left, tr.right, y + text_y_offset, STR_NETWORK_CLIENT_LIST_SPECTATORS, TC_SILVER);
} else if (company_id == COMPANY_NEW_COMPANY) {
DrawSprite(SPR_COMPANY_ICON, PALETTE_TO_GREY, icon_left, y + offset);
DrawString(tr.left, tr.right, y + text_y_offset, STR_NETWORK_CLIENT_LIST_NEW_COMPANY, TC_WHITE);
} else {
DrawCompanyIcon(company_id, icon_left, y + offset);
DrawString(tr.left, tr.right, y + text_y_offset, GetString(STR_COMPANY_NAME, company_id, company_id), TC_SILVER);
}
}
y += this->line_height;
line++;
for (const NetworkClientInfo *ci : NetworkClientInfo::Iterate()) {
if (ci->client_playas != company_id) continue;
/* Draw the player line (if in range of scrollbar). */
if (IsInsideMM(line, line_start, line_end)) {
Rect tr = r.Indent(WidgetDimensions::scaled.hsep_indent, rtl);
/* If there are buttons for this client, draw them. */
auto button_find = this->buttons.find(line);
if (button_find != this->buttons.end()) {
int &x = rtl ? tr.left : tr.right;
this->DrawButtons(x, y, button_find->second);
}
SpriteID player_icon = 0;
if (ci->client_id == _network_own_client_id) {
player_icon = SPR_PLAYER_SELF;
} else if (ci->client_id == CLIENT_ID_SERVER) {
player_icon = SPR_PLAYER_HOST;
}
if (player_icon != 0) {
Dimension d2 = GetSpriteSize(player_icon);
int offset_y = CentreBounds(0, this->line_height, d2.height);
DrawSprite(player_icon, PALETTE_TO_GREY, rtl ? tr.right - d2.width : tr.left, y + offset_y);
tr = tr.Indent(d2.width + WidgetDimensions::scaled.hsep_normal, rtl);
}
DrawString(tr.left, tr.right, y + text_y_offset, GetString(STR_JUST_RAW_STRING, ci->client_name), TC_BLACK);
}
y += this->line_height;
line++;
}
}
void DrawWidget(const Rect &r, WidgetID widget) const override
{
switch (widget) {
case WID_CL_MATRIX: {
Rect ir = r.Shrink(WidgetDimensions::scaled.framerect, RectPadding::zero);
uint line = 0;
Rect ir = r.WithHeight(this->line_height).Shrink(WidgetDimensions::scaled.framerect, RectPadding::zero);
if (this->hover_index >= 0) {
Rect br = r.WithHeight(this->line_height).Translate(0, this->hover_index * this->line_height);
GfxFillRect(br.Shrink(WidgetDimensions::scaled.bevel), GREY_SCALE(9));
}
NetworkClientInfo *own_ci = NetworkClientInfo::GetByClientID(_network_own_client_id);
CompanyID client_playas = own_ci == nullptr ? COMPANY_SPECTATOR : own_ci->client_playas;
if (client_playas == COMPANY_SPECTATOR && !NetworkMaxCompaniesReached()) {
this->DrawCompany(COMPANY_NEW_COMPANY, ir, line);
auto [first, last] = this->vscroll->GetVisibleRangeIterators(this->buttons);
for (auto it = first; it != last; ++it) {
(*it)->Draw(ir);
ir = ir.Translate(0, this->line_height);
}
if (client_playas != COMPANY_SPECTATOR) {
this->DrawCompany(client_playas, ir, line);
}
for (const Company *c : Company::Iterate()) {
if (client_playas == c->index) continue;
this->DrawCompany(c->index, ir, line);
}
/* Spectators */
this->DrawCompany(COMPANY_SPECTATOR, ir, line);
break;
}
}