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_FLAG_CHECKERED = "\xEF\x84\x9E";
|
||||||
MAYBE_UNUSED static const char *FONT_ICON_BAN = "\xEF\x81\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_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_HOUSE = "\xEF\x80\x95";
|
||||||
MAYBE_UNUSED static const char *FONT_ICON_NEWSPAPER = "\xEF\x87\xAA";
|
MAYBE_UNUSED static const char *FONT_ICON_NEWSPAPER = "\xEF\x87\xAA";
|
||||||
|
|
|
@ -74,8 +74,6 @@ CMenus::CMenus()
|
||||||
|
|
||||||
str_copy(m_aCurrentDemoFolder, "demos");
|
str_copy(m_aCurrentDemoFolder, "demos");
|
||||||
|
|
||||||
m_FriendlistSelectedIndex = -1;
|
|
||||||
|
|
||||||
m_DemoPlayerState = DEMOPLAYER_NONE;
|
m_DemoPlayerState = DEMOPLAYER_NONE;
|
||||||
m_Dummy = false;
|
m_Dummy = false;
|
||||||
|
|
||||||
|
|
|
@ -452,36 +452,55 @@ protected:
|
||||||
static int DemolistFetchCallback(const CFsFileInfo *pInfo, int IsDir, int StorageType, void *pUser);
|
static int DemolistFetchCallback(const CFsFileInfo *pInfo, int IsDir, int StorageType, void *pUser);
|
||||||
|
|
||||||
// friends
|
// friends
|
||||||
struct CFriendItem
|
class CFriendItem
|
||||||
{
|
{
|
||||||
const CFriendInfo *m_pFriendInfo;
|
char m_aName[MAX_NAME_LENGTH];
|
||||||
int m_NumFound;
|
char m_aClan[MAX_CLAN_LENGTH];
|
||||||
|
const CServerInfo *m_pServerInfo;
|
||||||
|
int m_FriendState;
|
||||||
|
bool m_IsPlayer;
|
||||||
|
|
||||||
|
public:
|
||||||
CFriendItem() {}
|
CFriendItem() {}
|
||||||
CFriendItem(const CFriendInfo *pFriendInfo) :
|
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
|
bool operator<(const CFriendItem &Other) const
|
||||||
{
|
{
|
||||||
if(m_NumFound && !Other.m_NumFound)
|
const int Result = str_comp(m_aName, Other.m_aName);
|
||||||
return true;
|
return Result < 0 || (Result == 0 && str_comp(m_aClan, Other.m_aClan) < 0);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<CFriendItem> m_vFriends;
|
enum
|
||||||
int m_FriendlistSelectedIndex;
|
{
|
||||||
|
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();
|
void FriendlistOnUpdate();
|
||||||
|
|
||||||
|
|
|
@ -196,10 +196,6 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
|
||||||
}
|
}
|
||||||
m_SelectedIndex = -1;
|
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) {
|
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;
|
float FontSize = 14.0f;
|
||||||
if(SmallFont)
|
if(SmallFont)
|
||||||
|
@ -239,18 +235,6 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
|
||||||
if(pItem->m_aClients[j].m_FriendState != IFriends::FRIEND_NO)
|
if(pItem->m_aClients[j].m_FriendState != IFriends::FRIEND_NO)
|
||||||
{
|
{
|
||||||
FriendsOnServer++;
|
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()
|
void CMenus::FriendlistOnUpdate()
|
||||||
{
|
{
|
||||||
m_vFriends.clear();
|
// TODO: friends are currently updated every frame; optimize and only update friends when necessary
|
||||||
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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CMenus::RenderServerbrowserFriends(CUIRect View)
|
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;
|
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
|
// 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);
|
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);
|
UI()->DoLabel(&FilterHeader, Localize("Friends"), FontSize + 4.0f, TEXTALIGN_MC);
|
||||||
|
|
||||||
CUIRect List;
|
View.HSplitBottom(84.0f, &List, &ServerFriends);
|
||||||
ServerFriends.Margin(3.0f, &ServerFriends);
|
List.HSplitTop(3.0f, nullptr, &List);
|
||||||
ServerFriends.VMargin(3.0f, &ServerFriends);
|
List.VSplitLeft(3.0f, nullptr, &List);
|
||||||
ServerFriends.HSplitBottom(100.0f, &List, &ServerFriends);
|
|
||||||
|
|
||||||
// friends list(remove friend)
|
// calculate friends
|
||||||
static CListBox s_ListBox;
|
// TODO: optimize this
|
||||||
if(m_FriendlistSelectedIndex >= (int)m_vFriends.size())
|
m_pRemoveFriend = nullptr;
|
||||||
m_FriendlistSelectedIndex = m_vFriends.size() - 1;
|
for(auto &vFriends : m_avFriends)
|
||||||
s_ListBox.DoAutoSpacing(3.0f);
|
vFriends.clear();
|
||||||
s_ListBox.DoStart(30.0f, m_vFriends.size(), 1, 3, m_FriendlistSelectedIndex, &List);
|
|
||||||
|
|
||||||
std::sort(m_vFriends.begin(), m_vFriends.end());
|
for(int FriendIndex = 0; FriendIndex < m_pClient->Friends()->NumFriends(); ++FriendIndex)
|
||||||
for(size_t i = 0; i < m_vFriends.size(); ++i)
|
|
||||||
{
|
{
|
||||||
const auto &Friend = m_vFriends[i];
|
m_avFriends[FRIEND_OFF].emplace_back(m_pClient->Friends()->GetFriend(FriendIndex));
|
||||||
const CListboxItem Item = s_ListBox.DoNextItem(&Friend, m_FriendlistSelectedIndex >= 0 && (size_t)m_FriendlistSelectedIndex == i);
|
|
||||||
if(!Item.m_Visible)
|
|
||||||
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);
|
|
||||||
|
|
||||||
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();
|
for(int ServerIndex = 0; ServerIndex < ServerBrowser()->NumSortedServers(); ++ServerIndex)
|
||||||
|
|
||||||
// activate found server with friend
|
|
||||||
if(s_ListBox.WasItemActivated() && m_vFriends[m_FriendlistSelectedIndex].m_NumFound)
|
|
||||||
{
|
{
|
||||||
bool Found = false;
|
const CServerInfo *pEntry = ServerBrowser()->SortedGet(ServerIndex);
|
||||||
int NumServers = ServerBrowser()->NumSortedServers();
|
if(pEntry->m_FriendState == IFriends::FRIEND_NO)
|
||||||
for(int i = 0; i < NumServers && !Found; i++)
|
continue;
|
||||||
|
|
||||||
|
for(int ClientIndex = 0; ClientIndex < pEntry->m_NumClients; ++ClientIndex)
|
||||||
{
|
{
|
||||||
int ItemIndex = m_SelectedIndex != -1 ? (m_SelectedIndex + i + 1) % NumServers : i;
|
const CServerInfo::CClient &CurrentClient = pEntry->m_aClients[ClientIndex];
|
||||||
const CServerInfo *pItem = ServerBrowser()->SortedGet(ItemIndex);
|
if(CurrentClient.m_FriendState == IFriends::FRIEND_NO)
|
||||||
if(pItem->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.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
|
||||||
{
|
{
|
||||||
for(int j = 0; j < pItem->m_NumReceivedClients && !Found; ++j)
|
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)
|
||||||
{
|
{
|
||||||
if(pItem->m_aClients[j].m_FriendState != IFriends::FRIEND_NO &&
|
CUIRect Space;
|
||||||
((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) &&
|
List.HSplitTop(SpacingH, &Space, &List);
|
||||||
(!m_vFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_aName[0] ||
|
s_ScrollRegion.AddRect(Space);
|
||||||
str_quickhash(pItem->m_aClients[j].m_aName) == m_vFriends[m_FriendlistSelectedIndex].m_pFriendInfo->m_NameHash))
|
}
|
||||||
|
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())
|
||||||
{
|
{
|
||||||
str_copy(g_Config.m_UiServerAddress, pItem->m_aAddress);
|
Client()->Connect(g_Config.m_UiServerAddress);
|
||||||
m_SelectedIndex = ItemIndex;
|
|
||||||
Found = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
CUIRect Button;
|
if(m_pRemoveFriend != nullptr)
|
||||||
ServerFriends.HSplitTop(2.5f, 0, &ServerFriends);
|
|
||||||
ServerFriends.HSplitTop(20.0f, &Button, &ServerFriends);
|
|
||||||
if(m_FriendlistSelectedIndex != -1)
|
|
||||||
{
|
{
|
||||||
static CButtonContainer s_RemoveButton;
|
char aMessage[256];
|
||||||
if(DoButton_Menu(&s_RemoveButton, Localize("Remove"), 0, &Button))
|
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?"),
|
||||||
const CFriendInfo *pRemoveFriend = m_vFriends[m_FriendlistSelectedIndex].m_pFriendInfo;
|
m_pRemoveFriend->FriendState() == IFriends::FRIEND_PLAYER ? m_pRemoveFriend->Name() : m_pRemoveFriend->Clan());
|
||||||
const bool IsPlayer = m_pClient->Friends()->GetFriendState(pRemoveFriend->m_aName, pRemoveFriend->m_aClan) == IFriends::FRIEND_PLAYER;
|
PopupConfirm(Localize("Remove friend"), aMessage, Localize("Yes"), Localize("No"), &CMenus::PopupConfirmRemoveFriend);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add friend
|
// add friend
|
||||||
if(m_pClient->Friends()->NumFriends() < IFriends::MAX_FRIENDS)
|
if(m_pClient->Friends()->NumFriends() < IFriends::MAX_FRIENDS)
|
||||||
{
|
{
|
||||||
ServerFriends.HSplitTop(10.0f, 0, &ServerFriends);
|
CUIRect Button;
|
||||||
ServerFriends.HSplitTop(19.0f, &Button, &ServerFriends);
|
ServerFriends.Margin(3.0f, &ServerFriends);
|
||||||
char aBuf[64];
|
|
||||||
|
ServerFriends.HSplitTop(18.0f, &Button, &ServerFriends);
|
||||||
str_format(aBuf, sizeof(aBuf), "%s:", Localize("Name"));
|
str_format(aBuf, sizeof(aBuf), "%s:", Localize("Name"));
|
||||||
UI()->DoLabel(&Button, aBuf, FontSize + 2, TEXTALIGN_ML);
|
UI()->DoLabel(&Button, aBuf, FontSize + 2.0f, TEXTALIGN_ML);
|
||||||
Button.VSplitLeft(80.0f, 0, &Button);
|
Button.VSplitLeft(80.0f, nullptr, &Button);
|
||||||
static CLineInputBuffered<MAX_NAME_LENGTH> s_NameInput;
|
static CLineInputBuffered<MAX_NAME_LENGTH> s_NameInput;
|
||||||
UI()->DoEditBox(&s_NameInput, &Button, FontSize + 2.0f);
|
UI()->DoEditBox(&s_NameInput, &Button, FontSize + 2.0f);
|
||||||
|
|
||||||
ServerFriends.HSplitTop(3.0f, 0, &ServerFriends);
|
ServerFriends.HSplitTop(3.0f, nullptr, &ServerFriends);
|
||||||
ServerFriends.HSplitTop(19.0f, &Button, &ServerFriends);
|
ServerFriends.HSplitTop(18.0f, &Button, &ServerFriends);
|
||||||
str_format(aBuf, sizeof(aBuf), "%s:", Localize("Clan"));
|
str_format(aBuf, sizeof(aBuf), "%s:", Localize("Clan"));
|
||||||
UI()->DoLabel(&Button, aBuf, FontSize + 2, TEXTALIGN_ML);
|
UI()->DoLabel(&Button, aBuf, FontSize + 2.0f, TEXTALIGN_ML);
|
||||||
Button.VSplitLeft(80.0f, 0, &Button);
|
Button.VSplitLeft(80.0f, nullptr, &Button);
|
||||||
static CLineInputBuffered<MAX_CLAN_LENGTH> s_ClanInput;
|
static CLineInputBuffered<MAX_CLAN_LENGTH> s_ClanInput;
|
||||||
UI()->DoEditBox(&s_ClanInput, &Button, FontSize + 2.0f);
|
UI()->DoEditBox(&s_ClanInput, &Button, FontSize + 2.0f);
|
||||||
|
|
||||||
ServerFriends.HSplitTop(3.0f, 0, &ServerFriends);
|
ServerFriends.HSplitTop(3.0f, nullptr, &ServerFriends);
|
||||||
ServerFriends.HSplitTop(20.0f, &Button, &ServerFriends);
|
ServerFriends.HSplitTop(18.0f, &Button, &ServerFriends);
|
||||||
static CButtonContainer s_AddButton;
|
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());
|
m_pClient->Friends()->AddFriend(s_NameInput.GetString(), s_ClanInput.GetString());
|
||||||
s_NameInput.Clear();
|
s_NameInput.Clear();
|
||||||
|
@ -1432,10 +1522,10 @@ void CMenus::RenderServerbrowserFriends(CUIRect View)
|
||||||
|
|
||||||
void CMenus::PopupConfirmRemoveFriend()
|
void CMenus::PopupConfirmRemoveFriend()
|
||||||
{
|
{
|
||||||
const CFriendInfo *pRemoveFriend = m_vFriends[m_FriendlistSelectedIndex].m_pFriendInfo;
|
m_pClient->Friends()->RemoveFriend(m_pRemoveFriend->Name(), m_pRemoveFriend->Clan());
|
||||||
m_pClient->Friends()->RemoveFriend(pRemoveFriend->m_aName, pRemoveFriend->m_aClan);
|
|
||||||
FriendlistOnUpdate();
|
FriendlistOnUpdate();
|
||||||
Client()->ServerBrowserUpdate();
|
Client()->ServerBrowserUpdate();
|
||||||
|
m_pRemoveFriend = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CMenus::RenderServerbrowser(CUIRect MainView)
|
void CMenus::RenderServerbrowser(CUIRect MainView)
|
||||||
|
|
Loading…
Reference in a new issue