mirror of
https://github.com/ddnet/ddnet.git
synced 2024-10-21 08:18:18 +00:00
fa787d354e
Allow deselecting just one filter entry by right clicking the entry (country or game type) when all entries are currently selected (i.e. none are excluded). Previously the first right click always deselected all filters except the clicked one (same as the left click). Closes #6746.
1741 lines
65 KiB
C++
1741 lines
65 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;
|
||
const int FilterExcludeTypesSize = Network == IServerBrowser::NETWORK_DDNET ? sizeof(g_Config.m_BrFilterExcludeTypes) : sizeof(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')
|
||
{
|
||
if(Click == 1)
|
||
{
|
||
// Left click: when all are active, only activate one
|
||
for(int j = 0; j < MaxTypes; ++j)
|
||
{
|
||
if(j != TypeIndex)
|
||
ServerBrowser()->DDNetFilterAdd(pFilterExcludeTypes, FilterExcludeTypesSize, ServerBrowser()->GetType(Network, j));
|
||
}
|
||
}
|
||
else if(Click == 2)
|
||
{
|
||
// Right click: when all are active, only deactivate one
|
||
ServerBrowser()->DDNetFilterAdd(pFilterExcludeTypes, FilterExcludeTypesSize, ServerBrowser()->GetType(Network, TypeIndex));
|
||
}
|
||
}
|
||
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, FilterExcludeTypesSize, pName);
|
||
}
|
||
else
|
||
{
|
||
ServerBrowser()->DDNetFilterRem(pFilterExcludeTypes, FilterExcludeTypesSize, 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;
|
||
const int FilterExcludeCountriesSize = Network == IServerBrowser::NETWORK_DDNET ? sizeof(g_Config.m_BrFilterExcludeCountries) : sizeof(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')
|
||
{
|
||
if(Click == 1)
|
||
{
|
||
// Left click: when all are active, only activate one
|
||
for(int j = 0; j < MaxFlags; ++j)
|
||
{
|
||
if(j != CountryIndex)
|
||
ServerBrowser()->DDNetFilterAdd(pFilterExcludeCountries, FilterExcludeCountriesSize, ServerBrowser()->GetCountryName(Network, j));
|
||
}
|
||
}
|
||
else if(Click == 2)
|
||
{
|
||
// Right click: when all are active, only deactivate one
|
||
ServerBrowser()->DDNetFilterAdd(pFilterExcludeCountries, FilterExcludeCountriesSize, ServerBrowser()->GetCountryName(Network, CountryIndex));
|
||
}
|
||
}
|
||
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, FilterExcludeCountriesSize, pName);
|
||
}
|
||
else
|
||
{
|
||
ServerBrowser()->DDNetFilterRem(pFilterExcludeCountries, FilterExcludeCountriesSize, 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);
|
||
}
|