Refactor tooltips to render last, add some tooltips

This commit is contained in:
Edgar 2022-04-18 09:34:05 +02:00
parent d4dcaa2471
commit faab2ded74
No known key found for this signature in database
GPG key ID: 8731E6C0166EAA85
7 changed files with 191 additions and 64 deletions

View file

@ -1958,6 +1958,8 @@ if(CLIENT)
components/spectator.h
components/statboard.cpp
components/statboard.h
components/tooltips.cpp
components/tooltips.h
components/voting.cpp
components/voting.h
gameclient.cpp

View file

@ -2832,64 +2832,4 @@ bool CMenus::HandleListInputs(const CUIRect &View, float &ScrollValue, const flo
}
return NewIndex != -1;
}
void CMenus::DoToolTip(const void *pID, const CUIRect *pNearRect, const char *pText, float WidthHint)
{
static int64_t HoverTime = -1;
if(!UI()->MouseInside(pNearRect))
{
if(pID == UI()->ActiveTooltipItem())
HoverTime = -1;
return;
}
UI()->SetActiveTooltipItem(pID);
if(HoverTime == -1)
HoverTime = time_get();
// Delay tooltip until 1 second passed.
if(HoverTime > time_get() - time_freq())
return;
const float MARGIN = 5.0f;
CUIRect Rect;
Rect.w = WidthHint;
if(WidthHint < 0.0f)
Rect.w = TextRender()->TextWidth(0, 14.0f, pText, -1, -1.0f) + 4.0f;
Rect.h = 30.0f;
CUIRect *pScreen = UI()->Screen();
// Try the top side.
if(pNearRect->y - Rect.h - MARGIN > pScreen->y)
{
Rect.x = clamp(UI()->MouseX() - Rect.w / 2.0f, MARGIN, pScreen->w - Rect.w - MARGIN);
Rect.y = pNearRect->y - Rect.h - MARGIN;
}
// Try the bottom side.
else if(pNearRect->y + pNearRect->h + MARGIN < pScreen->h)
{
Rect.x = clamp(UI()->MouseX() - Rect.w / 2.0f, MARGIN, pScreen->w - Rect.w - MARGIN);
Rect.y = pNearRect->y + pNearRect->h + MARGIN;
}
// Try the right side.
else if(pNearRect->x + pNearRect->w + MARGIN + Rect.w < pScreen->w)
{
Rect.x = pNearRect->x + pNearRect->w + MARGIN;
Rect.y = clamp(UI()->MouseY() - Rect.h / 2.0f, MARGIN, pScreen->h - Rect.h - MARGIN);
}
// Try the left side.
else if(pNearRect->x - Rect.w - MARGIN > pScreen->x)
{
Rect.x = pNearRect->x - Rect.w - MARGIN;
Rect.y = clamp(UI()->MouseY() - Rect.h / 2.0f, MARGIN, pScreen->h - Rect.h - MARGIN);
}
RenderTools()->DrawUIRect(&Rect, ColorRGBA(0.2, 0.2, 0.2, 0.80f), CUI::CORNER_ALL, 5.0f);
Rect.Margin(2.0f, &Rect);
UI()->DoLabel(&Rect, pText, 14.0f, TEXTALIGN_LEFT);
}
}

View file

