mirror of
https://github.com/ddnet/ddnet.git
synced 2024-10-21 16:28:19 +00:00
15f839960a
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.
2589 lines
81 KiB
C++
2589 lines
81 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 <algorithm>
|
||
#include <chrono>
|
||
#include <cmath>
|
||
#include <vector>
|
||
|
||
#include <base/math.h>
|
||
#include <base/system.h>
|
||
#include <base/vmath.h>
|
||
|
||
#include <engine/client.h>
|
||
#include <engine/config.h>
|
||
#include <engine/editor.h>
|
||
#include <engine/friends.h>
|
||
#include <engine/graphics.h>
|
||
#include <engine/keys.h>
|
||
#include <engine/serverbrowser.h>
|
||
#include <engine/shared/config.h>
|
||
#include <engine/storage.h>
|
||
#include <engine/textrender.h>
|
||
|
||
#include <game/generated/protocol.h>
|
||
|
||
#include <engine/client/updater.h>
|
||
|
||
#include <game/client/components/binds.h>
|
||
#include <game/client/components/console.h>
|
||
#include <game/client/components/menu_background.h>
|
||
#include <game/client/components/sounds.h>
|
||
#include <game/client/gameclient.h>
|
||
#include <game/client/ui_listbox.h>
|
||
#include <game/generated/client_data.h>
|
||
#include <game/localization.h>
|
||
|
||
#include "countryflags.h"
|
||
#include "menus.h"
|
||
|
||
using namespace FontIcons;
|
||
using namespace std::chrono_literals;
|
||
|
||
ColorRGBA CMenus::ms_GuiColor;
|
||
ColorRGBA CMenus::ms_ColorTabbarInactiveOutgame;
|
||
ColorRGBA CMenus::ms_ColorTabbarActiveOutgame;
|
||
ColorRGBA CMenus::ms_ColorTabbarHoverOutgame;
|
||
ColorRGBA CMenus::ms_ColorTabbarInactive;
|
||
ColorRGBA CMenus::ms_ColorTabbarActive = ColorRGBA(0, 0, 0, 0.5f);
|
||
ColorRGBA CMenus::ms_ColorTabbarHover;
|
||
ColorRGBA CMenus::ms_ColorTabbarInactiveIngame;
|
||
ColorRGBA CMenus::ms_ColorTabbarActiveIngame;
|
||
ColorRGBA CMenus::ms_ColorTabbarHoverIngame;
|
||
|
||
SColorPicker CMenus::ms_ColorPicker;
|
||
bool CMenus::ms_ValueSelectorTextMode;
|
||
|
||
float CMenus::ms_ButtonHeight = 25.0f;
|
||
float CMenus::ms_ListheaderHeight = 17.0f;
|
||
|
||
CMenus::CMenus()
|
||
{
|
||
m_Popup = POPUP_NONE;
|
||
m_ActivePage = PAGE_INTERNET;
|
||
m_MenuPage = 0;
|
||
m_GamePage = PAGE_GAME;
|
||
m_JoinTutorial = false;
|
||
|
||
m_NeedRestartGraphics = false;
|
||
m_NeedRestartSound = false;
|
||
m_NeedSendinfo = false;
|
||
m_NeedSendDummyinfo = false;
|
||
m_MenuActive = true;
|
||
m_ShowStart = true;
|
||
|
||
str_copy(m_aCurrentDemoFolder, "demos");
|
||
|
||
m_DemoPlayerState = DEMOPLAYER_NONE;
|
||
m_Dummy = false;
|
||
|
||
m_ServerProcess.m_Process = INVALID_PROCESS;
|
||
|
||
for(SUIAnimator &animator : m_aAnimatorsSettingsTab)
|
||
{
|
||
animator.m_YOffset = -2.5f;
|
||
animator.m_HOffset = 5.0f;
|
||
animator.m_WOffset = 5.0f;
|
||
animator.m_RepositionLabel = true;
|
||
}
|
||
|
||
for(SUIAnimator &animator : m_aAnimatorsBigPage)
|
||
{
|
||
animator.m_YOffset = -5.0f;
|
||
animator.m_HOffset = 5.0f;
|
||
}
|
||
|
||
for(SUIAnimator &animator : m_aAnimatorsSmallPage)
|
||
{
|
||
animator.m_YOffset = -2.5f;
|
||
animator.m_HOffset = 2.5f;
|
||
}
|
||
|
||
m_PasswordInput.SetBuffer(g_Config.m_Password, sizeof(g_Config.m_Password));
|
||
m_PasswordInput.SetHidden(true);
|
||
}
|
||
|
||
int CMenus::DoButton_Toggle(const void *pID, int Checked, const CUIRect *pRect, bool Active)
|
||
{
|
||
Graphics()->TextureSet(g_pData->m_aImages[IMAGE_GUIBUTTONS].m_Id);
|
||
Graphics()->QuadsBegin();
|
||
if(!Active)
|
||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.5f);
|
||
RenderTools()->SelectSprite(Checked ? SPRITE_GUIBUTTON_ON : SPRITE_GUIBUTTON_OFF);
|
||
IGraphics::CQuadItem QuadItem(pRect->x, pRect->y, pRect->w, pRect->h);
|
||
Graphics()->QuadsDrawTL(&QuadItem, 1);
|
||
if(UI()->HotItem() == pID && Active)
|
||
{
|
||
RenderTools()->SelectSprite(SPRITE_GUIBUTTON_HOVER);
|
||
QuadItem = IGraphics::CQuadItem(pRect->x, pRect->y, pRect->w, pRect->h);
|
||
Graphics()->QuadsDrawTL(&QuadItem, 1);
|
||
}
|
||
Graphics()->QuadsEnd();
|
||
|
||
return Active ? UI()->DoButtonLogic(pID, Checked, pRect) : 0;
|
||
}
|
||
|
||
int CMenus::DoButton_Menu(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, const char *pImageName, int Corners, float r, float FontFactor, vec4 ColorHot, vec4 Color, bool CheckForActiveColorPicker)
|
||
{
|
||
CUIRect Text = *pRect;
|
||
|
||
bool MouseInsideColorPicker = false;
|
||
|
||
if(CheckForActiveColorPicker)
|
||
{
|
||
if(ms_ColorPicker.m_Active)
|
||
{
|
||
CUIRect PickerRect;
|
||
PickerRect.x = ms_ColorPicker.m_X;
|
||
PickerRect.y = ms_ColorPicker.m_Y;
|
||
PickerRect.w = ms_ColorPicker.ms_Width;
|
||
PickerRect.h = ms_ColorPicker.ms_Height;
|
||
|
||
MouseInsideColorPicker = UI()->MouseInside(&PickerRect);
|
||
}
|
||
}
|
||
|
||
if(!MouseInsideColorPicker)
|
||
Color.a *= UI()->ButtonColorMul(pButtonContainer);
|
||
pRect->Draw(Color, Corners, r);
|
||
|
||
if(pImageName)
|
||
{
|
||
CUIRect Image;
|
||
pRect->VSplitRight(pRect->h * 4.0f, &Text, &Image); // always correct ratio for image
|
||
|
||
// render image
|
||
const CMenuImage *pImage = FindMenuImage(pImageName);
|
||
if(pImage)
|
||
{
|
||
Graphics()->TextureSet(UI()->HotItem() == pButtonContainer ? pImage->m_OrgTexture : pImage->m_GreyTexture);
|
||
Graphics()->WrapClamp();
|
||
Graphics()->QuadsBegin();
|
||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||
IGraphics::CQuadItem QuadItem(Image.x, Image.y, Image.w, Image.h);
|
||
Graphics()->QuadsDrawTL(&QuadItem, 1);
|
||
Graphics()->QuadsEnd();
|
||
Graphics()->WrapNormal();
|
||
}
|
||
}
|
||
|
||
Text.HMargin(pRect->h >= 20.0f ? 2.0f : 1.0f, &Text);
|
||
Text.HMargin((Text.h * FontFactor) / 2.0f, &Text);
|
||
UI()->DoLabel(&Text, pText, Text.h * CUI::ms_FontmodHeight, TEXTALIGN_MC);
|
||
|
||
if(MouseInsideColorPicker)
|
||
return 0;
|
||
|
||
return UI()->DoButtonLogic(pButtonContainer, Checked, pRect);
|
||
}
|
||
|
||
void CMenus::DoButton_KeySelect(const void *pID, const char *pText, const CUIRect *pRect)
|
||
{
|
||
pRect->Draw(ColorRGBA(1, 1, 1, 0.5f * UI()->ButtonColorMul(pID)), IGraphics::CORNER_ALL, 5.0f);
|
||
CUIRect Temp;
|
||
pRect->HMargin(1.0f, &Temp);
|
||
UI()->DoLabel(&Temp, pText, Temp.h * CUI::ms_FontmodHeight, TEXTALIGN_MC);
|
||
}
|
||
|
||
int CMenus::DoButton_MenuTab(CButtonContainer *pButtonContainer, const char *pText, int Checked, const CUIRect *pRect, int Corners, SUIAnimator *pAnimator, const ColorRGBA *pDefaultColor, const ColorRGBA *pActiveColor, const ColorRGBA *pHoverColor, float EdgeRounding)
|
||
{
|
||
const bool MouseInside = UI()->MouseInside(pRect);
|
||
CUIRect Rect = *pRect;
|
||
|
||
if(pAnimator != NULL)
|
||
{
|
||
auto Time = time_get_nanoseconds();
|
||
|
||
if(pAnimator->m_Time + 100ms < Time)
|
||
{
|
||
pAnimator->m_Value = pAnimator->m_Active ? 1 : 0;
|
||
pAnimator->m_Time = Time;
|
||
}
|
||
|
||
pAnimator->m_Active = Checked || MouseInside;
|
||
|
||
if(pAnimator->m_Active)
|
||
pAnimator->m_Value = clamp<float>(pAnimator->m_Value + (Time - pAnimator->m_Time).count() / (double)std::chrono::nanoseconds(100ms).count(), 0, 1);
|
||
else
|
||
pAnimator->m_Value = clamp<float>(pAnimator->m_Value - (Time - pAnimator->m_Time).count() / (double)std::chrono::nanoseconds(100ms).count(), 0, 1);
|
||
|
||
Rect.w += pAnimator->m_Value * pAnimator->m_WOffset;
|
||
Rect.h += pAnimator->m_Value * pAnimator->m_HOffset;
|
||
Rect.x += pAnimator->m_Value * pAnimator->m_XOffset;
|
||
Rect.y += pAnimator->m_Value * pAnimator->m_YOffset;
|
||
|
||
pAnimator->m_Time = Time;
|
||
}
|
||
|
||
if(Checked)
|
||
{
|
||
ColorRGBA ColorMenuTab = ms_ColorTabbarActive;
|
||
if(pActiveColor)
|
||
ColorMenuTab = *pActiveColor;
|
||
|
||
Rect.Draw(ColorMenuTab, Corners, EdgeRounding);
|
||
}
|
||
else
|
||
{
|
||
if(MouseInside)
|
||
{
|
||
ColorRGBA HoverColorMenuTab = ms_ColorTabbarHover;
|
||
if(pHoverColor)
|
||
HoverColorMenuTab = *pHoverColor;
|
||
|
||
Rect.Draw(HoverColorMenuTab, Corners, EdgeRounding);
|
||
}
|
||
else
|
||
{
|
||
ColorRGBA ColorMenuTab = ms_ColorTabbarInactive;
|
||
if(pDefaultColor)
|
||
ColorMenuTab = *pDefaultColor;
|
||
|
||
Rect.Draw(ColorMenuTab, Corners, EdgeRounding);
|
||
}
|
||
}
|
||
|
||
if(pAnimator != NULL)
|
||
{
|
||
if(pAnimator->m_RepositionLabel)
|
||
{
|
||
Rect.x += Rect.w - pRect->w + Rect.x - pRect->x;
|
||
Rect.y += Rect.h - pRect->h + Rect.y - pRect->y;
|
||
}
|
||
|
||
if(!pAnimator->m_ScaleLabel)
|
||
{
|
||
Rect.w = pRect->w;
|
||
Rect.h = pRect->h;
|
||
}
|
||
}
|
||
|
||
CUIRect Temp;
|
||
Rect.HMargin(2.0f, &Temp);
|
||
UI()->DoLabel(&Temp, pText, Temp.h * CUI::ms_FontmodHeight, TEXTALIGN_MC);
|
||
|
||
return UI()->DoButtonLogic(pButtonContainer, Checked, pRect);
|
||
}
|
||
|
||
int CMenus::DoButton_GridHeader(const void *pID, const char *pText, int Checked, const CUIRect *pRect)
|
||
{
|
||
if(Checked == 2)
|
||
pRect->Draw(ColorRGBA(1, 0.98f, 0.5f, 0.55f), IGraphics::CORNER_T, 5.0f);
|
||
else if(Checked)
|
||
pRect->Draw(ColorRGBA(1, 1, 1, 0.5f), IGraphics::CORNER_T, 5.0f);
|
||
|
||
CUIRect Temp;
|
||
pRect->VSplitLeft(5.0f, nullptr, &Temp);
|
||
UI()->DoLabel(&Temp, pText, pRect->h * CUI::ms_FontmodHeight, TEXTALIGN_ML);
|
||
return UI()->DoButtonLogic(pID, Checked, pRect);
|
||
}
|
||
|
||
int CMenus::DoButton_CheckBox_Common(const void *pID, const char *pText, const char *pBoxText, const CUIRect *pRect)
|
||
{
|
||
CUIRect Box, Label;
|
||
pRect->VSplitLeft(pRect->h, &Box, &Label);
|
||
Label.VSplitLeft(5.0f, nullptr, &Label);
|
||
|
||
Box.Margin(2.0f, &Box);
|
||
Box.Draw(ColorRGBA(1, 1, 1, 0.25f * UI()->ButtonColorMul(pID)), IGraphics::CORNER_ALL, 3.0f);
|
||
|
||
const bool Checkable = *pBoxText == 'X';
|
||
if(Checkable)
|
||
{
|
||
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 | ETextRenderFlags::TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT);
|
||
TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT));
|
||
UI()->DoLabel(&Box, FONT_ICON_XMARK, Box.h * CUI::ms_FontmodHeight, TEXTALIGN_MC);
|
||
TextRender()->SetCurFont(nullptr);
|
||
}
|
||
else
|
||
UI()->DoLabel(&Box, pBoxText, Box.h * CUI::ms_FontmodHeight, TEXTALIGN_MC);
|
||
|
||
TextRender()->SetRenderFlags(0);
|
||
UI()->DoLabel(&Label, pText, Box.h * CUI::ms_FontmodHeight, TEXTALIGN_ML);
|
||
|
||
return UI()->DoButtonLogic(pID, 0, pRect);
|
||
}
|
||
|
||
void CMenus::DoLaserPreview(const CUIRect *pRect, const ColorHSLA LaserOutlineColor, const ColorHSLA LaserInnerColor, const int LaserType)
|
||
{
|
||
ColorRGBA LaserRGB;
|
||
CUIRect Section = *pRect;
|
||
vec2 From = vec2(Section.x + 50.0f, Section.y + Section.h / 2.0f);
|
||
vec2 Pos = vec2(Section.x + Section.w - 10.0f, Section.y + Section.h / 2.0f);
|
||
|
||
Graphics()->BlendNormal();
|
||
Graphics()->TextureClear();
|
||
Graphics()->QuadsBegin();
|
||
|
||
LaserRGB = color_cast<ColorRGBA, ColorHSLA>(LaserOutlineColor);
|
||
ColorRGBA OuterColor(LaserRGB.r, LaserRGB.g, LaserRGB.b, 1.0f);
|
||
Graphics()->SetColor(LaserRGB.r, LaserRGB.g, LaserRGB.b, 1.0f);
|
||
vec2 Out = vec2(0.0f, -1.0f) * (3.15f);
|
||
|
||
IGraphics::CFreeformItem Freeform(From.x - Out.x, From.y - Out.y, From.x + Out.x, From.y + Out.y, Pos.x - Out.x, Pos.y - Out.y, Pos.x + Out.x, Pos.y + Out.y);
|
||
Graphics()->QuadsDrawFreeform(&Freeform, 1);
|
||
|
||
LaserRGB = color_cast<ColorRGBA, ColorHSLA>(LaserInnerColor);
|
||
ColorRGBA InnerColor(LaserRGB.r, LaserRGB.g, LaserRGB.b, 1.0f);
|
||
Out = vec2(0.0f, -1.0f) * (2.25f);
|
||
Graphics()->SetColor(InnerColor.r, InnerColor.g, InnerColor.b, 1.0f);
|
||
|
||
Freeform = IGraphics::CFreeformItem(From.x - Out.x, From.y - Out.y, From.x + Out.x, From.y + Out.y, Pos.x - Out.x, Pos.y - Out.y, Pos.x + Out.x, Pos.y + Out.y);
|
||
Graphics()->QuadsDrawFreeform(&Freeform, 1);
|
||
Graphics()->QuadsEnd();
|
||
|
||
Graphics()->BlendNormal();
|
||
int SpriteIndex = time_get() % 3;
|
||
Graphics()->TextureSet(GameClient()->m_ParticlesSkin.m_aSpriteParticleSplat[SpriteIndex]);
|
||
Graphics()->QuadsBegin();
|
||
Graphics()->QuadsSetRotation(time_get());
|
||
Graphics()->SetColor(OuterColor.r, OuterColor.g, OuterColor.b, 1.0f);
|
||
IGraphics::CQuadItem QuadItem(Pos.x, Pos.y, 24, 24);
|
||
Graphics()->QuadsDraw(&QuadItem, 1);
|
||
Graphics()->SetColor(InnerColor.r, InnerColor.g, InnerColor.b, 1.0f);
|
||
QuadItem = IGraphics::CQuadItem(Pos.x, Pos.y, 20, 20);
|
||
Graphics()->QuadsDraw(&QuadItem, 1);
|
||
Graphics()->QuadsEnd();
|
||
|
||
switch(LaserType)
|
||
{
|
||
case LASERTYPE_RIFLE:
|
||
Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpriteWeaponLaser);
|
||
RenderTools()->SelectSprite(SPRITE_WEAPON_LASER_BODY);
|
||
Graphics()->QuadsBegin();
|
||
Graphics()->QuadsSetSubset(0, 0, 1, 1);
|
||
RenderTools()->DrawSprite(Section.x + 30.0f, Section.y + Section.h / 2.0f, 60.0f);
|
||
Graphics()->QuadsEnd();
|
||
break;
|
||
case LASERTYPE_SHOTGUN:
|
||
Graphics()->TextureSet(GameClient()->m_GameSkin.m_SpriteWeaponShotgun);
|
||
RenderTools()->SelectSprite(SPRITE_WEAPON_SHOTGUN_BODY);
|
||
Graphics()->QuadsBegin();
|
||
Graphics()->QuadsSetSubset(0, 0, 1, 1);
|
||
RenderTools()->DrawSprite(Section.x + 30.0f, Section.y + Section.h / 2.0f, 60.0f);
|
||
Graphics()->QuadsEnd();
|
||
break;
|
||
default:
|
||
Graphics()->QuadsBegin();
|
||
Graphics()->SetColor(OuterColor.r, OuterColor.g, OuterColor.b, 1.0f);
|
||
QuadItem = IGraphics::CQuadItem(From.x, From.y, 24, 24);
|
||
Graphics()->QuadsDraw(&QuadItem, 1);
|
||
Graphics()->SetColor(InnerColor.r, InnerColor.g, InnerColor.b, 1.0f);
|
||
QuadItem = IGraphics::CQuadItem(From.x, From.y, 20, 20);
|
||
Graphics()->QuadsDraw(&QuadItem, 1);
|
||
Graphics()->QuadsEnd();
|
||
}
|
||
}
|
||
|
||
ColorHSLA CMenus::DoLine_ColorPicker(CButtonContainer *pResetID, const float LineSize, const float LabelSize, const float BottomMargin, CUIRect *pMainRect, const char *pText, unsigned int *pColorValue, const ColorRGBA DefaultColor, bool CheckBoxSpacing, int *pCheckBoxValue)
|
||
{
|
||
CUIRect Section, ColorPickerButton, ResetButton, Label;
|
||
|
||
pMainRect->HSplitTop(LineSize, &Section, pMainRect);
|
||
pMainRect->HSplitTop(BottomMargin, nullptr, pMainRect);
|
||
|
||
if(CheckBoxSpacing || pCheckBoxValue != nullptr)
|
||
{
|
||
CUIRect CheckBox;
|
||
Section.VSplitLeft(Section.h, &CheckBox, &Section);
|
||
if(pCheckBoxValue != nullptr)
|
||
{
|
||
CheckBox.Margin(2.0f, &CheckBox);
|
||
if(DoButton_CheckBox(pCheckBoxValue, "", *pCheckBoxValue, &CheckBox))
|
||
*pCheckBoxValue ^= 1;
|
||
}
|
||
}
|
||
|
||
Section.VSplitLeft(5.0f, nullptr, &Section);
|
||
Section.VSplitMid(&Label, &Section, Section.h);
|
||
Section.VSplitRight(60.0f, &Section, &ResetButton);
|
||
Section.VSplitRight(8.0f, &Section, nullptr);
|
||
Section.VSplitRight(Section.h, &Section, &ColorPickerButton);
|
||
|
||
UI()->DoLabel(&Label, pText, LabelSize, TEXTALIGN_ML);
|
||
|
||
ColorHSLA PickedColor = RenderHSLColorPicker(&ColorPickerButton, pColorValue, false);
|
||
|
||
ResetButton.HMargin(2.0f, &ResetButton);
|
||
if(DoButton_Menu(pResetID, Localize("Reset"), 0, &ResetButton, nullptr, IGraphics::CORNER_ALL, 8.0f, 0.0f, vec4(1, 1, 1, 0.5f), vec4(1, 1, 1, 0.25f), true))
|
||
{
|
||
*pColorValue = color_cast<ColorHSLA>(DefaultColor).Pack(false);
|
||
}
|
||
|
||
return PickedColor;
|
||
}
|
||
|
||
int CMenus::DoButton_CheckBoxAutoVMarginAndSet(const void *pID, const char *pText, int *pValue, CUIRect *pRect, float VMargin)
|
||
{
|
||
CUIRect CheckBoxRect;
|
||
pRect->HSplitTop(VMargin, &CheckBoxRect, pRect);
|
||
|
||
int Logic = DoButton_CheckBox_Common(pID, pText, *pValue ? "X" : "", &CheckBoxRect);
|
||
|
||
if(Logic)
|
||
*pValue ^= 1;
|
||
|
||
return Logic;
|
||
}
|
||
|
||
int CMenus::DoButton_CheckBox(const void *pID, const char *pText, int Checked, const CUIRect *pRect)
|
||
{
|
||
return DoButton_CheckBox_Common(pID, pText, Checked ? "X" : "", pRect);
|
||
}
|
||
|
||
int CMenus::DoButton_CheckBox_Number(const void *pID, const char *pText, int Checked, const CUIRect *pRect)
|
||
{
|
||
char aBuf[16];
|
||
str_format(aBuf, sizeof(aBuf), "%d", Checked);
|
||
return DoButton_CheckBox_Common(pID, pText, aBuf, pRect);
|
||
}
|
||
|
||
int CMenus::DoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, bool UseScroll, int Current, int Min, int Max, int Step, float Scale, bool IsHex, float Round, ColorRGBA *pColor)
|
||
{
|
||
// logic
|
||
static float s_Value;
|
||
static CLineInputNumber s_NumberInput;
|
||
static void *s_pLastTextpID = pID;
|
||
const bool Inside = UI()->MouseInside(pRect);
|
||
|
||
if(Inside)
|
||
UI()->SetHotItem(pID);
|
||
|
||
const int Base = IsHex ? 16 : 10;
|
||
|
||
if(UI()->MouseButtonReleased(1) && UI()->HotItem() == pID)
|
||
{
|
||
s_pLastTextpID = pID;
|
||
ms_ValueSelectorTextMode = true;
|
||
s_NumberInput.SetInteger(Current, Base);
|
||
}
|
||
|
||
if(UI()->CheckActiveItem(pID))
|
||
{
|
||
if(!UI()->MouseButton(0))
|
||
{
|
||
//m_LockMouse = false;
|
||
UI()->SetActiveItem(nullptr);
|
||
ms_ValueSelectorTextMode = false;
|
||
}
|
||
}
|
||
|
||
if(ms_ValueSelectorTextMode && s_pLastTextpID == pID)
|
||
{
|
||
UI()->DoEditBox(&s_NumberInput, pRect, 10.0f);
|
||
UI()->SetActiveItem(&s_NumberInput);
|
||
|
||
if(Input()->KeyIsPressed(KEY_RETURN) || Input()->KeyIsPressed(KEY_KP_ENTER) ||
|
||
((UI()->MouseButtonClicked(1) || UI()->MouseButtonClicked(0)) && !Inside))
|
||
{
|
||
Current = clamp(s_NumberInput.GetInteger(Base), Min, Max);
|
||
//m_LockMouse = false;
|
||
UI()->SetActiveItem(nullptr);
|
||
ms_ValueSelectorTextMode = false;
|
||
}
|
||
|
||
if(Input()->KeyIsPressed(KEY_ESCAPE))
|
||
{
|
||
//m_LockMouse = false;
|
||
UI()->SetActiveItem(nullptr);
|
||
ms_ValueSelectorTextMode = false;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if(UI()->CheckActiveItem(pID))
|
||
{
|
||
if(UseScroll)
|
||
{
|
||
if(UI()->MouseButton(0))
|
||
{
|
||
float delta = UI()->MouseDeltaX();
|
||
|
||
if(Input()->ShiftIsPressed())
|
||
s_Value += delta * 0.05f;
|
||
else
|
||
s_Value += delta;
|
||
|
||
if(absolute(s_Value) > Scale)
|
||
{
|
||
int Count = (int)(s_Value / Scale);
|
||
s_Value = std::fmod(s_Value, Scale);
|
||
Current += Step * Count;
|
||
Current = clamp(Current, Min, Max);
|
||
|
||
// Constrain to discrete steps
|
||
if(Count > 0)
|
||
Current = Current / Step * Step;
|
||
else
|
||
Current = std::ceil(Current / (float)Step) * Step;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else if(UI()->HotItem() == pID)
|
||
{
|
||
if(UI()->MouseButtonClicked(0))
|
||
{
|
||
//m_LockMouse = true;
|
||
s_Value = 0;
|
||
UI()->SetActiveItem(pID);
|
||
}
|
||
}
|
||
|
||
// render
|
||
char aBuf[128];
|
||
if(pLabel[0] != '\0')
|
||
{
|
||
if(IsHex)
|
||
str_format(aBuf, sizeof(aBuf), "%s #%06X", pLabel, Current);
|
||
else
|
||
str_format(aBuf, sizeof(aBuf), "%s %d", pLabel, Current);
|
||
}
|
||
else
|
||
{
|
||
if(IsHex)
|
||
str_format(aBuf, sizeof(aBuf), "#%06X", Current);
|
||
else
|
||
str_format(aBuf, sizeof(aBuf), "%d", Current);
|
||
}
|
||
pRect->Draw(*pColor, IGraphics::CORNER_ALL, Round);
|
||
UI()->DoLabel(pRect, aBuf, 10.0f, TEXTALIGN_MC);
|
||
}
|
||
|
||
if(!ms_ValueSelectorTextMode)
|
||
s_NumberInput.Clear();
|
||
|
||
return Current;
|
||
}
|
||
|
||
int CMenus::DoKeyReader(void *pID, const CUIRect *pRect, int Key, int ModifierCombination, int *pNewModifierCombination)
|
||
{
|
||
// process
|
||
static void *pGrabbedID = 0;
|
||
static bool MouseReleased = true;
|
||
static int s_ButtonUsed = 0;
|
||
const bool Inside = UI()->MouseHovered(pRect);
|
||
int NewKey = Key;
|
||
*pNewModifierCombination = ModifierCombination;
|
||
|
||
if(!UI()->MouseButton(0) && !UI()->MouseButton(1) && pGrabbedID == pID)
|
||
MouseReleased = true;
|
||
|
||
if(UI()->CheckActiveItem(pID))
|
||
{
|
||
if(m_Binder.m_GotKey)
|
||
{
|
||
// abort with escape key
|
||
if(m_Binder.m_Key.m_Key != KEY_ESCAPE)
|
||
{
|
||
NewKey = m_Binder.m_Key.m_Key;
|
||
*pNewModifierCombination = m_Binder.m_ModifierCombination;
|
||
}
|
||
m_Binder.m_GotKey = false;
|
||
UI()->SetActiveItem(nullptr);
|
||
MouseReleased = false;
|
||
pGrabbedID = pID;
|
||
}
|
||
|
||
if(s_ButtonUsed == 1 && !UI()->MouseButton(1))
|
||
{
|
||
if(Inside)
|
||
NewKey = 0;
|
||
UI()->SetActiveItem(nullptr);
|
||
}
|
||
}
|
||
else if(UI()->HotItem() == pID)
|
||
{
|
||
if(MouseReleased)
|
||
{
|
||
if(UI()->MouseButton(0))
|
||
{
|
||
m_Binder.m_TakeKey = true;
|
||
m_Binder.m_GotKey = false;
|
||
UI()->SetActiveItem(pID);
|
||
s_ButtonUsed = 0;
|
||
}
|
||
|
||
if(UI()->MouseButton(1))
|
||
{
|
||
UI()->SetActiveItem(pID);
|
||
s_ButtonUsed = 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
if(Inside)
|
||
UI()->SetHotItem(pID);
|
||
|
||
// draw
|
||
if(UI()->CheckActiveItem(pID) && s_ButtonUsed == 0)
|
||
DoButton_KeySelect(pID, "???", pRect);
|
||
else if(NewKey == 0)
|
||
DoButton_KeySelect(pID, "", pRect);
|
||
else
|
||
{
|
||
char aBuf[64];
|
||
str_format(aBuf, sizeof(aBuf), "%s%s", CBinds::GetKeyBindModifiersName(*pNewModifierCombination), Input()->KeyName(NewKey));
|
||
DoButton_KeySelect(pID, aBuf, pRect);
|
||
}
|
||
|
||
return NewKey;
|
||
}
|
||
|
||
int CMenus::RenderMenubar(CUIRect r)
|
||
{
|
||
CUIRect Box = r;
|
||
CUIRect Button;
|
||
|
||
m_ActivePage = m_MenuPage;
|
||
int NewPage = -1;
|
||
|
||
if(Client()->State() != IClient::STATE_OFFLINE)
|
||
m_ActivePage = m_GamePage;
|
||
|
||
if(Client()->State() == IClient::STATE_OFFLINE)
|
||
{
|
||
Box.VSplitLeft(33.0f, &Button, &Box);
|
||
static CButtonContainer s_StartButton;
|
||
|
||
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);
|
||
|
||
bool GotNewsOrUpdate = false;
|
||
|
||
#if defined(CONF_AUTOUPDATE)
|
||
int State = Updater()->GetCurrentState();
|
||
bool NeedUpdate = str_comp(Client()->LatestVersion(), "0");
|
||
if(State == IUpdater::CLEAN && NeedUpdate)
|
||
{
|
||
GotNewsOrUpdate = true;
|
||
}
|
||
#endif
|
||
|
||
GotNewsOrUpdate |= (bool)g_Config.m_UiUnreadNews;
|
||
|
||
ColorRGBA HomeButtonColorAlert(0, 1, 0, 0.25f);
|
||
ColorRGBA HomeButtonColorAlertHover(0, 1, 0, 0.5f);
|
||
ColorRGBA *pHomeButtonColor = NULL;
|
||
ColorRGBA *pHomeButtonColorHover = NULL;
|
||
|
||
const char *pHomeScreenButtonLabel = FONT_ICON_HOUSE;
|
||
if(GotNewsOrUpdate)
|
||
{
|
||
pHomeScreenButtonLabel = FONT_ICON_NEWSPAPER;
|
||
pHomeButtonColor = &HomeButtonColorAlert;
|
||
pHomeButtonColorHover = &HomeButtonColorAlertHover;
|
||
}
|
||
|
||
if(DoButton_MenuTab(&s_StartButton, pHomeScreenButtonLabel, false, &Button, IGraphics::CORNER_T, &m_aAnimatorsSmallPage[SMALL_TAB_HOME], pHomeButtonColor, pHomeButtonColor, pHomeButtonColorHover, 10.0f))
|
||
{
|
||
m_ShowStart = true;
|
||
}
|
||
|
||
TextRender()->SetRenderFlags(0);
|
||
TextRender()->SetCurFont(NULL);
|
||
|
||
Box.VSplitLeft(10.0f, 0, &Box);
|
||
|
||
// offline menus
|
||
if(m_ActivePage == PAGE_NEWS)
|
||
{
|
||
Box.VSplitLeft(100.0f, &Button, &Box);
|
||
static CButtonContainer s_NewsButton;
|
||
if(DoButton_MenuTab(&s_NewsButton, Localize("News"), m_ActivePage == PAGE_NEWS, &Button, IGraphics::CORNER_T, &m_aAnimatorsBigPage[BIG_TAB_NEWS]))
|
||
{
|
||
NewPage = PAGE_NEWS;
|
||
}
|
||
}
|
||
else if(m_ActivePage == PAGE_DEMOS)
|
||
{
|
||
Box.VSplitLeft(100.0f, &Button, &Box);
|
||
static CButtonContainer s_DemosButton;
|
||
if(DoButton_MenuTab(&s_DemosButton, Localize("Demos"), m_ActivePage == PAGE_DEMOS, &Button, IGraphics::CORNER_T, &m_aAnimatorsBigPage[BIG_TAB_DEMOS]))
|
||
{
|
||
DemolistPopulate();
|
||
NewPage = PAGE_DEMOS;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Box.VSplitLeft(100.0f, &Button, &Box);
|
||
static CButtonContainer s_InternetButton;
|
||
if(DoButton_MenuTab(&s_InternetButton, Localize("Internet"), m_ActivePage == PAGE_INTERNET, &Button, IGraphics::CORNER_T, &m_aAnimatorsBigPage[BIG_TAB_INTERNET]))
|
||
{
|
||
if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_INTERNET)
|
||
ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET);
|
||
NewPage = PAGE_INTERNET;
|
||
}
|
||
|
||
Box.VSplitLeft(100.0f, &Button, &Box);
|
||
static CButtonContainer s_LanButton;
|
||
if(DoButton_MenuTab(&s_LanButton, Localize("LAN"), m_ActivePage == PAGE_LAN, &Button, IGraphics::CORNER_T, &m_aAnimatorsBigPage[BIG_TAB_LAN]))
|
||
{
|
||
if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_LAN)
|
||
ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN);
|
||
NewPage = PAGE_LAN;
|
||
}
|
||
|
||
Box.VSplitLeft(100.0f, &Button, &Box);
|
||
static CButtonContainer s_FavoritesButton;
|
||
if(DoButton_MenuTab(&s_FavoritesButton, Localize("Favorites"), m_ActivePage == PAGE_FAVORITES, &Button, IGraphics::CORNER_T, &m_aAnimatorsBigPage[BIG_TAB_FAVORITES]))
|
||
{
|
||
if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_FAVORITES)
|
||
ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES);
|
||
NewPage = PAGE_FAVORITES;
|
||
}
|
||
|
||
Box.VSplitLeft(90.0f, &Button, &Box);
|
||
static CButtonContainer s_DDNetButton;
|
||
if(DoButton_MenuTab(&s_DDNetButton, "DDNet", m_ActivePage == PAGE_DDNET, &Button, IGraphics::CORNER_T, &m_aAnimatorsBigPage[BIG_TAB_DDNET]))
|
||
{
|
||
if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_DDNET)
|
||
{
|
||
Client()->RequestDDNetInfo();
|
||
ServerBrowser()->Refresh(IServerBrowser::TYPE_DDNET);
|
||
}
|
||
NewPage = PAGE_DDNET;
|
||
}
|
||
|
||
Box.VSplitLeft(90.0f, &Button, &Box);
|
||
static CButtonContainer s_KoGButton;
|
||
if(DoButton_MenuTab(&s_KoGButton, "KoG", m_ActivePage == PAGE_KOG, &Button, IGraphics::CORNER_T, &m_aAnimatorsBigPage[BIG_TAB_KOG]))
|
||
{
|
||
if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_KOG)
|
||
{
|
||
Client()->RequestDDNetInfo();
|
||
ServerBrowser()->Refresh(IServerBrowser::TYPE_KOG);
|
||
}
|
||
NewPage = PAGE_KOG;
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// online menus
|
||
Box.VSplitLeft(90.0f, &Button, &Box);
|
||
static CButtonContainer s_GameButton;
|
||
if(DoButton_MenuTab(&s_GameButton, Localize("Game"), m_ActivePage == PAGE_GAME, &Button, IGraphics::CORNER_TL))
|
||
NewPage = PAGE_GAME;
|
||
|
||
Box.VSplitLeft(90.0f, &Button, &Box);
|
||
static CButtonContainer s_PlayersButton;
|
||
if(DoButton_MenuTab(&s_PlayersButton, Localize("Players"), m_ActivePage == PAGE_PLAYERS, &Button, 0))
|
||
NewPage = PAGE_PLAYERS;
|
||
|
||
Box.VSplitLeft(130.0f, &Button, &Box);
|
||
static CButtonContainer s_ServerInfoButton;
|
||
if(DoButton_MenuTab(&s_ServerInfoButton, Localize("Server info"), m_ActivePage == PAGE_SERVER_INFO, &Button, 0))
|
||
NewPage = PAGE_SERVER_INFO;
|
||
|
||
Box.VSplitLeft(90.0f, &Button, &Box);
|
||
static CButtonContainer s_NetworkButton;
|
||
if(DoButton_MenuTab(&s_NetworkButton, Localize("Browser"), m_ActivePage == PAGE_NETWORK, &Button, 0))
|
||
NewPage = PAGE_NETWORK;
|
||
|
||
{
|
||
static CButtonContainer s_GhostButton;
|
||
if(GameClient()->m_GameInfo.m_Race)
|
||
{
|
||
Box.VSplitLeft(90.0f, &Button, &Box);
|
||
if(DoButton_MenuTab(&s_GhostButton, Localize("Ghost"), m_ActivePage == PAGE_GHOST, &Button, 0))
|
||
NewPage = PAGE_GHOST;
|
||
}
|
||
}
|
||
|
||
Box.VSplitLeft(100.0f, &Button, &Box);
|
||
Box.VSplitLeft(4.0f, 0, &Box);
|
||
static CButtonContainer s_CallVoteButton;
|
||
if(DoButton_MenuTab(&s_CallVoteButton, Localize("Call vote"), m_ActivePage == PAGE_CALLVOTE, &Button, IGraphics::CORNER_TR))
|
||
{
|
||
NewPage = PAGE_CALLVOTE;
|
||
m_ControlPageOpening = true;
|
||
}
|
||
}
|
||
|
||
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);
|
||
|
||
Box.VSplitRight(33.0f, &Box, &Button);
|
||
static CButtonContainer s_QuitButton;
|
||
ColorRGBA QuitColor(1, 0, 0, 0.5f);
|
||
if(DoButton_MenuTab(&s_QuitButton, FONT_ICON_POWER_OFF, 0, &Button, IGraphics::CORNER_T, &m_aAnimatorsSmallPage[SMALL_TAB_QUIT], nullptr, nullptr, &QuitColor, 10.0f))
|
||
{
|
||
if(m_pClient->Editor()->HasUnsavedData() || (Client()->GetCurrentRaceTime() / 60 >= g_Config.m_ClConfirmQuitTime && g_Config.m_ClConfirmQuitTime >= 0))
|
||
{
|
||
m_Popup = POPUP_QUIT;
|
||
}
|
||
else
|
||
{
|
||
Client()->Quit();
|
||
}
|
||
}
|
||
|
||
Box.VSplitRight(10.0f, &Box, &Button);
|
||
Box.VSplitRight(33.0f, &Box, &Button);
|
||
static CButtonContainer s_SettingsButton;
|
||
|
||
if(DoButton_MenuTab(&s_SettingsButton, FONT_ICON_GEAR, m_ActivePage == PAGE_SETTINGS, &Button, IGraphics::CORNER_T, &m_aAnimatorsSmallPage[SMALL_TAB_SETTINGS], nullptr, nullptr, nullptr, 10.0f))
|
||
NewPage = PAGE_SETTINGS;
|
||
|
||
Box.VSplitRight(10.0f, &Box, &Button);
|
||
Box.VSplitRight(33.0f, &Box, &Button);
|
||
static CButtonContainer s_EditorButton;
|
||
if(DoButton_MenuTab(&s_EditorButton, FONT_ICON_PEN_TO_SQUARE, 0, &Button, IGraphics::CORNER_T, &m_aAnimatorsSmallPage[SMALL_TAB_EDITOR], nullptr, nullptr, nullptr, 10.0f))
|
||
{
|
||
g_Config.m_ClEditor = 1;
|
||
}
|
||
|
||
if(Client()->State() == IClient::STATE_OFFLINE)
|
||
{
|
||
Box.VSplitRight(10.0f, &Box, &Button);
|
||
Box.VSplitRight(33.0f, &Box, &Button);
|
||
static CButtonContainer s_DemoButton;
|
||
|
||
if(DoButton_MenuTab(&s_DemoButton, FONT_ICON_CLAPPERBOARD, m_ActivePage == PAGE_DEMOS, &Button, IGraphics::CORNER_T, &m_aAnimatorsSmallPage[SMALL_TAB_DEMOBUTTON], nullptr, nullptr, nullptr, 10.0f))
|
||
NewPage = PAGE_DEMOS;
|
||
|
||
Box.VSplitRight(10.0f, &Box, &Button);
|
||
Box.VSplitRight(33.0f, &Box, &Button);
|
||
static CButtonContainer s_ServerButton;
|
||
|
||
if(DoButton_MenuTab(&s_ServerButton, FONT_ICON_EARTH_AMERICAS, m_ActivePage == g_Config.m_UiPage, &Button, IGraphics::CORNER_T, &m_aAnimatorsSmallPage[SMALL_TAB_SERVER], nullptr, nullptr, nullptr, 10.0f))
|
||
NewPage = g_Config.m_UiPage;
|
||
}
|
||
|
||
TextRender()->SetRenderFlags(0);
|
||
TextRender()->SetCurFont(nullptr);
|
||
|
||
if(NewPage != -1)
|
||
{
|
||
if(Client()->State() == IClient::STATE_OFFLINE)
|
||
SetMenuPage(NewPage);
|
||
else
|
||
m_GamePage = NewPage;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
void CMenus::RenderLoading(const char *pCaption, const char *pContent, int IncreaseCounter, bool RenderLoadingBar, bool RenderMenuBackgroundMap)
|
||
{
|
||
// TODO: not supported right now due to separate render thread
|
||
|
||
static std::chrono::nanoseconds s_LastLoadRender{0};
|
||
const int CurLoadRenderCount = m_LoadCurrent;
|
||
m_LoadCurrent += IncreaseCounter;
|
||
const float Percent = CurLoadRenderCount / (float)m_LoadTotal;
|
||
|
||
// make sure that we don't render for each little thing we load
|
||
// because that will slow down loading if we have vsync
|
||
if(time_get_nanoseconds() - s_LastLoadRender < std::chrono::nanoseconds(1s) / 60l)
|
||
return;
|
||
|
||
s_LastLoadRender = time_get_nanoseconds();
|
||
|
||
// need up date this here to get correct
|
||
ms_GuiColor = color_cast<ColorRGBA>(ColorHSLA(g_Config.m_UiColor, true));
|
||
|
||
UI()->MapScreen();
|
||
|
||
if(!RenderMenuBackgroundMap || !m_pBackground->Render())
|
||
{
|
||
RenderBackground();
|
||
}
|
||
|
||
CUIRect Box = *UI()->Screen();
|
||
Box.Margin(160.0f, &Box);
|
||
|
||
Graphics()->BlendNormal();
|
||
|
||
Graphics()->TextureClear();
|
||
Box.Draw(ColorRGBA{0, 0, 0, 0.50f}, IGraphics::CORNER_ALL, 15.0f);
|
||
|
||
CUIRect Part;
|
||
Box.HSplitTop(20.f, nullptr, &Box);
|
||
Box.HSplitTop(24.f, &Part, &Box);
|
||
Part.VMargin(20.f, &Part);
|
||
UI()->DoLabel(&Part, pCaption, 24.f, TEXTALIGN_MC);
|
||
|
||
Box.HSplitTop(20.f, nullptr, &Box);
|
||
Box.HSplitTop(24.f, &Part, &Box);
|
||
Part.VMargin(20.f, &Part);
|
||
UI()->DoLabel(&Part, pContent, 20.0f, TEXTALIGN_MC);
|
||
|
||
if(RenderLoadingBar)
|
||
Graphics()->DrawRect(Box.x + 40, Box.y + Box.h - 75, (Box.w - 80) * Percent, 25, ColorRGBA(1.0f, 1.0f, 1.0f, 0.75f), IGraphics::CORNER_ALL, 5.0f);
|
||
|
||
Client()->UpdateAndSwap();
|
||
}
|
||
|
||
void CMenus::RenderNews(CUIRect MainView)
|
||
{
|
||
g_Config.m_UiUnreadNews = false;
|
||
|
||
MainView.Draw(ms_ColorTabbarActive, IGraphics::CORNER_B, 10.0f);
|
||
|
||
MainView.HSplitTop(10.0f, nullptr, &MainView);
|
||
MainView.VSplitLeft(15.0f, nullptr, &MainView);
|
||
|
||
CUIRect Label;
|
||
|
||
const char *pStr = Client()->m_aNews;
|
||
char aLine[256];
|
||
while((pStr = str_next_token(pStr, "\n", aLine, sizeof(aLine))))
|
||
{
|
||
const int Len = str_length(aLine);
|
||
if(Len > 0 && aLine[0] == '|' && aLine[Len - 1] == '|')
|
||
{
|
||
MainView.HSplitTop(30.0f, &Label, &MainView);
|
||
aLine[Len - 1] = '\0';
|
||
UI()->DoLabel(&Label, aLine + 1, 20.0f, TEXTALIGN_ML);
|
||
}
|
||
else
|
||
{
|
||
MainView.HSplitTop(20.0f, &Label, &MainView);
|
||
UI()->DoLabel(&Label, aLine, 15.f, TEXTALIGN_ML);
|
||
}
|
||
}
|
||
}
|
||
|
||
void CMenus::OnInit()
|
||
{
|
||
if(g_Config.m_ClShowWelcome)
|
||
m_Popup = POPUP_LANGUAGE;
|
||
if(g_Config.m_ClSkipStartMenu)
|
||
m_ShowStart = false;
|
||
|
||
m_RefreshButton.Init(UI(), -1);
|
||
m_ConnectButton.Init(UI(), -1);
|
||
|
||
Console()->Chain("add_favorite", ConchainServerbrowserUpdate, this);
|
||
Console()->Chain("remove_favorite", ConchainServerbrowserUpdate, this);
|
||
Console()->Chain("add_friend", ConchainFriendlistUpdate, this);
|
||
Console()->Chain("remove_friend", ConchainFriendlistUpdate, this);
|
||
|
||
Console()->Chain("snd_enable", ConchainUpdateMusicState, this);
|
||
Console()->Chain("snd_enable_music", ConchainUpdateMusicState, this);
|
||
|
||
Console()->Chain("cl_assets_entities", ConchainAssetsEntities, this);
|
||
Console()->Chain("cl_asset_game", ConchainAssetGame, this);
|
||
Console()->Chain("cl_asset_emoticons", ConchainAssetEmoticons, this);
|
||
Console()->Chain("cl_asset_particles", ConchainAssetParticles, this);
|
||
Console()->Chain("cl_asset_hud", ConchainAssetHud, this);
|
||
Console()->Chain("cl_asset_extras", ConchainAssetExtras, this);
|
||
|
||
m_TextureBlob = Graphics()->LoadTexture("blob.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0);
|
||
|
||
// setup load amount
|
||
const int NumMenuImages = 5;
|
||
m_LoadCurrent = 0;
|
||
m_LoadTotal = g_pData->m_NumImages + NumMenuImages + GameClient()->ComponentCount();
|
||
if(!g_Config.m_ClThreadsoundloading)
|
||
m_LoadTotal += g_pData->m_NumSounds;
|
||
|
||
m_IsInit = true;
|
||
|
||
// load menu images
|
||
m_vMenuImages.clear();
|
||
Storage()->ListDirectory(IStorage::TYPE_ALL, "menuimages", MenuImageScan, this);
|
||
}
|
||
|
||
void CMenus::OnConsoleInit()
|
||
{
|
||
auto *pConfigManager = Kernel()->RequestInterface<IConfigManager>();
|
||
if(pConfigManager != nullptr)
|
||
pConfigManager->RegisterCallback(CMenus::ConfigSaveCallback, this);
|
||
Console()->Register("add_favorite_skin", "s[skin_name]", CFGFLAG_CLIENT, Con_AddFavoriteSkin, this, "Add a skin as a favorite");
|
||
Console()->Register("remove_favorite_skin", "s[skin_name]", CFGFLAG_CLIENT, Con_RemFavoriteSkin, this, "Remove a skin from the favorites");
|
||
}
|
||
|
||
void CMenus::ConchainUpdateMusicState(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
|
||
{
|
||
pfnCallback(pResult, pCallbackUserData);
|
||
auto *pSelf = (CMenus *)pUserData;
|
||
if(pResult->NumArguments())
|
||
pSelf->UpdateMusicState();
|
||
}
|
||
|
||
void CMenus::UpdateMusicState()
|
||
{
|
||
const bool ShouldPlay = Client()->State() == IClient::STATE_OFFLINE && g_Config.m_SndEnable && g_Config.m_SndMusic;
|
||
if(ShouldPlay && !m_pClient->m_Sounds.IsPlaying(SOUND_MENU))
|
||
m_pClient->m_Sounds.Enqueue(CSounds::CHN_MUSIC, SOUND_MENU);
|
||
else if(!ShouldPlay && m_pClient->m_Sounds.IsPlaying(SOUND_MENU))
|
||
m_pClient->m_Sounds.Stop(SOUND_MENU);
|
||
}
|
||
|
||
void CMenus::PopupMessage(const char *pTitle, const char *pMessage, const char *pButtonLabel, int NextPopup, FPopupButtonCallback pfnButtonCallback)
|
||
{
|
||
// reset active item
|
||
UI()->SetActiveItem(nullptr);
|
||
|
||
str_copy(m_aPopupTitle, pTitle);
|
||
str_copy(m_aPopupMessage, pMessage);
|
||
str_copy(m_aPopupButtons[BUTTON_CONFIRM].m_aLabel, pButtonLabel);
|
||
m_aPopupButtons[BUTTON_CONFIRM].m_NextPopup = NextPopup;
|
||
m_aPopupButtons[BUTTON_CONFIRM].m_pfnCallback = pfnButtonCallback;
|
||
m_Popup = POPUP_MESSAGE;
|
||
}
|
||
|
||
void CMenus::PopupConfirm(const char *pTitle, const char *pMessage, const char *pConfirmButtonLabel, const char *pCancelButtonLabel,
|
||
FPopupButtonCallback pfnConfirmButtonCallback, int ConfirmNextPopup, FPopupButtonCallback pfnCancelButtonCallback, int CancelNextPopup)
|
||
{
|
||
// reset active item
|
||
UI()->SetActiveItem(nullptr);
|
||
|
||
str_copy(m_aPopupTitle, pTitle);
|
||
str_copy(m_aPopupMessage, pMessage);
|
||
str_copy(m_aPopupButtons[BUTTON_CONFIRM].m_aLabel, pConfirmButtonLabel);
|
||
m_aPopupButtons[BUTTON_CONFIRM].m_NextPopup = ConfirmNextPopup;
|
||
m_aPopupButtons[BUTTON_CONFIRM].m_pfnCallback = pfnConfirmButtonCallback;
|
||
str_copy(m_aPopupButtons[BUTTON_CANCEL].m_aLabel, pCancelButtonLabel);
|
||
m_aPopupButtons[BUTTON_CANCEL].m_NextPopup = CancelNextPopup;
|
||
m_aPopupButtons[BUTTON_CANCEL].m_pfnCallback = pfnCancelButtonCallback;
|
||
m_Popup = POPUP_CONFIRM;
|
||
}
|
||
|
||
void CMenus::PopupWarning(const char *pTopic, const char *pBody, const char *pButton, std::chrono::nanoseconds Duration)
|
||
{
|
||
// no multiline support for console
|
||
std::string BodyStr = pBody;
|
||
while(BodyStr.find('\n') != std::string::npos)
|
||
BodyStr.replace(BodyStr.find('\n'), 1, " ");
|
||
dbg_msg(pTopic, "%s", BodyStr.c_str());
|
||
|
||
// reset active item
|
||
UI()->SetActiveItem(nullptr);
|
||
|
||
str_copy(m_aMessageTopic, pTopic);
|
||
str_copy(m_aMessageBody, pBody);
|
||
str_copy(m_aMessageButton, pButton);
|
||
m_Popup = POPUP_WARNING;
|
||
SetActive(true);
|
||
|
||
m_PopupWarningDuration = Duration;
|
||
m_PopupWarningLastTime = time_get_nanoseconds();
|
||
}
|
||
|
||
bool CMenus::CanDisplayWarning()
|
||
{
|
||
return m_Popup == POPUP_NONE;
|
||
}
|
||
|
||
void CMenus::RenderColorPicker()
|
||
{
|
||
if(UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
|
||
{
|
||
ms_ColorPicker.m_Active = false;
|
||
ms_ValueSelectorTextMode = false;
|
||
UI()->SetActiveItem(nullptr);
|
||
}
|
||
|
||
if(!ms_ColorPicker.m_Active)
|
||
return;
|
||
|
||
// First check if we should disable color picker
|
||
CUIRect PickerRect;
|
||
PickerRect.x = ms_ColorPicker.m_X;
|
||
PickerRect.y = ms_ColorPicker.m_Y;
|
||
PickerRect.w = ms_ColorPicker.ms_Width;
|
||
PickerRect.h = ms_ColorPicker.ms_Height;
|
||
|
||
if(UI()->MouseButtonClicked(0) && !UI()->MouseInside(&PickerRect) && !UI()->MouseInside(&ms_ColorPicker.m_AttachedRect))
|
||
{
|
||
ms_ColorPicker.m_Active = false;
|
||
ms_ValueSelectorTextMode = false;
|
||
UI()->SetActiveItem(nullptr);
|
||
return;
|
||
}
|
||
|
||
// Prevent activation of UI elements outside of active color picker
|
||
if(UI()->MouseInside(&PickerRect))
|
||
UI()->SetHotItem(&ms_ColorPicker);
|
||
|
||
// Render
|
||
ColorRGBA BackgroundColor(0.1f, 0.1f, 0.1f, 1.0f);
|
||
PickerRect.Draw(BackgroundColor, 0, 0);
|
||
|
||
CUIRect ColorsArea, HueArea, ValuesHitbox, BottomArea, HueRect, SatRect, ValueRect, HexRect, AlphaRect;
|
||
PickerRect.Margin(3, &ColorsArea);
|
||
|
||
ColorsArea.HSplitBottom(ms_ColorPicker.ms_Height - 140.0f, &ColorsArea, &ValuesHitbox);
|
||
ColorsArea.VSplitRight(20, &ColorsArea, &HueArea);
|
||
|
||
BottomArea = ValuesHitbox;
|
||
BottomArea.HSplitTop(3, 0x0, &BottomArea);
|
||
HueArea.VSplitLeft(3, 0x0, &HueArea);
|
||
|
||
BottomArea.HSplitTop(20, &HueRect, &BottomArea);
|
||
BottomArea.HSplitTop(3, 0x0, &BottomArea);
|
||
|
||
constexpr float ValuePadding = 5.0f;
|
||
const float HsvValueWidth = (HueRect.w - ValuePadding * 2) / 3.0f;
|
||
const float HexValueWidth = HsvValueWidth * 2 + ValuePadding;
|
||
|
||
HueRect.VSplitLeft(HsvValueWidth, &HueRect, &SatRect);
|
||
SatRect.VSplitLeft(ValuePadding, 0x0, &SatRect);
|
||
SatRect.VSplitLeft(HsvValueWidth, &SatRect, &ValueRect);
|
||
ValueRect.VSplitLeft(ValuePadding, 0x0, &ValueRect);
|
||
|
||
BottomArea.HSplitTop(20, &HexRect, &BottomArea);
|
||
HexRect.VSplitLeft(HexValueWidth, &HexRect, &AlphaRect);
|
||
AlphaRect.VSplitLeft(ValuePadding, 0x0, &AlphaRect);
|
||
|
||
if(UI()->MouseButtonReleased(1) && !UI()->MouseInside(&ValuesHitbox))
|
||
{
|
||
ms_ColorPicker.m_Active = false;
|
||
ms_ValueSelectorTextMode = false;
|
||
UI()->SetActiveItem(nullptr);
|
||
return;
|
||
}
|
||
|
||
ColorRGBA BlackColor(0, 0, 0, 0.5f);
|
||
|
||
HueArea.Draw(BlackColor, 0, 0);
|
||
HueArea.Margin(1, &HueArea);
|
||
|
||
ColorsArea.Draw(BlackColor, 0, 0);
|
||
ColorsArea.Margin(1, &ColorsArea);
|
||
|
||
ColorHSVA PickerColorHSV(ms_ColorPicker.m_HSVColor);
|
||
unsigned H = (unsigned)(PickerColorHSV.x * 255.0f);
|
||
unsigned S = (unsigned)(PickerColorHSV.y * 255.0f);
|
||
unsigned V = (unsigned)(PickerColorHSV.z * 255.0f);
|
||
|
||
// Color Area
|
||
vec4 TL = color_cast<ColorRGBA>(ColorHSVA(PickerColorHSV.x, 0.0f, 1.0f));
|
||
vec4 TR = color_cast<ColorRGBA>(ColorHSVA(PickerColorHSV.x, 1.0f, 1.0f));
|
||
vec4 BL = color_cast<ColorRGBA>(ColorHSVA(PickerColorHSV.x, 0.0f, 1.0f));
|
||
vec4 BR = color_cast<ColorRGBA>(ColorHSVA(PickerColorHSV.x, 1.0f, 1.0f));
|
||
|
||
ColorsArea.Draw4(TL, TR, BL, BR, IGraphics::CORNER_NONE, 0.0f);
|
||
|
||
TL = vec4(0.0f, 0.0f, 0.0f, 0.0f);
|
||
TR = vec4(0.0f, 0.0f, 0.0f, 0.0f);
|
||
BL = vec4(0.0f, 0.0f, 0.0f, 1.0f);
|
||
BR = vec4(0.0f, 0.0f, 0.0f, 1.0f);
|
||
|
||
ColorsArea.Draw4(TL, TR, BL, BR, IGraphics::CORNER_NONE, 0.0f);
|
||
|
||
// Hue Area
|
||
static const float s_aColorIndices[7][3] = {
|
||
{1.0f, 0.0f, 0.0f}, // red
|
||
{1.0f, 0.0f, 1.0f}, // magenta
|
||
{0.0f, 0.0f, 1.0f}, // blue
|
||
{0.0f, 1.0f, 1.0f}, // cyan
|
||
{0.0f, 1.0f, 0.0f}, // green
|
||
{1.0f, 1.0f, 0.0f}, // yellow
|
||
{1.0f, 0.0f, 0.0f} // red
|
||
};
|
||
|
||
float HuePickerOffset = HueArea.h / 6.0f;
|
||
CUIRect HuePartialArea = HueArea;
|
||
HuePartialArea.h = HuePickerOffset;
|
||
|
||
for(int j = 0; j < 6; j++)
|
||
{
|
||
TL = vec4(s_aColorIndices[j][0], s_aColorIndices[j][1], s_aColorIndices[j][2], 1.0f);
|
||
BL = vec4(s_aColorIndices[j + 1][0], s_aColorIndices[j + 1][1], s_aColorIndices[j + 1][2], 1.0f);
|
||
|
||
HuePartialArea.y = HueArea.y + HuePickerOffset * j;
|
||
HuePartialArea.Draw4(TL, TL, BL, BL, IGraphics::CORNER_NONE, 0.0f);
|
||
}
|
||
|
||
// Editboxes Area
|
||
ColorRGBA EditboxBackground(0, 0, 0, 0.4f);
|
||
|
||
static int s_aValueSelectorIds[4];
|
||
|
||
H = DoValueSelector(&s_aValueSelectorIds[0], &HueRect, "H:", true, H, 0, 255, 1, 1, false, 5.0f, &EditboxBackground);
|
||
S = DoValueSelector(&s_aValueSelectorIds[1], &SatRect, "S:", true, S, 0, 255, 1, 1, false, 5.0f, &EditboxBackground);
|
||
V = DoValueSelector(&s_aValueSelectorIds[2], &ValueRect, "V:", true, V, 0, 255, 1, 1, false, 5.0f, &EditboxBackground);
|
||
|
||
PickerColorHSV = ColorHSVA(H / 255.0f, S / 255.0f, V / 255.0f);
|
||
|
||
unsigned int Hex = color_cast<ColorRGBA>(PickerColorHSV).Pack(false);
|
||
unsigned int NewHex = DoValueSelector(&s_aValueSelectorIds[3], &HexRect, "HEX:", false, Hex, 0, 0xFFFFFF, 1, 1, true, 5.0f, &EditboxBackground);
|
||
|
||
if(Hex != NewHex)
|
||
PickerColorHSV = color_cast<ColorHSVA>(ColorRGBA(NewHex));
|
||
|
||
// TODO : ALPHA SUPPORT
|
||
UI()->DoLabel(&AlphaRect, "A: 255", 10, TEXTALIGN_MC);
|
||
AlphaRect.Draw(ColorRGBA(0, 0, 0, 0.65f), IGraphics::CORNER_ALL, 5.0f);
|
||
|
||
// Logic
|
||
float PickerX, PickerY;
|
||
|
||
static int s_ColorPickerId = 0;
|
||
static int s_HuePickerId = 0;
|
||
|
||
if(UI()->DoPickerLogic(&s_ColorPickerId, &ColorsArea, &PickerX, &PickerY))
|
||
{
|
||
PickerColorHSV.y = PickerX / ColorsArea.w;
|
||
PickerColorHSV.z = 1.0f - PickerY / ColorsArea.h;
|
||
}
|
||
|
||
if(UI()->DoPickerLogic(&s_HuePickerId, &HueArea, &PickerX, &PickerY))
|
||
PickerColorHSV.x = 1.0f - PickerY / HueArea.h;
|
||
|
||
// Marker Color Area
|
||
float MarkerX = ColorsArea.x + ColorsArea.w * PickerColorHSV.y;
|
||
float MarkerY = ColorsArea.y + ColorsArea.h * (1.0f - PickerColorHSV.z);
|
||
|
||
const float MarkerOutlineInd = PickerColorHSV.z > 0.5f ? 0.0f : 1.0f;
|
||
ColorRGBA MarkerOutline(MarkerOutlineInd, MarkerOutlineInd, MarkerOutlineInd, 1.0f);
|
||
|
||
Graphics()->TextureClear();
|
||
Graphics()->QuadsBegin();
|
||
Graphics()->SetColor(MarkerOutline);
|
||
Graphics()->DrawCircle(MarkerX, MarkerY, 4.5f, 32);
|
||
Graphics()->SetColor(color_cast<ColorRGBA>(PickerColorHSV));
|
||
Graphics()->DrawCircle(MarkerX, MarkerY, 3.5f, 32);
|
||
Graphics()->QuadsEnd();
|
||
|
||
// Marker Hue Area
|
||
CUIRect HueMarker;
|
||
HueArea.Margin(-2.5f, &HueMarker);
|
||
HueMarker.h = 6.5f;
|
||
HueMarker.y = (HueArea.y + HueArea.h * (1.0f - PickerColorHSV.x)) - HueMarker.h / 2.0f;
|
||
|
||
ColorRGBA HueMarkerColor = color_cast<ColorRGBA>(ColorHSVA(PickerColorHSV.x, 1, 1, 1));
|
||
const float HueMarkerOutlineColor = PickerColorHSV.x > 0.75f ? 1.0f : 0.0f;
|
||
ColorRGBA HueMarkerOutline(HueMarkerOutlineColor, HueMarkerOutlineColor, HueMarkerOutlineColor, 1);
|
||
|
||
HueMarker.Draw(HueMarkerOutline, IGraphics::CORNER_ALL, 1.2f);
|
||
HueMarker.Margin(1.2f, &HueMarker);
|
||
HueMarker.Draw(HueMarkerColor, IGraphics::CORNER_ALL, 1.2f);
|
||
|
||
ms_ColorPicker.m_HSVColor = PickerColorHSV.Pack(false);
|
||
*ms_ColorPicker.m_pColor = color_cast<ColorHSLA>(PickerColorHSV).Pack(false);
|
||
}
|
||
|
||
int CMenus::Render()
|
||
{
|
||
if(Client()->State() == IClient::STATE_DEMOPLAYBACK && m_Popup == POPUP_NONE)
|
||
return 0;
|
||
|
||
CUIRect Screen = *UI()->Screen();
|
||
UI()->MapScreen();
|
||
UI()->ResetMouseSlow();
|
||
|
||
static int s_Frame = 0;
|
||
if(s_Frame == 0)
|
||
{
|
||
m_MenuPage = g_Config.m_UiPage;
|
||
s_Frame++;
|
||
}
|
||
else if(s_Frame == 1)
|
||
{
|
||
UpdateMusicState();
|
||
s_Frame++;
|
||
|
||
RefreshBrowserTab(g_Config.m_UiPage);
|
||
if(g_Config.m_UiPage == PAGE_INTERNET)
|
||
ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET);
|
||
else if(g_Config.m_UiPage == PAGE_LAN)
|
||
ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN);
|
||
else if(g_Config.m_UiPage == PAGE_FAVORITES)
|
||
ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES);
|
||
else if(g_Config.m_UiPage == PAGE_DDNET)
|
||
ServerBrowser()->Refresh(IServerBrowser::TYPE_DDNET);
|
||
else if(g_Config.m_UiPage == PAGE_KOG)
|
||
ServerBrowser()->Refresh(IServerBrowser::TYPE_KOG);
|
||
}
|
||
|
||
if(Client()->State() >= IClient::STATE_ONLINE)
|
||
{
|
||
ms_ColorTabbarInactive = ms_ColorTabbarInactiveIngame;
|
||
ms_ColorTabbarActive = ms_ColorTabbarActiveIngame;
|
||
ms_ColorTabbarHover = ms_ColorTabbarHoverIngame;
|
||
}
|
||
else
|
||
{
|
||
if(!m_pBackground->Render())
|
||
{
|
||
RenderBackground();
|
||
}
|
||
ms_ColorTabbarInactive = ms_ColorTabbarInactiveOutgame;
|
||
ms_ColorTabbarActive = ms_ColorTabbarActiveOutgame;
|
||
ms_ColorTabbarHover = ms_ColorTabbarHoverOutgame;
|
||
}
|
||
|
||
CUIRect TabBar;
|
||
CUIRect MainView;
|
||
|
||
// some margin around the screen
|
||
Screen.Margin(10.0f, &Screen);
|
||
|
||
static bool s_SoundCheck = false;
|
||
if(!s_SoundCheck && m_Popup == POPUP_NONE)
|
||
{
|
||
if(Client()->SoundInitFailed())
|
||
PopupMessage(Localize("Sound error"), Localize("The audio device couldn't be initialised."), Localize("Ok"));
|
||
s_SoundCheck = true;
|
||
}
|
||
|
||
if(m_Popup == POPUP_NONE)
|
||
{
|
||
if(m_JoinTutorial && !Client()->InfoTaskRunning() && !ServerBrowser()->IsGettingServerlist())
|
||
{
|
||
m_JoinTutorial = false;
|
||
const char *pAddr = ServerBrowser()->GetTutorialServer();
|
||
if(pAddr)
|
||
Client()->Connect(pAddr);
|
||
}
|
||
if(m_ShowStart && Client()->State() == IClient::STATE_OFFLINE)
|
||
{
|
||
m_pBackground->ChangePosition(CMenuBackground::POS_START);
|
||
RenderStartMenu(Screen);
|
||
}
|
||
else
|
||
{
|
||
Screen.HSplitTop(24.0f, &TabBar, &MainView);
|
||
|
||
if(Client()->State() == IClient::STATE_OFFLINE && UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
|
||
{
|
||
m_ShowStart = true;
|
||
}
|
||
|
||
// render news
|
||
if(m_MenuPage < PAGE_NEWS || m_MenuPage > PAGE_SETTINGS || (Client()->State() == IClient::STATE_OFFLINE && m_MenuPage >= PAGE_GAME && m_MenuPage <= PAGE_CALLVOTE))
|
||
{
|
||
ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET);
|
||
SetMenuPage(PAGE_INTERNET);
|
||
}
|
||
|
||
// render current page
|
||
if(Client()->State() != IClient::STATE_OFFLINE)
|
||
{
|
||
if(m_GamePage == PAGE_GAME)
|
||
{
|
||
RenderGame(MainView);
|
||
RenderIngameHint();
|
||
}
|
||
else if(m_GamePage == PAGE_PLAYERS)
|
||
{
|
||
RenderPlayers(MainView);
|
||
}
|
||
else if(m_GamePage == PAGE_SERVER_INFO)
|
||
{
|
||
RenderServerInfo(MainView);
|
||
}
|
||
else if(m_GamePage == PAGE_NETWORK)
|
||
{
|
||
RenderInGameNetwork(MainView);
|
||
}
|
||
else if(m_GamePage == PAGE_GHOST)
|
||
{
|
||
RenderGhost(MainView);
|
||
}
|
||
else if(m_GamePage == PAGE_CALLVOTE)
|
||
{
|
||
RenderServerControl(MainView);
|
||
}
|
||
else if(m_GamePage == PAGE_SETTINGS)
|
||
{
|
||
RenderSettings(MainView);
|
||
}
|
||
}
|
||
else if(m_MenuPage == PAGE_NEWS)
|
||
{
|
||
m_pBackground->ChangePosition(CMenuBackground::POS_NEWS);
|
||
RenderNews(MainView);
|
||
}
|
||
else if(m_MenuPage == PAGE_INTERNET)
|
||
{
|
||
m_pBackground->ChangePosition(CMenuBackground::POS_BROWSER_INTERNET);
|
||
RenderServerbrowser(MainView);
|
||
}
|
||
else if(m_MenuPage == PAGE_LAN)
|
||
{
|
||
m_pBackground->ChangePosition(CMenuBackground::POS_BROWSER_LAN);
|
||
RenderServerbrowser(MainView);
|
||
}
|
||
else if(m_MenuPage == PAGE_DEMOS)
|
||
{
|
||
m_pBackground->ChangePosition(CMenuBackground::POS_DEMOS);
|
||
RenderDemoList(MainView);
|
||
}
|
||
else if(m_MenuPage == PAGE_FAVORITES)
|
||
{
|
||
m_pBackground->ChangePosition(CMenuBackground::POS_BROWSER_FAVORITES);
|
||
RenderServerbrowser(MainView);
|
||
}
|
||
else if(m_MenuPage == PAGE_DDNET)
|
||
{
|
||
m_pBackground->ChangePosition(CMenuBackground::POS_BROWSER_CUSTOM0);
|
||
RenderServerbrowser(MainView);
|
||
}
|
||
else if(m_MenuPage == PAGE_KOG)
|
||
{
|
||
m_pBackground->ChangePosition(CMenuBackground::POS_BROWSER_CUSTOM0 + 1);
|
||
RenderServerbrowser(MainView);
|
||
}
|
||
else if(m_MenuPage == PAGE_SETTINGS)
|
||
RenderSettings(MainView);
|
||
|
||
// do tab bar
|
||
RenderMenubar(TabBar);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
char aBuf[1536];
|
||
const char *pTitle = "";
|
||
const char *pExtraText = "";
|
||
const char *pButtonText = "";
|
||
int ExtraAlign = 0;
|
||
bool UseIpLabel = false;
|
||
|
||
ColorRGBA BgColor = ColorRGBA(0.0f, 0.0f, 0.0f, 0.5f);
|
||
if(m_Popup == POPUP_MESSAGE || m_Popup == POPUP_CONFIRM)
|
||
{
|
||
pTitle = m_aPopupTitle;
|
||
pExtraText = m_aPopupMessage;
|
||
}
|
||
else if(m_Popup == POPUP_CONNECTING)
|
||
{
|
||
pTitle = Localize("Connecting to");
|
||
UseIpLabel = true;
|
||
pButtonText = Localize("Abort");
|
||
if(Client()->State() == IClient::STATE_CONNECTING && time_get() - Client()->StateStartTime() > time_freq())
|
||
{
|
||
int Connectivity = Client()->UdpConnectivity(Client()->ConnectNetTypes());
|
||
switch(Connectivity)
|
||
{
|
||
case IClient::CONNECTIVITY_UNKNOWN:
|
||
break;
|
||
case IClient::CONNECTIVITY_CHECKING:
|
||
pExtraText = Localize("Trying to determine UDP connectivity...");
|
||
break;
|
||
case IClient::CONNECTIVITY_UNREACHABLE:
|
||
pExtraText = Localize("UDP seems to be filtered.");
|
||
break;
|
||
case IClient::CONNECTIVITY_DIFFERING_UDP_TCP_IP_ADDRESSES:
|
||
pExtraText = Localize("UDP and TCP IP addresses seem to be different. Try disabling VPN, proxy or network accelerators.");
|
||
break;
|
||
case IClient::CONNECTIVITY_REACHABLE:
|
||
pExtraText = Localize("No answer from server yet.");
|
||
break;
|
||
}
|
||
}
|
||
else if(Client()->MapDownloadTotalsize() > 0)
|
||
{
|
||
str_format(aBuf, sizeof(aBuf), "%s: %s", Localize("Downloading map"), Client()->MapDownloadName());
|
||
pTitle = aBuf;
|
||
UseIpLabel = false;
|
||
}
|
||
else if(Client()->State() == IClient::STATE_LOADING)
|
||
{
|
||
UseIpLabel = false;
|
||
if(Client()->LoadingStateDetail() == IClient::LOADING_STATE_DETAIL_INITIAL)
|
||
{
|
||
pTitle = Localize("Connected");
|
||
pExtraText = Localize("Getting game info");
|
||
}
|
||
else if(Client()->LoadingStateDetail() == IClient::LOADING_STATE_DETAIL_LOADING_MAP)
|
||
{
|
||
pTitle = Localize("Connected");
|
||
pExtraText = Localize("Loading map file from storage");
|
||
}
|
||
else if(Client()->LoadingStateDetail() == IClient::LOADING_STATE_DETAIL_SENDING_READY)
|
||
{
|
||
pTitle = Localize("Connected");
|
||
pExtraText = Localize("Requesting to join the game");
|
||
}
|
||
else if(Client()->LoadingStateDetail() == IClient::LOADING_STATE_DETAIL_GETTING_READY)
|
||
{
|
||
pTitle = Localize("Connected");
|
||
pExtraText = Localize("Sending initial client info");
|
||
}
|
||
}
|
||
}
|
||
else if(m_Popup == POPUP_DISCONNECTED)
|
||
{
|
||
pTitle = Localize("Disconnected");
|
||
pExtraText = Client()->ErrorString();
|
||
pButtonText = Localize("Ok");
|
||
if(Client()->m_ReconnectTime > 0)
|
||
{
|
||
str_format(aBuf, sizeof(aBuf), Localize("Reconnect in %d sec"), (int)((Client()->m_ReconnectTime - time_get()) / time_freq()));
|
||
pTitle = Client()->ErrorString();
|
||
pExtraText = aBuf;
|
||
pButtonText = Localize("Abort");
|
||
}
|
||
ExtraAlign = 0;
|
||
}
|
||
else if(m_Popup == POPUP_RENAME_DEMO)
|
||
{
|
||
pTitle = Localize("Rename demo");
|
||
}
|
||
#if defined(CONF_VIDEORECORDER)
|
||
else if(m_Popup == POPUP_RENDER_DEMO)
|
||
{
|
||
pTitle = Localize("Render demo");
|
||
}
|
||
#endif
|
||
else if(m_Popup == POPUP_PASSWORD)
|
||
{
|
||
pTitle = Localize("Password incorrect");
|
||
pButtonText = Localize("Try again");
|
||
}
|
||
else if(m_Popup == POPUP_QUIT)
|
||
{
|
||
pTitle = Localize("Quit");
|
||
pExtraText = Localize("Are you sure that you want to quit?");
|
||
}
|
||
else if(m_Popup == POPUP_FIRST_LAUNCH)
|
||
{
|
||
pTitle = Localize("Welcome to DDNet");
|
||
str_format(aBuf, sizeof(aBuf), "%s\n\n%s\n\n%s\n\n%s",
|
||
Localize("DDraceNetwork is a cooperative online game where the goal is for you and your group of tees to reach the finish line of the map. As a newcomer you should start on Novice servers, which host the easiest maps. Consider the ping to choose a server close to you."),
|
||
Localize("Use k key to kill (restart), q to pause and watch other players. See settings for other key binds."),
|
||
Localize("It's recommended that you check the settings to adjust them to your liking before joining a server."),
|
||
Localize("Please enter your nickname below."));
|
||
pExtraText = aBuf;
|
||
pButtonText = Localize("Ok");
|
||
ExtraAlign = -1;
|
||
}
|
||
else if(m_Popup == POPUP_POINTS)
|
||
{
|
||
pTitle = Localize("Existing Player");
|
||
if(Client()->m_Points > 50)
|
||
{
|
||
str_format(aBuf, sizeof(aBuf), Localize("Your nickname '%s' is already used (%d points). Do you still want to use it?"), Client()->PlayerName(), Client()->m_Points);
|
||
pExtraText = aBuf;
|
||
ExtraAlign = -1;
|
||
}
|
||
else if(Client()->m_Points >= 0)
|
||
{
|
||
m_Popup = POPUP_NONE;
|
||
}
|
||
else
|
||
{
|
||
pExtraText = Localize("Checking for existing player with your name");
|
||
}
|
||
}
|
||
else if(m_Popup == POPUP_WARNING)
|
||
{
|
||
BgColor = ColorRGBA(0.5f, 0.0f, 0.0f, 0.7f);
|
||
pTitle = m_aMessageTopic;
|
||
pExtraText = m_aMessageBody;
|
||
pButtonText = m_aMessageButton;
|
||
ExtraAlign = -1;
|
||
}
|
||
|
||
CUIRect Box, Part;
|
||
Box = Screen;
|
||
if(m_Popup != POPUP_FIRST_LAUNCH)
|
||
Box.Margin(150.0f, &Box);
|
||
|
||
// render the box
|
||
Box.Draw(BgColor, IGraphics::CORNER_ALL, 15.0f);
|
||
|
||
Box.HSplitTop(20.f, &Part, &Box);
|
||
Box.HSplitTop(24.f, &Part, &Box);
|
||
Part.VMargin(20.f, &Part);
|
||
SLabelProperties Props;
|
||
Props.m_MaxWidth = (int)Part.w;
|
||
|
||
if(TextRender()->TextWidth(24.f, pTitle, -1, -1.0f) > Part.w)
|
||
UI()->DoLabel(&Part, pTitle, 24.f, TEXTALIGN_ML, Props);
|
||
else
|
||
UI()->DoLabel(&Part, pTitle, 24.f, TEXTALIGN_MC);
|
||
|
||
Box.HSplitTop(20.f, &Part, &Box);
|
||
Box.HSplitTop(24.f, &Part, &Box);
|
||
Part.VMargin(20.f, &Part);
|
||
|
||
float FontSize = m_Popup == POPUP_FIRST_LAUNCH ? 16.0f : 20.f;
|
||
|
||
if(UseIpLabel)
|
||
{
|
||
UI()->DoLabel(&Part, Client()->ConnectAddressString(), FontSize, TEXTALIGN_MC);
|
||
Box.HSplitTop(20.f, &Part, &Box);
|
||
Box.HSplitTop(24.f, &Part, &Box);
|
||
}
|
||
|
||
Props.m_MaxWidth = (int)Part.w;
|
||
if(ExtraAlign == -1)
|
||
UI()->DoLabel(&Part, pExtraText, FontSize, TEXTALIGN_ML, Props);
|
||
else
|
||
{
|
||
if(TextRender()->TextWidth(FontSize, pExtraText, -1, -1.0f) > Part.w)
|
||
UI()->DoLabel(&Part, pExtraText, FontSize, TEXTALIGN_ML, Props);
|
||
else
|
||
UI()->DoLabel(&Part, pExtraText, FontSize, TEXTALIGN_MC);
|
||
}
|
||
|
||
if(m_Popup == POPUP_MESSAGE || m_Popup == POPUP_CONFIRM)
|
||
{
|
||
CUIRect ButtonBar;
|
||
Box.HSplitBottom(20.0f, &Box, nullptr);
|
||
Box.HSplitBottom(24.0f, &Box, &ButtonBar);
|
||
ButtonBar.VMargin(100.0f, &ButtonBar);
|
||
|
||
if(m_Popup == POPUP_MESSAGE)
|
||
{
|
||
static CButtonContainer s_ButtonConfirm;
|
||
if(DoButton_Menu(&s_ButtonConfirm, m_aPopupButtons[BUTTON_CONFIRM].m_aLabel, 0, &ButtonBar) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER))
|
||
{
|
||
m_Popup = m_aPopupButtons[BUTTON_CONFIRM].m_NextPopup;
|
||
(this->*m_aPopupButtons[BUTTON_CONFIRM].m_pfnCallback)();
|
||
}
|
||
}
|
||
else if(m_Popup == POPUP_CONFIRM)
|
||
{
|
||
CUIRect CancelButton, ConfirmButton;
|
||
ButtonBar.VSplitMid(&CancelButton, &ConfirmButton, 40.0f);
|
||
|
||
static CButtonContainer s_ButtonCancel;
|
||
if(DoButton_Menu(&s_ButtonCancel, m_aPopupButtons[BUTTON_CANCEL].m_aLabel, 0, &CancelButton) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
|
||
{
|
||
m_Popup = m_aPopupButtons[BUTTON_CANCEL].m_NextPopup;
|
||
(this->*m_aPopupButtons[BUTTON_CANCEL].m_pfnCallback)();
|
||
}
|
||
|
||
static CButtonContainer s_ButtonConfirm;
|
||
if(DoButton_Menu(&s_ButtonConfirm, m_aPopupButtons[BUTTON_CONFIRM].m_aLabel, 0, &ConfirmButton) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER))
|
||
{
|
||
m_Popup = m_aPopupButtons[BUTTON_CONFIRM].m_NextPopup;
|
||
(this->*m_aPopupButtons[BUTTON_CONFIRM].m_pfnCallback)();
|
||
}
|
||
}
|
||
}
|
||
else if(m_Popup == POPUP_QUIT)
|
||
{
|
||
CUIRect Yes, No;
|
||
Box.HSplitBottom(20.f, &Box, &Part);
|
||
Box.HSplitBottom(24.f, &Box, &Part);
|
||
|
||
// additional info
|
||
Box.VMargin(20.f, &Box);
|
||
if(m_pClient->Editor()->HasUnsavedData())
|
||
{
|
||
str_format(aBuf, sizeof(aBuf), "%s\n%s", Localize("There's an unsaved map in the editor, you might want to save it before you quit the game."), Localize("Quit anyway?"));
|
||
Props.m_MaxWidth = Part.w - 20.0f;
|
||
UI()->DoLabel(&Box, aBuf, 20.f, TEXTALIGN_ML, Props);
|
||
}
|
||
|
||
// buttons
|
||
Part.VMargin(80.0f, &Part);
|
||
Part.VSplitMid(&No, &Yes);
|
||
Yes.VMargin(20.0f, &Yes);
|
||
No.VMargin(20.0f, &No);
|
||
|
||
static CButtonContainer s_ButtonAbort;
|
||
if(DoButton_Menu(&s_ButtonAbort, Localize("No"), 0, &No) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
|
||
m_Popup = POPUP_NONE;
|
||
|
||
static CButtonContainer s_ButtonTryAgain;
|
||
if(DoButton_Menu(&s_ButtonTryAgain, Localize("Yes"), 0, &Yes) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER))
|
||
{
|
||
m_Popup = POPUP_NONE;
|
||
Client()->Quit();
|
||
}
|
||
}
|
||
else if(m_Popup == POPUP_PASSWORD)
|
||
{
|
||
CUIRect Label, TextBox, TryAgain, Abort;
|
||
|
||
Box.HSplitBottom(20.f, &Box, &Part);
|
||
Box.HSplitBottom(24.f, &Box, &Part);
|
||
Part.VMargin(80.0f, &Part);
|
||
|
||
Part.VSplitMid(&Abort, &TryAgain);
|
||
|
||
TryAgain.VMargin(20.0f, &TryAgain);
|
||
Abort.VMargin(20.0f, &Abort);
|
||
|
||
static CButtonContainer s_ButtonAbort;
|
||
if(DoButton_Menu(&s_ButtonAbort, Localize("Abort"), 0, &Abort) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
|
||
m_Popup = POPUP_NONE;
|
||
|
||
static CButtonContainer s_ButtonTryAgain;
|
||
if(DoButton_Menu(&s_ButtonTryAgain, Localize("Try again"), 0, &TryAgain) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER))
|
||
{
|
||
Client()->Connect(g_Config.m_UiServerAddress, g_Config.m_Password);
|
||
}
|
||
|
||
Box.HSplitBottom(60.f, &Box, &Part);
|
||
Box.HSplitBottom(24.f, &Box, &Part);
|
||
|
||
Part.VSplitLeft(60.0f, 0, &Label);
|
||
Label.VSplitLeft(100.0f, 0, &TextBox);
|
||
TextBox.VSplitLeft(20.0f, 0, &TextBox);
|
||
TextBox.VSplitRight(60.0f, &TextBox, 0);
|
||
UI()->DoLabel(&Label, Localize("Password"), 18.0f, TEXTALIGN_ML);
|
||
UI()->DoClearableEditBox(&m_PasswordInput, &TextBox, 12.0f);
|
||
}
|
||
else if(m_Popup == POPUP_CONNECTING)
|
||
{
|
||
Box = Screen;
|
||
Box.Margin(150.0f, &Box);
|
||
Box.HSplitBottom(20.f, &Box, &Part);
|
||
Box.HSplitBottom(24.f, &Box, &Part);
|
||
Part.VMargin(120.0f, &Part);
|
||
|
||
static CButtonContainer s_Button;
|
||
if(DoButton_Menu(&s_Button, pButtonText, 0, &Part) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
|
||
{
|
||
Client()->Disconnect();
|
||
m_Popup = POPUP_NONE;
|
||
RefreshBrowserTab(g_Config.m_UiPage);
|
||
}
|
||
|
||
if(Client()->MapDownloadTotalsize() > 0)
|
||
{
|
||
int64_t Now = time_get();
|
||
if(Now - m_DownloadLastCheckTime >= time_freq())
|
||
{
|
||
if(m_DownloadLastCheckSize > Client()->MapDownloadAmount())
|
||
{
|
||
// map downloaded restarted
|
||
m_DownloadLastCheckSize = 0;
|
||
}
|
||
|
||
// update download speed
|
||
float Diff = (Client()->MapDownloadAmount() - m_DownloadLastCheckSize) / ((int)((Now - m_DownloadLastCheckTime) / time_freq()));
|
||
float StartDiff = m_DownloadLastCheckSize - 0.0f;
|
||
if(StartDiff + Diff > 0.0f)
|
||
m_DownloadSpeed = (Diff / (StartDiff + Diff)) * (Diff / 1.0f) + (StartDiff / (Diff + StartDiff)) * m_DownloadSpeed;
|
||
else
|
||
m_DownloadSpeed = 0.0f;
|
||
m_DownloadLastCheckTime = Now;
|
||
m_DownloadLastCheckSize = Client()->MapDownloadAmount();
|
||
}
|
||
|
||
Box.HSplitTop(64.f, 0, &Box);
|
||
Box.HSplitTop(24.f, &Part, &Box);
|
||
str_format(aBuf, sizeof(aBuf), "%d/%d KiB (%.1f KiB/s)", Client()->MapDownloadAmount() / 1024, Client()->MapDownloadTotalsize() / 1024, m_DownloadSpeed / 1024.0f);
|
||
UI()->DoLabel(&Part, aBuf, 20.f, TEXTALIGN_MC);
|
||
|
||
// time left
|
||
int TimeLeft = maximum(1, m_DownloadSpeed > 0.0f ? static_cast<int>((Client()->MapDownloadTotalsize() - Client()->MapDownloadAmount()) / m_DownloadSpeed) : 1);
|
||
if(TimeLeft >= 60)
|
||
{
|
||
TimeLeft /= 60;
|
||
str_format(aBuf, sizeof(aBuf), TimeLeft == 1 ? Localize("%i minute left") : Localize("%i minutes left"), TimeLeft);
|
||
}
|
||
else
|
||
{
|
||
str_format(aBuf, sizeof(aBuf), TimeLeft == 1 ? Localize("%i second left") : Localize("%i seconds left"), TimeLeft);
|
||
}
|
||
Box.HSplitTop(20.f, 0, &Box);
|
||
Box.HSplitTop(24.f, &Part, &Box);
|
||
UI()->DoLabel(&Part, aBuf, 20.f, TEXTALIGN_MC);
|
||
|
||
// progress bar
|
||
Box.HSplitTop(20.f, 0, &Box);
|
||
Box.HSplitTop(24.f, &Part, &Box);
|
||
Part.VMargin(40.0f, &Part);
|
||
Part.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), IGraphics::CORNER_ALL, 5.0f);
|
||
Part.w = maximum(10.0f, (Part.w * Client()->MapDownloadAmount()) / Client()->MapDownloadTotalsize());
|
||
Part.Draw(ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f), IGraphics::CORNER_ALL, 5.0f);
|
||
}
|
||
}
|
||
else if(m_Popup == POPUP_LANGUAGE)
|
||
{
|
||
CUIRect Button;
|
||
Screen.Margin(150.0f, &Box);
|
||
Box.HSplitTop(20.0f, nullptr, &Box);
|
||
Box.HSplitBottom(20.0f, &Box, nullptr);
|
||
Box.HSplitBottom(24.0f, &Box, &Button);
|
||
Box.HSplitBottom(20.0f, &Box, nullptr);
|
||
Box.VMargin(20.0f, &Box);
|
||
const bool Activated = RenderLanguageSelection(Box);
|
||
Button.VMargin(120.0f, &Button);
|
||
|
||
static CButtonContainer s_Button;
|
||
if(DoButton_Menu(&s_Button, Localize("Ok"), 0, &Button) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || Activated)
|
||
m_Popup = POPUP_FIRST_LAUNCH;
|
||
}
|
||
else if(m_Popup == POPUP_COUNTRY)
|
||
{
|
||
CUIRect ButtonBar;
|
||
Screen.Margin(150.0f, &Box);
|
||
Box.HSplitTop(20.0f, nullptr, &Box);
|
||
Box.HSplitBottom(20.0f, &Box, nullptr);
|
||
Box.HSplitBottom(24.0f, &Box, &ButtonBar);
|
||
Box.HSplitBottom(20.0f, &Box, nullptr);
|
||
Box.VMargin(20.0f, &Box);
|
||
ButtonBar.VMargin(100.0f, &ButtonBar);
|
||
|
||
static int s_CurSelection = -2;
|
||
if(s_CurSelection == -2)
|
||
s_CurSelection = g_Config.m_BrFilterCountryIndex;
|
||
|
||
static CListBox s_ListBox;
|
||
int OldSelected = -1;
|
||
s_ListBox.DoStart(50.0f, m_pClient->m_CountryFlags.Num(), 10, 1, OldSelected, &Box);
|
||
|
||
for(size_t i = 0; i < m_pClient->m_CountryFlags.Num(); ++i)
|
||
{
|
||
const CCountryFlags::CCountryFlag *pEntry = m_pClient->m_CountryFlags.GetByIndex(i);
|
||
if(pEntry->m_CountryCode == s_CurSelection)
|
||
OldSelected = i;
|
||
|
||
const CListboxItem Item = s_ListBox.DoNextItem(pEntry, OldSelected >= 0 && (size_t)OldSelected == i);
|
||
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;
|
||
ColorRGBA Color(1.0f, 1.0f, 1.0f, 1.0f);
|
||
m_pClient->m_CountryFlags.Render(pEntry->m_CountryCode, &Color, FlagRect.x, FlagRect.y, FlagRect.w, FlagRect.h);
|
||
|
||
UI()->DoLabel(&Label, pEntry->m_aCountryCodeString, 10.0f, TEXTALIGN_ML);
|
||
}
|
||
|
||
const int NewSelected = s_ListBox.DoEnd();
|
||
if(OldSelected != NewSelected)
|
||
s_CurSelection = m_pClient->m_CountryFlags.GetByIndex(NewSelected)->m_CountryCode;
|
||
|
||
CUIRect CancelButton, OkButton;
|
||
ButtonBar.VSplitMid(&CancelButton, &OkButton, 40.0f);
|
||
|
||
static CButtonContainer s_CancelButton;
|
||
if(DoButton_Menu(&s_CancelButton, Localize("Cancel"), 0, &CancelButton) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
|
||
{
|
||
s_CurSelection = g_Config.m_BrFilterCountryIndex;
|
||
m_Popup = POPUP_NONE;
|
||
}
|
||
|
||
static CButtonContainer s_OkButton;
|
||
if(DoButton_Menu(&s_OkButton, Localize("Ok"), 0, &OkButton) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || s_ListBox.WasItemActivated())
|
||
{
|
||
g_Config.m_BrFilterCountryIndex = s_CurSelection;
|
||
Client()->ServerBrowserUpdate();
|
||
m_Popup = POPUP_NONE;
|
||
}
|
||
}
|
||
else if(m_Popup == POPUP_RENAME_DEMO)
|
||
{
|
||
CUIRect Label, TextBox, Ok, Abort;
|
||
|
||
Box.HSplitBottom(20.f, &Box, &Part);
|
||
Box.HSplitBottom(24.f, &Box, &Part);
|
||
Part.VMargin(80.0f, &Part);
|
||
|
||
Part.VSplitMid(&Abort, &Ok);
|
||
|
||
Ok.VMargin(20.0f, &Ok);
|
||
Abort.VMargin(20.0f, &Abort);
|
||
|
||
static CButtonContainer s_ButtonAbort;
|
||
if(DoButton_Menu(&s_ButtonAbort, Localize("Abort"), 0, &Abort) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
|
||
m_Popup = POPUP_NONE;
|
||
|
||
static CButtonContainer s_ButtonOk;
|
||
if(DoButton_Menu(&s_ButtonOk, Localize("Ok"), 0, &Ok) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER))
|
||
{
|
||
m_Popup = POPUP_NONE;
|
||
// rename demo
|
||
if(m_DemolistSelectedIndex >= 0 && !m_DemolistSelectedIsDir)
|
||
{
|
||
char aBufOld[IO_MAX_PATH_LENGTH];
|
||
str_format(aBufOld, sizeof(aBufOld), "%s/%s", m_aCurrentDemoFolder, m_vDemos[m_DemolistSelectedIndex].m_aFilename);
|
||
char aBufNew[IO_MAX_PATH_LENGTH];
|
||
str_format(aBufNew, sizeof(aBufNew), "%s/%s", m_aCurrentDemoFolder, m_DemoRenameInput.GetString());
|
||
if(!str_endswith(aBufNew, ".demo"))
|
||
str_append(aBufNew, ".demo", sizeof(aBufNew));
|
||
if(Storage()->RenameFile(aBufOld, aBufNew, m_vDemos[m_DemolistSelectedIndex].m_StorageType))
|
||
{
|
||
DemolistPopulate();
|
||
DemolistOnUpdate(false);
|
||
}
|
||
else
|
||
PopupMessage(Localize("Error"), Localize("Unable to rename the demo"), Localize("Ok"));
|
||
}
|
||
}
|
||
|
||
Box.HSplitBottom(60.f, &Box, &Part);
|
||
Box.HSplitBottom(24.f, &Box, &Part);
|
||
|
||
Part.VSplitLeft(60.0f, 0, &Label);
|
||
Label.VSplitLeft(120.0f, 0, &TextBox);
|
||
TextBox.VSplitLeft(20.0f, 0, &TextBox);
|
||
TextBox.VSplitRight(60.0f, &TextBox, 0);
|
||
UI()->DoLabel(&Label, Localize("New name:"), 18.0f, TEXTALIGN_ML);
|
||
UI()->DoEditBox(&m_DemoRenameInput, &TextBox, 12.0f);
|
||
}
|
||
#if defined(CONF_VIDEORECORDER)
|
||
else if(m_Popup == POPUP_RENDER_DEMO)
|
||
{
|
||
CUIRect Label, TextBox, Ok, Abort, IncSpeed, DecSpeed, Button;
|
||
|
||
Box.HSplitBottom(20.f, &Box, &Part);
|
||
#if defined(__ANDROID__)
|
||
Box.HSplitBottom(60.f, &Box, &Part);
|
||
#else
|
||
Box.HSplitBottom(24.f, &Box, &Part);
|
||
#endif
|
||
Part.VMargin(80.0f, &Part);
|
||
|
||
Part.VSplitMid(&Abort, &Ok);
|
||
|
||
Ok.VMargin(20.0f, &Ok);
|
||
Abort.VMargin(20.0f, &Abort);
|
||
|
||
static CButtonContainer s_ButtonAbort;
|
||
if(DoButton_Menu(&s_ButtonAbort, Localize("Abort"), 0, &Abort) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
|
||
m_Popup = POPUP_NONE;
|
||
|
||
static CButtonContainer s_ButtonOk;
|
||
if(DoButton_Menu(&s_ButtonOk, Localize("Ok"), 0, &Ok) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER))
|
||
{
|
||
m_Popup = POPUP_NONE;
|
||
// name video
|
||
if(m_DemolistSelectedIndex >= 0 && !m_DemolistSelectedIsDir)
|
||
{
|
||
if(!str_endswith(m_DemoRenderInput.GetString(), ".mp4"))
|
||
m_DemoRenderInput.Append(".mp4");
|
||
char aWholePath[IO_MAX_PATH_LENGTH];
|
||
// store new video filename to origin buffer
|
||
if(Storage()->FindFile(m_DemoRenderInput.GetString(), "videos", IStorage::TYPE_ALL, aWholePath, sizeof(aWholePath)))
|
||
{
|
||
char aMessage[128 + IO_MAX_PATH_LENGTH];
|
||
str_format(aMessage, sizeof(aMessage), Localize("File '%s' already exists, do you want to overwrite it?"), m_DemoRenderInput.GetString());
|
||
PopupConfirm(Localize("Replace video"), aMessage, Localize("Yes"), Localize("No"), &CMenus::PopupConfirmDemoReplaceVideo, POPUP_NONE, &CMenus::DefaultButtonCallback, POPUP_RENDER_DEMO);
|
||
}
|
||
else
|
||
{
|
||
PopupConfirmDemoReplaceVideo();
|
||
}
|
||
}
|
||
}
|
||
Box.HSplitBottom(30.f, &Box, 0);
|
||
Box.HSplitBottom(20.f, &Box, &Part);
|
||
Box.HSplitBottom(10.f, &Box, 0);
|
||
|
||
float ButtonSize = 20.0f;
|
||
Part.VSplitLeft(113.0f, 0, &Part);
|
||
Part.VSplitLeft(200.0f, &Button, &Part);
|
||
if(DoButton_CheckBox(&g_Config.m_ClVideoShowChat, Localize("Show chat"), g_Config.m_ClVideoShowChat, &Button))
|
||
g_Config.m_ClVideoShowChat ^= 1;
|
||
Part.VSplitLeft(32.0f, 0, &Part);
|
||
if(DoButton_CheckBox(&g_Config.m_ClVideoSndEnable, Localize("Use sounds"), g_Config.m_ClVideoSndEnable, &Part))
|
||
g_Config.m_ClVideoSndEnable ^= 1;
|
||
|
||
Box.HSplitBottom(20.f, &Box, &Part);
|
||
Part.VSplitLeft(60.0f, 0, &Part);
|
||
Part.VSplitLeft(60.0f, 0, &Label);
|
||
Part.VSplitMid(&IncSpeed, &DecSpeed);
|
||
|
||
IncSpeed.VMargin(20.0f, &IncSpeed);
|
||
DecSpeed.VMargin(20.0f, &DecSpeed);
|
||
|
||
Part.VSplitLeft(20.0f, &Button, &Part);
|
||
bool IncDemoSpeed = false, DecDemoSpeed = false;
|
||
// slowdown
|
||
Part.VSplitLeft(5.0f, 0, &Part);
|
||
Part.VSplitLeft(ButtonSize, &Button, &Part);
|
||
static CButtonContainer s_SlowDownButton;
|
||
if(DoButton_FontIcon(&s_SlowDownButton, FONT_ICON_BACKWARD, 0, &Button, IGraphics::CORNER_ALL))
|
||
DecDemoSpeed = true;
|
||
|
||
// fastforward
|
||
Part.VSplitLeft(5.0f, 0, &Part);
|
||
Part.VSplitLeft(ButtonSize, &Button, &Part);
|
||
static CButtonContainer s_FastForwardButton;
|
||
if(DoButton_FontIcon(&s_FastForwardButton, FONT_ICON_FORWARD, 0, &Button, IGraphics::CORNER_ALL))
|
||
IncDemoSpeed = true;
|
||
|
||
// speed meter
|
||
Part.VSplitLeft(8.0f, 0, &Part);
|
||
char aBuffer[64];
|
||
str_format(aBuffer, sizeof(aBuffer), "%s: ×%g", Localize("Speed"), g_aSpeeds[m_Speed]);
|
||
UI()->DoLabel(&Part, aBuffer, 12.8f, TEXTALIGN_ML);
|
||
|
||
if(IncDemoSpeed)
|
||
m_Speed = clamp(m_Speed + 1, 0, (int)(g_DemoSpeeds - 1));
|
||
else if(DecDemoSpeed)
|
||
m_Speed = clamp(m_Speed - 1, 0, (int)(g_DemoSpeeds - 1));
|
||
|
||
Part.VSplitLeft(207.0f, 0, &Part);
|
||
if(DoButton_CheckBox(&g_Config.m_ClVideoShowhud, Localize("Show ingame HUD"), g_Config.m_ClVideoShowhud, &Part))
|
||
g_Config.m_ClVideoShowhud ^= 1;
|
||
|
||
Box.HSplitBottom(20.f, &Box, &Part);
|
||
#if defined(__ANDROID__)
|
||
Box.HSplitBottom(60.f, &Box, &Part);
|
||
#else
|
||
Box.HSplitBottom(24.f, &Box, &Part);
|
||
#endif
|
||
|
||
Part.VSplitLeft(60.0f, 0, &Label);
|
||
Label.VSplitLeft(120.0f, 0, &TextBox);
|
||
TextBox.VSplitLeft(20.0f, 0, &TextBox);
|
||
TextBox.VSplitRight(60.0f, &TextBox, 0);
|
||
UI()->DoLabel(&Label, Localize("Video name:"), 18.0f, TEXTALIGN_ML);
|
||
UI()->DoEditBox(&m_DemoRenderInput, &TextBox, 12.0f);
|
||
}
|
||
#endif
|
||
else if(m_Popup == POPUP_FIRST_LAUNCH)
|
||
{
|
||
CUIRect Label, TextBox, Skip, Join;
|
||
|
||
Box.HSplitBottom(20.f, &Box, &Part);
|
||
Box.HSplitBottom(24.f, &Box, &Part);
|
||
Part.VMargin(80.0f, &Part);
|
||
Part.VSplitMid(&Skip, &Join);
|
||
Skip.VMargin(20.0f, &Skip);
|
||
Join.VMargin(20.0f, &Join);
|
||
|
||
static CButtonContainer s_JoinTutorialButton;
|
||
if(DoButton_Menu(&s_JoinTutorialButton, Localize("Join Tutorial Server"), 0, &Join) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER))
|
||
{
|
||
m_JoinTutorial = true;
|
||
Client()->RequestDDNetInfo();
|
||
m_Popup = g_Config.m_BrIndicateFinished ? POPUP_POINTS : POPUP_NONE;
|
||
}
|
||
|
||
static CButtonContainer s_SkipTutorialButton;
|
||
if(DoButton_Menu(&s_SkipTutorialButton, Localize("Skip Tutorial"), 0, &Skip) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
|
||
{
|
||
m_JoinTutorial = false;
|
||
Client()->RequestDDNetInfo();
|
||
m_Popup = g_Config.m_BrIndicateFinished ? POPUP_POINTS : POPUP_NONE;
|
||
}
|
||
|
||
Box.HSplitBottom(20.f, &Box, &Part);
|
||
Box.HSplitBottom(24.f, &Box, &Part);
|
||
|
||
Part.VSplitLeft(30.0f, 0, &Part);
|
||
str_format(aBuf, sizeof(aBuf), "%s\n(%s)",
|
||
Localize("Show DDNet map finishes in server browser"),
|
||
Localize("transmits your player name to info.ddnet.org"));
|
||
|
||
if(DoButton_CheckBox(&g_Config.m_BrIndicateFinished, aBuf, g_Config.m_BrIndicateFinished, &Part))
|
||
g_Config.m_BrIndicateFinished ^= 1;
|
||
|
||
Box.HSplitBottom(20.f, &Box, &Part);
|
||
Box.HSplitBottom(24.f, &Box, &Part);
|
||
|
||
Part.VSplitLeft(60.0f, 0, &Label);
|
||
Label.VSplitLeft(100.0f, 0, &TextBox);
|
||
TextBox.VSplitLeft(20.0f, 0, &TextBox);
|
||
TextBox.VSplitRight(60.0f, &TextBox, 0);
|
||
UI()->DoLabel(&Label, Localize("Nickname"), 16.0f, TEXTALIGN_ML);
|
||
static CLineInput s_PlayerNameInput(g_Config.m_PlayerName, sizeof(g_Config.m_PlayerName));
|
||
s_PlayerNameInput.SetEmptyText(Client()->PlayerName());
|
||
UI()->DoEditBox(&s_PlayerNameInput, &TextBox, 12.0f);
|
||
}
|
||
else if(m_Popup == POPUP_POINTS)
|
||
{
|
||
CUIRect Yes, No;
|
||
|
||
Box.HSplitBottom(20.f, &Box, &Part);
|
||
Box.HSplitBottom(24.f, &Box, &Part);
|
||
Part.VMargin(80.0f, &Part);
|
||
|
||
Part.VSplitMid(&No, &Yes);
|
||
|
||
Yes.VMargin(20.0f, &Yes);
|
||
No.VMargin(20.0f, &No);
|
||
|
||
static CButtonContainer s_ButtonNo;
|
||
if(DoButton_Menu(&s_ButtonNo, Localize("No"), 0, &No) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
|
||
m_Popup = POPUP_FIRST_LAUNCH;
|
||
|
||
static CButtonContainer s_ButtonYes;
|
||
if(DoButton_Menu(&s_ButtonYes, Localize("Yes"), 0, &Yes) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER))
|
||
m_Popup = POPUP_NONE;
|
||
}
|
||
else if(m_Popup == POPUP_WARNING)
|
||
{
|
||
Box.HSplitBottom(20.f, &Box, &Part);
|
||
Box.HSplitBottom(24.f, &Box, &Part);
|
||
Part.VMargin(120.0f, &Part);
|
||
|
||
static CButtonContainer s_Button;
|
||
if(DoButton_Menu(&s_Button, pButtonText, 0, &Part) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER) || (time_get_nanoseconds() - m_PopupWarningLastTime >= m_PopupWarningDuration))
|
||
{
|
||
m_Popup = POPUP_NONE;
|
||
SetActive(false);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Box.HSplitBottom(20.f, &Box, &Part);
|
||
Box.HSplitBottom(24.f, &Box, &Part);
|
||
Part.VMargin(120.0f, &Part);
|
||
|
||
static CButtonContainer s_Button;
|
||
if(DoButton_Menu(&s_Button, pButtonText, 0, &Part) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER))
|
||
{
|
||
if(m_Popup == POPUP_DISCONNECTED && Client()->m_ReconnectTime > 0)
|
||
Client()->m_ReconnectTime = 0;
|
||
m_Popup = POPUP_NONE;
|
||
}
|
||
}
|
||
|
||
if(m_Popup == POPUP_NONE)
|
||
UI()->SetActiveItem(nullptr);
|
||
}
|
||
|
||
UI()->RenderPopupMenus();
|
||
|
||
return 0;
|
||
}
|
||
|
||
#if defined(CONF_VIDEORECORDER)
|
||
void CMenus::PopupConfirmDemoReplaceVideo()
|
||
{
|
||
char aBuf[IO_MAX_PATH_LENGTH];
|
||
str_format(aBuf, sizeof(aBuf), "%s/%s", m_aCurrentDemoFolder, m_vDemos[m_DemolistSelectedIndex].m_aFilename);
|
||
const char *pError = Client()->DemoPlayer_Render(aBuf, m_vDemos[m_DemolistSelectedIndex].m_StorageType, m_DemoRenderInput.GetString(), m_Speed);
|
||
m_Speed = 4;
|
||
if(pError)
|
||
PopupMessage(Localize("Error"), str_comp(pError, "error loading demo") ? pError : Localize("Error loading demo"), Localize("Ok"));
|
||
}
|
||
#endif
|
||
|
||
void CMenus::RenderThemeSelection(CUIRect MainView)
|
||
{
|
||
const std::vector<CTheme> &vThemes = m_pBackground->GetThemes();
|
||
|
||
int SelectedTheme = -1;
|
||
for(int i = 0; i < (int)vThemes.size(); i++)
|
||
{
|
||
if(str_comp(vThemes[i].m_Name.c_str(), g_Config.m_ClMenuMap) == 0)
|
||
{
|
||
SelectedTheme = i;
|
||
break;
|
||
}
|
||
}
|
||
const int OldSelected = SelectedTheme;
|
||
|
||
static CListBox s_ListBox;
|
||
s_ListBox.DoHeader(&MainView, Localize("Theme"), 20.0f);
|
||
s_ListBox.DoStart(20.0f, vThemes.size(), 1, 3, SelectedTheme, nullptr, true);
|
||
|
||
for(int i = 0; i < (int)vThemes.size(); i++)
|
||
{
|
||
const CTheme &Theme = vThemes[i];
|
||
const CListboxItem Item = s_ListBox.DoNextItem(&Theme.m_Name, i == SelectedTheme);
|
||
|
||
if(!Item.m_Visible)
|
||
continue;
|
||
|
||
CUIRect Icon, Label;
|
||
Item.m_Rect.VSplitLeft(Item.m_Rect.h * 2.0f, &Icon, &Label);
|
||
|
||
// draw icon if it exists
|
||
if(Theme.m_IconTexture.IsValid())
|
||
{
|
||
Icon.VMargin(6.0f, &Icon);
|
||
Icon.HMargin(3.0f, &Icon);
|
||
Graphics()->TextureSet(Theme.m_IconTexture);
|
||
Graphics()->QuadsBegin();
|
||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||
IGraphics::CQuadItem QuadItem(Icon.x, Icon.y, Icon.w, Icon.h);
|
||
Graphics()->QuadsDrawTL(&QuadItem, 1);
|
||
Graphics()->QuadsEnd();
|
||
}
|
||
|
||
char aName[128];
|
||
if(Theme.m_Name.empty())
|
||
str_copy(aName, "(none)");
|
||
else if(str_comp(Theme.m_Name.c_str(), "auto") == 0)
|
||
str_copy(aName, "(seasons)");
|
||
else if(str_comp(Theme.m_Name.c_str(), "rand") == 0)
|
||
str_copy(aName, "(random)");
|
||
else if(Theme.m_HasDay && Theme.m_HasNight)
|
||
str_copy(aName, Theme.m_Name.c_str());
|
||
else if(Theme.m_HasDay && !Theme.m_HasNight)
|
||
str_format(aName, sizeof(aName), "%s (day)", Theme.m_Name.c_str());
|
||
else if(!Theme.m_HasDay && Theme.m_HasNight)
|
||
str_format(aName, sizeof(aName), "%s (night)", Theme.m_Name.c_str());
|
||
else // generic
|
||
str_copy(aName, Theme.m_Name.c_str());
|
||
|
||
UI()->DoLabel(&Label, aName, 16.0f * CUI::ms_FontmodHeight, TEXTALIGN_ML);
|
||
}
|
||
|
||
SelectedTheme = s_ListBox.DoEnd();
|
||
|
||
if(OldSelected != SelectedTheme)
|
||
{
|
||
const CTheme &Theme = vThemes[SelectedTheme];
|
||
str_copy(g_Config.m_ClMenuMap, Theme.m_Name.c_str());
|
||
m_pBackground->LoadMenuBackground(Theme.m_HasDay, Theme.m_HasNight);
|
||
}
|
||
}
|
||
|
||
void CMenus::SetActive(bool Active)
|
||
{
|
||
if(Active != m_MenuActive)
|
||
{
|
||
ms_ColorPicker.m_Active = false;
|
||
UI()->SetHotItem(nullptr);
|
||
UI()->SetActiveItem(nullptr);
|
||
}
|
||
m_MenuActive = Active;
|
||
if(!m_MenuActive)
|
||
{
|
||
if(m_NeedSendinfo)
|
||
{
|
||
m_pClient->SendInfo(false);
|
||
m_NeedSendinfo = false;
|
||
}
|
||
|
||
if(m_NeedSendDummyinfo)
|
||
{
|
||
m_pClient->SendDummyInfo(false);
|
||
m_NeedSendDummyinfo = false;
|
||
}
|
||
|
||
if(Client()->State() == IClient::STATE_ONLINE)
|
||
{
|
||
m_pClient->OnRelease();
|
||
}
|
||
}
|
||
else if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
|
||
{
|
||
m_pClient->OnRelease();
|
||
}
|
||
}
|
||
|
||
void CMenus::OnReset()
|
||
{
|
||
}
|
||
|
||
void CMenus::OnShutdown()
|
||
{
|
||
KillServer();
|
||
}
|
||
|
||
bool CMenus::OnCursorMove(float x, float y, IInput::ECursorType CursorType)
|
||
{
|
||
if(!m_MenuActive)
|
||
return false;
|
||
|
||
UI()->ConvertMouseMove(&x, &y, CursorType);
|
||
|
||
m_MousePos.x = clamp(m_MousePos.x + x, 0.f, (float)Graphics()->WindowWidth());
|
||
m_MousePos.y = clamp(m_MousePos.y + y, 0.f, (float)Graphics()->WindowHeight());
|
||
|
||
return true;
|
||
}
|
||
|
||
bool CMenus::OnInput(const IInput::CEvent &Event)
|
||
{
|
||
// special handle esc and enter for popup purposes
|
||
if(Event.m_Flags & IInput::FLAG_PRESS && Event.m_Key == KEY_ESCAPE)
|
||
{
|
||
SetActive(!IsActive());
|
||
UI()->OnInput(Event);
|
||
return true;
|
||
}
|
||
if(IsActive())
|
||
{
|
||
UI()->OnInput(Event);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
void CMenus::OnStateChange(int NewState, int OldState)
|
||
{
|
||
// reset active item
|
||
UI()->SetActiveItem(nullptr);
|
||
|
||
if(OldState == IClient::STATE_ONLINE || OldState == IClient::STATE_OFFLINE)
|
||
TextRender()->DeleteTextContainer(m_MotdTextContainerIndex);
|
||
|
||
if(NewState == IClient::STATE_OFFLINE)
|
||
{
|
||
if(OldState >= IClient::STATE_ONLINE && NewState < IClient::STATE_QUITTING)
|
||
UpdateMusicState();
|
||
m_Popup = POPUP_NONE;
|
||
if(Client()->ErrorString() && Client()->ErrorString()[0] != 0)
|
||
{
|
||
if(str_find(Client()->ErrorString(), "password"))
|
||
{
|
||
m_Popup = POPUP_PASSWORD;
|
||
m_PasswordInput.SelectAll();
|
||
UI()->SetActiveItem(&m_PasswordInput);
|
||
}
|
||
else
|
||
m_Popup = POPUP_DISCONNECTED;
|
||
}
|
||
}
|
||
else if(NewState == IClient::STATE_LOADING)
|
||
{
|
||
m_Popup = POPUP_CONNECTING;
|
||
m_DownloadLastCheckTime = time_get();
|
||
m_DownloadLastCheckSize = 0;
|
||
m_DownloadSpeed = 0.0f;
|
||
}
|
||
else if(NewState == IClient::STATE_CONNECTING)
|
||
{
|
||
m_Popup = POPUP_CONNECTING;
|
||
}
|
||
else if(NewState == IClient::STATE_ONLINE || NewState == IClient::STATE_DEMOPLAYBACK)
|
||
{
|
||
if(m_Popup != POPUP_WARNING)
|
||
{
|
||
m_Popup = POPUP_NONE;
|
||
SetActive(false);
|
||
}
|
||
}
|
||
}
|
||
|
||
void CMenus::OnWindowResize()
|
||
{
|
||
TextRender()->DeleteTextContainer(m_MotdTextContainerIndex);
|
||
}
|
||
|
||
void CMenus::OnRender()
|
||
{
|
||
UI()->StartCheck();
|
||
|
||
if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK)
|
||
SetActive(true);
|
||
|
||
if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
|
||
{
|
||
UI()->MapScreen();
|
||
RenderDemoPlayer(*UI()->Screen());
|
||
}
|
||
|
||
if(Client()->State() == IClient::STATE_ONLINE && m_pClient->m_ServerMode == m_pClient->SERVERMODE_PUREMOD)
|
||
{
|
||
Client()->Disconnect();
|
||
SetActive(true);
|
||
PopupMessage(Localize("Disconnected"), Localize("The server is running a non-standard tuning on a pure game type."), Localize("Ok"));
|
||
}
|
||
|
||
if(!IsActive())
|
||
{
|
||
UI()->FinishCheck();
|
||
UI()->ClearHotkeys();
|
||
return;
|
||
}
|
||
|
||
// update colors
|
||
ms_GuiColor = color_cast<ColorRGBA>(ColorHSLA(g_Config.m_UiColor, true));
|
||
|
||
ms_ColorTabbarInactiveOutgame = ColorRGBA(0, 0, 0, 0.25f);
|
||
ms_ColorTabbarActiveOutgame = ColorRGBA(0, 0, 0, 0.5f);
|
||
ms_ColorTabbarHoverOutgame = ColorRGBA(1, 1, 1, 0.25f);
|
||
|
||
float ColorIngameScaleI = 0.5f;
|
||
float ColorIngameAcaleA = 0.2f;
|
||
|
||
ms_ColorTabbarInactiveIngame = ColorRGBA(
|
||
ms_GuiColor.r * ColorIngameScaleI,
|
||
ms_GuiColor.g * ColorIngameScaleI,
|
||
ms_GuiColor.b * ColorIngameScaleI,
|
||
ms_GuiColor.a * 0.8f);
|
||
|
||
ms_ColorTabbarActiveIngame = ColorRGBA(
|
||
ms_GuiColor.r * ColorIngameAcaleA,
|
||
ms_GuiColor.g * ColorIngameAcaleA,
|
||
ms_GuiColor.b * ColorIngameAcaleA,
|
||
ms_GuiColor.a);
|
||
|
||
ms_ColorTabbarHoverIngame = ColorRGBA(1, 1, 1, 0.75f);
|
||
|
||
// update the ui
|
||
const CUIRect *pScreen = UI()->Screen();
|
||
float mx = (m_MousePos.x / (float)Graphics()->WindowWidth()) * pScreen->w;
|
||
float my = (m_MousePos.y / (float)Graphics()->WindowHeight()) * pScreen->h;
|
||
|
||
UI()->Update(mx, my, mx * 3.0f, my * 3.0f);
|
||
|
||
Render();
|
||
RenderTools()->RenderCursor(vec2(mx, my), 24.0f);
|
||
|
||
// render debug information
|
||
if(g_Config.m_Debug)
|
||
{
|
||
UI()->MapScreen();
|
||
|
||
char aBuf[512];
|
||
str_format(aBuf, sizeof(aBuf), "%p %p %p", UI()->HotItem(), UI()->ActiveItem(), UI()->LastActiveItem());
|
||
CTextCursor Cursor;
|
||
TextRender()->SetCursor(&Cursor, 10, 10, 10, TEXTFLAG_RENDER);
|
||
TextRender()->TextEx(&Cursor, aBuf, -1);
|
||
}
|
||
|
||
UI()->FinishCheck();
|
||
UI()->ClearHotkeys();
|
||
}
|
||
|
||
void CMenus::RenderBackground()
|
||
{
|
||
Graphics()->BlendNormal();
|
||
|
||
float sw = 300 * Graphics()->ScreenAspect();
|
||
float sh = 300;
|
||
Graphics()->MapScreen(0, 0, sw, sh);
|
||
|
||
// render background color
|
||
Graphics()->TextureClear();
|
||
Graphics()->QuadsBegin();
|
||
ColorRGBA Bottom(ms_GuiColor.r, ms_GuiColor.g, ms_GuiColor.b, 1.0f);
|
||
ColorRGBA Top(ms_GuiColor.r, ms_GuiColor.g, ms_GuiColor.b, 1.0f);
|
||
IGraphics::CColorVertex Array[4] = {
|
||
IGraphics::CColorVertex(0, Top.r, Top.g, Top.b, Top.a),
|
||
IGraphics::CColorVertex(1, Top.r, Top.g, Top.b, Top.a),
|
||
IGraphics::CColorVertex(2, Bottom.r, Bottom.g, Bottom.b, Bottom.a),
|
||
IGraphics::CColorVertex(3, Bottom.r, Bottom.g, Bottom.b, Bottom.a)};
|
||
Graphics()->SetColorVertex(Array, 4);
|
||
IGraphics::CQuadItem QuadItem(0, 0, sw, sh);
|
||
Graphics()->QuadsDrawTL(&QuadItem, 1);
|
||
Graphics()->QuadsEnd();
|
||
|
||
// render the tiles
|
||
Graphics()->TextureClear();
|
||
Graphics()->QuadsBegin();
|
||
float Size = 15.0f;
|
||
float OffsetTime = std::fmod(LocalTime() * 0.15f, 2.0f);
|
||
for(int y = -2; y < (int)(sw / Size); y++)
|
||
for(int x = -2; x < (int)(sh / Size); x++)
|
||
{
|
||
Graphics()->SetColor(0, 0, 0, 0.045f);
|
||
QuadItem = IGraphics::CQuadItem((x - OffsetTime) * Size * 2 + (y & 1) * Size, (y + OffsetTime) * Size, Size, Size);
|
||
Graphics()->QuadsDrawTL(&QuadItem, 1);
|
||
}
|
||
Graphics()->QuadsEnd();
|
||
|
||
// render border fade
|
||
Graphics()->TextureSet(m_TextureBlob);
|
||
Graphics()->QuadsBegin();
|
||
Graphics()->SetColor(1, 1, 1, 1);
|
||
QuadItem = IGraphics::CQuadItem(-100, -100, sw + 200, sh + 200);
|
||
Graphics()->QuadsDrawTL(&QuadItem, 1);
|
||
Graphics()->QuadsEnd();
|
||
|
||
// restore screen
|
||
UI()->MapScreen();
|
||
}
|
||
|
||
bool CMenus::CheckHotKey(int Key) const
|
||
{
|
||
return m_Popup == POPUP_NONE &&
|
||
!Input()->ShiftIsPressed() && !Input()->ModifierIsPressed() && // no modifier
|
||
Input()->KeyIsPressed(Key) && m_pClient->m_GameConsole.IsClosed();
|
||
}
|
||
|
||
int CMenus::DoButton_CheckBox_Tristate(const void *pID, const char *pText, TRISTATE Checked, const CUIRect *pRect)
|
||
{
|
||
switch(Checked)
|
||
{
|
||
case TRISTATE::NONE:
|
||
return DoButton_CheckBox_Common(pID, pText, "", pRect);
|
||
case TRISTATE::SOME:
|
||
return DoButton_CheckBox_Common(pID, pText, "O", pRect);
|
||
case TRISTATE::ALL:
|
||
return DoButton_CheckBox_Common(pID, pText, "X", pRect);
|
||
default:
|
||
dbg_assert(false, "invalid tristate");
|
||
}
|
||
dbg_break();
|
||
}
|
||
|
||
int CMenus::MenuImageScan(const char *pName, int IsDir, int DirType, void *pUser)
|
||
{
|
||
CMenus *pSelf = (CMenus *)pUser;
|
||
if(IsDir || !str_endswith(pName, ".png"))
|
||
return 0;
|
||
|
||
char aBuf[IO_MAX_PATH_LENGTH];
|
||
bool ImgExists = false;
|
||
for(const auto &Img : pSelf->m_vMenuImages)
|
||
{
|
||
str_format(aBuf, std::size(aBuf), "%s.png", Img.m_aName);
|
||
if(str_comp(aBuf, pName) == 0)
|
||
{
|
||
ImgExists = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if(!ImgExists)
|
||
{
|
||
str_format(aBuf, sizeof(aBuf), "menuimages/%s", pName);
|
||
CImageInfo Info;
|
||
if(!pSelf->Graphics()->LoadPNG(&Info, aBuf, DirType))
|
||
{
|
||
str_format(aBuf, sizeof(aBuf), "failed to load menu image from %s", pName);
|
||
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "game", aBuf);
|
||
return 0;
|
||
}
|
||
|
||
CMenuImage MenuImage;
|
||
MenuImage.m_OrgTexture = pSelf->Graphics()->LoadTextureRaw(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, Info.m_Format, 0);
|
||
|
||
unsigned char *pData = (unsigned char *)Info.m_pData;
|
||
//int Pitch = Info.m_Width*4;
|
||
|
||
// create colorless version
|
||
int Step = Info.m_Format == CImageInfo::FORMAT_RGBA ? 4 : 3;
|
||
|
||
// make the texture gray scale
|
||
for(int i = 0; i < Info.m_Width * Info.m_Height; i++)
|
||
{
|
||
int v = (pData[i * Step] + pData[i * Step + 1] + pData[i * Step + 2]) / 3;
|
||
pData[i * Step] = v;
|
||
pData[i * Step + 1] = v;
|
||
pData[i * Step + 2] = v;
|
||
}
|
||
|
||
MenuImage.m_GreyTexture = pSelf->Graphics()->LoadTextureRaw(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, Info.m_Format, 0);
|
||
pSelf->Graphics()->FreePNG(&Info);
|
||
|
||
// set menu image data
|
||
str_truncate(MenuImage.m_aName, sizeof(MenuImage.m_aName), pName, str_length(pName) - 4);
|
||
pSelf->m_vMenuImages.push_back(MenuImage);
|
||
pSelf->RenderLoading(Localize("Loading DDNet Client"), Localize("Loading menu images"), 1);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
const CMenus::CMenuImage *CMenus::FindMenuImage(const char *pName)
|
||
{
|
||
for(auto &Image : m_vMenuImages)
|
||
if(str_comp(Image.m_aName, pName) == 0)
|
||
return &Image;
|
||
return nullptr;
|
||
}
|
||
|
||
void CMenus::SetMenuPage(int NewPage)
|
||
{
|
||
m_MenuPage = NewPage;
|
||
if(NewPage >= PAGE_INTERNET && NewPage <= PAGE_KOG)
|
||
g_Config.m_UiPage = NewPage;
|
||
}
|
||
|
||
void CMenus::RefreshBrowserTab(int UiPage)
|
||
{
|
||
if(UiPage == PAGE_INTERNET)
|
||
ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET);
|
||
else if(UiPage == PAGE_LAN)
|
||
ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN);
|
||
else if(UiPage == PAGE_FAVORITES)
|
||
ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES);
|
||
else if(UiPage == PAGE_DDNET)
|
||
{
|
||
// start a new server list request
|
||
Client()->RequestDDNetInfo();
|
||
ServerBrowser()->Refresh(IServerBrowser::TYPE_DDNET);
|
||
}
|
||
else if(UiPage == PAGE_KOG)
|
||
{
|
||
// start a new server list request
|
||
Client()->RequestDDNetInfo();
|
||
ServerBrowser()->Refresh(IServerBrowser::TYPE_KOG);
|
||
}
|
||
}
|