1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#include "menus.h"
4
5#include <base/str.h>
6
7#include <engine/graphics.h>
8#include <engine/shared/config.h>
9
10#include <game/client/components/console.h>
11#include <game/client/components/countryflags.h>
12#include <game/client/gameclient.h>
13#include <game/client/ui.h>
14#include <game/client/ui_listbox.h>
15#include <game/localization.h>
16
17#include <vector>
18
19void CMenus::RenderSettingsPlayer(CUIRect MainView)
20{
21 CUIRect TabBar, PlayerTab, DummyTab, ChangeInfo, QuickSearch;
22 MainView.HSplitTop(Cut: 20.0f, pTop: &TabBar, pBottom: &MainView);
23 TabBar.VSplitMid(pLeft: &TabBar, pRight: &ChangeInfo, Spacing: 20.f);
24 TabBar.VSplitMid(pLeft: &PlayerTab, pRight: &DummyTab);
25 MainView.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &MainView);
26
27 static CButtonContainer s_PlayerTabButton;
28 if(DoButton_MenuTab(pButtonContainer: &s_PlayerTabButton, pText: Localize(pStr: "Player"), Checked: !m_Dummy, pRect: &PlayerTab, Corners: IGraphics::CORNER_L, pAnimator: nullptr, pDefaultColor: nullptr, pActiveColor: nullptr, pHoverColor: nullptr, EdgeRounding: 4.0f))
29 {
30 m_Dummy = false;
31 }
32
33 static CButtonContainer s_DummyTabButton;
34 if(DoButton_MenuTab(pButtonContainer: &s_DummyTabButton, pText: Localize(pStr: "Dummy"), Checked: m_Dummy, pRect: &DummyTab, Corners: IGraphics::CORNER_R, pAnimator: nullptr, pDefaultColor: nullptr, pActiveColor: nullptr, pHoverColor: nullptr, EdgeRounding: 4.0f))
35 {
36 m_Dummy = true;
37 }
38
39 if(Client()->State() == IClient::STATE_ONLINE &&
40 GameClient()->m_aNextChangeInfo[m_Dummy] > Client()->GameTick(Conn: m_Dummy))
41 {
42 char aChangeInfo[128], aTimeLeft[32];
43 str_format(buffer: aTimeLeft, buffer_size: sizeof(aTimeLeft), format: Localize(pStr: "%ds left"), (GameClient()->m_aNextChangeInfo[m_Dummy] - Client()->GameTick(Conn: m_Dummy) + Client()->GameTickSpeed() - 1) / Client()->GameTickSpeed());
44 str_format(buffer: aChangeInfo, buffer_size: sizeof(aChangeInfo), format: "%s: %s", Localize(pStr: "Player info change cooldown"), aTimeLeft);
45 Ui()->DoLabel(pRect: &ChangeInfo, pText: aChangeInfo, Size: 10.f, Align: TEXTALIGN_ML);
46 }
47
48 static CLineInput s_NameInput;
49 static CLineInput s_ClanInput;
50
51 int *pCountry;
52 if(!m_Dummy)
53 {
54 pCountry = &g_Config.m_PlayerCountry;
55 s_NameInput.SetBuffer(pStr: g_Config.m_PlayerName, MaxSize: sizeof(g_Config.m_PlayerName));
56 s_NameInput.SetEmptyText(Client()->PlayerName());
57 s_ClanInput.SetBuffer(pStr: g_Config.m_PlayerClan, MaxSize: sizeof(g_Config.m_PlayerClan));
58 }
59 else
60 {
61 pCountry = &g_Config.m_ClDummyCountry;
62 s_NameInput.SetBuffer(pStr: g_Config.m_ClDummyName, MaxSize: sizeof(g_Config.m_ClDummyName));
63 s_NameInput.SetEmptyText(Client()->DummyName());
64 s_ClanInput.SetBuffer(pStr: g_Config.m_ClDummyClan, MaxSize: sizeof(g_Config.m_ClDummyClan));
65 }
66
67 // player name
68 CUIRect Button, Label;
69 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
70 Button.VSplitLeft(Cut: 80.0f, pLeft: &Label, pRight: &Button);
71 Button.VSplitLeft(Cut: 150.0f, pLeft: &Button, pRight: nullptr);
72 char aBuf[128];
73 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s:", Localize(pStr: "Name"));
74 Ui()->DoLabel(pRect: &Label, pText: aBuf, Size: 14.0f, Align: TEXTALIGN_ML);
75 if(Ui()->DoEditBox(pLineInput: &s_NameInput, pRect: &Button, FontSize: 14.0f))
76 {
77 SetNeedSendInfo();
78 }
79
80 // player clan
81 MainView.HSplitTop(Cut: 5.0f, pTop: nullptr, pBottom: &MainView);
82 MainView.HSplitTop(Cut: 20.0f, pTop: &Button, pBottom: &MainView);
83 Button.VSplitLeft(Cut: 80.0f, pLeft: &Label, pRight: &Button);
84 Button.VSplitLeft(Cut: 150.0f, pLeft: &Button, pRight: nullptr);
85 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s:", Localize(pStr: "Clan"));
86 Ui()->DoLabel(pRect: &Label, pText: aBuf, Size: 14.0f, Align: TEXTALIGN_ML);
87 if(Ui()->DoEditBox(pLineInput: &s_ClanInput, pRect: &Button, FontSize: 14.0f))
88 {
89 SetNeedSendInfo();
90 }
91
92 // country flag selector
93 static CLineInputBuffered<25> s_FlagFilterInput;
94
95 class CCountryFlagEntry
96 {
97 public:
98 const CCountryFlags::CCountryFlag *m_pFlag;
99 std::optional<std::pair<int, int>> m_NameMatch;
100 };
101 std::vector<CCountryFlagEntry> vFilteredFlags;
102 for(size_t i = 0; i < GameClient()->m_CountryFlags.Num(); ++i)
103 {
104 const CCountryFlags::CCountryFlag &Entry = GameClient()->m_CountryFlags.GetByIndex(Index: i);
105 if(!s_FlagFilterInput.IsEmpty())
106 {
107 const char *pNameMatchEnd;
108 const char *pNameMatchStart = str_utf8_find_nocase(haystack: Entry.m_aCountryCodeString, needle: s_FlagFilterInput.GetString(), end: &pNameMatchEnd);
109 if(pNameMatchStart != nullptr)
110 {
111 vFilteredFlags.emplace_back(args: &Entry, args: std::make_pair<int, int>(x: pNameMatchStart - Entry.m_aCountryCodeString, y: pNameMatchEnd - pNameMatchStart));
112 }
113 }
114 else
115 {
116 vFilteredFlags.emplace_back(args: &Entry, args: std::nullopt);
117 }
118 }
119
120 MainView.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &MainView);
121 MainView.HSplitBottom(Cut: 20.0f, pTop: &MainView, pBottom: &QuickSearch);
122 MainView.HSplitBottom(Cut: 5.0f, pTop: &MainView, pBottom: nullptr);
123 QuickSearch.VSplitLeft(Cut: 220.0f, pLeft: &QuickSearch, pRight: nullptr);
124
125 int OldSelected = -1;
126 static CListBox s_ListBox;
127 s_ListBox.DoStart(RowHeight: 48.0f, NumItems: vFilteredFlags.size(), ItemsPerRow: 10, RowsPerScroll: 3, SelectedIndex: OldSelected, pRect: &MainView);
128
129 for(size_t i = 0; i < vFilteredFlags.size(); i++)
130 {
131 const CCountryFlagEntry &Entry = vFilteredFlags[i];
132
133 if(Entry.m_pFlag->m_CountryCode == *pCountry)
134 OldSelected = i;
135
136 const CListboxItem Item = s_ListBox.DoNextItem(pId: &Entry.m_pFlag->m_CountryCode, Selected: OldSelected >= 0 && (size_t)OldSelected == i);
137 if(!Item.m_Visible)
138 continue;
139
140 CUIRect FlagRect;
141 Item.m_Rect.Margin(Cut: 5.0f, pOtherRect: &FlagRect);
142 FlagRect.HSplitBottom(Cut: 12.0f, pTop: &FlagRect, pBottom: &Label);
143 Label.HSplitTop(Cut: 2.0f, pTop: nullptr, pBottom: &Label);
144 const float OldWidth = FlagRect.w;
145 FlagRect.w = FlagRect.h * 2;
146 FlagRect.x += (OldWidth - FlagRect.w) / 2.0f;
147 GameClient()->m_CountryFlags.Render(CountryCode: Entry.m_pFlag->m_CountryCode, Color: ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f), x: FlagRect.x, y: FlagRect.y, w: FlagRect.w, h: FlagRect.h);
148
149 if(Entry.m_pFlag->m_Texture.IsValid() || Entry.m_pFlag->m_CountryCode == CountryCode::DEFAULT)
150 {
151 SLabelProperties Props;
152 Props.m_MaxWidth = Label.w - 5.0f;
153 if(Entry.m_NameMatch.has_value())
154 {
155 const auto [MatchStart, MatchLength] = Entry.m_NameMatch.value();
156 Props.m_vColorSplits.emplace_back(args: MatchStart, args: MatchLength, args: ColorRGBA(0.4f, 0.4f, 1.0f, 1.0f));
157 }
158 Ui()->DoLabel(pRect: &Label, pText: Entry.m_pFlag->m_aCountryCodeString, Size: 10.0f, Align: TEXTALIGN_MC, LabelProps: Props);
159 }
160 }
161
162 const int NewSelected = s_ListBox.DoEnd();
163 if(OldSelected != NewSelected)
164 {
165 *pCountry = vFilteredFlags[NewSelected].m_pFlag->m_CountryCode;
166 SetNeedSendInfo();
167 }
168
169 if(GameClient()->m_CountryFlags.Num() > 0 && vFilteredFlags.empty())
170 {
171 CUIRect FilterLabel, ResetButton;
172 MainView.HMargin(Cut: (MainView.h - (16.0f + 18.0f + 8.0f)) / 2.0f, pOtherRect: &FilterLabel);
173 FilterLabel.HSplitTop(Cut: 16.0f, pTop: &FilterLabel, pBottom: &ResetButton);
174 ResetButton.HSplitTop(Cut: 8.0f, pTop: nullptr, pBottom: &ResetButton);
175 ResetButton.VMargin(Cut: (ResetButton.w - 200.0f) / 2.0f, pOtherRect: &ResetButton);
176 Ui()->DoLabel(pRect: &FilterLabel, pText: Localize(pStr: "No country flags match your filter criteria"), Size: 16.0f, Align: TEXTALIGN_MC);
177 static CButtonContainer s_ResetButton;
178 if(DoButton_Menu(pButtonContainer: &s_ResetButton, pText: Localize(pStr: "Reset filter"), Checked: 0, pRect: &ResetButton))
179 {
180 s_FlagFilterInput.Clear();
181 }
182 }
183
184 Ui()->DoEditBox_Search(pLineInput: &s_FlagFilterInput, pRect: &QuickSearch, FontSize: 14.0f, HotkeyEnabled: !Ui()->IsPopupOpen() && !GameClient()->m_GameConsole.IsActive());
185}
186