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/dbg.h>
6#include <base/math.h>
7#include <base/str.h>
8
9#include <engine/graphics.h>
10#include <engine/shared/config.h>
11
12#include <game/client/components/menu_background.h>
13#include <game/client/gameclient.h>
14#include <game/client/ui.h>
15#include <game/localization.h>
16
17void CMenus::SetNeedSendInfo()
18{
19 if(m_Dummy)
20 m_NeedSendDummyinfo = true;
21 else
22 m_NeedSendinfo = true;
23}
24
25void CMenus::RenderSettings(CUIRect MainView)
26{
27 // render background
28 CUIRect Button, TabBar, RestartBar;
29 MainView.VSplitRight(Cut: 120.0f, pLeft: &MainView, pRight: &TabBar);
30 MainView.Draw(Color: ms_ColorTabbarActive, Corners: IGraphics::CORNER_B, Rounding: 10.0f);
31 MainView.Margin(Cut: 20.0f, pOtherRect: &MainView);
32
33 const bool NeedRestart = m_NeedRestartGraphics || m_NeedRestartSound || m_NeedRestartUpdate;
34 if(NeedRestart)
35 {
36 MainView.HSplitBottom(Cut: 20.0f, pTop: &MainView, pBottom: &RestartBar);
37 MainView.HSplitBottom(Cut: 10.0f, pTop: &MainView, pBottom: nullptr);
38 }
39
40 TabBar.HSplitTop(Cut: 50.0f, pTop: &Button, pBottom: &TabBar);
41 Button.Draw(Color: ms_ColorTabbarActive, Corners: IGraphics::CORNER_BR, Rounding: 10.0f);
42
43 const char *apTabs[SETTINGS_LENGTH] = {
44 Localize(pStr: "Language"),
45 Localize(pStr: "General"),
46 Localize(pStr: "Player"),
47 Client()->IsSixup() ? "Tee 0.7" : Localize(pStr: "Tee"),
48 Localize(pStr: "Appearance"),
49 Localize(pStr: "Controls"),
50 Localize(pStr: "Graphics"),
51 Localize(pStr: "Sound"),
52 Localize(pStr: "DDNet"),
53 Localize(pStr: "Assets"),
54 Localize(pStr: "Credits")};
55 static CButtonContainer s_aTabButtons[SETTINGS_LENGTH];
56
57 for(int i = 0; i < SETTINGS_LENGTH; i++)
58 {
59 TabBar.HSplitTop(Cut: 10.0f, pTop: nullptr, pBottom: &TabBar);
60 TabBar.HSplitTop(Cut: 26.0f, pTop: &Button, pBottom: &TabBar);
61 if(DoButton_MenuTab(pButtonContainer: &s_aTabButtons[i], pText: apTabs[i], Checked: g_Config.m_UiSettingsPage == i, pRect: &Button, Corners: IGraphics::CORNER_R, pAnimator: &m_aAnimatorsSettingsTab[i]))
62 g_Config.m_UiSettingsPage = i;
63 }
64
65 if(g_Config.m_UiSettingsPage == SETTINGS_LANGUAGE)
66 {
67 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_LANGUAGE);
68 RenderLanguageSettings(MainView);
69 }
70 else if(g_Config.m_UiSettingsPage == SETTINGS_GENERAL)
71 {
72 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_GENERAL);
73 RenderSettingsGeneral(MainView);
74 }
75 else if(g_Config.m_UiSettingsPage == SETTINGS_PLAYER)
76 {
77 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_PLAYER);
78 RenderSettingsPlayer(MainView);
79 }
80 else if(g_Config.m_UiSettingsPage == SETTINGS_TEE)
81 {
82 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_TEE);
83 if(Client()->IsSixup())
84 RenderSettingsTee7(MainView);
85 else
86 RenderSettingsTee(MainView);
87 }
88 else if(g_Config.m_UiSettingsPage == SETTINGS_APPEARANCE)
89 {
90 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_APPEARANCE);
91 RenderSettingsAppearance(MainView);
92 }
93 else if(g_Config.m_UiSettingsPage == SETTINGS_CONTROLS)
94 {
95 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_CONTROLS);
96 m_MenusSettingsControls.Render(MainView);
97 }
98 else if(g_Config.m_UiSettingsPage == SETTINGS_GRAPHICS)
99 {
100 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_GRAPHICS);
101 RenderSettingsGraphics(MainView);
102 }
103 else if(g_Config.m_UiSettingsPage == SETTINGS_SOUND)
104 {
105 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_SOUND);
106 RenderSettingsSound(MainView);
107 }
108 else if(g_Config.m_UiSettingsPage == SETTINGS_DDNET)
109 {
110 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_DDNET);
111 RenderSettingsDDNet(MainView);
112 }
113 else if(g_Config.m_UiSettingsPage == SETTINGS_ASSETS)
114 {
115 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_ASSETS);
116 RenderSettingsAssets(MainView);
117 }
118 else if(g_Config.m_UiSettingsPage == SETTINGS_CREDITS)
119 {
120 GameClient()->m_MenuBackground.ChangePosition(PositionNumber: CMenuBackground::POS_SETTINGS_CREDITS);
121 RenderSettingsCredits(MainView);
122 }
123 else
124 {
125 dbg_assert_failed("ui_settings_page invalid");
126 }
127
128 if(NeedRestart)
129 {
130 CUIRect RestartWarning, RestartButton;
131 RestartBar.VSplitRight(Cut: 125.0f, pLeft: &RestartWarning, pRight: &RestartButton);
132 RestartWarning.VSplitRight(Cut: 10.0f, pLeft: &RestartWarning, pRight: nullptr);
133 if(m_NeedRestartUpdate)
134 {
135 TextRender()->TextColor(r: 1.0f, g: 0.4f, b: 0.4f, a: 1.0f);
136 Ui()->DoLabel(pRect: &RestartWarning, pText: Localize(pStr: "DDNet Client needs to be restarted to complete update!"), Size: 14.0f, Align: TEXTALIGN_ML);
137 TextRender()->TextColor(r: 1.0f, g: 1.0f, b: 1.0f, a: 1.0f);
138 }
139 else
140 {
141 Ui()->DoLabel(pRect: &RestartWarning, pText: Localize(pStr: "You must restart the game for all settings to take effect."), Size: 14.0f, Align: TEXTALIGN_ML);
142 }
143
144 static CButtonContainer s_RestartButton;
145 if(DoButton_Menu(pButtonContainer: &s_RestartButton, pText: Localize(pStr: "Restart"), Checked: 0, pRect: &RestartButton))
146 {
147 if(Client()->State() == IClient::STATE_ONLINE || GameClient()->Editor()->HasUnsavedData())
148 {
149 m_Popup = POPUP_RESTART;
150 }
151 else
152 {
153 Client()->Restart();
154 }
155 }
156 }
157}
158
159bool CMenus::RenderHslaScrollbars(CUIRect *pRect, unsigned int *pColor, bool Alpha, float DarkestLight)
160{
161 const unsigned PrevPackedColor = *pColor;
162 ColorHSLA Color(*pColor, Alpha);
163 const ColorHSLA OriginalColor = Color;
164 const char *apLabels[] = {Localize(pStr: "Hue"), Localize(pStr: "Sat."), Localize(pStr: "Lht."), Localize(pStr: "Alpha")};
165 const float SizePerEntry = 20.0f;
166 const float MarginPerEntry = 5.0f;
167 const float PreviewMargin = 2.5f;
168 const float PreviewHeight = 40.0f + 2 * PreviewMargin;
169 const float OffY = (SizePerEntry + MarginPerEntry) * (3 + (Alpha ? 1 : 0)) - PreviewHeight;
170
171 CUIRect Preview;
172 pRect->VSplitLeft(Cut: PreviewHeight, pLeft: &Preview, pRight: pRect);
173 Preview.HSplitTop(Cut: OffY / 2.0f, pTop: nullptr, pBottom: &Preview);
174 Preview.HSplitTop(Cut: PreviewHeight, pTop: &Preview, pBottom: nullptr);
175
176 Preview.Draw(Color: ColorRGBA(0.15f, 0.15f, 0.15f, 1.0f), Corners: IGraphics::CORNER_ALL, Rounding: 4.0f + PreviewMargin);
177 Preview.Margin(Cut: PreviewMargin, pOtherRect: &Preview);
178 Preview.Draw(Color: color_cast<ColorRGBA>(hsl: Color.UnclampLighting(Darkest: DarkestLight)), Corners: IGraphics::CORNER_ALL, Rounding: 4.0f + PreviewMargin);
179
180 auto &&RenderHueRect = [&](CUIRect *pColorRect) {
181 float CurXOff = pColorRect->x;
182 const float SizeColor = pColorRect->w / 6;
183
184 // red to yellow
185 {
186 IGraphics::CColorVertex aColorVertices[] = {
187 IGraphics::CColorVertex(0, 1, 0, 0, 1),
188 IGraphics::CColorVertex(1, 1, 1, 0, 1),
189 IGraphics::CColorVertex(2, 1, 0, 0, 1),
190 IGraphics::CColorVertex(3, 1, 1, 0, 1)};
191 Graphics()->SetColorVertex(pArray: aColorVertices, Num: std::size(aColorVertices));
192
193 IGraphics::CFreeformItem Freeform(
194 CurXOff, pColorRect->y,
195 CurXOff + SizeColor, pColorRect->y,
196 CurXOff, pColorRect->y + pColorRect->h,
197 CurXOff + SizeColor, pColorRect->y + pColorRect->h);
198 Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1);
199 }
200
201 // yellow to green
202 CurXOff += SizeColor;
203 {
204 IGraphics::CColorVertex aColorVertices[] = {
205 IGraphics::CColorVertex(0, 1, 1, 0, 1),
206 IGraphics::CColorVertex(1, 0, 1, 0, 1),
207 IGraphics::CColorVertex(2, 1, 1, 0, 1),
208 IGraphics::CColorVertex(3, 0, 1, 0, 1)};
209 Graphics()->SetColorVertex(pArray: aColorVertices, Num: std::size(aColorVertices));
210
211 IGraphics::CFreeformItem Freeform(
212 CurXOff, pColorRect->y,
213 CurXOff + SizeColor, pColorRect->y,
214 CurXOff, pColorRect->y + pColorRect->h,
215 CurXOff + SizeColor, pColorRect->y + pColorRect->h);
216 Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1);
217 }
218
219 CurXOff += SizeColor;
220 // green to turquoise
221 {
222 IGraphics::CColorVertex aColorVertices[] = {
223 IGraphics::CColorVertex(0, 0, 1, 0, 1),
224 IGraphics::CColorVertex(1, 0, 1, 1, 1),
225 IGraphics::CColorVertex(2, 0, 1, 0, 1),
226 IGraphics::CColorVertex(3, 0, 1, 1, 1)};
227 Graphics()->SetColorVertex(pArray: aColorVertices, Num: std::size(aColorVertices));
228
229 IGraphics::CFreeformItem Freeform(
230 CurXOff, pColorRect->y,
231 CurXOff + SizeColor, pColorRect->y,
232 CurXOff, pColorRect->y + pColorRect->h,
233 CurXOff + SizeColor, pColorRect->y + pColorRect->h);
234 Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1);
235 }
236
237 CurXOff += SizeColor;
238 // turquoise to blue
239 {
240 IGraphics::CColorVertex aColorVertices[] = {
241 IGraphics::CColorVertex(0, 0, 1, 1, 1),
242 IGraphics::CColorVertex(1, 0, 0, 1, 1),
243 IGraphics::CColorVertex(2, 0, 1, 1, 1),
244 IGraphics::CColorVertex(3, 0, 0, 1, 1)};
245 Graphics()->SetColorVertex(pArray: aColorVertices, Num: std::size(aColorVertices));
246
247 IGraphics::CFreeformItem Freeform(
248 CurXOff, pColorRect->y,
249 CurXOff + SizeColor, pColorRect->y,
250 CurXOff, pColorRect->y + pColorRect->h,
251 CurXOff + SizeColor, pColorRect->y + pColorRect->h);
252 Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1);
253 }
254
255 CurXOff += SizeColor;
256 // blue to purple
257 {
258 IGraphics::CColorVertex aColorVertices[] = {
259 IGraphics::CColorVertex(0, 0, 0, 1, 1),
260 IGraphics::CColorVertex(1, 1, 0, 1, 1),
261 IGraphics::CColorVertex(2, 0, 0, 1, 1),
262 IGraphics::CColorVertex(3, 1, 0, 1, 1)};
263 Graphics()->SetColorVertex(pArray: aColorVertices, Num: std::size(aColorVertices));
264
265 IGraphics::CFreeformItem Freeform(
266 CurXOff, pColorRect->y,
267 CurXOff + SizeColor, pColorRect->y,
268 CurXOff, pColorRect->y + pColorRect->h,
269 CurXOff + SizeColor, pColorRect->y + pColorRect->h);
270 Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1);
271 }
272
273 CurXOff += SizeColor;
274 // purple to red
275 {
276 IGraphics::CColorVertex aColorVertices[] = {
277 IGraphics::CColorVertex(0, 1, 0, 1, 1),
278 IGraphics::CColorVertex(1, 1, 0, 0, 1),
279 IGraphics::CColorVertex(2, 1, 0, 1, 1),
280 IGraphics::CColorVertex(3, 1, 0, 0, 1)};
281 Graphics()->SetColorVertex(pArray: aColorVertices, Num: std::size(aColorVertices));
282
283 IGraphics::CFreeformItem Freeform(
284 CurXOff, pColorRect->y,
285 CurXOff + SizeColor, pColorRect->y,
286 CurXOff, pColorRect->y + pColorRect->h,
287 CurXOff + SizeColor, pColorRect->y + pColorRect->h);
288 Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1);
289 }
290 };
291
292 auto &&RenderSaturationRect = [&](CUIRect *pColorRect, const ColorRGBA &CurColor) {
293 ColorHSLA LeftColor = color_cast<ColorHSLA>(rgb: CurColor);
294 ColorHSLA RightColor = color_cast<ColorHSLA>(rgb: CurColor);
295
296 LeftColor.s = 0.0f;
297 RightColor.s = 1.0f;
298
299 const ColorRGBA LeftColorRGBA = color_cast<ColorRGBA>(hsl: LeftColor);
300 const ColorRGBA RightColorRGBA = color_cast<ColorRGBA>(hsl: RightColor);
301
302 Graphics()->SetColor4(TopLeft: LeftColorRGBA, TopRight: RightColorRGBA, BottomLeft: RightColorRGBA, BottomRight: LeftColorRGBA);
303
304 IGraphics::CFreeformItem Freeform(
305 pColorRect->x, pColorRect->y,
306 pColorRect->x + pColorRect->w, pColorRect->y,
307 pColorRect->x, pColorRect->y + pColorRect->h,
308 pColorRect->x + pColorRect->w, pColorRect->y + pColorRect->h);
309 Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1);
310 };
311
312 auto &&RenderLightingRect = [&](CUIRect *pColorRect, const ColorRGBA &CurColor) {
313 ColorHSLA LeftColor = color_cast<ColorHSLA>(rgb: CurColor);
314 ColorHSLA RightColor = color_cast<ColorHSLA>(rgb: CurColor);
315
316 LeftColor.l = DarkestLight;
317 RightColor.l = 1.0f;
318
319 const ColorRGBA LeftColorRGBA = color_cast<ColorRGBA>(hsl: LeftColor);
320 const ColorRGBA RightColorRGBA = color_cast<ColorRGBA>(hsl: RightColor);
321
322 Graphics()->SetColor4(TopLeft: LeftColorRGBA, TopRight: RightColorRGBA, BottomLeft: RightColorRGBA, BottomRight: LeftColorRGBA);
323
324 IGraphics::CFreeformItem Freeform(
325 pColorRect->x, pColorRect->y,
326 pColorRect->x + pColorRect->w, pColorRect->y,
327 pColorRect->x, pColorRect->y + pColorRect->h,
328 pColorRect->x + pColorRect->w, pColorRect->y + pColorRect->h);
329 Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1);
330 };
331
332 auto &&RenderAlphaRect = [&](CUIRect *pColorRect, const ColorRGBA &CurColorFull) {
333 const ColorRGBA LeftColorRGBA = color_cast<ColorRGBA>(hsl: color_cast<ColorHSLA>(rgb: CurColorFull).WithAlpha(alpha: 0.0f));
334 const ColorRGBA RightColorRGBA = color_cast<ColorRGBA>(hsl: color_cast<ColorHSLA>(rgb: CurColorFull).WithAlpha(alpha: 1.0f));
335
336 Graphics()->SetColor4(TopLeft: LeftColorRGBA, TopRight: RightColorRGBA, BottomLeft: RightColorRGBA, BottomRight: LeftColorRGBA);
337
338 IGraphics::CFreeformItem Freeform(
339 pColorRect->x, pColorRect->y,
340 pColorRect->x + pColorRect->w, pColorRect->y,
341 pColorRect->x, pColorRect->y + pColorRect->h,
342 pColorRect->x + pColorRect->w, pColorRect->y + pColorRect->h);
343 Graphics()->QuadsDrawFreeform(pArray: &Freeform, Num: 1);
344 };
345
346 for(int i = 0; i < 3 + Alpha; i++)
347 {
348 CUIRect Button, Label;
349 pRect->HSplitTop(Cut: SizePerEntry, pTop: &Button, pBottom: pRect);
350 pRect->HSplitTop(Cut: MarginPerEntry, pTop: nullptr, pBottom: pRect);
351 Button.VSplitLeft(Cut: 140.0f, pLeft: &Label, pRight: &Button);
352 Label.VMargin(Cut: 10.0f, pOtherRect: &Label);
353
354 Button.Draw(Color: ColorRGBA(0.15f, 0.15f, 0.15f, 1.0f), Corners: IGraphics::CORNER_ALL, Rounding: 1.0f);
355
356 CUIRect Rail;
357 Button.Margin(Cut: 2.0f, pOtherRect: &Rail);
358
359 char aBuf[32];
360
361 // Hue
362 if(i == 0)
363 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s: %.1f° (%03d)", apLabels[i], Color[i] * 360.0f, round_to_int(f: Color[i] * 255.0f));
364 // Lht
365 else if(i == 2)
366 {
367 // handle internal light clamping, see `UnclampLighting`
368 float Lht = DarkestLight + Color[i] * (1.0f - DarkestLight);
369 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s: %.1f%% (%03d)", apLabels[i], Lht * 100.0f, round_to_int(f: Color[i] * 255.0f));
370 }
371 // Sat and Alpha
372 else
373 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s: %.1f%% (%03d)", apLabels[i], Color[i] * 100.0f, round_to_int(f: Color[i] * 255.0f));
374 Ui()->DoLabel(pRect: &Label, pText: aBuf, Size: 12.0f, Align: TEXTALIGN_ML);
375
376 ColorRGBA HandleColor;
377 Graphics()->TextureClear();
378 Graphics()->TrianglesBegin();
379 if(i == 0)
380 {
381 RenderHueRect(&Rail);
382 HandleColor = color_cast<ColorRGBA>(hsl: ColorHSLA(Color.h, 1.0f, 0.5f, 1.0f));
383 }
384 else if(i == 1)
385 {
386 RenderSaturationRect(&Rail, color_cast<ColorRGBA>(hsl: ColorHSLA(Color.h, 1.0f, 0.5f, 1.0f)));
387 HandleColor = color_cast<ColorRGBA>(hsl: ColorHSLA(Color.h, Color.s, 0.5f, 1.0f));
388 }
389 else if(i == 2)
390 {
391 RenderLightingRect(&Rail, color_cast<ColorRGBA>(hsl: ColorHSLA(Color.h, Color.s, 0.5f, 1.0f)));
392 HandleColor = color_cast<ColorRGBA>(hsl: ColorHSLA(Color.h, Color.s, Color.l, 1.0f).UnclampLighting(Darkest: DarkestLight));
393 }
394 else if(i == 3)
395 {
396 RenderAlphaRect(&Rail, color_cast<ColorRGBA>(hsl: ColorHSLA(Color.h, Color.s, Color.l, 1.0f).UnclampLighting(Darkest: DarkestLight)));
397 HandleColor = color_cast<ColorRGBA>(hsl: Color.UnclampLighting(Darkest: DarkestLight));
398 }
399 Graphics()->TrianglesEnd();
400
401 Color[i] = Ui()->DoScrollbarH(pId: &((char *)pColor)[i], pRect: &Button, Current: Color[i], pColorInner: &HandleColor);
402 }
403
404 if(OriginalColor != Color)
405 {
406 *pColor = Color.Pack(Alpha);
407 }
408 return PrevPackedColor != *pColor;
409}
410