mirror of
https://github.com/ddnet/ddnet.git
synced 2024-10-21 08:18:18 +00:00
8d65e0ac34
- Use `IGraphics::CORNER_NONE` instead of `0`. - Remove redundant usage of `Localize` for an already localized string. - Remove redundant `HSplitBottom` with zero size.
1723 lines
64 KiB
C++
1723 lines
64 KiB
C++
/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
|
||
/* If you are missing that file, acquire a complete release at teeworlds.com. */
|
||
#include <base/log.h>
|
||
|
||
#include <engine/favorites.h>
|
||
#include <engine/friends.h>
|
||
#include <engine/keys.h>
|
||
#include <engine/serverbrowser.h>
|
||
#include <engine/shared/config.h>
|
||
#include <engine/shared/localization.h>
|
||
#include <engine/textrender.h>
|
||
|
||
#include <game/client/animstate.h>
|
||
#include <game/client/components/console.h>
|
||
#include <game/client/components/countryflags.h>
|
||
#include <game/client/gameclient.h>
|
||
#include <game/client/render.h>
|
||
#include <game/client/ui.h>
|
||
#include <game/client/ui_listbox.h>
|
||
#include <game/localization.h>
|
||
|
||
#include "menus.h"
|
||
|
||
using namespace FontIcons;
|
||
|
||
static const int gs_OffsetColFlagLock = 2;
|
||
static const int gs_OffsetColFav = gs_OffsetColFlagLock + 3;
|
||
static const int gs_OffsetColOff = gs_OffsetColFav + 3;
|
||
static const int gs_OffsetColName = gs_OffsetColOff + 3;
|
||
static const int gs_OffsetColGameType = gs_OffsetColName + 3;
|
||
static const int gs_OffsetColMap = gs_OffsetColGameType + 3;
|
||
static const int gs_OffsetColPlayers = gs_OffsetColMap + 3;
|
||
static const int gs_OffsetColPing = gs_OffsetColPlayers + 3;
|
||
static const int gs_OffsetColVersion = gs_OffsetColPing + 3;
|
||
|
||
void FormatServerbrowserPing(char *pBuffer, int BufferLength, const CServerInfo *pInfo)
|
||
{
|
||
if(!pInfo->m_LatencyIsEstimated)
|
||
{
|
||
str_format(pBuffer, BufferLength, "%d", pInfo->m_Latency);
|
||
return;
|
||
}
|
||
static const char *LOCATION_NAMES[CServerInfo::NUM_LOCS] = {
|
||
"", // LOC_UNKNOWN
|
||
Localizable("AFR"), // LOC_AFRICA
|
||
Localizable("ASI"), // LOC_ASIA
|
||
Localizable("AUS"), // LOC_AUSTRALIA
|
||
Localizable("EUR"), // LOC_EUROPE
|
||
Localizable("NA"), // LOC_NORTH_AMERICA
|
||
Localizable("SA"), // LOC_SOUTH_AMERICA
|
||
Localizable("CHN"), // LOC_CHINA
|
||
};
|
||
dbg_assert(0 <= pInfo->m_Location && pInfo->m_Location < CServerInfo::NUM_LOCS, "location out of range");
|
||
str_copy(pBuffer, Localize(LOCATION_NAMES[pInfo->m_Location]), BufferLength);
|
||
}
|
||
|
||
void CMenus::RenderServerbrowserServerList(CUIRect View)
|
||
{
|
||
CUIRect Headers;
|
||
CUIRect Status;
|
||
|
||
View.HSplitTop(ms_ListheaderHeight, &Headers, &View);
|
||
View.HSplitBottom(65.0f, &View, &Status);
|
||
|
||
// split of the scrollbar
|
||
Headers.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_T, 5.0f);
|
||
Headers.VSplitRight(20.0f, &Headers, 0);
|
||
|
||
struct CColumn
|
||
{
|
||
int m_ID;
|
||
int m_Sort;
|
||
CLocConstString m_Caption;
|
||
int m_Direction;
|
||
float m_Width;
|
||
CUIRect m_Rect;
|
||
CUIRect m_Spacer;
|
||
};
|
||
|
||
enum
|
||
{
|
||
COL_FLAG_LOCK = 0,
|
||
COL_FLAG_FAV,
|
||
COL_FLAG_OFFICIAL,
|
||
COL_NAME,
|
||
COL_GAMETYPE,
|
||
COL_MAP,
|
||
COL_PLAYERS,
|
||
COL_PING,
|
||
COL_VERSION,
|
||
};
|
||
|
||
CColumn s_aCols[] = {
|
||
{-1, -1, " ", -1, 2.0f, {0}, {0}},
|
||
{COL_FLAG_LOCK, -1, " ", -1, 14.0f, {0}, {0}},
|
||
{COL_FLAG_FAV, -1, " ", -1, 14.0f, {0}, {0}},
|
||
{COL_FLAG_OFFICIAL, -1, " ", -1, 14.0f, {0}, {0}},
|
||
{COL_NAME, IServerBrowser::SORT_NAME, "Name", 0, 50.0f, {0}, {0}}, // Localize - these strings are localized within CLocConstString
|
||
{COL_GAMETYPE, IServerBrowser::SORT_GAMETYPE, Localizable("Type"), 1, 50.0f, {0}, {0}},
|
||
{COL_MAP, IServerBrowser::SORT_MAP, "Map", 1, 120.0f + (Headers.w - 480) / 8, {0}, {0}},
|
||
{COL_PLAYERS, IServerBrowser::SORT_NUMPLAYERS, "Players", 1, 85.0f, {0}, {0}},
|
||
{-1, -1, " ", 1, 10.0f, {0}, {0}},
|
||
{COL_PING, IServerBrowser::SORT_PING, "Ping", 1, 40.0f, {0}, {0}},
|
||
};
|
||
|
||
int NumCols = std::size(s_aCols);
|
||
|
||
// do layout
|
||
for(int i = 0; i < NumCols; i++)
|
||
{
|
||
if(s_aCols[i].m_Direction == -1)
|
||
{
|
||
Headers.VSplitLeft(s_aCols[i].m_Width, &s_aCols[i].m_Rect, &Headers);
|
||
|
||
if(i + 1 < NumCols)
|
||
{
|
||
Headers.VSplitLeft(2, &s_aCols[i].m_Spacer, &Headers);
|
||
}
|
||
}
|
||
}
|
||
|
||
for(int i = NumCols - 1; i >= 0; i--)
|
||
{
|
||
if(s_aCols[i].m_Direction == 1)
|
||
{
|
||
Headers.VSplitRight(s_aCols[i].m_Width, &Headers, &s_aCols[i].m_Rect);
|
||
Headers.VSplitRight(2, &Headers, &s_aCols[i].m_Spacer);
|
||
}
|
||
}
|
||
|
||
for(int i = 0; i < NumCols; i++)
|
||
{
|
||
if(s_aCols[i].m_Direction == 0)
|
||
s_aCols[i].m_Rect = Headers;
|
||
}
|
||
|
||
const bool PlayersOrPing = (g_Config.m_BrSort == IServerBrowser::SORT_NUMPLAYERS || g_Config.m_BrSort == IServerBrowser::SORT_PING);
|
||
|
||
// do headers
|
||
for(int i = 0; i < NumCols; i++)
|
||
{
|
||
int Checked = g_Config.m_BrSort == s_aCols[i].m_Sort;
|
||
if(PlayersOrPing && g_Config.m_BrSortOrder == 2 && (s_aCols[i].m_Sort == IServerBrowser::SORT_NUMPLAYERS || s_aCols[i].m_Sort == IServerBrowser::SORT_PING))
|
||
Checked = 2;
|
||
|
||
if(DoButton_GridHeader(s_aCols[i].m_Caption, Localize(s_aCols[i].m_Caption), Checked, &s_aCols[i].m_Rect))
|
||
{
|
||
if(s_aCols[i].m_Sort != -1)
|
||
{
|
||
if(g_Config.m_BrSort == s_aCols[i].m_Sort)
|
||
{
|
||
if(PlayersOrPing)
|
||
g_Config.m_BrSortOrder = (g_Config.m_BrSortOrder + 1) % 3;
|
||
else
|
||
g_Config.m_BrSortOrder = (g_Config.m_BrSortOrder + 1) % 2;
|
||
}
|
||
else
|
||
g_Config.m_BrSortOrder = 0;
|
||
g_Config.m_BrSort = s_aCols[i].m_Sort;
|
||
}
|
||
}
|
||
}
|
||
|
||
View.Draw(ColorRGBA(0, 0, 0, 0.15f), 0, 0);
|
||
|
||
int NumServers = ServerBrowser()->NumSortedServers();
|
||
|
||
// display important messages in the middle of the screen so no
|
||
// users misses it
|
||
{
|
||
CUIRect MsgBox = View;
|
||
|
||
if(!ServerBrowser()->NumServers() && ServerBrowser()->IsGettingServerlist())
|
||
UI()->DoLabel(&MsgBox, Localize("Getting server list from master server"), 16.0f, TEXTALIGN_MC);
|
||
else if(!ServerBrowser()->NumServers())
|
||
UI()->DoLabel(&MsgBox, Localize("No servers found"), 16.0f, TEXTALIGN_MC);
|
||
else if(ServerBrowser()->NumServers() && !NumServers)
|
||
UI()->DoLabel(&MsgBox, Localize("No servers match your filter criteria"), 16.0f, TEXTALIGN_MC);
|
||
}
|
||
|
||
if(UI()->ConsumeHotkey(CUI::HOTKEY_TAB))
|
||
{
|
||
const int Direction = Input()->ShiftIsPressed() ? -1 : 1;
|
||
g_Config.m_UiToolboxPage = (g_Config.m_UiToolboxPage + 3 + Direction) % 3;
|
||
}
|
||
|
||
static CListBox s_ListBox;
|
||
s_ListBox.SetActive(!UI()->IsPopupOpen());
|
||
s_ListBox.DoStart(ms_ListheaderHeight, NumServers, 1, 3, -1, &View, false);
|
||
|
||
int NumPlayers = 0;
|
||
static int s_PrevSelectedIndex = -1;
|
||
if(s_PrevSelectedIndex != m_SelectedIndex)
|
||
{
|
||
s_ListBox.ScrollToSelected();
|
||
s_PrevSelectedIndex = m_SelectedIndex;
|
||
}
|
||
m_SelectedIndex = -1;
|
||
|
||
auto RenderBrowserIcons = [this](CUIElement::SUIElementRect &UIRect, CUIRect *pRect, const ColorRGBA &TextColor, const ColorRGBA &TextOutlineColor, const char *pText, int TextAlign, bool SmallFont = false) {
|
||
float FontSize = 14.0f;
|
||
if(SmallFont)
|
||
FontSize = 6.0f;
|
||
TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT));
|
||
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
|
||
TextRender()->TextColor(TextColor);
|
||
TextRender()->TextOutlineColor(TextOutlineColor);
|
||
UI()->DoLabelStreamed(UIRect, pRect, pText, FontSize, TextAlign, -1.0f);
|
||
TextRender()->TextOutlineColor(TextRender()->DefaultTextOutlineColor());
|
||
TextRender()->TextColor(TextRender()->DefaultTextColor());
|
||
TextRender()->SetRenderFlags(0);
|
||
TextRender()->SetCurFont(nullptr);
|
||
};
|
||
|
||
for(int i = 0; i < NumServers; i++)
|
||
{
|
||
const CServerInfo *pItem = ServerBrowser()->SortedGet(i);
|
||
NumPlayers += pItem->m_NumFilteredPlayers;
|
||
|
||
if(pItem->m_pUIElement == nullptr)
|
||
{
|
||
const int UIRectCount = 2 + (COL_VERSION + 1) * 3;
|
||
pItem->m_pUIElement = UI()->GetNewUIElement(UIRectCount);
|
||
}
|
||
|
||
const CListboxItem ListItem = s_ListBox.DoNextItem(pItem, str_comp(pItem->m_aAddress, g_Config.m_UiServerAddress) == 0);
|
||
if(ListItem.m_Selected)
|
||
m_SelectedIndex = i;
|
||
|
||
// update friend counter
|
||
int FriendsOnServer = 0;
|
||
if(pItem->m_FriendState != IFriends::FRIEND_NO)
|
||
{
|
||
for(int j = 0; j < pItem->m_NumReceivedClients; ++j)
|
||
{
|
||
if(pItem->m_aClients[j].m_FriendState != IFriends::FRIEND_NO)
|
||
{
|
||
FriendsOnServer++;
|
||
}
|
||
}
|
||
}
|
||
|
||
if(!ListItem.m_Visible)
|
||
{
|
||
// reset active item, if not visible
|
||
if(UI()->CheckActiveItem(pItem))
|
||
UI()->SetActiveItem(nullptr);
|
||
|
||
// don't render invisible items
|
||
continue;
|
||
}
|
||
|
||
for(int c = 0; c < NumCols; c++)
|
||
{
|
||
CUIRect Button;
|
||
char aTemp[64];
|
||
Button.x = s_aCols[c].m_Rect.x;
|
||
Button.y = ListItem.m_Rect.y;
|
||
Button.h = ListItem.m_Rect.h;
|
||
Button.w = s_aCols[c].m_Rect.w;
|
||
|
||
int ID = s_aCols[c].m_ID;
|
||
|
||
if(ID == COL_FLAG_LOCK)
|
||
{
|
||
if(pItem->m_Flags & SERVER_FLAG_PASSWORD)
|
||
{
|
||
RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColFlagLock + 0), &Button, {0.75f, 0.75f, 0.75f, 1}, TextRender()->DefaultTextOutlineColor(), FONT_ICON_LOCK, TEXTALIGN_MC);
|
||
}
|
||
}
|
||
else if(ID == COL_FLAG_FAV)
|
||
{
|
||
if(pItem->m_Favorite != TRISTATE::NONE)
|
||
{
|
||
RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColFav + 0), &Button, {0.94f, 0.4f, 0.4f, 1}, TextRender()->DefaultTextOutlineColor(), FONT_ICON_HEART, TEXTALIGN_MC);
|
||
}
|
||
}
|
||
else if(ID == COL_FLAG_OFFICIAL)
|
||
{
|
||
if(pItem->m_Official && g_Config.m_UiPage != PAGE_DDNET && g_Config.m_UiPage != PAGE_KOG)
|
||
{
|
||
RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColOff + 0), &Button, {0.4f, 0.7f, 0.94f, 1}, {0.0f, 0.0f, 0.0f, 1.0f}, FONT_ICON_CERTIFICATE, TEXTALIGN_MC);
|
||
RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColOff + 1), &Button, {0.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f, 0.0f}, FONT_ICON_CHECK, TEXTALIGN_MC, true);
|
||
}
|
||
}
|
||
else if(ID == COL_NAME)
|
||
{
|
||
float FontSize = 12.0f;
|
||
bool Printed = false;
|
||
|
||
if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_SERVERNAME))
|
||
Printed = PrintHighlighted(pItem->m_aName, [this, pItem, FontSize, &Button](const char *pFilteredStr, const int FilterLen) {
|
||
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 0), &Button, pItem->m_aName, FontSize, TEXTALIGN_ML, Button.w, true, (int)(pFilteredStr - pItem->m_aName));
|
||
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1);
|
||
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 1), &Button, pFilteredStr, FontSize, TEXTALIGN_ML, Button.w, true, FilterLen, &pItem->m_pUIElement->Rect(gs_OffsetColName + 0)->m_Cursor);
|
||
TextRender()->TextColor(1, 1, 1, 1);
|
||
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName + 2), &Button, pFilteredStr + FilterLen, FontSize, TEXTALIGN_ML, Button.w, true, -1, &pItem->m_pUIElement->Rect(gs_OffsetColName + 1)->m_Cursor);
|
||
});
|
||
if(!Printed)
|
||
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColName), &Button, pItem->m_aName, FontSize, TEXTALIGN_ML, Button.w, true);
|
||
}
|
||
else if(ID == COL_MAP)
|
||
{
|
||
if(g_Config.m_UiPage == PAGE_DDNET)
|
||
{
|
||
CUIRect Icon;
|
||
Button.VMargin(4.0f, &Button);
|
||
Button.VSplitLeft(Button.h, &Icon, &Button);
|
||
Icon.Margin(2.0f, &Icon);
|
||
|
||
if(g_Config.m_BrIndicateFinished && pItem->m_HasRank == 1)
|
||
{
|
||
RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColFlagLock + 1), &Icon, TextRender()->DefaultTextColor(), TextRender()->DefaultTextOutlineColor(), FONT_ICON_FLAG_CHECKERED, TEXTALIGN_MC);
|
||
}
|
||
}
|
||
|
||
float FontSize = 12.0f;
|
||
bool Printed = false;
|
||
if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_MAPNAME))
|
||
Printed = PrintHighlighted(pItem->m_aMap, [this, pItem, FontSize, &Button](const char *pFilteredStr, const int FilterLen) {
|
||
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 0), &Button, pItem->m_aMap, FontSize, TEXTALIGN_ML, Button.w, true, (int)(pFilteredStr - pItem->m_aMap));
|
||
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1);
|
||
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 1), &Button, pFilteredStr, FontSize, TEXTALIGN_ML, Button.w, true, FilterLen, &pItem->m_pUIElement->Rect(gs_OffsetColMap + 0)->m_Cursor);
|
||
TextRender()->TextColor(1, 1, 1, 1);
|
||
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap + 2), &Button, pFilteredStr + FilterLen, FontSize, TEXTALIGN_ML, Button.w, true, -1, &pItem->m_pUIElement->Rect(gs_OffsetColMap + 1)->m_Cursor);
|
||
});
|
||
if(!Printed)
|
||
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColMap), &Button, pItem->m_aMap, FontSize, TEXTALIGN_ML, Button.w, true);
|
||
}
|
||
else if(ID == COL_PLAYERS)
|
||
{
|
||
CUIRect Icon, IconText;
|
||
Button.VMargin(2.0f, &Button);
|
||
if(pItem->m_FriendState != IFriends::FRIEND_NO)
|
||
{
|
||
Button.VSplitRight(50.0f, &Icon, &Button);
|
||
Icon.Margin(2.0f, &Icon);
|
||
Icon.HSplitBottom(6.0f, 0, &IconText);
|
||
RenderBrowserIcons(*pItem->m_pUIElement->Rect(gs_OffsetColFav + 1), &Icon, {0.94f, 0.4f, 0.4f, 1}, TextRender()->DefaultTextOutlineColor(), FONT_ICON_HEART, TEXTALIGN_MC);
|
||
if(FriendsOnServer > 1)
|
||
{
|
||
char aBufFriendsOnServer[64];
|
||
str_format(aBufFriendsOnServer, sizeof(aBufFriendsOnServer), "%i", FriendsOnServer);
|
||
TextRender()->TextColor(0.94f, 0.8f, 0.8f, 1);
|
||
UI()->DoLabel(&IconText, aBufFriendsOnServer, 10.0f, TEXTALIGN_MC);
|
||
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1);
|
||
}
|
||
}
|
||
|
||
str_format(aTemp, sizeof(aTemp), "%i/%i", pItem->m_NumFilteredPlayers, ServerBrowser()->Max(*pItem));
|
||
if(g_Config.m_BrFilterString[0] && (pItem->m_QuickSearchHit & IServerBrowser::QUICK_PLAYER))
|
||
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1);
|
||
float FontSize = 12.0f;
|
||
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColPlayers), &Button, aTemp, FontSize, TEXTALIGN_MR, -1.0f, false);
|
||
TextRender()->TextColor(1, 1, 1, 1);
|
||
}
|
||
else if(ID == COL_PING)
|
||
{
|
||
Button.VMargin(4.0f, &Button);
|
||
FormatServerbrowserPing(aTemp, sizeof(aTemp), pItem);
|
||
if(g_Config.m_UiColorizePing)
|
||
{
|
||
ColorRGBA rgb = color_cast<ColorRGBA>(ColorHSLA((300.0f - clamp(pItem->m_Latency, 0, 300)) / 1000.0f, 1.0f, 0.5f));
|
||
TextRender()->TextColor(rgb);
|
||
}
|
||
|
||
float FontSize = 12.0f;
|
||
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColPing), &Button, aTemp, FontSize, TEXTALIGN_MR, -1.0f, false);
|
||
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||
}
|
||
else if(ID == COL_VERSION)
|
||
{
|
||
const char *pVersion = pItem->m_aVersion;
|
||
float FontSize = 12.0f;
|
||
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColVersion), &Button, pVersion, FontSize, TEXTALIGN_MR, -1.0f, false);
|
||
}
|
||
else if(ID == COL_GAMETYPE)
|
||
{
|
||
float FontSize = 12.0f;
|
||
|
||
if(g_Config.m_UiColorizeGametype)
|
||
{
|
||
ColorHSLA hsl = ColorHSLA(1.0f, 1.0f, 1.0f);
|
||
|
||
if(str_comp(pItem->m_aGameType, "DM") == 0 || str_comp(pItem->m_aGameType, "TDM") == 0 || str_comp(pItem->m_aGameType, "CTF") == 0)
|
||
hsl = ColorHSLA(0.33f, 1.0f, 0.75f);
|
||
else if(str_find_nocase(pItem->m_aGameType, "catch"))
|
||
hsl = ColorHSLA(0.17f, 1.0f, 0.75f);
|
||
else if(str_find_nocase(pItem->m_aGameType, "idm") || str_find_nocase(pItem->m_aGameType, "itdm") || str_find_nocase(pItem->m_aGameType, "ictf") || str_find_nocase(pItem->m_aGameType, "f-ddrace"))
|
||
hsl = ColorHSLA(0.00f, 1.0f, 0.75f);
|
||
else if(str_find_nocase(pItem->m_aGameType, "fng"))
|
||
hsl = ColorHSLA(0.83f, 1.0f, 0.75f);
|
||
else if(str_find_nocase(pItem->m_aGameType, "gores"))
|
||
hsl = ColorHSLA(0.525f, 1.0f, 0.75f);
|
||
else if(str_find_nocase(pItem->m_aGameType, "BW"))
|
||
hsl = ColorHSLA(0.050f, 1.0f, 0.75f);
|
||
else if(str_find_nocase(pItem->m_aGameType, "ddracenet") || str_find_nocase(pItem->m_aGameType, "ddnet") || str_find_nocase(pItem->m_aGameType, "0xf"))
|
||
hsl = ColorHSLA(0.58f, 1.0f, 0.75f);
|
||
else if(str_find_nocase(pItem->m_aGameType, "ddrace") || str_find_nocase(pItem->m_aGameType, "mkrace"))
|
||
hsl = ColorHSLA(0.75f, 1.0f, 0.75f);
|
||
else if(str_find_nocase(pItem->m_aGameType, "race") || str_find_nocase(pItem->m_aGameType, "fastcap"))
|
||
hsl = ColorHSLA(0.46f, 1.0f, 0.75f);
|
||
else if(str_find_nocase(pItem->m_aGameType, "s-ddr"))
|
||
hsl = ColorHSLA(1.0f, 1.0f, 0.70f);
|
||
|
||
ColorRGBA rgb = color_cast<ColorRGBA>(hsl);
|
||
TextRender()->TextColor(rgb);
|
||
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColGameType), &Button, pItem->m_aGameType, FontSize, TEXTALIGN_ML, Button.w, true);
|
||
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||
}
|
||
else
|
||
UI()->DoLabelStreamed(*pItem->m_pUIElement->Rect(gs_OffsetColGameType), &Button, pItem->m_aGameType, FontSize, TEXTALIGN_ML, Button.w, true);
|
||
}
|
||
}
|
||
}
|
||
|
||
const int NewSelected = s_ListBox.DoEnd();
|
||
if(NewSelected != m_SelectedIndex)
|
||
{
|
||
m_SelectedIndex = NewSelected;
|
||
if(m_SelectedIndex >= 0)
|
||
{
|
||
// select the new server
|
||
const CServerInfo *pItem = ServerBrowser()->SortedGet(NewSelected);
|
||
if(pItem)
|
||
{
|
||
str_copy(g_Config.m_UiServerAddress, pItem->m_aAddress);
|
||
}
|
||
}
|
||
}
|
||
if(s_ListBox.WasItemActivated())
|
||
Connect(g_Config.m_UiServerAddress);
|
||
|
||
// Render bar that shows the loading progression.
|
||
// The bar is only shown while loading and fades out when it's done.
|
||
CUIRect RefreshBar;
|
||
Status.HSplitTop(5.0f, &RefreshBar, &Status);
|
||
static float s_LoadingProgressionFadeEnd = 0.0f;
|
||
if(ServerBrowser()->IsRefreshing() && ServerBrowser()->LoadingProgression() < 100)
|
||
{
|
||
s_LoadingProgressionFadeEnd = Client()->GlobalTime() + 2.0f;
|
||
}
|
||
const float LoadingProgressionTimeDiff = s_LoadingProgressionFadeEnd - Client()->GlobalTime();
|
||
if(LoadingProgressionTimeDiff > 0.0f)
|
||
{
|
||
const float RefreshBarAlpha = minimum(LoadingProgressionTimeDiff, 0.8f);
|
||
RefreshBar.h = 2.0f;
|
||
RefreshBar.w *= ServerBrowser()->LoadingProgression() / 100.0f;
|
||
RefreshBar.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, RefreshBarAlpha), IGraphics::CORNER_NONE, 0.0f);
|
||
}
|
||
|
||
CUIRect SearchInfoAndAddr, ServersAndConnect, Status3;
|
||
Status.VSplitRight(125.0f, &SearchInfoAndAddr, &ServersAndConnect);
|
||
if(SearchInfoAndAddr.w > 350.0f)
|
||
SearchInfoAndAddr.VSplitLeft(350.0f, &SearchInfoAndAddr, NULL);
|
||
CUIRect SearchAndInfo, ServerAddr, ConnectButtons;
|
||
SearchInfoAndAddr.HSplitTop(40.0f, &SearchAndInfo, &ServerAddr);
|
||
ServersAndConnect.HSplitTop(35.0f, &Status3, &ConnectButtons);
|
||
CUIRect QuickSearch, QuickExclude;
|
||
|
||
SearchAndInfo.HSplitTop(20.f, &QuickSearch, &QuickExclude);
|
||
QuickSearch.Margin(2.f, &QuickSearch);
|
||
QuickExclude.Margin(2.f, &QuickExclude);
|
||
|
||
float SearchExcludeAddrStrMax = 130.0f;
|
||
|
||
float SearchIconWidth = 0;
|
||
float ExcludeIconWidth = 0;
|
||
float ExcludeSearchIconMax = 0;
|
||
|
||
// render quick search
|
||
{
|
||
TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT));
|
||
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
|
||
|
||
UI()->DoLabel(&QuickSearch, FONT_ICON_MAGNIFYING_GLASS, 16.0f, TEXTALIGN_ML);
|
||
SearchIconWidth = TextRender()->TextWidth(16.0f, FONT_ICON_MAGNIFYING_GLASS, -1, -1.0f);
|
||
ExcludeIconWidth = TextRender()->TextWidth(16.0f, FONT_ICON_BAN, -1, -1.0f);
|
||
ExcludeSearchIconMax = maximum(SearchIconWidth, ExcludeIconWidth);
|
||
TextRender()->SetRenderFlags(0);
|
||
TextRender()->SetCurFont(NULL);
|
||
QuickSearch.VSplitLeft(ExcludeSearchIconMax, 0, &QuickSearch);
|
||
QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch);
|
||
|
||
char aBufSearch[64];
|
||
str_format(aBufSearch, sizeof(aBufSearch), "%s:", Localize("Search"));
|
||
UI()->DoLabel(&QuickSearch, aBufSearch, 14.0f, TEXTALIGN_ML);
|
||
QuickSearch.VSplitLeft(SearchExcludeAddrStrMax, 0, &QuickSearch);
|
||
QuickSearch.VSplitLeft(5.0f, 0, &QuickSearch);
|
||
|
||
static CLineInput s_FilterInput(g_Config.m_BrFilterString, sizeof(g_Config.m_BrFilterString));
|
||
if(Input()->KeyPress(KEY_F) && Input()->ModifierIsPressed())
|
||
{
|
||
UI()->SetActiveItem(&s_FilterInput);
|
||
s_FilterInput.SelectAll();
|
||
}
|
||
if(UI()->DoClearableEditBox(&s_FilterInput, &QuickSearch, 12.0f))
|
||
Client()->ServerBrowserUpdate();
|
||
}
|
||
|
||
// render quick exclude
|
||
{
|
||
TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT));
|
||
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
|
||
|
||
UI()->DoLabel(&QuickExclude, FONT_ICON_BAN, 16.0f, TEXTALIGN_ML);
|
||
TextRender()->SetRenderFlags(0);
|
||
TextRender()->SetCurFont(NULL);
|
||
QuickExclude.VSplitLeft(ExcludeSearchIconMax, 0, &QuickExclude);
|
||
QuickExclude.VSplitLeft(5.0f, 0, &QuickExclude);
|
||
|
||
char aBufExclude[64];
|
||
str_format(aBufExclude, sizeof(aBufExclude), "%s:", Localize("Exclude"));
|
||
UI()->DoLabel(&QuickExclude, aBufExclude, 14.0f, TEXTALIGN_ML);
|
||
QuickExclude.VSplitLeft(SearchExcludeAddrStrMax, 0, &QuickExclude);
|
||
QuickExclude.VSplitLeft(5.0f, 0, &QuickExclude);
|
||
|
||
static CLineInput s_ExcludeInput(g_Config.m_BrExcludeString, sizeof(g_Config.m_BrExcludeString));
|
||
if(Input()->KeyPress(KEY_X) && Input()->ShiftIsPressed() && Input()->ModifierIsPressed())
|
||
UI()->SetActiveItem(&s_ExcludeInput);
|
||
if(UI()->DoClearableEditBox(&s_ExcludeInput, &QuickExclude, 12.0f))
|
||
Client()->ServerBrowserUpdate();
|
||
}
|
||
|
||
// render status
|
||
char aBufSvr[128];
|
||
char aBufPyr[128];
|
||
if(ServerBrowser()->NumServers() != 1)
|
||
str_format(aBufSvr, sizeof(aBufSvr), Localize("%d of %d servers"), ServerBrowser()->NumSortedServers(), ServerBrowser()->NumServers());
|
||
else
|
||
str_format(aBufSvr, sizeof(aBufSvr), Localize("%d of %d server"), ServerBrowser()->NumSortedServers(), ServerBrowser()->NumServers());
|
||
if(NumPlayers != 1)
|
||
str_format(aBufPyr, sizeof(aBufPyr), Localize("%d players"), NumPlayers);
|
||
else
|
||
str_format(aBufPyr, sizeof(aBufPyr), Localize("%d player"), NumPlayers);
|
||
|
||
CUIRect SvrsOnline, PlysOnline;
|
||
Status3.HSplitTop(20.f, &PlysOnline, &SvrsOnline);
|
||
PlysOnline.VSplitRight(TextRender()->TextWidth(12.0f, aBufPyr, -1, -1.0f), 0, &PlysOnline);
|
||
UI()->DoLabel(&PlysOnline, aBufPyr, 12.0f, TEXTALIGN_ML);
|
||
SvrsOnline.VSplitRight(TextRender()->TextWidth(12.0f, aBufSvr, -1, -1.0f), 0, &SvrsOnline);
|
||
UI()->DoLabel(&SvrsOnline, aBufSvr, 12.0f, TEXTALIGN_ML);
|
||
|
||
// status box
|
||
{
|
||
ServerAddr.Margin(2.0f, &ServerAddr);
|
||
|
||
// address info
|
||
UI()->DoLabel(&ServerAddr, Localize("Server address:"), 14.0f, TEXTALIGN_ML);
|
||
ServerAddr.VSplitLeft(SearchExcludeAddrStrMax + 5.0f + ExcludeSearchIconMax + 5.0f, NULL, &ServerAddr);
|
||
static CLineInput s_ServerAddressInput(g_Config.m_UiServerAddress, sizeof(g_Config.m_UiServerAddress));
|
||
UI()->DoClearableEditBox(&s_ServerAddressInput, &ServerAddr, 12.0f);
|
||
|
||
// button area
|
||
CUIRect ButtonRefresh, ButtonConnect;
|
||
ConnectButtons.VSplitMid(&ButtonRefresh, &ButtonConnect);
|
||
ButtonRefresh.HSplitTop(5.0f, NULL, &ButtonRefresh);
|
||
ButtonConnect.HSplitTop(5.0f, NULL, &ButtonConnect);
|
||
ButtonConnect.VSplitLeft(5.0f, NULL, &ButtonConnect);
|
||
|
||
// refresh button
|
||
{
|
||
char aLabelBuf[32] = {0};
|
||
const auto RefreshLabelFunc = [this, aLabelBuf]() mutable {
|
||
if(ServerBrowser()->IsRefreshing() || ServerBrowser()->IsGettingServerlist())
|
||
str_format(aLabelBuf, sizeof(aLabelBuf), "%s%s", FONT_ICON_ARROW_ROTATE_RIGHT, FONT_ICON_ELLIPSIS);
|
||
else
|
||
str_copy(aLabelBuf, FONT_ICON_ARROW_ROTATE_RIGHT);
|
||
return aLabelBuf;
|
||
};
|
||
|
||
SMenuButtonProperties Props;
|
||
Props.m_HintRequiresStringCheck = true;
|
||
Props.m_UseIconFont = true;
|
||
|
||
static CButtonContainer s_RefreshButton;
|
||
if(UI()->DoButton_Menu(m_RefreshButton, &s_RefreshButton, RefreshLabelFunc, &ButtonRefresh, Props) || Input()->KeyPress(KEY_F5) || (Input()->KeyPress(KEY_R) && Input()->ModifierIsPressed()))
|
||
{
|
||
RefreshBrowserTab(g_Config.m_UiPage);
|
||
}
|
||
}
|
||
|
||
// connect button
|
||
{
|
||
const auto ConnectLabelFunc = []() { return FONT_ICON_RIGHT_TO_BRACKET; };
|
||
|
||
SMenuButtonProperties Props;
|
||
Props.m_UseIconFont = true;
|
||
Props.m_Color = ColorRGBA(0.5f, 1.0f, 0.5f, 0.5f);
|
||
|
||
static CButtonContainer s_ConnectButton;
|
||
if(UI()->DoButton_Menu(m_ConnectButton, &s_ConnectButton, ConnectLabelFunc, &ButtonConnect, Props))
|
||
{
|
||
Connect(g_Config.m_UiServerAddress);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void CMenus::Connect(const char *pAddress)
|
||
{
|
||
if(Client()->State() == IClient::STATE_ONLINE && Client()->GetCurrentRaceTime() / 60 >= g_Config.m_ClConfirmDisconnectTime && g_Config.m_ClConfirmDisconnectTime >= 0)
|
||
{
|
||
str_copy(m_aNextServer, pAddress);
|
||
PopupConfirm(Localize("Disconnect"), Localize("Are you sure that you want to disconnect and switch to a different server?"), Localize("Yes"), Localize("No"), &CMenus::PopupConfirmSwitchServer);
|
||
}
|
||
else
|
||
Client()->Connect(pAddress);
|
||
}
|
||
|
||
void CMenus::PopupConfirmSwitchServer()
|
||
{
|
||
Client()->Connect(m_aNextServer);
|
||
}
|
||
|
||
void CMenus::RenderServerbrowserFilters(CUIRect View)
|
||
{
|
||
CUIRect ServerFilter = View, FilterHeader;
|
||
const float FontSize = 12.0f;
|
||
|
||
// server filter
|
||
ServerFilter.HSplitTop(ms_ListheaderHeight, &FilterHeader, &ServerFilter);
|
||
FilterHeader.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_T, 4.0f);
|
||
|
||
ServerFilter.Draw(ColorRGBA(0, 0, 0, 0.15f), IGraphics::CORNER_B, 4.0f);
|
||
UI()->DoLabel(&FilterHeader, Localize("Server filter"), FontSize + 2.0f, TEXTALIGN_MC);
|
||
CUIRect Button, Button2;
|
||
|
||
ServerFilter.Margin(3.0f, &ServerFilter);
|
||
ServerFilter.VMargin(5.0f, &ServerFilter);
|
||
|
||
ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
|
||
if(DoButton_CheckBox(&g_Config.m_BrFilterEmpty, Localize("Has people playing"), g_Config.m_BrFilterEmpty, &Button))
|
||
g_Config.m_BrFilterEmpty ^= 1;
|
||
|
||
ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
|
||
if(DoButton_CheckBox(&g_Config.m_BrFilterSpectators, Localize("Count players only"), g_Config.m_BrFilterSpectators, &Button))
|
||
g_Config.m_BrFilterSpectators ^= 1;
|
||
|
||
ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
|
||
if(DoButton_CheckBox(&g_Config.m_BrFilterFull, Localize("Server not full"), g_Config.m_BrFilterFull, &Button))
|
||
g_Config.m_BrFilterFull ^= 1;
|
||
|
||
ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
|
||
if(DoButton_CheckBox(&g_Config.m_BrFilterFriends, Localize("Show friends only"), g_Config.m_BrFilterFriends, &Button))
|
||
g_Config.m_BrFilterFriends ^= 1;
|
||
|
||
ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
|
||
if(DoButton_CheckBox(&g_Config.m_BrFilterPw, Localize("No password"), g_Config.m_BrFilterPw, &Button))
|
||
g_Config.m_BrFilterPw ^= 1;
|
||
|
||
ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
|
||
if(DoButton_CheckBox(&g_Config.m_BrFilterGametypeStrict, Localize("Strict gametype filter"), g_Config.m_BrFilterGametypeStrict, &Button))
|
||
g_Config.m_BrFilterGametypeStrict ^= 1;
|
||
|
||
ServerFilter.HSplitTop(5.0f, 0, &ServerFilter);
|
||
|
||
ServerFilter.HSplitTop(19.0f, &Button, &ServerFilter);
|
||
UI()->DoLabel(&Button, Localize("Game types:"), FontSize, TEXTALIGN_ML);
|
||
Button.VSplitRight(60.0f, 0, &Button);
|
||
ServerFilter.HSplitTop(3.0f, 0, &ServerFilter);
|
||
static CLineInput s_GametypeInput(g_Config.m_BrFilterGametype, sizeof(g_Config.m_BrFilterGametype));
|
||
if(UI()->DoEditBox(&s_GametypeInput, &Button, FontSize))
|
||
Client()->ServerBrowserUpdate();
|
||
|
||
// server address
|
||
ServerFilter.HSplitTop(3.0f, 0, &ServerFilter);
|
||
ServerFilter.HSplitTop(19.0f, &Button, &ServerFilter);
|
||
UI()->DoLabel(&Button, Localize("Server address:"), FontSize, TEXTALIGN_ML);
|
||
Button.VSplitRight(60.0f, 0, &Button);
|
||
static CLineInput s_FilterServerAddressInput(g_Config.m_BrFilterServerAddress, sizeof(g_Config.m_BrFilterServerAddress));
|
||
if(UI()->DoEditBox(&s_FilterServerAddressInput, &Button, FontSize))
|
||
Client()->ServerBrowserUpdate();
|
||
|
||
// player country
|
||
{
|
||
CUIRect Rect;
|
||
ServerFilter.HSplitTop(3.0f, nullptr, &ServerFilter);
|
||
ServerFilter.HSplitTop(26.0f, &Button, &ServerFilter);
|
||
Button.HMargin(3.0f, &Button);
|
||
Button.VSplitRight(60.0f, &Button, &Rect);
|
||
if(DoButton_CheckBox(&g_Config.m_BrFilterCountry, Localize("Player country:"), g_Config.m_BrFilterCountry, &Button))
|
||
g_Config.m_BrFilterCountry ^= 1;
|
||
|
||
float OldWidth = Rect.w;
|
||
Rect.w = Rect.h * 2;
|
||
Rect.x += (OldWidth - Rect.w) / 2.0f;
|
||
m_pClient->m_CountryFlags.Render(g_Config.m_BrFilterCountryIndex, ColorRGBA(1.0f, 1.0f, 1.0f, UI()->MouseHovered(&Rect) ? 1.0f : g_Config.m_BrFilterCountry ? 0.9f : 0.5f), Rect.x, Rect.y, Rect.w, Rect.h);
|
||
|
||
if(UI()->DoButtonLogic(&g_Config.m_BrFilterCountryIndex, 0, &Rect))
|
||
{
|
||
static SPopupMenuId s_PopupCountryId;
|
||
static SPopupCountrySelectionContext s_PopupCountryContext;
|
||
s_PopupCountryContext.m_pMenus = this;
|
||
s_PopupCountryContext.m_Selection = g_Config.m_BrFilterCountryIndex;
|
||
s_PopupCountryContext.m_New = true;
|
||
UI()->DoPopupMenu(&s_PopupCountryId, Rect.x, Rect.y + Rect.h, 490, 210, &s_PopupCountryContext, PopupCountrySelection);
|
||
}
|
||
}
|
||
|
||
ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
|
||
if(DoButton_CheckBox(&g_Config.m_BrFilterConnectingPlayers, Localize("Filter connecting players"), g_Config.m_BrFilterConnectingPlayers, &Button))
|
||
g_Config.m_BrFilterConnectingPlayers ^= 1;
|
||
|
||
CUIRect FilterTabs;
|
||
ServerFilter.HSplitBottom(23, &ServerFilter, &FilterTabs);
|
||
|
||
CUIRect ResetButton;
|
||
|
||
ServerFilter.HSplitBottom(ms_ButtonHeight - 5.0f, &ServerFilter, &ResetButton);
|
||
|
||
// ddnet country filters
|
||
if(g_Config.m_UiPage == PAGE_DDNET)
|
||
{
|
||
ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
|
||
if(DoButton_CheckBox(&g_Config.m_BrIndicateFinished, Localize("Indicate map finish"), g_Config.m_BrIndicateFinished, &Button))
|
||
{
|
||
g_Config.m_BrIndicateFinished ^= 1;
|
||
|
||
if(g_Config.m_BrIndicateFinished)
|
||
ServerBrowser()->Refresh(ServerBrowser()->GetCurrentType());
|
||
}
|
||
|
||
if(g_Config.m_BrIndicateFinished)
|
||
{
|
||
ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
|
||
if(DoButton_CheckBox(&g_Config.m_BrFilterUnfinishedMap, Localize("Unfinished map"), g_Config.m_BrFilterUnfinishedMap, &Button))
|
||
g_Config.m_BrFilterUnfinishedMap ^= 1;
|
||
}
|
||
else
|
||
{
|
||
g_Config.m_BrFilterUnfinishedMap = 0;
|
||
}
|
||
}
|
||
|
||
if(g_Config.m_UiPage == PAGE_DDNET || g_Config.m_UiPage == PAGE_KOG)
|
||
{
|
||
int Network = g_Config.m_UiPage == PAGE_DDNET ? IServerBrowser::NETWORK_DDNET : IServerBrowser::NETWORK_KOG;
|
||
// add more space
|
||
ServerFilter.HSplitTop(5.0f, 0, &ServerFilter);
|
||
ServerFilter.HSplitTop(20.0f, &Button, &ServerFilter);
|
||
ServerFilter.HSplitTop(120.0f, &ServerFilter, 0);
|
||
|
||
ServerFilter.Draw(ms_ColorTabbarActive, IGraphics::CORNER_B, 10.0f);
|
||
|
||
Button.VSplitMid(&Button, &Button2);
|
||
|
||
static int s_ActivePage = 0;
|
||
|
||
static CButtonContainer s_CountriesButton;
|
||
if(DoButton_MenuTab(&s_CountriesButton, Localize("Countries"), s_ActivePage == 0, &Button, IGraphics::CORNER_TL))
|
||
{
|
||
s_ActivePage = 0;
|
||
}
|
||
|
||
static CButtonContainer s_TypesButton;
|
||
if(DoButton_MenuTab(&s_TypesButton, Localize("Types"), s_ActivePage == 1, &Button2, IGraphics::CORNER_TR))
|
||
{
|
||
s_ActivePage = 1;
|
||
}
|
||
|
||
if(s_ActivePage == 1)
|
||
{
|
||
char *pFilterExcludeTypes = Network == IServerBrowser::NETWORK_DDNET ? g_Config.m_BrFilterExcludeTypes : g_Config.m_BrFilterExcludeTypesKoG;
|
||
int MaxTypes = ServerBrowser()->NumTypes(Network);
|
||
int NumTypes = ServerBrowser()->NumTypes(Network);
|
||
int PerLine = 3;
|
||
|
||
ServerFilter.HSplitTop(4.0f, 0, &ServerFilter);
|
||
ServerFilter.HSplitBottom(4.0f, &ServerFilter, 0);
|
||
|
||
const float TypesWidth = 40.0f;
|
||
const float TypesHeight = ServerFilter.h / std::ceil(MaxTypes / (float)PerLine);
|
||
|
||
CUIRect TypesRect, Left, Right;
|
||
|
||
static std::vector<unsigned char> s_vTypeButtons;
|
||
s_vTypeButtons.resize(MaxTypes);
|
||
|
||
while(NumTypes > 0)
|
||
{
|
||
ServerFilter.HSplitTop(TypesHeight, &TypesRect, &ServerFilter);
|
||
TypesRect.VSplitMid(&Left, &Right);
|
||
|
||
for(int i = 0; i < PerLine && NumTypes > 0; i++, NumTypes--)
|
||
{
|
||
int TypeIndex = MaxTypes - NumTypes;
|
||
const char *pName = ServerBrowser()->GetType(Network, TypeIndex);
|
||
bool Active = !ServerBrowser()->DDNetFiltered(pFilterExcludeTypes, pName);
|
||
|
||
vec2 Pos = vec2(TypesRect.x + TypesRect.w * ((i + 0.5f) / (float)PerLine), TypesRect.y);
|
||
|
||
// correct pos
|
||
Pos.x -= TypesWidth / 2.0f;
|
||
|
||
// create button logic
|
||
CUIRect Rect;
|
||
|
||
Rect.x = Pos.x;
|
||
Rect.y = Pos.y;
|
||
Rect.w = TypesWidth;
|
||
Rect.h = TypesHeight;
|
||
|
||
int Click = UI()->DoButtonLogic(&s_vTypeButtons[TypeIndex], 0, &Rect);
|
||
if(Click == 1 || Click == 2)
|
||
{
|
||
// left/right click to toggle filter
|
||
if(pFilterExcludeTypes[0] == '\0')
|
||
{
|
||
// when all are active, only activate one
|
||
for(int j = 0; j < MaxTypes; ++j)
|
||
{
|
||
if(j != TypeIndex)
|
||
ServerBrowser()->DDNetFilterAdd(pFilterExcludeTypes, ServerBrowser()->GetType(Network, j));
|
||
}
|
||
}
|
||
else
|
||
{
|
||
bool AllFilteredExceptUs = true;
|
||
for(int j = 0; j < MaxTypes; ++j)
|
||
{
|
||
if(j != TypeIndex && !ServerBrowser()->DDNetFiltered(pFilterExcludeTypes, ServerBrowser()->GetType(Network, j)))
|
||
{
|
||
AllFilteredExceptUs = false;
|
||
break;
|
||
}
|
||
}
|
||
// when last one is removed, reset (re-enable all)
|
||
if(AllFilteredExceptUs)
|
||
{
|
||
pFilterExcludeTypes[0] = '\0';
|
||
}
|
||
else if(Active)
|
||
{
|
||
ServerBrowser()->DDNetFilterAdd(pFilterExcludeTypes, pName);
|
||
}
|
||
else
|
||
{
|
||
ServerBrowser()->DDNetFilterRem(pFilterExcludeTypes, pName);
|
||
}
|
||
}
|
||
|
||
ServerBrowser()->Refresh(ServerBrowser()->GetCurrentType());
|
||
}
|
||
else if(Click == 3)
|
||
{
|
||
// middle click to reset (re-enable all)
|
||
pFilterExcludeTypes[0] = '\0';
|
||
ServerBrowser()->Refresh(ServerBrowser()->GetCurrentType());
|
||
}
|
||
|
||
TextRender()->TextColor(1.0f, 1.0f, 1.0f, (Active ? 0.9f : 0.2f) + (UI()->HotItem() == &s_vTypeButtons[TypeIndex] ? 0.1f : 0.0f));
|
||
UI()->DoLabel(&Rect, pName, FontSize, TEXTALIGN_MC);
|
||
TextRender()->TextColor(1.0, 1.0, 1.0, 1.0f);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
char *pFilterExcludeCountries = Network == IServerBrowser::NETWORK_DDNET ? g_Config.m_BrFilterExcludeCountries : g_Config.m_BrFilterExcludeCountriesKoG;
|
||
ServerFilter.HSplitTop(15.0f, &ServerFilter, &ServerFilter);
|
||
|
||
const float FlagWidth = 40.0f;
|
||
const float FlagHeight = 20.0f;
|
||
|
||
int MaxFlags = ServerBrowser()->NumCountries(Network);
|
||
int NumFlags = ServerBrowser()->NumCountries(Network);
|
||
int PerLine = MaxFlags > 8 ? 5 : 4;
|
||
|
||
CUIRect FlagsRect;
|
||
|
||
static std::vector<unsigned char> s_vFlagButtons;
|
||
s_vFlagButtons.resize(MaxFlags);
|
||
|
||
while(NumFlags > 0)
|
||
{
|
||
ServerFilter.HSplitTop(23.0f, &FlagsRect, &ServerFilter);
|
||
|
||
for(int i = 0; i < PerLine && NumFlags > 0; i++, NumFlags--)
|
||
{
|
||
int CountryIndex = MaxFlags - NumFlags;
|
||
const char *pName = ServerBrowser()->GetCountryName(Network, CountryIndex);
|
||
bool Active = !ServerBrowser()->DDNetFiltered(pFilterExcludeCountries, pName);
|
||
int FlagID = ServerBrowser()->GetCountryFlag(Network, CountryIndex);
|
||
|
||
vec2 Pos = vec2(FlagsRect.x + FlagsRect.w * ((i + 0.5f) / (float)PerLine), FlagsRect.y);
|
||
|
||
// correct pos
|
||
Pos.x -= FlagWidth / 2.0f;
|
||
Pos.y -= FlagHeight / 2.0f;
|
||
|
||
// create button logic
|
||
CUIRect Rect;
|
||
|
||
Rect.x = Pos.x;
|
||
Rect.y = Pos.y;
|
||
Rect.w = FlagWidth;
|
||
Rect.h = FlagHeight;
|
||
|
||
int Click = UI()->DoButtonLogic(&s_vFlagButtons[CountryIndex], 0, &Rect);
|
||
if(Click == 1 || Click == 2)
|
||
{
|
||
// left/right click to toggle filter
|
||
if(pFilterExcludeCountries[0] == '\0')
|
||
{
|
||
// when all are active, only activate one
|
||
for(int j = 0; j < MaxFlags; ++j)
|
||
{
|
||
if(j != CountryIndex)
|
||
ServerBrowser()->DDNetFilterAdd(pFilterExcludeCountries, ServerBrowser()->GetCountryName(Network, j));
|
||
}
|
||
}
|
||
else
|
||
{
|
||
bool AllFilteredExceptUs = true;
|
||
for(int j = 0; j < MaxFlags; ++j)
|
||
{
|
||
if(j != CountryIndex && !ServerBrowser()->DDNetFiltered(pFilterExcludeCountries, ServerBrowser()->GetCountryName(Network, j)))
|
||
{
|
||
AllFilteredExceptUs = false;
|
||
break;
|
||
}
|
||
}
|
||
// when last one is removed, reset (re-enable all)
|
||
if(AllFilteredExceptUs)
|
||
{
|
||
pFilterExcludeCountries[0] = '\0';
|
||
}
|
||
else if(Active)
|
||
{
|
||
ServerBrowser()->DDNetFilterAdd(pFilterExcludeCountries, pName);
|
||
}
|
||
else
|
||
{
|
||
ServerBrowser()->DDNetFilterRem(pFilterExcludeCountries, pName);
|
||
}
|
||
}
|
||
|
||
ServerBrowser()->Refresh(ServerBrowser()->GetCurrentType());
|
||
}
|
||
else if(Click == 3)
|
||
{
|
||
// middle click to reset (re-enable all)
|
||
pFilterExcludeCountries[0] = '\0';
|
||
ServerBrowser()->Refresh(ServerBrowser()->GetCurrentType());
|
||
}
|
||
|
||
m_pClient->m_CountryFlags.Render(FlagID, ColorRGBA(1.0f, 1.0f, 1.0f, (Active ? 0.9f : 0.2f) + (UI()->HotItem() == &s_vFlagButtons[CountryIndex] ? 0.1f : 0.0f)), Pos.x, Pos.y, FlagWidth, FlagHeight);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
static CButtonContainer s_ClearButton;
|
||
if(DoButton_Menu(&s_ClearButton, Localize("Reset filter"), 0, &ResetButton))
|
||
{
|
||
g_Config.m_BrFilterString[0] = 0;
|
||
g_Config.m_BrExcludeString[0] = 0;
|
||
g_Config.m_BrFilterFull = 0;
|
||
g_Config.m_BrFilterEmpty = 0;
|
||
g_Config.m_BrFilterSpectators = 0;
|
||
g_Config.m_BrFilterFriends = 0;
|
||
g_Config.m_BrFilterCountry = 0;
|
||
g_Config.m_BrFilterCountryIndex = -1;
|
||
g_Config.m_BrFilterPw = 0;
|
||
g_Config.m_BrFilterGametype[0] = 0;
|
||
g_Config.m_BrFilterGametypeStrict = 0;
|
||
g_Config.m_BrFilterConnectingPlayers = 1;
|
||
g_Config.m_BrFilterUnfinishedMap = 0;
|
||
g_Config.m_BrFilterServerAddress[0] = 0;
|
||
g_Config.m_BrFilterExcludeCountries[0] = 0;
|
||
g_Config.m_BrFilterExcludeTypes[0] = 0;
|
||
if(g_Config.m_UiPage == PAGE_DDNET || g_Config.m_UiPage == PAGE_KOG)
|
||
ServerBrowser()->Refresh(ServerBrowser()->GetCurrentType());
|
||
else
|
||
Client()->ServerBrowserUpdate();
|
||
}
|
||
}
|
||
|
||
CUI::EPopupMenuFunctionResult CMenus::PopupCountrySelection(void *pContext, CUIRect View, bool Active)
|
||
{
|
||
SPopupCountrySelectionContext *pPopupContext = static_cast<SPopupCountrySelectionContext *>(pContext);
|
||
CMenus *pMenus = pPopupContext->m_pMenus;
|
||
|
||
static CListBox s_ListBox;
|
||
s_ListBox.SetActive(Active);
|
||
s_ListBox.DoStart(50.0f, pMenus->m_pClient->m_CountryFlags.Num(), 8, 1, -1, &View, false);
|
||
|
||
if(pPopupContext->m_New)
|
||
{
|
||
pPopupContext->m_New = false;
|
||
s_ListBox.ScrollToSelected();
|
||
}
|
||
|
||
for(size_t i = 0; i < pMenus->m_pClient->m_CountryFlags.Num(); ++i)
|
||
{
|
||
const CCountryFlags::CCountryFlag *pEntry = pMenus->m_pClient->m_CountryFlags.GetByIndex(i);
|
||
|
||
const CListboxItem Item = s_ListBox.DoNextItem(pEntry, pEntry->m_CountryCode == pPopupContext->m_Selection);
|
||
if(!Item.m_Visible)
|
||
continue;
|
||
|
||
CUIRect FlagRect, Label;
|
||
Item.m_Rect.Margin(5.0f, &FlagRect);
|
||
FlagRect.HSplitBottom(12.0f, &FlagRect, &Label);
|
||
Label.HSplitTop(2.0f, nullptr, &Label);
|
||
const float OldWidth = FlagRect.w;
|
||
FlagRect.w = FlagRect.h * 2.0f;
|
||
FlagRect.x += (OldWidth - FlagRect.w) / 2.0f;
|
||
pMenus->m_pClient->m_CountryFlags.Render(pEntry->m_CountryCode, ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f), FlagRect.x, FlagRect.y, FlagRect.w, FlagRect.h);
|
||
|
||
pMenus->UI()->DoLabel(&Label, pEntry->m_aCountryCodeString, 10.0f, TEXTALIGN_MC);
|
||
}
|
||
|
||
const int NewSelected = s_ListBox.DoEnd();
|
||
pPopupContext->m_Selection = NewSelected >= 0 ? pMenus->m_pClient->m_CountryFlags.GetByIndex(NewSelected)->m_CountryCode : -1;
|
||
if(s_ListBox.WasItemSelected() || s_ListBox.WasItemActivated())
|
||
{
|
||
g_Config.m_BrFilterCountry = 1;
|
||
g_Config.m_BrFilterCountryIndex = pPopupContext->m_Selection;
|
||
pMenus->Client()->ServerBrowserUpdate();
|
||
return CUI::POPUP_CLOSE_CURRENT;
|
||
}
|
||
|
||
return CUI::POPUP_KEEP_OPEN;
|
||
}
|
||
|
||
void CMenus::RenderServerbrowserServerDetail(CUIRect View)
|
||
{
|
||
CUIRect ServerDetails = View;
|
||
CUIRect ServerScoreBoard, ServerHeader;
|
||
|
||
const CServerInfo *pSelectedServer = ServerBrowser()->SortedGet(m_SelectedIndex);
|
||
|
||
// split off a piece to use for scoreboard
|
||
ServerDetails.HSplitTop(110.0f, &ServerDetails, &ServerScoreBoard);
|
||
|
||
// server details
|
||
const float FontSize = 12.0f;
|
||
ServerDetails.HSplitTop(ms_ListheaderHeight, &ServerHeader, &ServerDetails);
|
||
ServerHeader.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_T, 4.0f);
|
||
ServerDetails.Draw(ColorRGBA(0, 0, 0, 0.15f), IGraphics::CORNER_B, 4.0f);
|
||
UI()->DoLabel(&ServerHeader, Localize("Server details"), FontSize + 2.0f, TEXTALIGN_MC);
|
||
|
||
if(pSelectedServer)
|
||
{
|
||
ServerDetails.Margin(5.0f, &ServerDetails);
|
||
|
||
CUIRect Row;
|
||
static CLocConstString s_aLabels[] = {
|
||
"Version", // Localize - these strings are localized within CLocConstString
|
||
"Game type",
|
||
"Ping"};
|
||
|
||
// copy info button
|
||
{
|
||
CUIRect Button;
|
||
ServerDetails.HSplitBottom(15.0f, &ServerDetails, &Button);
|
||
static CButtonContainer s_CopyButton;
|
||
if(DoButton_Menu(&s_CopyButton, Localize("Copy info"), 0, &Button))
|
||
{
|
||
char aInfo[256];
|
||
pSelectedServer->InfoToString(aInfo, sizeof(aInfo));
|
||
Input()->SetClipboardText(aInfo);
|
||
}
|
||
}
|
||
|
||
ServerDetails.HSplitBottom(2.5f, &ServerDetails, nullptr);
|
||
|
||
// favorite checkbox
|
||
{
|
||
CUIRect Button;
|
||
ServerDetails.HSplitBottom(20.0f, &ServerDetails, &Button);
|
||
CUIRect ButtonAddFav;
|
||
CUIRect ButtonLeakIp;
|
||
Button.VSplitMid(&ButtonAddFav, &ButtonLeakIp);
|
||
static int s_AddFavButton = 0;
|
||
static int s_LeakIpButton = 0;
|
||
if(DoButton_CheckBox_Tristate(&s_AddFavButton, Localize("Favorite"), pSelectedServer->m_Favorite, &ButtonAddFav))
|
||
{
|
||
if(pSelectedServer->m_Favorite != TRISTATE::NONE)
|
||
{
|
||
Favorites()->Remove(pSelectedServer->m_aAddresses, pSelectedServer->m_NumAddresses);
|
||
}
|
||
else
|
||
{
|
||
Favorites()->Add(pSelectedServer->m_aAddresses, pSelectedServer->m_NumAddresses);
|
||
if(g_Config.m_UiPage == PAGE_LAN)
|
||
{
|
||
Favorites()->AllowPing(pSelectedServer->m_aAddresses, pSelectedServer->m_NumAddresses, true);
|
||
}
|
||
}
|
||
Client()->ServerBrowserUpdate();
|
||
}
|
||
if(pSelectedServer->m_Favorite != TRISTATE::NONE)
|
||
{
|
||
if(DoButton_CheckBox_Tristate(&s_LeakIpButton, Localize("Leak IP"), pSelectedServer->m_FavoriteAllowPing, &ButtonLeakIp))
|
||
{
|
||
Favorites()->AllowPing(pSelectedServer->m_aAddresses, pSelectedServer->m_NumAddresses, pSelectedServer->m_FavoriteAllowPing == TRISTATE::NONE);
|
||
}
|
||
Client()->ServerBrowserUpdate();
|
||
}
|
||
}
|
||
|
||
CUIRect LeftColumn, RightColumn;
|
||
ServerDetails.VSplitLeft(80.0f, &LeftColumn, &RightColumn);
|
||
|
||
for(auto &Label : s_aLabels)
|
||
{
|
||
LeftColumn.HSplitTop(15.0f, &Row, &LeftColumn);
|
||
UI()->DoLabel(&Row, Label, FontSize, TEXTALIGN_ML);
|
||
}
|
||
|
||
RightColumn.HSplitTop(15.0f, &Row, &RightColumn);
|
||
UI()->DoLabel(&Row, pSelectedServer->m_aVersion, FontSize, TEXTALIGN_ML);
|
||
|
||
RightColumn.HSplitTop(15.0f, &Row, &RightColumn);
|
||
UI()->DoLabel(&Row, pSelectedServer->m_aGameType, FontSize, TEXTALIGN_ML);
|
||
|
||
char aTemp[16];
|
||
FormatServerbrowserPing(aTemp, sizeof(aTemp), pSelectedServer);
|
||
RightColumn.HSplitTop(15.0f, &Row, &RightColumn);
|
||
UI()->DoLabel(&Row, aTemp, FontSize, TEXTALIGN_ML);
|
||
}
|
||
else
|
||
{
|
||
UI()->DoLabel(&ServerDetails, Localize("No server selected"), FontSize, TEXTALIGN_MC);
|
||
}
|
||
|
||
// server scoreboard
|
||
ServerScoreBoard.HSplitBottom(23.0f, &ServerScoreBoard, 0x0);
|
||
|
||
CTextCursor Cursor;
|
||
if(pSelectedServer)
|
||
{
|
||
static CListBox s_ListBox;
|
||
s_ListBox.DoAutoSpacing(1.0f);
|
||
s_ListBox.DoStart(25.0f, pSelectedServer->m_NumReceivedClients, 1, 3, -1, &ServerScoreBoard);
|
||
|
||
int ClientScoreKind = pSelectedServer->m_ClientScoreKind;
|
||
|
||
for(int i = 0; i < pSelectedServer->m_NumReceivedClients; i++)
|
||
{
|
||
const CServerInfo::CClient &CurrentClient = pSelectedServer->m_aClients[i];
|
||
const CListboxItem Item = s_ListBox.DoNextItem(&CurrentClient);
|
||
|
||
if(!Item.m_Visible)
|
||
continue;
|
||
|
||
const bool HasTeeToRender = pSelectedServer->m_aClients[i].m_aSkin[0] != '\0';
|
||
|
||
CUIRect Skin, Name, Clan, Score, Flag;
|
||
Name = Item.m_Rect;
|
||
|
||
ColorRGBA Color = CurrentClient.m_FriendState == IFriends::FRIEND_NO ?
|
||
ColorRGBA(1.0f, 1.0f, 1.0f, (i % 2 + 1) * 0.05f) :
|
||
ColorRGBA(0.5f, 1.0f, 0.5f, 0.15f + (i % 2 + 1) * 0.05f);
|
||
Name.Draw(Color, IGraphics::CORNER_ALL, 4.0f);
|
||
Name.VSplitLeft(1.0f, nullptr, &Name);
|
||
Name.VSplitLeft(34.0f, &Score, &Name);
|
||
Name.VSplitLeft(18.0f, &Skin, &Name);
|
||
Name.VSplitRight(20.0, &Name, &Flag);
|
||
Flag.HMargin(4.0f, &Flag);
|
||
Name.HSplitTop(12.0f, &Name, &Clan);
|
||
|
||
// score
|
||
char aTemp[16];
|
||
|
||
if(!CurrentClient.m_Player)
|
||
{
|
||
str_copy(aTemp, "SPEC");
|
||
}
|
||
else if(ClientScoreKind == CServerInfo::CLIENT_SCORE_KIND_POINTS)
|
||
{
|
||
str_format(aTemp, sizeof(aTemp), "%d", CurrentClient.m_Score);
|
||
}
|
||
else
|
||
{
|
||
std::optional<int> Time = {};
|
||
|
||
if(ClientScoreKind == CServerInfo::CLIENT_SCORE_KIND_TIME_BACKCOMPAT)
|
||
{
|
||
int TempTime = absolute(CurrentClient.m_Score);
|
||
if(TempTime != 0 && TempTime != 9999)
|
||
Time = TempTime;
|
||
}
|
||
else
|
||
{
|
||
// CServerInfo::CLIENT_SCORE_KIND_POINTS
|
||
if(CurrentClient.m_Score >= 0)
|
||
Time = CurrentClient.m_Score;
|
||
}
|
||
|
||
if(Time.has_value())
|
||
{
|
||
str_time((int64_t)Time.value() * 100, TIME_HOURS, aTemp, sizeof(aTemp));
|
||
}
|
||
else
|
||
{
|
||
aTemp[0] = 0;
|
||
}
|
||
}
|
||
|
||
UI()->DoLabel(&Score, aTemp, FontSize, TEXTALIGN_ML);
|
||
|
||
// render tee if available
|
||
if(HasTeeToRender)
|
||
{
|
||
CTeeRenderInfo TeeInfo;
|
||
const CSkin *pSkin = m_pClient->m_Skins.Find(CurrentClient.m_aSkin);
|
||
TeeInfo.m_OriginalRenderSkin = pSkin->m_OriginalSkin;
|
||
TeeInfo.m_ColorableRenderSkin = pSkin->m_ColorableSkin;
|
||
TeeInfo.m_SkinMetrics = pSkin->m_Metrics;
|
||
TeeInfo.m_CustomColoredSkin = CurrentClient.m_CustomSkinColors;
|
||
if(CurrentClient.m_CustomSkinColors)
|
||
{
|
||
TeeInfo.m_ColorBody = color_cast<ColorRGBA>(ColorHSLA(CurrentClient.m_CustomSkinColorBody).UnclampLighting());
|
||
TeeInfo.m_ColorFeet = color_cast<ColorRGBA>(ColorHSLA(CurrentClient.m_CustomSkinColorFeet).UnclampLighting());
|
||
}
|
||
else
|
||
{
|
||
TeeInfo.m_ColorBody = ColorRGBA(1.0f, 1.0f, 1.0f);
|
||
TeeInfo.m_ColorFeet = ColorRGBA(1.0f, 1.0f, 1.0f);
|
||
}
|
||
TeeInfo.m_Size = minimum(Skin.w, Skin.h);
|
||
|
||
const CAnimState *pIdleState = CAnimState::GetIdle();
|
||
vec2 OffsetToMid;
|
||
RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid);
|
||
vec2 TeeRenderPos(Skin.x + TeeInfo.m_Size / 2, Skin.y + Skin.h / 2 + OffsetToMid.y);
|
||
|
||
RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos);
|
||
}
|
||
|
||
// name
|
||
TextRender()->SetCursor(&Cursor, Name.x, Name.y + (Name.h - (FontSize - 2)) / 2.f, FontSize - 2, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
|
||
Cursor.m_LineWidth = Name.w;
|
||
const char *pName = CurrentClient.m_aName;
|
||
bool Printed = false;
|
||
if(g_Config.m_BrFilterString[0])
|
||
Printed = PrintHighlighted(pName, [this, &Cursor, pName](const char *pFilteredStr, const int FilterLen) {
|
||
TextRender()->TextEx(&Cursor, pName, (int)(pFilteredStr - pName));
|
||
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f);
|
||
TextRender()->TextEx(&Cursor, pFilteredStr, FilterLen);
|
||
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||
TextRender()->TextEx(&Cursor, pFilteredStr + FilterLen, -1);
|
||
});
|
||
if(!Printed)
|
||
TextRender()->TextEx(&Cursor, pName, -1);
|
||
|
||
// clan
|
||
TextRender()->SetCursor(&Cursor, Clan.x, Clan.y + (Clan.h - (FontSize - 2)) / 2.f, FontSize - 2, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
|
||
Cursor.m_LineWidth = Clan.w;
|
||
const char *pClan = CurrentClient.m_aClan;
|
||
Printed = false;
|
||
if(g_Config.m_BrFilterString[0])
|
||
Printed = PrintHighlighted(pClan, [this, &Cursor, pClan](const char *pFilteredStr, const int FilterLen) {
|
||
TextRender()->TextEx(&Cursor, pClan, (int)(pFilteredStr - pClan));
|
||
TextRender()->TextColor(0.4f, 0.4f, 1.0f, 1.0f);
|
||
TextRender()->TextEx(&Cursor, pFilteredStr, FilterLen);
|
||
TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||
TextRender()->TextEx(&Cursor, pFilteredStr + FilterLen, -1);
|
||
});
|
||
if(!Printed)
|
||
TextRender()->TextEx(&Cursor, pClan, -1);
|
||
|
||
// flag
|
||
m_pClient->m_CountryFlags.Render(CurrentClient.m_Country, ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f),
|
||
Flag.x, Flag.y + ((Flag.h - Flag.w / 2) / 2), Flag.w, Flag.w / 2);
|
||
}
|
||
|
||
const int NewSelected = s_ListBox.DoEnd();
|
||
if(s_ListBox.WasItemSelected())
|
||
{
|
||
const CServerInfo::CClient &SelectedClient = pSelectedServer->m_aClients[NewSelected];
|
||
if(SelectedClient.m_FriendState == IFriends::FRIEND_PLAYER)
|
||
m_pClient->Friends()->RemoveFriend(SelectedClient.m_aName, SelectedClient.m_aClan);
|
||
else
|
||
m_pClient->Friends()->AddFriend(SelectedClient.m_aName, SelectedClient.m_aClan);
|
||
FriendlistOnUpdate();
|
||
Client()->ServerBrowserUpdate();
|
||
}
|
||
}
|
||
}
|
||
|
||
template<typename F>
|
||
bool CMenus::PrintHighlighted(const char *pName, F &&PrintFn)
|
||
{
|
||
const char *pStr = g_Config.m_BrFilterString;
|
||
char aFilterStr[sizeof(g_Config.m_BrFilterString)];
|
||
while((pStr = str_next_token(pStr, IServerBrowser::SEARCH_EXCLUDE_TOKEN, aFilterStr, sizeof(aFilterStr))))
|
||
{
|
||
// highlight the parts that matches
|
||
const char *pFilteredStr;
|
||
int FilterLen = str_length(aFilterStr);
|
||
if(aFilterStr[0] == '"' && aFilterStr[FilterLen - 1] == '"')
|
||
{
|
||
aFilterStr[FilterLen - 1] = '\0';
|
||
pFilteredStr = str_comp(pName, &aFilterStr[1]) == 0 ? pName : nullptr;
|
||
FilterLen -= 2;
|
||
}
|
||
else
|
||
{
|
||
pFilteredStr = str_utf8_find_nocase(pName, aFilterStr);
|
||
}
|
||
if(pFilteredStr)
|
||
{
|
||
PrintFn(pFilteredStr, FilterLen);
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
void CMenus::FriendlistOnUpdate()
|
||
{
|
||
// TODO: friends are currently updated every frame; optimize and only update friends when necessary
|
||
}
|
||
|
||
void CMenus::RenderServerbrowserFriends(CUIRect View)
|
||
{
|
||
const float FontSize = 10.0f;
|
||
static bool s_aListExtended[NUM_FRIEND_TYPES] = {true, true, false};
|
||
static const ColorRGBA s_aListColors[NUM_FRIEND_TYPES] = {ColorRGBA(0.5f, 1.0f, 0.5f, 1.0f), ColorRGBA(0.4f, 0.4f, 1.0f, 1.0f), ColorRGBA(1.0f, 0.5f, 0.5f, 1.0f)};
|
||
const ColorRGBA OfflineClanColor = ColorRGBA(0.7f, 0.45f, 0.75f, 1.0f);
|
||
const float SpacingH = 2.0f;
|
||
|
||
char aBuf[256];
|
||
CUIRect ServerFriends, FilterHeader, List;
|
||
|
||
// header
|
||
View.HSplitTop(ms_ListheaderHeight, &FilterHeader, &View);
|
||
FilterHeader.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_T, 4.0f);
|
||
View.Draw(ColorRGBA(0, 0, 0, 0.15f), 0, 4.0f);
|
||
UI()->DoLabel(&FilterHeader, Localize("Friends"), FontSize + 4.0f, TEXTALIGN_MC);
|
||
|
||
View.HSplitBottom(84.0f, &List, &ServerFriends);
|
||
List.HSplitTop(3.0f, nullptr, &List);
|
||
List.VSplitLeft(3.0f, nullptr, &List);
|
||
|
||
// calculate friends
|
||
// TODO: optimize this
|
||
m_pRemoveFriend = nullptr;
|
||
for(auto &vFriends : m_avFriends)
|
||
vFriends.clear();
|
||
|
||
for(int FriendIndex = 0; FriendIndex < m_pClient->Friends()->NumFriends(); ++FriendIndex)
|
||
{
|
||
m_avFriends[FRIEND_OFF].emplace_back(m_pClient->Friends()->GetFriend(FriendIndex));
|
||
}
|
||
|
||
for(int ServerIndex = 0; ServerIndex < ServerBrowser()->NumSortedServers(); ++ServerIndex)
|
||
{
|
||
const CServerInfo *pEntry = ServerBrowser()->SortedGet(ServerIndex);
|
||
if(pEntry->m_FriendState == IFriends::FRIEND_NO)
|
||
continue;
|
||
|
||
for(int ClientIndex = 0; ClientIndex < pEntry->m_NumClients; ++ClientIndex)
|
||
{
|
||
const CServerInfo::CClient &CurrentClient = pEntry->m_aClients[ClientIndex];
|
||
if(CurrentClient.m_FriendState == IFriends::FRIEND_NO)
|
||
continue;
|
||
|
||
const int FriendIndex = CurrentClient.m_FriendState == IFriends::FRIEND_PLAYER ? FRIEND_PLAYER_ON : FRIEND_CLAN_ON;
|
||
m_avFriends[FriendIndex].emplace_back(CurrentClient, pEntry);
|
||
const auto &&RemovalPredicate = [CurrentClient](const CFriendItem &Friend) {
|
||
return (Friend.Name()[0] == '\0' || str_comp(Friend.Name(), CurrentClient.m_aName) == 0) && ((Friend.Name()[0] != '\0' && g_Config.m_ClFriendsIgnoreClan) || str_comp(Friend.Clan(), CurrentClient.m_aClan) == 0);
|
||
};
|
||
m_avFriends[FRIEND_OFF].erase(std::remove_if(m_avFriends[FRIEND_OFF].begin(), m_avFriends[FRIEND_OFF].end(), RemovalPredicate), m_avFriends[FRIEND_OFF].end());
|
||
}
|
||
}
|
||
for(auto &vFriends : m_avFriends)
|
||
std::sort(vFriends.begin(), vFriends.end());
|
||
|
||
// friends list
|
||
static CScrollRegion s_ScrollRegion;
|
||
if(!s_ScrollRegion.IsScrollbarShown())
|
||
List.VSplitRight(3.0f, &List, nullptr);
|
||
vec2 ScrollOffset(0.0f, 0.0f);
|
||
CScrollRegionParams ScrollParams;
|
||
ScrollParams.m_ScrollbarWidth = 14.0f;
|
||
ScrollParams.m_ScrollbarMargin = 4.0f;
|
||
ScrollParams.m_ScrollUnit = 80.0f;
|
||
s_ScrollRegion.Begin(&List, &ScrollOffset, &ScrollParams);
|
||
List.y += ScrollOffset.y;
|
||
|
||
for(size_t FriendType = 0; FriendType < NUM_FRIEND_TYPES; ++FriendType)
|
||
{
|
||
// header
|
||
CUIRect Header, GroupIcon, GroupLabel;
|
||
List.HSplitTop(ms_ListheaderHeight, &Header, &List);
|
||
s_ScrollRegion.AddRect(Header);
|
||
Header.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, UI()->MouseHovered(&Header) ? 0.4f : 0.25f), IGraphics::CORNER_ALL, 5.0f);
|
||
Header.VSplitLeft(Header.h, &GroupIcon, &GroupLabel);
|
||
GroupIcon.Margin(2.0f, &GroupIcon);
|
||
TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT));
|
||
TextRender()->TextColor(UI()->MouseHovered(&Header) ? TextRender()->DefaultTextColor() : ColorRGBA(0.6f, 0.6f, 0.6f, 1.0f));
|
||
UI()->DoLabel(&GroupIcon, s_aListExtended[FriendType] ? FONT_ICON_SQUARE_MINUS : FONT_ICON_SQUARE_PLUS, GroupIcon.h * CUI::ms_FontmodHeight, TEXTALIGN_MC);
|
||
TextRender()->TextColor(TextRender()->DefaultTextColor());
|
||
TextRender()->SetCurFont(nullptr);
|
||
switch(FriendType)
|
||
{
|
||
case FRIEND_PLAYER_ON:
|
||
str_format(aBuf, sizeof(aBuf), Localize("Online players (%d)"), (int)m_avFriends[FriendType].size());
|
||
break;
|
||
case FRIEND_CLAN_ON:
|
||
str_format(aBuf, sizeof(aBuf), Localize("Online clanmates (%d)"), (int)m_avFriends[FriendType].size());
|
||
break;
|
||
case FRIEND_OFF:
|
||
str_format(aBuf, sizeof(aBuf), Localize("Offline (%d)", "friends (server browser)"), (int)m_avFriends[FriendType].size());
|
||
break;
|
||
default:
|
||
dbg_assert(false, "FriendType invalid");
|
||
break;
|
||
}
|
||
UI()->DoLabel(&GroupLabel, aBuf, FontSize, TEXTALIGN_ML);
|
||
if(UI()->DoButtonLogic(&s_aListExtended[FriendType], 0, &Header))
|
||
{
|
||
s_aListExtended[FriendType] = !s_aListExtended[FriendType];
|
||
}
|
||
|
||
// entries
|
||
if(s_aListExtended[FriendType])
|
||
{
|
||
for(size_t FriendIndex = 0; FriendIndex < m_avFriends[FriendType].size(); ++FriendIndex)
|
||
{
|
||
// space
|
||
{
|
||
CUIRect Space;
|
||
List.HSplitTop(SpacingH, &Space, &List);
|
||
s_ScrollRegion.AddRect(Space);
|
||
}
|
||
|
||
CUIRect Rect;
|
||
const auto &Friend = m_avFriends[FriendType][FriendIndex];
|
||
List.HSplitTop(11.0f + 10.0f + 2 * 2.0f + 1.0f + (Friend.ServerInfo() == nullptr ? 0.0f : 10.0f), &Rect, &List);
|
||
s_ScrollRegion.AddRect(Rect);
|
||
if(s_ScrollRegion.IsRectClipped(Rect))
|
||
continue;
|
||
|
||
const bool Inside = UI()->MouseHovered(&Rect);
|
||
bool ButtonResult = false;
|
||
if(Friend.ServerInfo())
|
||
{
|
||
ButtonResult = UI()->DoButtonLogic(Friend.ListItemId(), 0, &Rect);
|
||
GameClient()->m_Tooltips.DoToolTip(Friend.ListItemId(), &Rect, Localize("Click to select server. Double click to join your friend."));
|
||
}
|
||
const bool OfflineClan = Friend.FriendState() == IFriends::FRIEND_CLAN && FriendType == FRIEND_OFF;
|
||
Rect.Draw((OfflineClan ? OfflineClanColor : s_aListColors[FriendType]).WithAlpha(Inside ? 0.5f : 0.3f), IGraphics::CORNER_ALL, 5.0f);
|
||
Rect.Margin(2.0f, &Rect);
|
||
|
||
CUIRect RemoveButton, NameLabel, ClanLabel, InfoLabel;
|
||
Rect.HSplitTop(16.0f, &RemoveButton, nullptr);
|
||
RemoveButton.VSplitRight(13.0f, nullptr, &RemoveButton);
|
||
RemoveButton.HMargin((RemoveButton.h - RemoveButton.w) / 2.0f, &RemoveButton);
|
||
Rect.VSplitLeft(2.0f, nullptr, &Rect);
|
||
|
||
if(Friend.ServerInfo())
|
||
Rect.HSplitBottom(10.0f, &Rect, &InfoLabel);
|
||
Rect.HSplitTop(11.0f + 10.0f, &Rect, nullptr);
|
||
|
||
// tee
|
||
if(Friend.Skin()[0] != '\0')
|
||
{
|
||
CUIRect Skin;
|
||
Rect.VSplitLeft(Rect.h, &Skin, &Rect);
|
||
Rect.VSplitLeft(2.0f, nullptr, &Rect);
|
||
|
||
CTeeRenderInfo TeeInfo;
|
||
const CSkin *pSkin = m_pClient->m_Skins.Find(Friend.Skin());
|
||
TeeInfo.m_OriginalRenderSkin = pSkin->m_OriginalSkin;
|
||
TeeInfo.m_ColorableRenderSkin = pSkin->m_ColorableSkin;
|
||
TeeInfo.m_SkinMetrics = pSkin->m_Metrics;
|
||
TeeInfo.m_CustomColoredSkin = Friend.CustomSkinColors();
|
||
if(Friend.CustomSkinColors())
|
||
{
|
||
TeeInfo.m_ColorBody = color_cast<ColorRGBA>(ColorHSLA(Friend.CustomSkinColorBody()).UnclampLighting());
|
||
TeeInfo.m_ColorFeet = color_cast<ColorRGBA>(ColorHSLA(Friend.CustomSkinColorFeet()).UnclampLighting());
|
||
}
|
||
else
|
||
{
|
||
TeeInfo.m_ColorBody = ColorRGBA(1.0f, 1.0f, 1.0f);
|
||
TeeInfo.m_ColorFeet = ColorRGBA(1.0f, 1.0f, 1.0f);
|
||
}
|
||
TeeInfo.m_Size = minimum(Skin.w, Skin.h);
|
||
|
||
const CAnimState *pIdleState = CAnimState::GetIdle();
|
||
vec2 OffsetToMid;
|
||
RenderTools()->GetRenderTeeOffsetToRenderedTee(pIdleState, &TeeInfo, OffsetToMid);
|
||
vec2 TeeRenderPos(Skin.x + Skin.w / 2.0f, Skin.y + Skin.h * 0.55f + OffsetToMid.y);
|
||
|
||
RenderTools()->RenderTee(pIdleState, &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), TeeRenderPos);
|
||
}
|
||
Rect.HSplitTop(11.0f, &NameLabel, &ClanLabel);
|
||
|
||
// name
|
||
UI()->DoLabel(&NameLabel, Friend.Name(), FontSize - 1.0f, TEXTALIGN_ML);
|
||
|
||
// clan
|
||
UI()->DoLabel(&ClanLabel, Friend.Clan(), FontSize - 2.0f, TEXTALIGN_ML);
|
||
|
||
// server info
|
||
if(Friend.ServerInfo())
|
||
{
|
||
// official server icon
|
||
if(Friend.ServerInfo()->m_Official)
|
||
{
|
||
CUIRect OfficialIcon;
|
||
InfoLabel.VSplitLeft(InfoLabel.h, &OfficialIcon, &InfoLabel);
|
||
InfoLabel.VSplitLeft(1.0f, nullptr, &InfoLabel); // spacing
|
||
OfficialIcon.HSplitTop(1.0f, nullptr, &OfficialIcon); // alignment
|
||
|
||
SLabelProperties Props;
|
||
Props.m_EnableWidthCheck = false;
|
||
TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT));
|
||
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
|
||
TextRender()->TextColor(0.4f, 0.7f, 0.94f, 1.0f);
|
||
TextRender()->TextOutlineColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||
UI()->DoLabel(&OfficialIcon, FONT_ICON_CERTIFICATE, OfficialIcon.h, TEXTALIGN_MC, Props);
|
||
TextRender()->TextColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||
TextRender()->TextOutlineColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||
UI()->DoLabel(&OfficialIcon, FONT_ICON_CHECK, OfficialIcon.h * 0.5f, TEXTALIGN_MC, Props);
|
||
TextRender()->TextColor(TextRender()->DefaultTextColor());
|
||
TextRender()->TextOutlineColor(TextRender()->DefaultTextOutlineColor());
|
||
TextRender()->SetRenderFlags(0);
|
||
TextRender()->SetCurFont(nullptr);
|
||
}
|
||
|
||
// server info text
|
||
char aLatency[16];
|
||
FormatServerbrowserPing(aLatency, sizeof(aLatency), Friend.ServerInfo());
|
||
if(aLatency[0] != '\0')
|
||
str_format(aBuf, sizeof(aBuf), "%s | %s | %s", Friend.ServerInfo()->m_aMap, Friend.ServerInfo()->m_aGameType, aLatency);
|
||
else
|
||
str_format(aBuf, sizeof(aBuf), "%s | %s", Friend.ServerInfo()->m_aMap, Friend.ServerInfo()->m_aGameType);
|
||
UI()->DoLabel(&InfoLabel, aBuf, FontSize - 2.0f, TEXTALIGN_ML);
|
||
}
|
||
|
||
// remove button
|
||
TextRender()->TextColor(UI()->MouseHovered(&RemoveButton) ? TextRender()->DefaultTextColor() : ColorRGBA(0.4f, 0.4f, 0.4f, 1.0f));
|
||
TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE);
|
||
UI()->DoLabel(&RemoveButton, "×", RemoveButton.h * CUI::ms_FontmodHeight, TEXTALIGN_MC);
|
||
TextRender()->SetRenderFlags(0);
|
||
TextRender()->TextColor(TextRender()->DefaultTextColor());
|
||
if(UI()->DoButtonLogic(Friend.RemoveButtonId(), 0, &RemoveButton))
|
||
{
|
||
m_pRemoveFriend = &Friend;
|
||
ButtonResult = false;
|
||
}
|
||
GameClient()->m_Tooltips.DoToolTip(Friend.RemoveButtonId(), &RemoveButton, Friend.FriendState() == IFriends::FRIEND_PLAYER ? Localize("Click to remove this player from your friends list.") : Localize("Click to remove this clan from your friends list."));
|
||
|
||
// handle click and double click on item
|
||
if(ButtonResult && Friend.ServerInfo())
|
||
{
|
||
str_copy(g_Config.m_UiServerAddress, Friend.ServerInfo()->m_aAddress);
|
||
if(Input()->MouseDoubleClick())
|
||
{
|
||
Connect(g_Config.m_UiServerAddress);
|
||
}
|
||
}
|
||
}
|
||
|
||
if(m_avFriends[FriendType].empty())
|
||
{
|
||
CUIRect Label;
|
||
List.HSplitTop(12.0f, &Label, &List);
|
||
s_ScrollRegion.AddRect(Label);
|
||
UI()->DoLabel(&Label, Localize("None"), Label.h * CUI::ms_FontmodHeight, TEXTALIGN_ML);
|
||
}
|
||
}
|
||
|
||
// space
|
||
{
|
||
CUIRect Space;
|
||
List.HSplitTop(SpacingH, &Space, &List);
|
||
s_ScrollRegion.AddRect(Space);
|
||
}
|
||
}
|
||
s_ScrollRegion.End();
|
||
|
||
if(m_pRemoveFriend != nullptr)
|
||
{
|
||
char aMessage[256];
|
||
str_format(aMessage, sizeof(aMessage),
|
||
m_pRemoveFriend->FriendState() == IFriends::FRIEND_PLAYER ? Localize("Are you sure that you want to remove the player '%s' from your friends list?") : Localize("Are you sure that you want to remove the clan '%s' from your friends list?"),
|
||
m_pRemoveFriend->FriendState() == IFriends::FRIEND_PLAYER ? m_pRemoveFriend->Name() : m_pRemoveFriend->Clan());
|
||
PopupConfirm(Localize("Remove friend"), aMessage, Localize("Yes"), Localize("No"), &CMenus::PopupConfirmRemoveFriend);
|
||
}
|
||
|
||
// add friend
|
||
if(m_pClient->Friends()->NumFriends() < IFriends::MAX_FRIENDS)
|
||
{
|
||
CUIRect Button;
|
||
ServerFriends.Margin(3.0f, &ServerFriends);
|
||
|
||
ServerFriends.HSplitTop(18.0f, &Button, &ServerFriends);
|
||
str_format(aBuf, sizeof(aBuf), "%s:", Localize("Name"));
|
||
UI()->DoLabel(&Button, aBuf, FontSize + 2.0f, TEXTALIGN_ML);
|
||
Button.VSplitLeft(80.0f, nullptr, &Button);
|
||
static CLineInputBuffered<MAX_NAME_LENGTH> s_NameInput;
|
||
UI()->DoEditBox(&s_NameInput, &Button, FontSize + 2.0f);
|
||
|
||
ServerFriends.HSplitTop(3.0f, nullptr, &ServerFriends);
|
||
ServerFriends.HSplitTop(18.0f, &Button, &ServerFriends);
|
||
str_format(aBuf, sizeof(aBuf), "%s:", Localize("Clan"));
|
||
UI()->DoLabel(&Button, aBuf, FontSize + 2.0f, TEXTALIGN_ML);
|
||
Button.VSplitLeft(80.0f, nullptr, &Button);
|
||
static CLineInputBuffered<MAX_CLAN_LENGTH> s_ClanInput;
|
||
UI()->DoEditBox(&s_ClanInput, &Button, FontSize + 2.0f);
|
||
|
||
ServerFriends.HSplitTop(3.0f, nullptr, &ServerFriends);
|
||
ServerFriends.HSplitTop(18.0f, &Button, &ServerFriends);
|
||
static CButtonContainer s_AddButton;
|
||
if(DoButton_Menu(&s_AddButton, s_NameInput.IsEmpty() && !s_ClanInput.IsEmpty() ? Localize("Add Clan") : Localize("Add Friend"), 0, &Button))
|
||
{
|
||
m_pClient->Friends()->AddFriend(s_NameInput.GetString(), s_ClanInput.GetString());
|
||
s_NameInput.Clear();
|
||
s_ClanInput.Clear();
|
||
FriendlistOnUpdate();
|
||
Client()->ServerBrowserUpdate();
|
||
}
|
||
}
|
||
}
|
||
|
||
void CMenus::PopupConfirmRemoveFriend()
|
||
{
|
||
m_pClient->Friends()->RemoveFriend(m_pRemoveFriend->Name(), m_pRemoveFriend->Clan());
|
||
FriendlistOnUpdate();
|
||
Client()->ServerBrowserUpdate();
|
||
m_pRemoveFriend = nullptr;
|
||
}
|
||
|
||
void CMenus::RenderServerbrowser(CUIRect MainView)
|
||
{
|
||
/*
|
||
+-----------------+ +-------+
|
||
| | | |
|
||
| | | tool |
|
||
| server list | | box |
|
||
| | | |
|
||
| | | |
|
||
+-----------------+ | |
|
||
status box tab +-------+
|
||
*/
|
||
|
||
CUIRect ServerList, ToolBox;
|
||
|
||
// background
|
||
MainView.Draw(ms_ColorTabbarActive, IGraphics::CORNER_B, 10.0f);
|
||
MainView.Margin(10.0f, &MainView);
|
||
|
||
// create server list, status box, tab bar and tool box area
|
||
MainView.VSplitRight(205.0f, &ServerList, &ToolBox);
|
||
ServerList.VSplitRight(5.0f, &ServerList, 0);
|
||
|
||
// server list
|
||
{
|
||
RenderServerbrowserServerList(ServerList);
|
||
}
|
||
|
||
int ToolboxPage = g_Config.m_UiToolboxPage;
|
||
|
||
// tool box
|
||
{
|
||
ToolBox.Draw(ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), IGraphics::CORNER_ALL, 4.0f);
|
||
|
||
if(ToolboxPage == 0)
|
||
RenderServerbrowserFilters(ToolBox);
|
||
else if(ToolboxPage == 1)
|
||
RenderServerbrowserServerDetail(ToolBox);
|
||
else if(ToolboxPage == 2)
|
||
RenderServerbrowserFriends(ToolBox);
|
||
}
|
||
|
||
// tab bar
|
||
{
|
||
CUIRect TabBar;
|
||
ToolBox.HSplitBottom(18, &ToolBox, &TabBar);
|
||
CUIRect TabButton0, TabButton1, TabButton2;
|
||
float CurTabBarWidth = ToolBox.w;
|
||
TabBar.VSplitLeft(0.333f * CurTabBarWidth, &TabButton0, &TabBar);
|
||
TabBar.VSplitLeft(0.333f * CurTabBarWidth, &TabButton1, &TabBar);
|
||
TabBar.VSplitLeft(0.333f * CurTabBarWidth, &TabButton2, &TabBar);
|
||
ColorRGBA Active = ms_ColorTabbarActive;
|
||
ColorRGBA InActive = ms_ColorTabbarInactive;
|
||
ms_ColorTabbarActive = ColorRGBA(0.0f, 0.0f, 0.0f, 0.3f);
|
||
ms_ColorTabbarInactive = ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f);
|
||
|
||
static CButtonContainer s_FiltersTab;
|
||
if(DoButton_MenuTab(&s_FiltersTab, Localize("Filter"), ToolboxPage == 0, &TabButton0, IGraphics::CORNER_BL, NULL, NULL, NULL, NULL, 4.0f))
|
||
ToolboxPage = 0;
|
||
|
||
static CButtonContainer s_InfoTab;
|
||
if(DoButton_MenuTab(&s_InfoTab, Localize("Info"), ToolboxPage == 1, &TabButton1, IGraphics::CORNER_NONE, NULL, NULL, NULL, NULL, 4.0f))
|
||
ToolboxPage = 1;
|
||
|
||
static CButtonContainer s_FriendsTab;
|
||
if(DoButton_MenuTab(&s_FriendsTab, Localize("Friends"), ToolboxPage == 2, &TabButton2, IGraphics::CORNER_BR, NULL, NULL, NULL, NULL, 4.0f))
|
||
ToolboxPage = 2;
|
||
|
||
ms_ColorTabbarActive = Active;
|
||
ms_ColorTabbarInactive = InActive;
|
||
g_Config.m_UiToolboxPage = ToolboxPage;
|
||
}
|
||
}
|
||
|
||
void CMenus::ConchainFriendlistUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
|
||
{
|
||
pfnCallback(pResult, pCallbackUserData);
|
||
if(pResult->NumArguments() == 2 && ((CMenus *)pUserData)->Client()->State() == IClient::STATE_OFFLINE)
|
||
{
|
||
((CMenus *)pUserData)->FriendlistOnUpdate();
|
||
((CMenus *)pUserData)->Client()->ServerBrowserUpdate();
|
||
}
|
||
}
|
||
|
||
void CMenus::ConchainServerbrowserUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
|
||
{
|
||
pfnCallback(pResult, pCallbackUserData);
|
||
if(pResult->NumArguments() && g_Config.m_UiPage == PAGE_FAVORITES)
|
||
((CMenus *)pUserData)->ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES);
|
||
}
|