@ -530,7 +530,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
{
m_Dummy ^= 1;
}
DoToolTip(&m_Dummy, &DummyLabel, Localize("Toggle to edit your dummy settings."));
GameClient()->m_Tooltips.DoToolTip(&m_Dummy, &DummyLabel, Localize("Toggle to edit your dummy settings."));
Dummy.HSplitTop(20.0f, &DummyLabel, &Dummy);
@ -1265,7 +1265,7 @@ void CMenus::RenderSettingsGraphics(CUIRect MainView)
MainView.HSplitTop(20.0f, &Button, &MainView);
if(DoButton_CheckBox(&g_Config.m_GfxHighDetail, Localize("High Detail"), g_Config.m_GfxHighDetail, &Button))
g_Config.m_GfxHighDetail ^= 1;
DoToolTip(&g_Config.m_GfxHighDetail, &Button, Localize("Allows maps to render with more detail."));
GameClient()->m_Tooltips.DoToolTip(&g_Config.m_GfxHighDetail, &Button, Localize("Allows maps to render with more detail."));
MainView.HSplitTop(20.0f, &Button, &MainView);
if(DoButton_CheckBox(&g_Config.m_GfxHighdpi, Localize("Use high DPI"), g_Config.m_GfxHighdpi, &Button))
@ -2635,6 +2635,7 @@ void CMenus::RenderSettingsDDNet(CUIRect MainView)
{
g_Config.m_ClRaceGhost ^= 1;
}
GameClient()->m_Tooltips.DoToolTip(&g_Config.m_ClRaceGhost, &Button, Localize("When you cross the start line, show a ghost tee replicating the movements of your best time."));
if(g_Config.m_ClRaceGhost)
{
@ -2686,13 +2687,15 @@ void CMenus::RenderSettingsDDNet(CUIRect MainView)
Button.VSplitMid(&LeftLeft, &Button);
Button.VSplitLeft(50.0f, &Label, &Button);
UI()->DoLabelScaled(&Label, Localize("Alpha"), 14.0f, TEXTALIGN_LEFT);
UI()->DoLabelScaled(&Label, Localize("Opacity"), 14.0f, TEXTALIGN_LEFT);
g_Config.m_ClShowOthersAlpha = (int)(UIEx()->DoScrollbarH(&g_Config.m_ClShowOthersAlpha, &Button, g_Config.m_ClShowOthersAlpha / 100.0f) * 100.0f);
if(DoButton_CheckBox(&g_Config.m_ClShowOthers, Localize("Show others"), g_Config.m_ClShowOthers == SHOW_OTHERS_ON, &LeftLeft))
{
g_Config.m_ClShowOthers = g_Config.m_ClShowOthers != SHOW_OTHERS_ON ? SHOW_OTHERS_ON : SHOW_OTHERS_OFF;
}
GameClient()->m_Tooltips.DoToolTip(&g_Config.m_ClShowOthersAlpha, &Button, "Adjust the opacity of entities belonging to other teams, such as tees and nameplates");
}
Left.HSplitTop(20.0f, &Button, &Left);
@ -2707,6 +2710,7 @@ void CMenus::RenderSettingsDDNet(CUIRect MainView)
{
g_Config.m_ClShowQuads ^= 1;
}
GameClient()->m_Tooltips.DoToolTip(&g_Config.m_ClShowQuads, &Button, Localize("Quads are used for background decoration"));
Right.HSplitTop(20.0f, &Label, &Right);
Label.VSplitLeft(130.0f, &Label, &Button);
@ -2720,6 +2724,7 @@ void CMenus::RenderSettingsDDNet(CUIRect MainView)
{
g_Config.m_ClAntiPing ^= 1;
}
GameClient()->m_Tooltips.DoToolTip(&g_Config.m_ClAntiPing, &Button, Localize("Tries to predict other entities to give a feel of low latency."));
if(g_Config.m_ClAntiPing)
{

View file

@ -0,0 +1,117 @@
#include "tooltips.h"
#include <game/client/render.h>
CTooltips::CTooltips()
{
OnReset();
}
void CTooltips::OnReset()
{
HoverTime = -1;
m_Tooltips.clear();
}
void CTooltips::SetActiveTooltip(CTooltip &Tooltip)
{
if(m_ActiveTooltip.has_value())
return;
m_ActiveTooltip.emplace(Tooltip);
HoverTime = time_get();
}
inline void CTooltips::ClearActiveTooltip()
{
m_ActiveTooltip.reset();
}
void CTooltips::DoToolTip(const void *pID, const CUIRect *pNearRect, const char *pText, float WidthHint)
{
uintptr_t ID = reinterpret_cast<uintptr_t>(pID);
const auto &it = m_Tooltips.find(ID);
if(it == m_Tooltips.end())
{
CTooltip NewTooltip = {
.m_Rect = *pNearRect,
.m_pText = pText,
.m_WidthHint = WidthHint,
};
m_Tooltips[ID] = NewTooltip;
CTooltip &Tooltip = m_Tooltips[ID];
if(UI()->MouseInside(&Tooltip.m_Rect))
{
SetActiveTooltip(Tooltip);
}
}
else
{
if(UI()->MouseInside(&it->second.m_Rect))
{
SetActiveTooltip(it->second);
}
}
}
void CTooltips::OnRender()
{
if(m_ActiveTooltip.has_value())
{
CTooltip &Tooltip = m_ActiveTooltip.value();
if(!UI()->MouseInside(&Tooltip.m_Rect))
{
ClearActiveTooltip();
return;
}
// Delay tooltip until 1 second passed.
if(HoverTime > time_get() - time_freq())
return;
const float MARGIN = 5.0f;
CUIRect Rect;
Rect.w = Tooltip.m_WidthHint;
if(Tooltip.m_WidthHint < 0.0f)
Rect.w = TextRender()->TextWidth(0, 14.0f, Tooltip.m_pText, -1, -1.0f) + 4.0f;
Rect.h = 30.0f;
CUIRect *pScreen = UI()->Screen();
// Try the top side.
if(Tooltip.m_Rect.y - Rect.h - MARGIN > pScreen->y)
{
Rect.x = clamp(UI()->MouseX() - Rect.w / 2.0f, MARGIN, pScreen->w - Rect.w - MARGIN);
Rect.y = Tooltip.m_Rect.y - Rect.h - MARGIN;
}
// Try the bottom side.
else if(Tooltip.m_Rect.y + Tooltip.m_Rect.h + MARGIN < pScreen->h)
{
Rect.x = clamp(UI()->MouseX() - Rect.w / 2.0f, MARGIN, pScreen->w - Rect.w - MARGIN);
Rect.y = Tooltip.m_Rect.y + Tooltip.m_Rect.h + MARGIN;
}
// Try the right side.
else if(Tooltip.m_Rect.x + Tooltip.m_Rect.w + MARGIN + Rect.w < pScreen->w)
{
Rect.x = Tooltip.m_Rect.x + Tooltip.m_Rect.w + MARGIN;
Rect.y = clamp(UI()->MouseY() - Rect.h / 2.0f, MARGIN, pScreen->h - Rect.h - MARGIN);
}
// Try the left side.
else if(Tooltip.m_Rect.x - Rect.w - MARGIN > pScreen->x)
{
Rect.x = Tooltip.m_Rect.x - Rect.w - MARGIN;
Rect.y = clamp(UI()->MouseY() - Rect.h / 2.0f, MARGIN, pScreen->h - Rect.h - MARGIN);
}
RenderTools()->DrawUIRect(&Rect, ColorRGBA(0.2, 0.2, 0.2, 0.80f), CUI::CORNER_ALL, 5.0f);
Rect.Margin(2.0f, &Rect);
UI()->DoLabel(&Rect, Tooltip.m_pText, 14.0f, TEXTALIGN_LEFT);
}
}

View file

@ -0,0 +1,59 @@
#ifndef GAME_CLIENT_COMPONENTS_TOOLTIPS_H
#define GAME_CLIENT_COMPONENTS_TOOLTIPS_H
#include <cstdint>
#include <game/client/component.h>
#include <game/client/ui.h>
#include <optional>
#include <unordered_map>
struct CTooltip
{
CUIRect m_Rect;
const char *m_pText;
float m_WidthHint;
};
/**
* A component that manages and renders UI tooltips.
*
* Should be among the last components to render.
*/
class CTooltips : public CComponent
{
std::unordered_map<uintptr_t, CTooltip> m_Tooltips;
std::optional<std::reference_wrapper<CTooltip>> m_ActiveTooltip;
int64_t HoverTime;
/**
* The passed tooltip is only actually set if there is no currently active tooltip.
*
* @param Tooltip A reference to the tooltip that should be active.
*/
void SetActiveTooltip(CTooltip &Tooltip);
inline void ClearActiveTooltip();
public:
CTooltips();
virtual int Sizeof() const override { return sizeof(*this); }
/**
* Adds the tooltip to a cache and renders it when active.
*
* On the first call to this function, the data passed is cached, afterwards the calls are used to detect if the tooltip should be activated.
*
* For now only works correctly with single line tooltips, since Text width calculation gets broken when there are multiple lines.
*
* @param pID The ID of the tooltip. Usually a reference to some g_Config value.
* @param pNearTo Place the tooltip near this rect.
* @param pText The text to display in the tooltip
*/
void DoToolTip(const void *pID, const CUIRect *pNearRect, const char *pText, float WidthHint = -1.0f);
virtual void OnReset() override;
virtual void OnRender() override;
};
#endif

View file

@ -137,6 +137,7 @@ void CGameClient::OnConsoleInit()
m_All.Add(&m_Statboard);
m_All.Add(&m_Motd);
m_All.Add(&m_Menus);
m_All.Add(&m_Tooltips);
m_All.Add(&CMenus::m_Binder);
m_All.Add(&m_GameConsole);

View file

@ -54,6 +54,7 @@
#include "components/spectator.h"
#include "components/statboard.h"
#include "components/voting.h"
#include "components/tooltips.h"
class CGameInfo
{
@ -144,6 +145,8 @@ public:
CRaceDemo m_RaceDemo;
CGhost m_Ghost;
CTooltips m_Tooltips;
private:
class CStack
{