1#include "tooltips.h"
2
3#include <base/time.h>
4
5#include <game/client/ui.h>
6
7CTooltips::CTooltips()
8{
9 OnReset();
10}
11
12void CTooltips::OnReset()
13{
14 m_HoverTime = -1;
15 m_Tooltips.clear();
16 ClearActiveTooltip();
17}
18
19void CTooltips::SetActiveTooltip(CTooltip &Tooltip)
20{
21 m_ActiveTooltip.emplace(args&: Tooltip);
22}
23
24inline void CTooltips::ClearActiveTooltip()
25{
26 m_ActiveTooltip.reset();
27 m_PreviousTooltip.reset();
28}
29
30void CTooltips::DoToolTip(const void *pId, const CUIRect *pNearRect, const char *pText, float WidthHint)
31{
32 uintptr_t Id = reinterpret_cast<uintptr_t>(pId);
33 const auto &[Entry, WasInserted] = m_Tooltips.emplace(args&: Id, args: CTooltip{
34 .m_pId: pId,
35 .m_Rect: *pNearRect,
36 .m_pText: pText,
37 .m_WidthHint: WidthHint,
38 .m_OnScreen: false});
39 CTooltip &Tooltip = Entry->second;
40
41 if(!WasInserted)
42 {
43 Tooltip.m_Rect = *pNearRect; // update in case of window resize
44 Tooltip.m_pText = pText; // update in case of language change
45 }
46
47 Tooltip.m_OnScreen = true;
48
49 if(Ui()->HotItem() == Tooltip.m_pId)
50 {
51 SetActiveTooltip(Tooltip);
52 }
53}
54
55void CTooltips::OnRender()
56{
57 if(m_ActiveTooltip.has_value())
58 {
59 CTooltip &Tooltip = m_ActiveTooltip.value();
60
61 if(Ui()->HotItem() != Tooltip.m_pId || !Tooltip.m_Rect.Inside(Point: Ui()->MousePos()))
62 {
63 Tooltip.m_OnScreen = false;
64 ClearActiveTooltip();
65 return;
66 }
67 if(!Tooltip.m_OnScreen)
68 return;
69
70 // Reset hover time if a different tooltip is active.
71 // Only reset hover time when rendering, because multiple tooltips can be
72 // activated in the same frame, but only the last one should be rendered.
73 if(!m_PreviousTooltip.has_value() || m_PreviousTooltip.value().get().m_pText != Tooltip.m_pText)
74 m_HoverTime = time_get();
75 m_PreviousTooltip.emplace(args&: Tooltip);
76
77 // Delay tooltip until 1 second passed. Start fade-in in the last 0.25 seconds.
78 constexpr float SecondsBeforeFadeIn = 0.75f;
79 const float SecondsSinceActivation = (time_get() - m_HoverTime) / (float)time_freq();
80 if(SecondsSinceActivation < SecondsBeforeFadeIn)
81 return;
82 constexpr float SecondsFadeIn = 0.25f;
83 const float AlphaFactor = SecondsSinceActivation < SecondsBeforeFadeIn + SecondsFadeIn ? (SecondsSinceActivation - SecondsBeforeFadeIn) / SecondsFadeIn : 1.0f;
84
85 constexpr float FontSize = 14.0f;
86 constexpr float Margin = 5.0f;
87 constexpr float Padding = 5.0f;
88
89 const STextBoundingBox BoundingBox = TextRender()->TextBoundingBox(Size: FontSize, pText: Tooltip.m_pText, StrLength: -1, LineWidth: Tooltip.m_WidthHint);
90 CUIRect Rect;
91 Rect.w = BoundingBox.m_W + 2 * Padding;
92 Rect.h = BoundingBox.m_H + 2 * Padding;
93
94 const CUIRect *pScreen = Ui()->Screen();
95 Rect.w = minimum(a: Rect.w, b: pScreen->w - 2 * Margin);
96 Rect.h = minimum(a: Rect.h, b: pScreen->h - 2 * Margin);
97
98 // Try the top side.
99 if(Tooltip.m_Rect.y - Rect.h - Margin > pScreen->y)
100 {
101 Rect.x = std::clamp(val: Ui()->MouseX() - Rect.w / 2.0f, lo: Margin, hi: pScreen->w - Rect.w - Margin);
102 Rect.y = Tooltip.m_Rect.y - Rect.h - Margin;
103 }
104 // Try the bottom side.
105 else if(Tooltip.m_Rect.y + Tooltip.m_Rect.h + Margin < pScreen->h)
106 {
107 Rect.x = std::clamp(val: Ui()->MouseX() - Rect.w / 2.0f, lo: Margin, hi: pScreen->w - Rect.w - Margin);
108 Rect.y = Tooltip.m_Rect.y + Tooltip.m_Rect.h + Margin;
109 }
110 // Try the right side.
111 else if(Tooltip.m_Rect.x + Tooltip.m_Rect.w + Margin + Rect.w < pScreen->w)
112 {
113 Rect.x = Tooltip.m_Rect.x + Tooltip.m_Rect.w + Margin;
114 Rect.y = std::clamp(val: Ui()->MouseY() - Rect.h / 2.0f, lo: Margin, hi: pScreen->h - Rect.h - Margin);
115 }
116 // Try the left side.
117 else if(Tooltip.m_Rect.x - Rect.w - Margin > pScreen->x)
118 {
119 Rect.x = Tooltip.m_Rect.x - Rect.w - Margin;
120 Rect.y = std::clamp(val: Ui()->MouseY() - Rect.h / 2.0f, lo: Margin, hi: pScreen->h - Rect.h - Margin);
121 }
122
123 Rect.Draw(Color: ColorRGBA(0.2f, 0.2f, 0.2f, 0.8f * AlphaFactor), Corners: IGraphics::CORNER_ALL, Rounding: Padding);
124 Rect.Margin(Cut: Padding, pOtherRect: &Rect);
125
126 CTextCursor Cursor;
127 Cursor.SetPosition(Rect.TopLeft());
128 Cursor.m_FontSize = FontSize;
129 Cursor.m_LineWidth = Tooltip.m_WidthHint;
130
131 STextContainerIndex TextContainerIndex;
132 const unsigned OldRenderFlags = TextRender()->GetRenderFlags();
133 TextRender()->SetRenderFlags(OldRenderFlags | TEXT_RENDER_FLAG_ONE_TIME_USE);
134 TextRender()->CreateTextContainer(TextContainerIndex, pCursor: &Cursor, pText: Tooltip.m_pText);
135 TextRender()->SetRenderFlags(OldRenderFlags);
136
137 if(TextContainerIndex.Valid())
138 {
139 ColorRGBA TextColor = TextRender()->DefaultTextColor();
140 TextColor.a *= AlphaFactor;
141 ColorRGBA OutlineColor = TextRender()->DefaultTextOutlineColor();
142 OutlineColor.a *= AlphaFactor;
143 TextRender()->RenderTextContainer(TextContainerIndex, TextColor, TextOutlineColor: OutlineColor);
144 }
145
146 TextRender()->DeleteTextContainer(TextContainerIndex);
147
148 Tooltip.m_OnScreen = false;
149 }
150}
151