mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 10:08:18 +00:00
Merge #6612
6612: Port friends list UI from 0.7 r=edg-l a=Robyt3 Separate friends list into three groups, which can be expanded and collapsed: online players, online clanmates and offline friends. Friends with the same name/clan are no longer grouped together. Instead, each individual player that is online and has name/clan matching a friend is shown in either the online players or online clanmates group. Friends for which no matching players are found are shown in the offline group. Friends in the friend list can no longer be selected. Instead, left-clicking a friend selects the server that the friend is on. Double-clicking a friend joins the server that they are playing on. Render small X button in top-right corner of every friend list entry to remove the respective friend instead of using one button that removes the selected friend. Change "Add Friend" button to "Add Clan" when only clan is entered. Remove excess empty space from layout. Closes #6326. Screenshots: - Before: ![screenshot_2023-05-18_15-39-18](https://github.com/ddnet/ddnet/assets/23437060/59a32a45-7d0d-461e-b25d-75c5a6267166) - After: ![screenshot_2023-05-18_15-38-59](https://github.com/ddnet/ddnet/assets/23437060/7648debc-8919-47fe-91b1-cf2f25ebe2a7) ## Checklist - [X] Tested the change ingame - [X] Provided screenshots if it is a visual change - [X] Tested in combination with possibly related configuration options (`cl_friends_ignore_clan`) - [ ] Written a unit test (especially base/) or added coverage to integration test - [X] Considered possible null pointers and out of bounds array indexing - [X] Changed no physics that affect existing maps - [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional) Co-authored-by: Robert Müller <robytemueller@gmail.com>
This commit is contained in:
commit
5eeb3fa375
|
@ -76,6 +76,8 @@ MAYBE_UNUSED static const char *FONT_ICON_CERTIFICATE = "\xEF\x82\xA3";
|
|||
MAYBE_UNUSED static const char *FONT_ICON_FLAG_CHECKERED = "\xEF\x84\x9E";
|
||||
MAYBE_UNUSED static const char *FONT_ICON_BAN = "\xEF\x81\x9E";
|
||||
MAYBE_UNUSED static const char *FONT_ICON_CIRCLE_CHEVRON_DOWN = "\xEF\x84\xBA";
|
||||
MAYBE_UNUSED static const char *FONT_ICON_SQUARE_MINUS = "\xEF\x85\x86";
|
||||
MAYBE_UNUSED static const char *FONT_ICON_SQUARE_PLUS = "\xEF\x83\xBE";
|
||||
|
||||
MAYBE_UNUSED static const char *FONT_ICON_HOUSE = "\xEF\x80\x95";
|
||||
MAYBE_UNUSED static const char *FONT_ICON_NEWSPAPER = "\xEF\x87\xAA";
|
||||
|
|
|
@ -74,8 +74,6 @@ CMenus::CMenus()
|
|||
|
||||
str_copy(m_aCurrentDemoFolder, "demos");
|
||||
|
||||
m_FriendlistSelectedIndex = -1;
|
||||
|
||||
m_DemoPlayerState = DEMOPLAYER_NONE;
|
||||
m_Dummy = false;
|
||||
|
||||
|
|
|
@ -452,36 +452,55 @@ protected:
|
|||
static int DemolistFetchCallback(const CFsFileInfo *pInfo, int IsDir, int StorageType, void *pUser);
|
||||
|
||||
// friends
|
||||
struct CFriendItem
|
||||
class CFriendItem
|
||||
{
|
||||
const CFriendInfo *m_pFriendInfo;
|
||||
int m_NumFound;
|
||||
char m_aName[MAX_NAME_LENGTH];
|
||||
char m_aClan[MAX_CLAN_LENGTH];
|
||||
const CServerInfo *m_pServerInfo;
|
||||
int m_FriendState;
|
||||
bool m_IsPlayer;
|
||||
|
||||
public:
|
||||
CFriendItem() {}
|
||||
CFriendItem(const CFriendInfo *pFriendInfo) :
|
||||
m_pFriendInfo(pFriendInfo), m_NumFound(0)
|
||||
m_pServerInfo(nullptr), m_IsPlayer(false)
|
||||
{
|
||||
str_copy(m_aName, pFriendInfo->m_aName);
|
||||
str_copy(m_aClan, pFriendInfo->m_aClan);
|
||||
m_FriendState = m_aName[0] == '\0' ? IFriends::FRIEND_CLAN : IFriends::FRIEND_PLAYER;
|
||||
}
|
||||
CFriendItem(const char *pName, const char *pClan, const CServerInfo *pServerInfo, int FriendState, bool IsPlayer) :
|
||||
m_pServerInfo(pServerInfo), m_FriendState(FriendState), m_IsPlayer(IsPlayer)
|
||||
{
|
||||
str_copy(m_aName, pName);
|
||||
str_copy(m_aClan, pClan);
|
||||
}
|
||||
|
||||
const char *Name() const { return m_aName; }
|
||||
const char *Clan() const { return m_aClan; }
|
||||
const CServerInfo *ServerInfo() const { return m_pServerInfo; }
|
||||
int FriendState() const { return m_FriendState; }
|
||||
bool IsPlayer() const { return m_IsPlayer; }
|
||||
|
||||
const void *ListItemId() const { return &m_aName; }
|
||||
const void *RemoveButtonId() const { return &m_FriendState; }
|
||||
|
||||
bool operator<(const CFriendItem &Other) const
|
||||
{
|
||||
if(m_NumFound && !Other.m_NumFound)
|
||||
return true;
|
||||
else if(!m_NumFound && Other.m_NumFound)
|
||||
return false;
|
||||
else
|
||||
{
|
||||
int Result = str_comp(m_pFriendInfo->m_aName, Other.m_pFriendInfo->m_aName);
|
||||
if(Result)
|
||||
return Result < 0;
|
||||
else
|
||||
return str_comp(m_pFriendInfo->m_aClan, Other.m_pFriendInfo->m_aClan) < 0;
|
||||
}
|
||||
const int Result = str_comp(m_aName, Other.m_aName);
|
||||
return Result < 0 || (Result == 0 && str_comp(m_aClan, Other.m_aClan) < 0);
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<CFriendItem> m_vFriends;
|
||||
int m_FriendlistSelectedIndex;
|
||||
enum
|
||||
{
|
||||
FRIEND_PLAYER_ON = 0,
|
||||
FRIEND_CLAN_ON,
|
||||
FRIEND_OFF,
|
||||
NUM_FRIEND_TYPES
|
||||
};
|
||||
std::vector<CFriendItem> m_avFriends[NUM_FRIEND_TYPES];
|
||||
const CFriendItem *m_pRemoveFriend = nullptr;
|
||||
|
||||
void FriendlistOnUpdate();
|
||||
|
||||
|
|
|
@ -196,10 +196,6 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
|
|||
}
|
||||
m_SelectedIndex = -1;
|
||||
|
||||
// reset friend counter
|
||||
for(auto &Friend : m_vFriends)
|
||||
Friend.m_NumFound = 0;
|
||||
|
||||
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)
|
||||
|
@ -239,18 +235,6 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
|
|||
if(pItem->m_aClients[j].m_FriendState != IFriends::FRIEND_NO)
|
||||
{
|
||||
FriendsOnServer++;
|
||||
unsigned NameHash = str_quickhash(pItem->m_aClients[j].m_aName);
|
||||
unsigned ClanHash = str_quickhash(pItem->m_aClients[j].m_aClan);
|
||||
for(auto &Friend : m_vFriends)
|
||||
{
|
||||
if(((g_Config.m_ClFriendsIgnoreClan && Friend.m_pFriendInfo->m_aName[0]) || (ClanHash == Friend.m_pFriendInfo->m_ClanHash && !str_comp(Friend.m_pFriendInfo->m_aClan, pItem->m_aClients[j].m_aClan))) &&
|
||||
(!Friend.m_pFriendInfo->m_aName[0] || (NameHash == Friend.m_pFriendInfo->m_NameHash && !str_comp(Friend.m_pFriendInfo->m_aName, pItem->m_aClients[j].m_aName))))
|
||||
{
|
||||
Friend.m_NumFound++;
|
||||
if(Friend.m_pFriendInfo->m_aName[0])
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1286,140 +1270,246 @@ bool CMenus::PrintHighlighted(const char *pName, F &&PrintFn)
|
|||
|
||||
void CMenus::FriendlistOnUpdate()
|
||||
{
|
||||
m_vFriends.clear();
|
||||
for(int i = 0; i < m_pClient->Friends()->NumFriends(); ++i)
|
||||
m_vFriends.emplace_back(m_pClient->Friends()->GetFriend(i));
|
||||
std::sort(m_vFriends.begin(), m_vFriends.end());
|
||||
// TODO: friends are currently updated every frame; optimize and only update friends when necessary
|
||||
}
|
||||
|
||||
void CMenus::RenderServerbrowserFriends(CUIRect View)
|
||||
{
|
||||
static int s_Inited = 0;
|
||||
if(!s_Inited)
|
||||
{
|
||||
FriendlistOnUpdate();
|
||||
s_Inited = 1;
|
||||
}
|
||||
|
||||
CUIRect ServerFriends = View, FilterHeader;
|
||||
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 float SpacingH = 2.0f;
|
||||
|
||||
ServerFriends.HSplitBottom(18.0f, &ServerFriends, NULL);
|
||||
char aBuf[256];
|
||||
CUIRect ServerFriends, FilterHeader, List;
|
||||
|
||||
// header
|
||||
ServerFriends.HSplitTop(ms_ListheaderHeight, &FilterHeader, &ServerFriends);
|
||||
View.HSplitTop(ms_ListheaderHeight, &FilterHeader, &View);
|
||||
FilterHeader.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_T, 4.0f);
|
||||
ServerFriends.Draw(ColorRGBA(0, 0, 0, 0.15f), 0, 4.0f);
|
||||
View.Draw(ColorRGBA(0, 0, 0, 0.15f), 0, 4.0f);
|
||||
UI()->DoLabel(&FilterHeader, Localize("Friends"), FontSize + 4.0f, TEXTALIGN_MC);
|
||||
|
||||
CUIRect List;
|
||||
ServerFriends.Margin(3.0f, &ServerFriends);
|
||||
ServerFriends.VMargin(3.0f, &ServerFriends);
|
||||
ServerFriends.HSplitBottom(100.0f, &List, &ServerFriends);
|
||||
View.HSplitBottom(84.0f, &List, &ServerFriends);
|
||||
List.HSplitTop(3.0f, nullptr, &List);
|
||||
List.VSplitLeft(3.0f, nullptr, &List);
|
||||
|
||||
// friends list(remove friend)
|
||||
static CListBox s_ListBox;
|
||||
if(m_FriendlistSelectedIndex >= (int)m_vFriends.size())
|
||||
m_FriendlistSelectedIndex = m_vFriends.size() - 1;
|
||||
s_ListBox.DoAutoSpacing(3.0f);
|
||||
s_ListBox.DoStart(30.0f, m_vFriends.size(), 1, 3, m_FriendlistSelectedIndex, &List);
|
||||
// calculate friends
|
||||
// TODO: optimize this
|
||||
m_pRemoveFriend = nullptr;
|
||||
for(auto &vFriends : m_avFriends)
|
||||
vFriends.clear();
|
||||
|
||||
std::sort(m_vFriends.begin(), m_vFriends.end());
|
||||
for(size_t i = 0; i < m_vFriends.size(); ++i)
|
||||
for(int FriendIndex = 0; FriendIndex < m_pClient->Friends()->NumFriends(); ++FriendIndex)
|
||||
{
|
||||
const auto &Friend = m_vFriends[i];
|
||||
const CListboxItem Item = s_ListBox.DoNextItem(&Friend, m_FriendlistSelectedIndex >= 0 && (size_t)m_FriendlistSelectedIndex == i);
|
||||
if(!Item.m_Visible)
|
||||
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;
|
||||
|
||||
CUIRect NameClanLabels, NameLabel, ClanLabel, OnState;
|
||||
Item.m_Rect.VSplitRight(30.0f, &NameClanLabels, &OnState);
|
||||
NameClanLabels.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.1f), IGraphics::CORNER_L, 4.0f);
|
||||
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;
|
||||
|
||||
NameClanLabels.VMargin(2.5f, &NameClanLabels);
|
||||
NameClanLabels.HSplitTop(12.0f, &NameLabel, &ClanLabel);
|
||||
UI()->DoLabel(&NameLabel, Friend.m_pFriendInfo->m_aName, FontSize, TEXTALIGN_ML);
|
||||
UI()->DoLabel(&ClanLabel, Friend.m_pFriendInfo->m_aClan, FontSize, TEXTALIGN_ML);
|
||||
|
||||
OnState.Draw(Friend.m_NumFound ? ColorRGBA(0.0f, 1.0f, 0.0f, 0.25f) : ColorRGBA(1.0f, 0.0f, 0.0f, 0.25f), IGraphics::CORNER_R, 4.0f);
|
||||
OnState.HMargin((OnState.h - FontSize) / 3, &OnState);
|
||||
OnState.VMargin(5.0f, &OnState);
|
||||
char aBuf[64];
|
||||
str_format(aBuf, sizeof(aBuf), "%i", Friend.m_NumFound);
|
||||
UI()->DoLabel(&OnState, aBuf, FontSize + 2, TEXTALIGN_ML);
|
||||
}
|
||||
|
||||
m_FriendlistSelectedIndex = s_ListBox.DoEnd();
|
||||
|
||||
// activate found server with friend
|
||||
if(s_ListBox.WasItemActivated() && m_vFriends[m_FriendlistSelectedIndex].m_NumFound)
|
||||
{
|
||||
bool Found = false;
|
||||
int NumServers = ServerBrowser()->NumSortedServers();
|
||||
for(int i = 0; i < NumServers && !Found; i++)
|
||||
{
|
||||
int ItemIndex = m_SelectedIndex != -1 ? (m_SelectedIndex + i + 1) % NumServers : i;
|
||||
const CServerInfo *pItem = ServerBrowser()->SortedGet(ItemIndex);
|
||||
if(pItem->m_FriendState != IFriends::FRIEND_NO)
|
||||
{
|
||||
for(int j = 0; j < pItem->m_NumReceivedClients && !Found; ++j)
|
||||
{
|
||||
if(pItem->m_aClients[j].m_FriendState != IFriends::FRIEND_NO &&
|
||||
((g_Config.m_ClFriendsIgnoreClan && m_vFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_aName[0]) || str_quickhash(pItem->m_aClients[j].m_aClan) == m_vFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_ClanHash) &&
|
||||
(!m_vFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_aName[0] ||
|
||||
str_quickhash(pItem->m_aClients[j].m_aName) == m_vFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_NameHash))
|
||||
{
|
||||
str_copy(g_Config.m_UiServerAddress, pItem->m_aAddress);
|
||||
m_SelectedIndex = ItemIndex;
|
||||
Found = true;
|
||||
const int FriendIndex = CurrentClient.m_FriendState == IFriends::FRIEND_PLAYER ? FRIEND_PLAYER_ON : FRIEND_CLAN_ON;
|
||||
m_avFriends[FriendIndex].emplace_back(CurrentClient.m_aName, CurrentClient.m_aClan, pEntry, CurrentClient.m_FriendState, CurrentClient.m_Player);
|
||||
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])
|
||||
{
|
||||
// space
|
||||
{
|
||||
CUIRect Space;
|
||||
List.HSplitTop(SpacingH, &Space, &List);
|
||||
s_ScrollRegion.AddRect(Space);
|
||||
}
|
||||
|
||||
for(size_t FriendIndex = 0; FriendIndex < m_avFriends[FriendType].size(); ++FriendIndex)
|
||||
{
|
||||
CUIRect Rect;
|
||||
const auto &Friend = m_avFriends[FriendType][FriendIndex];
|
||||
List.HSplitTop((FriendType == FRIEND_OFF ? 8.0f : 20.0f) + ms_ListheaderHeight, &Rect, &List);
|
||||
s_ScrollRegion.AddRect(Rect);
|
||||
if(FriendIndex < m_avFriends[FriendType].size() - 1)
|
||||
{
|
||||
CUIRect Space;
|
||||
List.HSplitTop(SpacingH, &Space, &List);
|
||||
s_ScrollRegion.AddRect(Space);
|
||||
}
|
||||
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."));
|
||||
}
|
||||
Rect.Draw(s_aListColors[FriendType].WithAlpha(Inside ? 0.5f : 0.3f), IGraphics::CORNER_ALL, 5.0f);
|
||||
Rect.Margin(2.0f, &Rect);
|
||||
|
||||
CUIRect Label, RemoveButton;
|
||||
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);
|
||||
|
||||
// name
|
||||
Rect.HSplitTop(10.0f, &Label, &Rect);
|
||||
UI()->DoLabel(&Label, Friend.Name(), FontSize - 2.0f, TEXTALIGN_ML);
|
||||
|
||||
// clan
|
||||
Rect.HSplitTop(10.0f, &Label, &Rect);
|
||||
UI()->DoLabel(&Label, Friend.Clan(), FontSize - 2.0f, TEXTALIGN_ML);
|
||||
|
||||
// info
|
||||
if(Friend.ServerInfo())
|
||||
{
|
||||
Rect.HSplitTop(ms_ListheaderHeight, &Label, &Rect);
|
||||
if(Friend.IsPlayer())
|
||||
str_format(aBuf, sizeof(aBuf), Localize("Playing '%s' on '%s'", "Playing '(gametype)' on '(map)'"), Friend.ServerInfo()->m_aGameType, Friend.ServerInfo()->m_aMap);
|
||||
else
|
||||
str_format(aBuf, sizeof(aBuf), Localize("Spectating '%s' on '%s'", "Spectating '(gametype)' on '(map)'"), Friend.ServerInfo()->m_aGameType, Friend.ServerInfo()->m_aMap);
|
||||
UI()->DoLabel(&Label, 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())
|
||||
{
|
||||
Client()->Connect(g_Config.m_UiServerAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CUIRect Button;
|
||||
ServerFriends.HSplitTop(2.5f, 0, &ServerFriends);
|
||||
ServerFriends.HSplitTop(20.0f, &Button, &ServerFriends);
|
||||
if(m_FriendlistSelectedIndex != -1)
|
||||
if(m_avFriends[FriendType].empty())
|
||||
{
|
||||
static CButtonContainer s_RemoveButton;
|
||||
if(DoButton_Menu(&s_RemoveButton, Localize("Remove"), 0, &Button))
|
||||
{
|
||||
const CFriendInfo *pRemoveFriend = m_vFriends[m_FriendlistSelectedIndex].m_pFriendInfo;
|
||||
const bool IsPlayer = m_pClient->Friends()->GetFriendState(pRemoveFriend->m_aName, pRemoveFriend->m_aClan) == IFriends::FRIEND_PLAYER;
|
||||
char aBuf[256];
|
||||
str_format(aBuf, sizeof(aBuf),
|
||||
IsPlayer ? 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?"),
|
||||
IsPlayer ? pRemoveFriend->m_aName : pRemoveFriend->m_aClan);
|
||||
PopupConfirm(Localize("Remove friend"), aBuf, Localize("Yes"), Localize("No"), &CMenus::PopupConfirmRemoveFriend);
|
||||
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)
|
||||
{
|
||||
ServerFriends.HSplitTop(10.0f, 0, &ServerFriends);
|
||||
ServerFriends.HSplitTop(19.0f, &Button, &ServerFriends);
|
||||
char aBuf[64];
|
||||
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, TEXTALIGN_ML);
|
||||
Button.VSplitLeft(80.0f, 0, &Button);
|
||||
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, 0, &ServerFriends);
|
||||
ServerFriends.HSplitTop(19.0f, &Button, &ServerFriends);
|
||||
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, TEXTALIGN_ML);
|
||||
Button.VSplitLeft(80.0f, 0, &Button);
|
||||
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, 0, &ServerFriends);
|
||||
ServerFriends.HSplitTop(20.0f, &Button, &ServerFriends);
|
||||
ServerFriends.HSplitTop(3.0f, nullptr, &ServerFriends);
|
||||
ServerFriends.HSplitTop(18.0f, &Button, &ServerFriends);
|
||||
static CButtonContainer s_AddButton;
|
||||
if(DoButton_Menu(&s_AddButton, Localize("Add Friend"), 0, &Button))
|
||||
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();
|
||||
|
@ -1432,10 +1522,10 @@ void CMenus::RenderServerbrowserFriends(CUIRect View)
|
|||
|
||||
void CMenus::PopupConfirmRemoveFriend()
|
||||
{
|
||||
const CFriendInfo *pRemoveFriend = m_vFriends[m_FriendlistSelectedIndex].m_pFriendInfo;
|
||||
m_pClient->Friends()->RemoveFriend(pRemoveFriend->m_aName, pRemoveFriend->m_aClan);
|
||||
m_pClient->Friends()->RemoveFriend(m_pRemoveFriend->Name(), m_pRemoveFriend->Clan());
|
||||
FriendlistOnUpdate();
|
||||
Client()->ServerBrowserUpdate();
|
||||
m_pRemoveFriend = nullptr;
|
||||
}
|
||||
|
||||
void CMenus::RenderServerbrowser(CUIRect MainView)
|
||||
|
|
Loading…
Reference in a new issue