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