1 | #include "tooltips.h" |
2 | |
3 | #include <game/client/render.h> |
4 | #include <game/client/ui.h> |
5 | |
6 | CTooltips::CTooltips() |
7 | { |
8 | OnReset(); |
9 | } |
10 | |
11 | void CTooltips::OnReset() |
12 | { |
13 | m_HoverTime = -1; |
14 | m_Tooltips.clear(); |
15 | ClearActiveTooltip(); |
16 | } |
17 | |
18 | void CTooltips::SetActiveTooltip(CTooltip &Tooltip) |
19 | { |
20 | m_ActiveTooltip.emplace(args&: Tooltip); |
21 | } |
22 | |
23 | inline void CTooltips::ClearActiveTooltip() |
24 | { |
25 | m_ActiveTooltip.reset(); |
26 | m_PreviousTooltip.reset(); |
27 | } |
28 | |
29 | void 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 | |
54 | void 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 | |