1#include "tooltips.h"
2
3#include <game/client/render.h>
4#include <game/client/ui.h>
5
6CTooltips::CTooltips()
7{
8 OnReset();
9}
10
11void CTooltips::OnReset()
12{
13 m_HoverTime = -1;
14 m_Tooltips.clear();
15 ClearActiveTooltip();
16}
17
18void CTooltips::SetActiveTooltip(CTooltip &Tooltip)
19{
20 m_ActiveTooltip.emplace(args&: Tooltip);
21}
22
23inline void CTooltips::ClearActiveTooltip()
24{
25 m_ActiveTooltip.reset();
26 m_PreviousTooltip.reset();
27}
28
29void CTooltips::DoToolTip(const void *pId, const CUIRect *pNearRect, const char *pText, float WidthHint)
30{
31 uintptr_t Id = reinterpret_cast<uintptr_t>(pId);
32 const auto result = m_Tooltips.emplace(args&: Id, args: CTooltip{
33 .m_pId: pId,
34 .m_Rect: *pNearRect,
35 .m_pText: pText,
36 .m_WidthHint: WidthHint,
37 .m_OnScreen: false});
38 CTooltip &Tooltip = result.first->second;
39
40 if(!result.second)
41 {
42 Tooltip.m_Rect = *pNearRect; // update in case of window resize
43 Tooltip.m_pText = pText; // update in case of language change
44 }
45
46 Tooltip.m_OnScreen = true;
47
48 if(Ui()->HotItem() == Tooltip.m_pId)
49 {
50 SetActiveTooltip(Tooltip);
51 }
52}
53
54void CTooltips::OnRender()
55{
56 if(m_ActiveTooltip.has_value())
57 {
58 CTooltip &Tooltip = m_ActiveTooltip.value();
59
60 if(Ui()->HotItem() != Tooltip.m_pId)
61 {
62 Tooltip.m_OnScreen = false;
63 ClearActiveTooltip();
64 return;
65 }
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 = 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 = 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 = 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 = 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 TextRender()->SetCursor(pCursor: &Cursor, x: Rect.x, y: Rect.y, FontSize, Flags: TEXTFLAG_RENDER);
128 Cursor.m_LineWidth = Tooltip.m_WidthHint;
129
130 STextContainerIndex TextContainerIndex;
131 const unsigned OldRenderFlags = TextRender()->GetRenderFlags();
132 TextRender()->SetRenderFlags(OldRenderFlags | TEXT_RENDER_FLAG_ONE_TIME_USE);
133 TextRender()->CreateTextContainer(TextContainerIndex, pCursor: &Cursor, pText: Tooltip.m_pText);
134 TextRender()->SetRenderFlags(OldRenderFlags);
135
136 if(TextContainerIndex.Valid())
137 {
138 ColorRGBA TextColor = TextRender()->DefaultTextColor();
139 TextColor.a *= AlphaFactor;
140 ColorRGBA OutlineColor = TextRender()->DefaultTextOutlineColor();
141 OutlineColor.a *= AlphaFactor;
142 TextRender()->RenderTextContainer(TextContainerIndex, TextColor, TextOutlineColor: OutlineColor);
143 }
144
145 TextRender()->DeleteTextContainer(TextContainerIndex);
146
147 Tooltip.m_OnScreen = false;
148 }
149}
150