ddnet/src/game/client/components/menus.cpp

2402 lines
76 KiB
C++
Raw Normal View History

2010-11-20 10:37:14 +00:00
/* (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. */
2011-08-31 11:56:04 +00:00
#include <algorithm>
2022-05-29 16:33:38 +00:00
#include <chrono>
2022-02-14 23:22:52 +00:00
#include <cmath>
2022-05-29 16:33:38 +00:00
#include <vector>
2010-05-29 07:25:38 +00:00
#include <base/math.h>
2020-09-22 16:02:03 +00:00
#include <base/system.h>
2010-05-29 07:25:38 +00:00
#include <base/vmath.h>
#include <engine/client.h>
2022-10-04 16:40:24 +00:00
#include <engine/config.h>
#include <engine/editor.h>
2011-03-23 12:06:35 +00:00
#include <engine/friends.h>
2010-05-29 07:25:38 +00:00
#include <engine/graphics.h>
#include <engine/keys.h>
#include <engine/serverbrowser.h>
2020-09-22 16:02:03 +00:00
#include <engine/shared/config.h>
2010-10-06 21:07:35 +00:00
#include <engine/storage.h>
#include <engine/textrender.h>
2009-06-13 17:18:06 +00:00
2010-05-29 07:25:38 +00:00
#include <game/generated/protocol.h>
#include <engine/client/updater.h>
#include <game/client/components/binds.h>
#include <game/client/components/console.h>
2020-09-18 16:45:42 +00:00
#include <game/client/components/menu_background.h>
#include <game/client/components/sounds.h>
2010-05-29 07:25:38 +00:00
#include <game/client/gameclient.h>
#include <game/client/ui_listbox.h>
2020-09-10 18:14:47 +00:00
#include <game/generated/client_data.h>
2010-05-29 07:25:38 +00:00
#include <game/localization.h>
#include "countryflags.h"
2011-03-23 12:06:35 +00:00
#include "menus.h"
2022-05-18 16:00:05 +00:00
using namespace FontIcons;
2022-05-18 16:00:05 +00:00
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;
2020-10-28 02:59:50 +00:00
SColorPicker CMenus::ms_ColorPicker;
2010-05-29 07:25:38 +00:00
float CMenus::ms_ButtonHeight = 25.0f;
float CMenus::ms_ListheaderHeight = 17.0f;
2010-05-29 07:25:38 +00:00
CMenus::CMenus()
{
2010-05-29 07:25:38 +00:00
m_Popup = POPUP_NONE;
m_ActivePage = PAGE_INTERNET;
m_MenuPage = 0;
2010-05-29 07:25:38 +00:00
m_GamePage = PAGE_GAME;
m_JoinTutorial = false;
m_NeedRestartGraphics = false;
m_NeedRestartSound = false;
2010-05-29 07:25:38 +00:00
m_NeedSendinfo = false;
2014-04-28 13:19:57 +00:00
m_NeedSendDummyinfo = false;
2010-05-29 07:25:38 +00:00
m_MenuActive = true;
m_ShowStart = true;
2022-07-09 16:14:56 +00:00
str_copy(m_aCurrentDemoFolder, "demos");
2011-03-23 12:06:35 +00:00
2014-08-23 15:48:04 +00:00
m_DemoPlayerState = DEMOPLAYER_NONE;
2015-08-28 18:44:07 +00:00
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;
2020-10-26 03:10:58 +00:00
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);
2020-09-22 16:02:03 +00:00
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)
2022-07-16 13:32:06 +00:00
Color.a *= UI()->ButtonColorMul(pButtonContainer);
pRect->Draw(Color, Corners, r);
if(pImageName)
{
CUIRect Image;
2020-09-10 18:14:47 +00:00
pRect->VSplitRight(pRect->h * 4.0f, &Text, &Image); // always correct ratio for image
// render image
const CMenuImage *pImage = FindMenuImage(pImageName);
if(pImage)
{
2022-07-16 13:32:06 +00:00
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();
}
}
2020-09-10 18:14:47 +00:00
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;
2022-07-16 13:32:06 +00:00
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)
{
2022-05-13 18:33:29 +00:00
const bool MouseInside = UI()->MouseInside(pRect);
2020-10-26 01:10:55 +00:00
CUIRect Rect = *pRect;
if(pAnimator != NULL)
{
auto Time = time_get_nanoseconds();
2020-10-26 01:10:55 +00:00
2022-05-18 16:00:05 +00:00
if(pAnimator->m_Time + 100ms < Time)
2020-10-26 01:10:55 +00:00
{
pAnimator->m_Value = pAnimator->m_Active ? 1 : 0;
pAnimator->m_Time = Time;
}
pAnimator->m_Active = Checked || MouseInside;
if(pAnimator->m_Active)
2022-05-18 16:00:05 +00:00
pAnimator->m_Value = clamp<float>(pAnimator->m_Value + (Time - pAnimator->m_Time).count() / (double)std::chrono::nanoseconds(100ms).count(), 0, 1);
2020-10-26 01:10:55 +00:00
else
2022-05-18 16:00:05 +00:00
pAnimator->m_Value = clamp<float>(pAnimator->m_Value - (Time - pAnimator->m_Time).count() / (double)std::chrono::nanoseconds(100ms).count(), 0, 1);
2020-10-26 01:10:55 +00:00
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;
2020-10-26 01:43:34 +00:00
pAnimator->m_Time = Time;
2020-10-26 01:10:55 +00:00
}
2009-10-27 14:38:53 +00:00
if(Checked)
{
ColorRGBA ColorMenuTab = ms_ColorTabbarActive;
if(pActiveColor)
ColorMenuTab = *pActiveColor;
2020-10-26 01:10:55 +00:00
Rect.Draw(ColorMenuTab, Corners, EdgeRounding);
}
else
{
2022-05-13 18:33:29 +00:00
if(MouseInside)
{
ColorRGBA HoverColorMenuTab = ms_ColorTabbarHover;
if(pHoverColor)
HoverColorMenuTab = *pHoverColor;
2020-10-26 01:10:55 +00:00
Rect.Draw(HoverColorMenuTab, Corners, EdgeRounding);
}
else
{
ColorRGBA ColorMenuTab = ms_ColorTabbarInactive;
if(pDefaultColor)
ColorMenuTab = *pDefaultColor;
2020-10-26 01:10:55 +00:00
Rect.Draw(ColorMenuTab, Corners, EdgeRounding);
}
}
2020-10-26 01:10:55 +00:00
if(pAnimator != NULL)
{
2020-10-26 03:10:58 +00:00
if(pAnimator->m_RepositionLabel)
{
Rect.x += Rect.w - pRect->w + Rect.x - pRect->x;
Rect.y += Rect.h - pRect->h + Rect.y - pRect->y;
}
2020-12-13 18:31:37 +00:00
if(!pAnimator->m_ScaleLabel)
{
Rect.w = pRect->w;
Rect.h = pRect->h;
}
}
2020-12-13 18:31:37 +00:00
2022-05-13 18:34:11 +00:00
CUIRect Temp;
Rect.HMargin(2.0f, &Temp);
UI()->DoLabel(&Temp, pText, Temp.h * CUI::ms_FontmodHeight, TEXTALIGN_MC);
2022-07-16 13:32:06 +00:00
return UI()->DoButtonLogic(pButtonContainer, Checked, pRect);
}
2010-05-29 07:25:38 +00:00
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);
}
2010-05-29 07:25:38 +00:00
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)
{
2020-10-06 10:25:10 +00:00
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);
2020-12-14 01:18:04 +00:00
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;
}
2010-05-29 07:25:38 +00:00
int CMenus::DoButton_CheckBox(const void *pID, const char *pText, int Checked, const CUIRect *pRect)
{
2020-09-22 16:02:03 +00:00
return DoButton_CheckBox_Common(pID, pText, Checked ? "X" : "", pRect);
}
2010-05-29 07:25:38 +00:00
int CMenus::DoButton_CheckBox_Number(const void *pID, const char *pText, int Checked, const CUIRect *pRect)
{
2010-05-29 07:25:38 +00:00
char aBuf[16];
str_format(aBuf, sizeof(aBuf), "%d", Checked);
return DoButton_CheckBox_Common(pID, pText, aBuf, pRect);
}
int CMenus::DoKeyReader(void *pID, const CUIRect *pRect, int Key, int ModifierCombination, int *pNewModifierCombination)
{
// process
2009-10-27 14:38:53 +00:00
static void *pGrabbedID = 0;
static bool MouseReleased = true;
static int s_ButtonUsed = 0;
const bool Inside = UI()->MouseHovered(pRect);
2009-10-27 14:38:53 +00:00
int NewKey = Key;
*pNewModifierCombination = ModifierCombination;
if(!UI()->MouseButton(0) && !UI()->MouseButton(1) && pGrabbedID == pID)
2009-10-27 14:38:53 +00:00
MouseReleased = true;
if(UI()->CheckActiveItem(pID))
{
2010-05-29 07:25:38 +00:00
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;
}
2010-05-29 07:25:38 +00:00
m_Binder.m_GotKey = false;
UI()->SetActiveItem(nullptr);
2009-10-27 14:38:53 +00:00
MouseReleased = false;
pGrabbedID = pID;
}
if(s_ButtonUsed == 1 && !UI()->MouseButton(1))
{
if(Inside)
NewKey = 0;
UI()->SetActiveItem(nullptr);
}
}
2009-10-27 14:38:53 +00:00
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;
}
}
}
2009-10-27 14:38:53 +00:00
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);
}
2009-10-27 14:38:53 +00:00
return NewKey;
}
2010-05-29 07:25:38 +00:00
int CMenus::RenderMenubar(CUIRect r)
{
2010-05-29 07:25:38 +00:00
CUIRect Box = r;
CUIRect Button;
m_ActivePage = m_MenuPage;
2010-05-29 07:25:38 +00:00
int NewPage = -1;
2010-05-29 07:25:38 +00:00
if(Client()->State() != IClient::STATE_OFFLINE)
m_ActivePage = m_GamePage;
2010-05-29 07:25:38 +00:00
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);
2020-09-05 21:43:39 +00:00
// offline menus
if(m_ActivePage == PAGE_NEWS)
{
2020-09-05 21:43:39 +00:00
Box.VSplitLeft(100.0f, &Button, &Box);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_NewsButton;
if(DoButton_MenuTab(&s_NewsButton, Localize("News"), m_ActivePage == PAGE_NEWS, &Button, IGraphics::CORNER_T, &m_aAnimatorsBigPage[BIG_TAB_NEWS]))
2020-09-05 21:43:39 +00:00
{
NewPage = PAGE_NEWS;
}
}
2020-09-05 21:43:39 +00:00
else if(m_ActivePage == PAGE_DEMOS)
{
2020-09-05 21:43:39 +00:00
Box.VSplitLeft(100.0f, &Button, &Box);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_DemosButton;
if(DoButton_MenuTab(&s_DemosButton, Localize("Demos"), m_ActivePage == PAGE_DEMOS, &Button, IGraphics::CORNER_T, &m_aAnimatorsBigPage[BIG_TAB_DEMOS]))
2020-09-05 21:43:39 +00:00
{
DemolistPopulate();
NewPage = PAGE_DEMOS;
}
}
2020-09-05 21:43:39 +00:00
else
{
2020-09-05 21:43:39 +00:00
Box.VSplitLeft(100.0f, &Button, &Box);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_InternetButton;
if(DoButton_MenuTab(&s_InternetButton, Localize("Internet"), m_ActivePage == PAGE_INTERNET, &Button, IGraphics::CORNER_T, &m_aAnimatorsBigPage[BIG_TAB_INTERNET]))
2020-09-05 21:43:39 +00:00
{
if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_INTERNET)
ServerBrowser()->Refresh(IServerBrowser::TYPE_INTERNET);
NewPage = PAGE_INTERNET;
}
2020-09-05 21:43:39 +00:00
Box.VSplitLeft(100.0f, &Button, &Box);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_LanButton;
if(DoButton_MenuTab(&s_LanButton, Localize("LAN"), m_ActivePage == PAGE_LAN, &Button, IGraphics::CORNER_T, &m_aAnimatorsBigPage[BIG_TAB_LAN]))
{
2020-09-05 21:43:39 +00:00
if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_LAN)
ServerBrowser()->Refresh(IServerBrowser::TYPE_LAN);
NewPage = PAGE_LAN;
}
2014-09-13 14:36:25 +00:00
2020-09-05 21:43:39 +00:00
Box.VSplitLeft(100.0f, &Button, &Box);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_FavoritesButton;
if(DoButton_MenuTab(&s_FavoritesButton, Localize("Favorites"), m_ActivePage == PAGE_FAVORITES, &Button, IGraphics::CORNER_T, &m_aAnimatorsBigPage[BIG_TAB_FAVORITES]))
{
2020-09-05 21:43:39 +00:00
if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_FAVORITES)
ServerBrowser()->Refresh(IServerBrowser::TYPE_FAVORITES);
NewPage = PAGE_FAVORITES;
}
Box.VSplitLeft(90.0f, &Button, &Box);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_DDNetButton;
if(DoButton_MenuTab(&s_DDNetButton, "DDNet", m_ActivePage == PAGE_DDNET, &Button, IGraphics::CORNER_T, &m_aAnimatorsBigPage[BIG_TAB_DDNET]))
2020-09-05 21:43:39 +00:00
{
if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_DDNET)
{
Client()->RequestDDNetInfo();
ServerBrowser()->Refresh(IServerBrowser::TYPE_DDNET);
}
NewPage = PAGE_DDNET;
}
Box.VSplitLeft(90.0f, &Button, &Box);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_KoGButton;
if(DoButton_MenuTab(&s_KoGButton, "KoG", m_ActivePage == PAGE_KOG, &Button, IGraphics::CORNER_T, &m_aAnimatorsBigPage[BIG_TAB_KOG]))
2020-09-05 21:43:39 +00:00
{
if(ServerBrowser()->GetCurrentType() != IServerBrowser::TYPE_KOG)
{
Client()->RequestDDNetInfo();
ServerBrowser()->Refresh(IServerBrowser::TYPE_KOG);
}
NewPage = PAGE_KOG;
}
}
}
else
{
2010-05-29 07:25:38 +00:00
// online menus
Box.VSplitLeft(90.0f, &Button, &Box);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_GameButton;
if(DoButton_MenuTab(&s_GameButton, Localize("Game"), m_ActivePage == PAGE_GAME, &Button, IGraphics::CORNER_TL))
2010-05-29 07:25:38 +00:00
NewPage = PAGE_GAME;
Box.VSplitLeft(90.0f, &Button, &Box);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_PlayersButton;
2020-09-22 16:02:03 +00:00
if(DoButton_MenuTab(&s_PlayersButton, Localize("Players"), m_ActivePage == PAGE_PLAYERS, &Button, 0))
NewPage = PAGE_PLAYERS;
Box.VSplitLeft(130.0f, &Button, &Box);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_ServerInfoButton;
2020-09-22 16:02:03 +00:00
if(DoButton_MenuTab(&s_ServerInfoButton, Localize("Server info"), m_ActivePage == PAGE_SERVER_INFO, &Button, 0))
2010-05-29 07:25:38 +00:00
NewPage = PAGE_SERVER_INFO;
Box.VSplitLeft(90.0f, &Button, &Box);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_NetworkButton;
2020-09-22 16:02:03 +00:00
if(DoButton_MenuTab(&s_NetworkButton, Localize("Browser"), m_ActivePage == PAGE_NETWORK, &Button, 0))
NewPage = PAGE_NETWORK;
{
2022-07-16 13:32:06 +00:00
static CButtonContainer s_GhostButton;
if(GameClient()->m_GameInfo.m_Race)
{
2020-09-05 21:43:39 +00:00
Box.VSplitLeft(90.0f, &Button, &Box);
2020-09-10 18:14:47 +00:00
if(DoButton_MenuTab(&s_GhostButton, Localize("Ghost"), m_ActivePage == PAGE_GHOST, &Button, 0))
NewPage = PAGE_GHOST;
}
}
2010-05-29 07:25:38 +00:00
Box.VSplitLeft(100.0f, &Button, &Box);
2010-05-29 07:25:38 +00:00
Box.VSplitLeft(4.0f, 0, &Box);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_CallVoteButton;
if(DoButton_MenuTab(&s_CallVoteButton, Localize("Call vote"), m_ActivePage == PAGE_CALLVOTE, &Button, IGraphics::CORNER_TR))
{
2010-05-29 07:25:38 +00:00
NewPage = PAGE_CALLVOTE;
m_ControlPageOpening = true;
}
}
TextRender()->SetCurFont(TextRender()->GetFont(TEXT_FONT_ICON_FONT));
2020-10-06 10:25:10 +00:00
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);
2022-07-16 13:32:06 +00:00
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();
}
}
2010-05-29 07:25:38 +00:00
Box.VSplitRight(10.0f, &Box, &Button);
Box.VSplitRight(33.0f, &Box, &Button);
2022-07-16 13:32:06 +00:00
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))
2010-05-29 07:25:38 +00:00
NewPage = PAGE_SETTINGS;
Box.VSplitRight(10.0f, &Box, &Button);
Box.VSplitRight(33.0f, &Box, &Button);
2022-07-16 13:32:06 +00:00
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);
2022-07-16 13:32:06 +00:00
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);
2022-07-16 13:32:06 +00:00
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);
2010-05-29 07:25:38 +00:00
if(NewPage != -1)
{
2010-05-29 07:25:38 +00:00
if(Client()->State() == IClient::STATE_OFFLINE)
SetMenuPage(NewPage);
else
2010-05-29 07:25:38 +00:00
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};
2023-05-18 10:21:34 +00:00
const int CurLoadRenderCount = m_LoadCurrent;
m_LoadCurrent += IncreaseCounter;
2023-05-18 10:21:34 +00:00
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));
2021-09-22 19:48:55 +00:00
UI()->MapScreen();
if(!RenderMenuBackgroundMap || !m_pBackground->Render())
2020-09-18 16:45:42 +00:00
{
RenderBackground();
}
CUIRect Box = *UI()->Screen();
Box.Margin(160.0f, &Box);
2009-10-27 14:38:53 +00:00
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();
}
2010-05-29 07:25:38 +00:00
void CMenus::RenderNews(CUIRect MainView)
{
g_Config.m_UiUnreadNews = false;
MainView.Draw(ms_ColorTabbarActive, IGraphics::CORNER_B, 10.0f);
2014-06-05 10:11:41 +00:00
MainView.HSplitTop(10.0f, nullptr, &MainView);
MainView.VSplitLeft(15.0f, nullptr, &MainView);
2014-06-05 10:11:41 +00:00
CUIRect Label;
const char *pStr = Client()->m_aNews;
char aLine[256];
while((pStr = str_next_token(pStr, "\n", aLine, sizeof(aLine))))
2014-06-05 10:11:41 +00:00
{
const int Len = str_length(aLine);
2020-09-22 16:02:03 +00:00
if(Len > 0 && aLine[0] == '|' && aLine[Len - 1] == '|')
2014-06-05 10:11:41 +00:00
{
MainView.HSplitTop(30.0f, &Label, &MainView);
2020-09-22 16:02:03 +00:00
aLine[Len - 1] = '\0';
UI()->DoLabel(&Label, aLine + 1, 20.0f, TEXTALIGN_ML);
2014-06-05 10:11:41 +00:00
}
else
{
MainView.HSplitTop(20.0f, &Label, &MainView);
UI()->DoLabel(&Label, aLine, 15.f, TEXTALIGN_ML);
2014-06-05 10:11:41 +00:00
}
}
}
2010-05-29 07:25:38 +00:00
void CMenus::OnInit()
{
2010-05-29 07:25:38 +00:00
if(g_Config.m_ClShowWelcome)
m_Popup = POPUP_LANGUAGE;
if(g_Config.m_ClSkipStartMenu)
m_ShowStart = false;
2021-09-13 21:48:10 +00:00
m_RefreshButton.Init(UI(), -1);
m_ConnectButton.Init(UI(), -1);
2020-10-12 10:29:47 +00:00
Console()->Chain("add_favorite", ConchainServerbrowserUpdate, this);
Console()->Chain("remove_favorite", ConchainServerbrowserUpdate, this);
2011-06-26 15:10:13 +00:00
Console()->Chain("add_friend", ConchainFriendlistUpdate, this);
Console()->Chain("remove_friend", ConchainFriendlistUpdate, this);
2011-02-27 16:56:03 +00:00
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);
2022-06-14 17:28:51 +00:00
Console()->Chain("cl_asset_extras", ConchainAssetExtras, this);
m_TextureBlob = Graphics()->LoadTexture("blob.png", IStorage::TYPE_ALL, CImageInfo::FORMAT_AUTO, 0);
2011-02-27 16:56:03 +00:00
// setup load amount
const int NumMenuImages = 5;
2011-02-27 16:56:03 +00:00
m_LoadCurrent = 0;
m_LoadTotal = g_pData->m_NumImages + NumMenuImages + GameClient()->ComponentCount();
2011-02-27 16:56:03 +00:00
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);
}
2022-10-04 16:40:24 +00:00
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;
2020-09-05 14:54:20 +00:00
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;
}
2022-05-18 16:00:05 +00:00
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);
2022-07-09 16:14:56 +00:00
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;
}
2020-10-28 02:59:50 +00:00
void CMenus::RenderColorPicker()
{
if(!ms_ColorPicker.m_Active)
return;
if(UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
{
ms_ColorPicker.m_Active = false;
UI()->SetValueSelectorTextMode(false);
UI()->SetActiveItem(nullptr);
2020-10-28 02:59:50 +00:00
return;
}
2020-10-28 02:59:50 +00:00
// 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;
2020-12-14 00:51:31 +00:00
if(UI()->MouseButtonClicked(0) && !UI()->MouseInside(&PickerRect) && !UI()->MouseInside(&ms_ColorPicker.m_AttachedRect))
2020-10-28 02:59:50 +00:00
{
ms_ColorPicker.m_Active = false;
UI()->SetValueSelectorTextMode(false);
UI()->SetActiveItem(nullptr);
2020-10-28 02:59:50 +00:00
return;
}
// Prevent activation of UI elements outside of active color picker
if(UI()->MouseInside(&PickerRect))
UI()->SetHotItem(&ms_ColorPicker);
2020-10-28 02:59:50 +00:00
// Render
2020-12-14 00:51:31 +00:00
ColorRGBA BackgroundColor(0.1f, 0.1f, 0.1f, 1.0f);
PickerRect.Draw(BackgroundColor, 0, 0);
2020-10-28 02:59:50 +00:00
CUIRect ColorsArea, HueArea, ValuesHitbox, BottomArea, HueRect, SatRect, ValueRect, HexRect, AlphaRect;
2020-10-28 02:59:50 +00:00
PickerRect.Margin(3, &ColorsArea);
2020-12-14 00:51:31 +00:00
ColorsArea.HSplitBottom(ms_ColorPicker.ms_Height - 140.0f, &ColorsArea, &ValuesHitbox);
2020-10-28 02:59:50 +00:00
ColorsArea.VSplitRight(20, &ColorsArea, &HueArea);
2020-12-14 00:51:31 +00:00
BottomArea = ValuesHitbox;
2020-10-28 02:59:50 +00:00
BottomArea.HSplitTop(3, 0x0, &BottomArea);
HueArea.VSplitLeft(3, 0x0, &HueArea);
BottomArea.HSplitTop(20, &HueRect, &BottomArea);
2020-12-14 00:51:31 +00:00
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;
2020-12-14 00:51:31 +00:00
HueRect.VSplitLeft(HsvValueWidth, &HueRect, &SatRect);
SatRect.VSplitLeft(ValuePadding, 0x0, &SatRect);
SatRect.VSplitLeft(HsvValueWidth, &SatRect, &ValueRect);
ValueRect.VSplitLeft(ValuePadding, 0x0, &ValueRect);
2020-12-14 00:51:31 +00:00
BottomArea.HSplitTop(20, &HexRect, &BottomArea);
HexRect.VSplitLeft(HexValueWidth, &HexRect, &AlphaRect);
AlphaRect.VSplitLeft(ValuePadding, 0x0, &AlphaRect);
2020-10-28 02:59:50 +00:00
2020-12-14 00:51:31 +00:00
if(UI()->MouseButtonReleased(1) && !UI()->MouseInside(&ValuesHitbox))
{
ms_ColorPicker.m_Active = false;
UI()->SetValueSelectorTextMode(false);
UI()->SetActiveItem(nullptr);
2020-12-14 00:51:31 +00:00
return;
}
ColorRGBA BlackColor(0, 0, 0, 0.5f);
HueArea.Draw(BlackColor, 0, 0);
2020-10-28 02:59:50 +00:00
HueArea.Margin(1, &HueArea);
ColorsArea.Draw(BlackColor, 0, 0);
2020-10-28 02:59:50 +00:00
ColorsArea.Margin(1, &ColorsArea);
2020-12-14 00:51:31 +00:00
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);
2020-10-28 02:59:50 +00:00
// 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));
2020-10-28 02:59:50 +00:00
ColorsArea.Draw4(TL, TR, BL, BR, IGraphics::CORNER_NONE, 0.0f);
2020-10-28 02:59:50 +00:00
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);
2020-10-28 02:59:50 +00:00
// 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);
2020-10-28 02:59:50 +00:00
}
// Editboxes Area
static char s_aValueSelectorIds[4];
2020-12-14 00:51:31 +00:00
H = UI()->DoValueSelector(&s_aValueSelectorIds[0], &HueRect, "H:", H, 0, 255);
S = UI()->DoValueSelector(&s_aValueSelectorIds[1], &SatRect, "S:", S, 0, 255);
V = UI()->DoValueSelector(&s_aValueSelectorIds[2], &ValueRect, "V:", V, 0, 255);
2020-10-28 02:59:50 +00:00
PickerColorHSV = ColorHSVA(H / 255.0f, S / 255.0f, V / 255.0f);
2020-10-28 02:59:50 +00:00
SValueSelectorProperties Props;
Props.m_UseScroll = false;
Props.m_IsHex = true;
unsigned int Hex = color_cast<ColorRGBA>(PickerColorHSV).Pack(false);
unsigned int NewHex = UI()->DoValueSelector(&s_aValueSelectorIds[3], &HexRect, "Hex:", Hex, 0, 0xFFFFFF, Props);
2020-12-14 00:51:31 +00:00
if(Hex != NewHex)
PickerColorHSV = color_cast<ColorHSVA>(ColorRGBA(NewHex));
2020-12-14 00:51:31 +00:00
// TODO : ALPHA SUPPORT
UI()->DoLabel(&AlphaRect, "A: 255", 10, TEXTALIGN_MC);
AlphaRect.Draw(ColorRGBA(0, 0, 0, 0.65f), IGraphics::CORNER_ALL, 5.0f);
2020-10-28 02:59:50 +00:00
// Logic
float PickerX, PickerY;
static int s_ColorPickerId = 0;
if(UI()->DoPickerLogic(&s_ColorPickerId, &ColorsArea, &PickerX, &PickerY))
2020-10-28 02:59:50 +00:00
{
2020-12-14 00:51:31 +00:00
PickerColorHSV.y = PickerX / ColorsArea.w;
PickerColorHSV.z = 1.0f - PickerY / ColorsArea.h;
2020-10-28 02:59:50 +00:00
}
static int s_HuePickerId = 0;
if(UI()->DoPickerLogic(&s_HuePickerId, &HueArea, &PickerX, &PickerY))
2020-12-14 00:51:31 +00:00
PickerColorHSV.x = 1.0f - PickerY / HueArea.h;
2020-10-28 02:59:50 +00:00
// Marker Color Area
const float MarkerX = ColorsArea.x + ColorsArea.w * PickerColorHSV.y;
const float MarkerY = ColorsArea.y + ColorsArea.h * (1.0f - PickerColorHSV.z);
2020-10-28 02:59:50 +00:00
const float MarkerOutlineInd = PickerColorHSV.z > 0.5f ? 0.0f : 1.0f;
const ColorRGBA MarkerOutline = ColorRGBA(MarkerOutlineInd, MarkerOutlineInd, MarkerOutlineInd, 1.0f);
2020-10-28 02:59:50 +00:00
2020-12-14 00:51:31 +00:00
Graphics()->TextureClear();
2020-10-28 02:59:50 +00:00
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);
2020-10-28 02:59:50 +00:00
Graphics()->QuadsEnd();
// Marker Hue Area
CUIRect HueMarker;
HueArea.Margin(-2.5f, &HueMarker);
2020-12-14 00:51:31 +00:00
HueMarker.h = 6.5f;
HueMarker.y = (HueArea.y + HueArea.h * (1.0f - PickerColorHSV.x)) - HueMarker.h / 2.0f;
const ColorRGBA HueMarkerColor = color_cast<ColorRGBA>(ColorHSVA(PickerColorHSV.x, 1, 1, 1));
const float HueMarkerOutlineColor = PickerColorHSV.x > 0.75f ? 1.0f : 0.0f;
const ColorRGBA HueMarkerOutline = ColorRGBA(HueMarkerOutlineColor, HueMarkerOutlineColor, HueMarkerOutlineColor, 1);
HueMarker.Draw(HueMarkerOutline, IGraphics::CORNER_ALL, 1.2f);
2020-12-14 00:51:31 +00:00
HueMarker.Margin(1.2f, &HueMarker);
HueMarker.Draw(HueMarkerColor, IGraphics::CORNER_ALL, 1.2f);
2020-12-14 00:51:31 +00:00
ms_ColorPicker.m_HSVColor = PickerColorHSV.Pack(false);
*ms_ColorPicker.m_pColor = color_cast<ColorHSLA>(PickerColorHSV).Pack(false);
2020-10-28 02:59:50 +00:00
}
2010-05-29 07:25:38 +00:00
int CMenus::Render()
{
if(Client()->State() == IClient::STATE_DEMOPLAYBACK && m_Popup == POPUP_NONE)
return 0;
CUIRect Screen = *UI()->Screen();
2021-09-22 19:48:55 +00:00
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);
2010-05-29 07:25:38 +00:00
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);
2014-09-13 14:36:25 +00:00
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)
{
2010-05-29 07:25:38 +00:00
ms_ColorTabbarInactive = ms_ColorTabbarInactiveIngame;
ms_ColorTabbarActive = ms_ColorTabbarActiveIngame;
ms_ColorTabbarHover = ms_ColorTabbarHoverIngame;
}
else
{
2020-09-18 16:45:42 +00:00
if(!m_pBackground->Render())
{
RenderBackground();
}
2010-05-29 07:25:38 +00:00
ms_ColorTabbarInactive = ms_ColorTabbarInactiveOutgame;
ms_ColorTabbarActive = ms_ColorTabbarActiveOutgame;
ms_ColorTabbarHover = ms_ColorTabbarHoverOutgame;
}
2010-05-29 07:25:38 +00:00
CUIRect TabBar;
CUIRect MainView;
// some margin around the screen
2010-05-29 07:25:38 +00:00
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;
}
2010-05-29 07:25:38 +00:00
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)
2020-09-18 16:45:42 +00:00
{
m_pBackground->ChangePosition(CMenuBackground::POS_START);
RenderStartMenu(Screen);
2020-09-18 16:45:42 +00:00
}
else
{
Screen.HSplitTop(24.0f, &TabBar, &MainView);
// 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)
2020-09-18 16:45:42 +00:00
{
m_pBackground->ChangePosition(CMenuBackground::POS_NEWS);
RenderNews(MainView);
2020-09-18 16:45:42 +00:00
}
else if(m_MenuPage == PAGE_INTERNET)
2020-09-18 16:45:42 +00:00
{
2020-09-26 07:37:35 +00:00
m_pBackground->ChangePosition(CMenuBackground::POS_BROWSER_INTERNET);
RenderServerbrowser(MainView);
2020-09-18 16:45:42 +00:00
}
else if(m_MenuPage == PAGE_LAN)
2020-09-18 16:45:42 +00:00
{
2020-09-26 07:37:35 +00:00
m_pBackground->ChangePosition(CMenuBackground::POS_BROWSER_LAN);
RenderServerbrowser(MainView);
2020-09-18 16:45:42 +00:00
}
else if(m_MenuPage == PAGE_DEMOS)
2020-09-18 16:45:42 +00:00
{
m_pBackground->ChangePosition(CMenuBackground::POS_DEMOS);
RenderDemoList(MainView);
2020-09-18 16:45:42 +00:00
}
else if(m_MenuPage == PAGE_FAVORITES)
2020-09-18 16:45:42 +00:00
{
2020-09-26 07:37:35 +00:00
m_pBackground->ChangePosition(CMenuBackground::POS_BROWSER_FAVORITES);
RenderServerbrowser(MainView);
2020-09-18 16:45:42 +00:00
}
else if(m_MenuPage == PAGE_DDNET)
2020-09-18 16:45:42 +00:00
{
2020-09-26 07:37:35 +00:00
m_pBackground->ChangePosition(CMenuBackground::POS_BROWSER_CUSTOM0);
RenderServerbrowser(MainView);
2020-09-18 16:45:42 +00:00
}
else if(m_MenuPage == PAGE_KOG)
2020-09-18 16:45:42 +00:00
{
2020-09-26 07:37:35 +00:00
m_pBackground->ChangePosition(CMenuBackground::POS_BROWSER_CUSTOM0 + 1);
RenderServerbrowser(MainView);
2020-09-18 16:45:42 +00:00
}
else if(m_MenuPage == PAGE_SETTINGS)
2010-05-29 07:25:38 +00:00
RenderSettings(MainView);
2020-10-28 02:59:50 +00:00
// do tab bar
RenderMenubar(TabBar);
}
}
else
{
char aBuf[1536];
2010-05-29 07:25:38 +00:00
const char *pTitle = "";
const char *pExtraText = "";
const char *pButtonText = "";
int ExtraAlign = 0;
2022-07-11 00:13:56 +00:00
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;
}
2010-05-29 07:25:38 +00:00
else if(m_Popup == POPUP_CONNECTING)
{
2010-05-29 07:25:38 +00:00
pTitle = Localize("Connecting to");
2022-07-11 00:13:56 +00:00
UseIpLabel = true;
2010-05-29 07:25:38 +00:00
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:
2022-07-11 00:13:56 +00:00
pExtraText = Localize("Trying to determine UDP connectivity...");
break;
case IClient::CONNECTIVITY_UNREACHABLE:
2022-07-11 00:13:56 +00:00
pExtraText = Localize("UDP seems to be filtered.");
break;
case IClient::CONNECTIVITY_DIFFERING_UDP_TCP_IP_ADDRESSES:
2022-07-11 00:13:56 +00:00
pExtraText = Localize("UDP and TCP IP addresses seem to be different. Try disabling VPN, proxy or network accelerators.");
break;
case IClient::CONNECTIVITY_REACHABLE:
2022-07-11 00:13:56 +00:00
pExtraText = Localize("No answer from server yet.");
break;
}
}
else if(Client()->MapDownloadTotalsize() > 0)
{
2014-08-15 23:06:17 +00:00
str_format(aBuf, sizeof(aBuf), "%s: %s", Localize("Downloading map"), Client()->MapDownloadName());
pTitle = aBuf;
2022-07-11 00:13:56 +00:00
UseIpLabel = false;
}
else if(Client()->State() == IClient::STATE_LOADING)
{
2022-07-11 00:13:56 +00:00
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");
2022-07-14 20:36:06 +00:00
pExtraText = Localize("Sending initial client info");
}
}
}
else if(m_Popup == POPUP_DISCONNECTED)
{
2010-05-29 07:25:38 +00:00
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");
}
2020-09-22 16:02:03 +00:00
#if defined(CONF_VIDEORECORDER)
else if(m_Popup == POPUP_RENDER_DEMO)
{
pTitle = Localize("Render demo");
}
2020-09-22 16:02:03 +00:00
#endif
2010-05-29 07:25:38 +00:00
else if(m_Popup == POPUP_PASSWORD)
{
2010-06-02 19:03:15 +00:00
pTitle = Localize("Password incorrect");
2010-05-29 07:25:38 +00:00
pButtonText = Localize("Try again");
}
2010-05-29 07:25:38 +00:00
else if(m_Popup == POPUP_QUIT)
{
2010-05-29 07:25:38 +00:00
pTitle = Localize("Quit");
pExtraText = Localize("Are you sure that you want to quit?");
}
2010-05-29 07:25:38 +00:00
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."),
2020-11-05 08:32:14 +00:00
Localize("Please enter your nickname below."));
pExtraText = aBuf;
2010-05-29 07:25:38 +00:00
pButtonText = Localize("Ok");
ExtraAlign = -1;
}
else if(m_Popup == POPUP_POINTS)
{
pTitle = Localize("Existing Player");
if(Client()->m_Points > 50)
{
2020-11-05 08:32:14 +00:00
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;
2022-07-11 00:13:56 +00:00
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;
2010-05-29 07:25:38 +00:00
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);
2022-03-11 16:34:48 +00:00
SLabelProperties Props;
Props.m_MaxWidth = (int)Part.w;
2022-07-11 00:13:56 +00:00
if(TextRender()->TextWidth(24.f, pTitle, -1, -1.0f) > Part.w)
UI()->DoLabel(&Part, pTitle, 24.f, TEXTALIGN_ML, Props);
2015-03-29 12:20:34 +00:00
else
UI()->DoLabel(&Part, pTitle, 24.f, TEXTALIGN_MC);
2022-07-11 00:13:56 +00:00
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;
2022-07-11 00:13:56 +00:00
if(UseIpLabel)
{
UI()->DoLabel(&Part, Client()->ConnectAddressString(), FontSize, TEXTALIGN_MC);
2022-07-11 00:13:56 +00:00
Box.HSplitTop(20.f, &Part, &Box);
Box.HSplitTop(24.f, &Part, &Box);
}
2022-03-11 16:34:48 +00:00
Props.m_MaxWidth = (int)Part.w;
2010-05-29 07:25:38 +00:00
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)
{
2010-05-29 07:25:38 +00:00
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?"));
2022-03-11 16:34:48 +00:00
Props.m_MaxWidth = Part.w - 20.0f;
UI()->DoLabel(&Box, aBuf, 20.f, TEXTALIGN_ML, Props);
}
// buttons
2010-05-29 07:25:38 +00:00
Part.VMargin(80.0f, &Part);
Part.VSplitMid(&No, &Yes);
Yes.VMargin(20.0f, &Yes);
No.VMargin(20.0f, &No);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_ButtonAbort;
if(DoButton_Menu(&s_ButtonAbort, Localize("No"), 0, &No) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
2010-05-29 07:25:38 +00:00
m_Popup = POPUP_NONE;
2022-07-16 13:32:06 +00:00
static CButtonContainer s_ButtonTryAgain;
if(DoButton_Menu(&s_ButtonTryAgain, Localize("Yes"), 0, &Yes) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER))
{
m_Popup = POPUP_NONE;
2010-05-29 07:25:38 +00:00
Client()->Quit();
}
}
2010-05-29 07:25:38 +00:00
else if(m_Popup == POPUP_PASSWORD)
{
2010-05-29 07:25:38 +00:00
CUIRect Label, TextBox, TryAgain, Abort;
2010-05-29 07:25:38 +00:00
Box.HSplitBottom(20.f, &Box, &Part);
Box.HSplitBottom(24.f, &Box, &Part);
Part.VMargin(80.0f, &Part);
2010-05-29 07:25:38 +00:00
Part.VSplitMid(&Abort, &TryAgain);
2010-05-29 07:25:38 +00:00
TryAgain.VMargin(20.0f, &TryAgain);
Abort.VMargin(20.0f, &Abort);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_ButtonAbort;
if(DoButton_Menu(&s_ButtonAbort, Localize("Abort"), 0, &Abort) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
2010-05-29 07:25:38 +00:00
m_Popup = POPUP_NONE;
2022-07-16 13:32:06 +00:00
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);
}
2010-05-29 07:25:38 +00:00
Box.HSplitBottom(60.f, &Box, &Part);
Box.HSplitBottom(24.f, &Box, &Part);
2010-05-29 07:25:38 +00:00
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);
}
2010-12-16 00:52:29 +00:00
else if(m_Popup == POPUP_CONNECTING)
{
Box = Screen;
Box.Margin(150.0f, &Box);
2010-12-16 00:52:29 +00:00
Box.HSplitBottom(20.f, &Box, &Part);
Box.HSplitBottom(24.f, &Box, &Part);
Part.VMargin(120.0f, &Part);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_Button;
if(DoButton_Menu(&s_Button, pButtonText, 0, &Part) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
2010-12-16 00:52:29 +00:00
{
Client()->Disconnect();
m_Popup = POPUP_NONE;
RefreshBrowserTab(g_Config.m_UiPage);
2010-12-16 00:52:29 +00:00
}
if(Client()->MapDownloadTotalsize() > 0)
{
2021-06-23 05:05:49 +00:00
int64_t Now = time_get();
2020-09-22 16:02:03 +00:00
if(Now - m_DownloadLastCheckTime >= time_freq())
2010-12-16 00:52:29 +00:00
{
if(m_DownloadLastCheckSize > Client()->MapDownloadAmount())
{
// map downloaded restarted
m_DownloadLastCheckSize = 0;
}
// update download speed
2020-09-22 16:02:03 +00:00
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;
2010-12-16 00:52:29 +00:00
m_DownloadLastCheckTime = Now;
m_DownloadLastCheckSize = Client()->MapDownloadAmount();
}
Box.HSplitTop(64.f, 0, &Box);
Box.HSplitTop(24.f, &Part, &Box);
2020-09-22 16:02:03 +00:00
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);
2010-12-16 00:52:29 +00:00
// time left
2020-09-22 16:02:03 +00:00
int TimeLeft = maximum(1, m_DownloadSpeed > 0.0f ? static_cast<int>((Client()->MapDownloadTotalsize() - Client()->MapDownloadAmount()) / m_DownloadSpeed) : 1);
2010-12-16 00:52:29 +00:00
if(TimeLeft >= 60)
{
TimeLeft /= 60;
str_format(aBuf, sizeof(aBuf), TimeLeft == 1 ? Localize("%i minute left") : Localize("%i minutes left"), TimeLeft);
2010-12-16 00:52:29 +00:00
}
else
{
str_format(aBuf, sizeof(aBuf), TimeLeft == 1 ? Localize("%i second left") : Localize("%i seconds left"), TimeLeft);
}
2010-12-16 00:52:29 +00:00
Box.HSplitTop(20.f, 0, &Box);
Box.HSplitTop(24.f, &Part, &Box);
UI()->DoLabel(&Part, aBuf, 20.f, TEXTALIGN_MC);
2010-12-16 00:52:29 +00:00
// 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);
2020-09-22 16:02:03 +00:00
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);
2010-12-16 00:52:29 +00:00
}
}
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);
2022-07-16 13:32:06 +00:00
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_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);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_ButtonAbort;
if(DoButton_Menu(&s_ButtonAbort, Localize("Abort"), 0, &Abort) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
m_Popup = POPUP_NONE;
2022-07-16 13:32:06 +00:00
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];
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
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()->FileExists(aBufNew, m_vDemos[m_DemolistSelectedIndex].m_StorageType))
{
PopupMessage(Localize("Error"), Localize("A demo with this name already exists"), Localize("Ok"), POPUP_RENAME_DEMO);
}
else 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"), POPUP_RENAME_DEMO);
}
}
}
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);
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
UI()->DoEditBox(&m_DemoRenameInput, &TextBox, 12.0f);
}
2019-09-27 07:22:50 +00:00
#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);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_ButtonAbort;
if(DoButton_Menu(&s_ButtonAbort, Localize("Abort"), 0, &Abort) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
m_Popup = POPUP_NONE;
2022-07-16 13:32:06 +00:00
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)
{
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
if(!str_endswith(m_DemoRenderInput.GetString(), ".mp4"))
m_DemoRenderInput.Append(".mp4");
char aWholePath[IO_MAX_PATH_LENGTH];
2019-09-27 06:51:08 +00:00
// store new video filename to origin buffer
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
if(Storage()->FindFile(m_DemoRenderInput.GetString(), "videos", IStorage::TYPE_ALL, aWholePath, sizeof(aWholePath)))
2019-09-27 06:51:08 +00:00
{
char aMessage[128 + IO_MAX_PATH_LENGTH];
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
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);
2019-09-27 06:51:08 +00:00
}
else
{
PopupConfirmDemoReplaceVideo();
2019-09-27 06:51:08 +00:00
}
}
}
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);
2022-03-18 13:31:52 +00:00
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;
2022-03-18 13:31:52 +00:00
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;
2019-09-28 04:18:38 +00:00
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);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_SlowDownButton;
if(DoButton_FontIcon(&s_SlowDownButton, FONT_ICON_BACKWARD, 0, &Button, IGraphics::CORNER_ALL))
2019-09-28 04:18:38 +00:00
DecDemoSpeed = true;
// fastforward
Part.VSplitLeft(5.0f, 0, &Part);
Part.VSplitLeft(ButtonSize, &Button, &Part);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_FastForwardButton;
if(DoButton_FontIcon(&s_FastForwardButton, FONT_ICON_FORWARD, 0, &Button, IGraphics::CORNER_ALL))
2019-09-28 04:18:38 +00:00
IncDemoSpeed = true;
// speed meter
Part.VSplitLeft(8.0f, 0, &Part);
2019-09-28 04:18:38 +00:00
char aBuffer[64];
str_format(aBuffer, sizeof(aBuffer), "%s: ×%g", Localize("Speed"), g_aSpeeds[m_Speed]);
UI()->DoLabel(&Part, aBuffer, 12.8f, TEXTALIGN_ML);
2019-09-28 04:18:38 +00:00
if(IncDemoSpeed)
m_Speed = clamp(m_Speed + 1, 0, (int)(g_DemoSpeeds - 1));
2019-09-28 04:18:38 +00:00
else if(DecDemoSpeed)
m_Speed = clamp(m_Speed - 1, 0, (int)(g_DemoSpeeds - 1));
2019-09-28 04:18:38 +00:00
2022-03-18 13:31:52 +00:00
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;
2019-09-28 04:18:38 +00:00
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);
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
UI()->DoEditBox(&m_DemoRenderInput, &TextBox, 12.0f);
}
2019-09-27 07:22:50 +00:00
#endif
2010-05-29 07:25:38 +00:00
else if(m_Popup == POPUP_FIRST_LAUNCH)
{
CUIRect Label, TextBox, Skip, Join;
2010-05-29 07:25:38 +00:00
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);
2022-07-16 13:32:06 +00:00
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;
}
2022-07-16 13:32:06 +00:00
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"),
2022-08-30 08:09:06 +00:00
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);
2010-05-29 07:25:38 +00:00
Box.HSplitBottom(24.f, &Box, &Part);
2010-05-29 07:25:38 +00:00
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);
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
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);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_ButtonNo;
if(DoButton_Menu(&s_ButtonNo, Localize("No"), 0, &No) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
m_Popup = POPUP_FIRST_LAUNCH;
2022-07-16 13:32:06 +00:00
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);
2022-07-16 13:32:06 +00:00
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
{
2010-05-29 07:25:38 +00:00
Box.HSplitBottom(20.f, &Box, &Part);
Box.HSplitBottom(24.f, &Box, &Part);
Part.VMargin(120.0f, &Part);
2022-07-16 13:32:06 +00:00
static CButtonContainer s_Button;
if(DoButton_Menu(&s_Button, pButtonText, 0, &Part) || UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE) || UI()->ConsumeHotkey(CUI::HOTKEY_ENTER))
2016-05-07 14:12:23 +00:00
{
if(m_Popup == POPUP_DISCONNECTED && Client()->m_ReconnectTime > 0)
Client()->m_ReconnectTime = 0;
2010-05-29 07:25:38 +00:00
m_Popup = POPUP_NONE;
2016-05-07 14:12:23 +00:00
}
}
2013-03-16 14:30:40 +00:00
if(m_Popup == POPUP_NONE)
UI()->SetActiveItem(nullptr);
}
UI()->RenderPopupMenus();
// Handle this escape hotkey after popup menus
if(m_MenuPage == PAGE_SETTINGS && !m_ShowStart && Client()->State() == IClient::STATE_OFFLINE && UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
{
m_ShowStart = true;
}
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);
Port line input and IME support from 0.7 Port the line input (UI edit boxes, chat, console) and Input Method Editor (IME) support from upstream. Closes #4397. General ------------------------------ Fix issues with the text input. Closes #4346. Closes #4524. Word skipping (when holding Ctrl) is overhauled to be consistent with the Windows / Firefox experience that I took as reference. Improve usability by not blinking (i.e. always rendering) the caret shortly after is has been moved. UI text input ------------------------------ Fix inconsistent mouse-based left and right scrolling (closes #4347). Support smooth left and right scrolling. Chat ------------------------------ Support keyboard-based text selection of the chat input. Mouse-based selection could be support in the future when we decide to add something like an ingame UI cursor. Support smooth up and down scrolling of the chat input, removing the old hack that offsets the input string to simulate scrolling. Console ------------------------------ Also support mouse-based text selection of the command input. Only text from either the command input or the console log can be selected at the same time. This ensures that Ctrl+C will always copy the text that is currently visually selected in the console. Check for Ctrl+C input event in event handler instead of in render function, to hopefully fix the issue that copying does not work sometimes (closes #5974 until further notice). When Ctrl+C is used to copy text from the console log, the selection is cleared. This should make it more clear when text was copied from the log. Fix an issue that was preventing the console log selection from being cleared, when all log lines are selected. Remove Ctrl+A/E hotkeys that move cursor to beginning/end respectively. Ctrl+A now selectes all text like for all other inputs. Home and End keys can still be used to go the beginning and end. Remove Ctrl+U/K hotkeys that clear everything before/after the cursor respectively. Hold shift and use Home/End to select everything instead. IME support ------------------------------ Render list of IME candidates in the client on Windows, so the candidate list can also be viewed in fullscreen mode. There is no API available to retrieve a candidate list on the other operating systems. Improve composition rendering by underlining the composition text instead of putting it in square brackets. Track active input globally to properly activate and deactivate IME through the SDL functions. Closes #1030. Closes #1008. Password rendering ------------------------------ Fix rendering of passwords containing unicode. Instead of rendering one star character for each UTF-8 `char`, render on star for every unicode codepoint. Show the composition text also for passwords. Without seeing the composition text it's hard to type a password containing those characters. The candidate window exposes the composition anyway. If you don't want to expose your password this way, e.g. while streaming, you could: 1. Use a latin password and switch off the IME for the password input with the IME hotkey. 2. Blank your screen with an external program while you are streaming and entering passwords. 3. Use binds to authenticate in rcon or to set the server browser password. Refactoring ------------------------------ Move all text input logic and general rendering to `CLineInput`. A `CLineInput` is associated with a particular `char` buffer given as a pointer either in the constructor or with `SetBuffer`. The maximum byte size of the buffer must also be specified. The maximum length in unicode codepoints can also be specified separately (e.g. on upstream, name are limited by the number of unicode codepoints instead). Add `CLineInputBuffered`, which is a `CLineInput` that own a `char` buffer of a fixed size, which is specified as a template argument. As `CLineInput` does not own a buffer anymore, this reduces duplicate code for line inputs that need their own buffer. Add `CLineInputNumber` which has additional convenience functions to consider the text as an `int` or `float`, to reduce duplicate code in those cases. In the future we could also add an input filter function so that only numbers can be entered in the number input. Add `CLineInput::SetClipboardLineCallback` to handle the case that multiple lines of text are pasted into a lineinput. This reduces duplicate code, as this behavior was previously implemented separately for chat and console. The behavior is also fixed to be consistent with the console on Windows, so the first line being pasted edits the current input text and then sends it instead of being sent on its own without the existing input text. Add `CalcFontSizeAndBoundingBox` to UI to reduce duplicate code. Expose `CalcAlignedCursorPos` as static member function to reuse it for line input. Dispatch input events to UI inputs through the event handler instead of storing them in a duplicate buffer. Use `size_t` for line input cursor position, length etc. and for `str_utf8_stats`. Add `IButtonColorFunction` to UI to describe a functions that defines colors for the Default, Active and Hovered states of UI elements. Add some default button color functions. Use button color function to reduce duplicate code in scrollbar rendering. Use `vec2` instead of two `floats` to represent the mouse positions in the text renderer. Remove `CaretPosition` again, as it does not calculate the correct Y position near line breaks due to the wrapping being different when not rendering the entire string. Instead, calculate the exact caret position when rending a text container and store the caret position in the text cursor for later use. IME usage guide (Windows) ------------------------------ 1. Install the respective language and the Microsoft-IME keyboard (e.g. for Chinese, Japanese or Korean). 2. Launch the game (or a text editor to first try out the IME). Note that Windows may track the input language separately for every application. You can change this in the Windows input settings so the input language is changed globally. 2. Switch the input language using the hotkey Windows+Space or another hotkey that you configured in the Windows input settings (Alt+Shift is the default, but you should consider disabling it, to avoid accidentally changing the input language while playing). 3. Switch from Latin/English input mode to the respective asian input mode. - Chinese: Use Ctrl+Space to switch between English and Chinese input mode. You can change this hotkey in the IME's settings. - Japanese: Use Ctrl+Space to switch between Alphanumeric and Hiragana/Katakana input mode. You can change this hotkey in the IME's settings. - Korean: Use Right Alt to switch between English and Hangul input mode. You cannot change this hotkey as of yet. - Note that the input mode is also tracked per application, but there is no setting to change this behavior as far as I know, so you'll need to switch for every application separately. 4. Start typing. The underlined text is the current composition text. While a composition is active, you can only edit the composition text. Confirm the composition with Space or by selecting a candidate from the candidate list with the arrow keys. Cancel the composition with Escape or by using Backspace to delete the composition text. Note that not all languages offer a candidate list. SDL version-specific issues ------------------------------ - 2.26.5, 2.24.2, 2.0.22: IME candidates work. But there are minor bugs when moving the composition cursor. - 2.0.18, 2.0.20: IME candidates work. - 2.0.16 (our current version): IME candidates cannot be determined with Windows API. Windows tries to draw the composition window like before, so this does not work in fullscreen mode. - 2.0.8 (upstream 0.7): IME candidates work. But this SDL version is too old for us.
2023-01-03 21:28:38 +00:00
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)
2020-09-18 16:45:42 +00:00
{
const std::vector<CTheme> &vThemes = m_pBackground->GetThemes();
2020-09-18 16:45:42 +00:00
int SelectedTheme = -1;
for(int i = 0; i < (int)vThemes.size(); i++)
2020-09-18 16:45:42 +00:00
{
if(str_comp(vThemes[i].m_Name.c_str(), g_Config.m_ClMenuMap) == 0)
2020-09-18 16:45:42 +00:00
{
SelectedTheme = i;
break;
}
}
const int OldSelected = SelectedTheme;
2020-09-18 16:45:42 +00:00
static CListBox s_ListBox;
s_ListBox.DoHeader(&MainView, Localize("Theme"), 20.0f);
s_ListBox.DoStart(20.0f, vThemes.size(), 1, 3, SelectedTheme, nullptr, true);
2020-09-18 16:45:42 +00:00
for(int i = 0; i < (int)vThemes.size(); i++)
2020-09-18 16:45:42 +00:00
{
const CTheme &Theme = vThemes[i];
const CListboxItem Item = s_ListBox.DoNextItem(&Theme.m_Name, i == SelectedTheme);
2020-09-18 16:45:42 +00:00
if(!Item.m_Visible)
continue;
CUIRect Icon, Label;
Item.m_Rect.VSplitLeft(Item.m_Rect.h * 2.0f, &Icon, &Label);
2020-09-18 16:45:42 +00:00
// draw icon if it exists
if(Theme.m_IconTexture.IsValid())
2020-09-18 16:45:42 +00:00
{
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())
2022-07-09 16:14:56 +00:00
str_copy(aName, "(none)");
else if(str_comp(Theme.m_Name.c_str(), "auto") == 0)
2022-07-09 16:14:56 +00:00
str_copy(aName, "(seasons)");
else if(str_comp(Theme.m_Name.c_str(), "rand") == 0)
2022-07-09 16:14:56 +00:00
str_copy(aName, "(random)");
2020-09-18 16:45:42 +00:00
else if(Theme.m_HasDay && Theme.m_HasNight)
str_copy(aName, Theme.m_Name.c_str());
2020-09-18 16:45:42 +00:00
else if(Theme.m_HasDay && !Theme.m_HasNight)
str_format(aName, sizeof(aName), "%s (day)", Theme.m_Name.c_str());
2020-09-18 16:45:42 +00:00
else if(!Theme.m_HasDay && Theme.m_HasNight)
str_format(aName, sizeof(aName), "%s (night)", Theme.m_Name.c_str());
2020-09-18 16:45:42 +00:00
else // generic
str_copy(aName, Theme.m_Name.c_str());
2020-09-18 16:45:42 +00:00
UI()->DoLabel(&Label, aName, 16.0f * CUI::ms_FontmodHeight, TEXTALIGN_ML);
2020-09-18 16:45:42 +00:00
}
SelectedTheme = s_ListBox.DoEnd();
2020-09-18 16:45:42 +00:00
if(OldSelected != SelectedTheme)
2020-09-18 16:45:42 +00:00
{
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);
2020-09-18 16:45:42 +00:00
}
}
2010-05-29 07:25:38 +00:00
void CMenus::SetActive(bool Active)
{
if(Active != m_MenuActive)
2020-12-16 04:55:41 +00:00
{
ms_ColorPicker.m_Active = false;
2022-07-11 16:52:51 +00:00
UI()->SetHotItem(nullptr);
UI()->SetActiveItem(nullptr);
2020-12-16 04:55:41 +00:00
}
2010-05-29 07:25:38 +00:00
m_MenuActive = Active;
if(!m_MenuActive)
{
if(m_NeedSendinfo)
{
m_pClient->SendInfo(false);
m_NeedSendinfo = false;
}
2014-04-28 13:19:57 +00:00
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();
}
}
2010-05-29 07:25:38 +00:00
void CMenus::OnReset()
{
}
2022-01-20 11:19:06 +00:00
void CMenus::OnShutdown()
{
KillServer();
}
bool CMenus::OnCursorMove(float x, float y, IInput::ECursorType CursorType)
{
2010-05-29 07:25:38 +00:00
if(!m_MenuActive)
return false;
UI()->ConvertMouseMove(&x, &y, CursorType);
UI()->OnCursorMove(x, y);
return true;
}
bool CMenus::OnInput(const IInput::CEvent &Event)
{
// Escape key is always handled to activate/deactivate menu
if((Event.m_Flags & IInput::FLAG_PRESS && Event.m_Key == KEY_ESCAPE) || IsActive())
2008-10-21 18:25:28 +00:00
{
UI()->OnInput(Event);
2008-08-27 20:23:50 +00:00
return true;
}
return false;
}
2010-05-29 07:25:38 +00:00
void CMenus::OnStateChange(int NewState, int OldState)
{
2010-05-29 07:25:38 +00:00
// reset active item
UI()->SetActiveItem(nullptr);
2010-05-29 07:25:38 +00:00
if(OldState == IClient::STATE_ONLINE || OldState == IClient::STATE_OFFLINE)
TextRender()->DeleteTextContainer(m_MotdTextContainerIndex);
2010-05-29 07:25:38 +00:00
if(NewState == IClient::STATE_OFFLINE)
{
2020-10-02 13:44:27 +00:00
if(OldState >= IClient::STATE_ONLINE && NewState < IClient::STATE_QUITTING)
UpdateMusicState();
2010-05-29 07:25:38 +00:00
m_Popup = POPUP_NONE;
if(Client()->ErrorString() && Client()->ErrorString()[0] != 0)
{
2010-05-29 07:25:38 +00:00
if(str_find(Client()->ErrorString(), "password"))
{
2010-05-29 07:25:38 +00:00
m_Popup = POPUP_PASSWORD;
m_PasswordInput.SelectAll();
UI()->SetActiveItem(&m_PasswordInput);
}
else
2010-05-29 07:25:38 +00:00
m_Popup = POPUP_DISCONNECTED;
}
}
else if(NewState == IClient::STATE_LOADING)
{
2010-05-29 07:25:38 +00:00
m_Popup = POPUP_CONNECTING;
2010-12-16 00:52:29 +00:00
m_DownloadLastCheckTime = time_get();
m_DownloadLastCheckSize = 0;
m_DownloadSpeed = 0.0f;
}
2010-05-29 07:25:38 +00:00
else if(NewState == IClient::STATE_CONNECTING)
{
2010-05-29 07:25:38 +00:00
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);
}
2010-05-29 07:25:38 +00:00
void CMenus::OnRender()
{
UI()->StartCheck();
2010-05-29 07:25:38 +00:00
if(Client()->State() != IClient::STATE_ONLINE && Client()->State() != IClient::STATE_DEMOPLAYBACK)
SetActive(true);
2010-05-29 07:25:38 +00:00
if(Client()->State() == IClient::STATE_DEMOPLAYBACK)
{
2021-09-22 19:48:55 +00:00
UI()->MapScreen();
RenderDemoPlayer(*UI()->Screen());
}
if(Client()->State() == IClient::STATE_ONLINE && m_pClient->m_ServerMode == m_pClient->SERVERMODE_PUREMOD)
{
2010-05-29 07:25:38 +00:00
Client()->Disconnect();
SetActive(true);
PopupMessage(Localize("Disconnected"), Localize("The server is running a non-standard tuning on a pure game type."), Localize("Ok"));
}
2010-05-29 07:25:38 +00:00
if(!IsActive())
{
if(UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
SetActive(true);
UI()->FinishCheck();
UI()->ClearHotkeys();
return;
}
UpdateColors();
UI()->Update();
Render();
RenderTools()->RenderCursor(UI()->MousePos(), 24.0f);
2009-01-10 11:16:21 +00:00
// render debug information
2010-05-29 07:25:38 +00:00
if(g_Config.m_Debug)
2023-06-04 10:27:16 +00:00
UI()->DebugRender();
if(UI()->ConsumeHotkey(CUI::HOTKEY_ESCAPE))
SetActive(false);
UI()->FinishCheck();
UI()->ClearHotkeys();
}
2008-09-11 22:45:28 +00:00
void CMenus::UpdateColors()
{
ms_GuiColor = color_cast<ColorRGBA>(ColorHSLA(g_Config.m_UiColor, true));
ms_ColorTabbarInactiveOutgame = ColorRGBA(0.0f, 0.0f, 0.0f, 0.25f);
ms_ColorTabbarActiveOutgame = ColorRGBA(0.0f, 0.0f, 0.0f, 0.5f);
ms_ColorTabbarHoverOutgame = ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f);
const float ColorIngameScaleI = 0.5f;
const 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.0f, 1.0f, 1.0f, 0.75f);
}
2010-05-29 07:25:38 +00:00
void CMenus::RenderBackground()
2008-09-11 22:45:28 +00:00
{
2020-08-31 14:18:45 +00:00
Graphics()->BlendNormal();
2020-09-22 16:02:03 +00:00
float sw = 300 * Graphics()->ScreenAspect();
float sh = 300;
2009-10-27 14:38:53 +00:00
Graphics()->MapScreen(0, 0, sw, sh);
// render background color
Graphics()->TextureClear();
2009-10-27 14:38:53 +00:00
Graphics()->QuadsBegin();
2020-09-22 16:02:03 +00:00
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);
2009-10-27 14:38:53 +00:00
Graphics()->QuadsEnd();
// render the tiles
Graphics()->TextureClear();
2009-10-27 14:38:53 +00:00
Graphics()->QuadsBegin();
2020-09-22 16:02:03 +00:00
float Size = 15.0f;
float OffsetTime = std::fmod(LocalTime() * 0.15f, 2.0f);
2020-09-22 16:02:03 +00:00
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);
2020-09-22 16:02:03 +00:00
Graphics()->QuadsDrawTL(&QuadItem, 1);
}
2009-10-27 14:38:53 +00:00
Graphics()->QuadsEnd();
// render border fade
Graphics()->TextureSet(m_TextureBlob);
2009-10-27 14:38:53 +00:00
Graphics()->QuadsBegin();
2020-09-22 16:02:03 +00:00
Graphics()->SetColor(1, 1, 1, 1);
QuadItem = IGraphics::CQuadItem(-100, -100, sw + 200, sh + 200);
Graphics()->QuadsDrawTL(&QuadItem, 1);
2009-10-27 14:38:53 +00:00
Graphics()->QuadsEnd();
// restore screen
2021-09-22 19:48:55 +00:00
UI()->MapScreen();
2008-09-11 22:45:28 +00:00
}
bool CMenus::CheckHotKey(int Key) const
{
return m_Popup == POPUP_NONE &&
!Input()->ShiftIsPressed() && !Input()->ModifierIsPressed() && // no modifier
2021-07-12 09:43:56 +00:00
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)
2022-09-23 22:56:43 +00:00
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);
}
}