Merge 'remotes/trunk'
[0ad.git] / source / gui / GUITooltip.cpp
blob3aca49442e2e4bb277e1d1e75c287ca46bcaf048
1 /* Copyright (C) 2016 Wildfire Games.
2 * This file is part of 0 A.D.
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
18 #include "precompiled.h"
20 #include "GUITooltip.h"
22 #include "CGUI.h"
23 #include "GUIutil.h"
24 #include "IGUIObject.h"
26 #include "lib/timer.h"
27 #include "ps/CLogger.h"
30 Tooltips:
31 When holding the mouse stationary over an object for some amount of time,
32 the tooltip is displayed. If the mouse moves off that object, the tooltip
33 disappears. If the mouse re-enters an object within a short time, the new
34 tooltip is displayed immediately. (This lets you run the mouse across a
35 series of buttons, without waiting ages for the text to pop up every time.)
37 See Visual Studio's toolbar buttons for an example.
40 Implemented as a state machine:
42 (where "*" lines are checked constantly, and "<" lines are handled
43 on entry to that state)
45 IN MOTION
46 * If the mouse stops, check whether it should have a tooltip and move to
47 'STATIONARY, NO TOOLTIP' or 'STATIONARY, TOOLIP'
48 * If the mouse enters an object with a tooltip delay of 0, switch to 'SHOWING'
50 STATIONARY, NO TOOLTIP
51 * If the mouse moves, switch to 'IN MOTION'
53 STATIONARY, TOOLTIP
54 < Set target time = now + tooltip time
55 * If the mouse moves, switch to 'IN MOTION'
56 * If now > target time, switch to 'SHOWING'
58 SHOWING
59 < Start displaying the tooltip
60 * If the mouse leaves the object, check whether the new object has a tooltip
61 and switch to 'SHOWING' or 'COOLING'
63 COOLING (since I can't think of a better name)
64 < Stop displaying the tooltip
65 < Set target time = now + cooldown time
66 * If the mouse has moved and is over a tooltipped object, switch to 'SHOWING'
67 * If now > target time, switch to 'STATIONARY, NO TOOLTIP'
70 enum
72 ST_IN_MOTION,
73 ST_STATIONARY_NO_TOOLTIP,
74 ST_STATIONARY_TOOLTIP,
75 ST_SHOWING,
76 ST_COOLING
79 GUITooltip::GUITooltip()
80 : m_State(ST_IN_MOTION), m_PreviousObject(NULL), m_PreviousTooltipName()
84 const double CooldownTime = 0.25; // TODO: Don't hard-code this value
86 bool GUITooltip::GetTooltip(IGUIObject* obj, CStr& style)
88 CStrW text;
90 if (obj && obj->SettingExists("_icon_tooltip_style") && obj->MouseOverIcon() &&
91 GUI<CStr>::GetSetting(obj, "_icon_tooltip_style", style) == PSRETURN_OK &&
92 GUI<CStrW>::GetSetting(obj, "_icon_tooltip", text) == PSRETURN_OK && !text.empty())
94 if (style.empty())
95 style = "default";
97 m_IsIconTooltip = true;
98 return true;
101 if (obj && obj->SettingExists("tooltip_style") &&
102 GUI<CStr>::GetSetting(obj, "tooltip_style", style) == PSRETURN_OK &&
103 GUI<CStrW>::GetSetting(obj, "tooltip", text) == PSRETURN_OK &&
104 !text.empty())
106 if (style.empty())
107 style = "default";
109 m_IsIconTooltip = false;
110 return true;
113 return false;
116 void GUITooltip::ShowTooltip(IGUIObject* obj, const CPos& pos, const CStr& style, CGUI* gui)
118 ENSURE(obj);
120 if (style.empty())
121 return;
123 IGUIObject* tooltipobj = gui->FindObjectByName("__tooltip_"+style);
124 if (!tooltipobj)
126 LOGERROR("Cannot find tooltip named '%s'", style.c_str());
127 return;
130 IGUIObject* usedobj = tooltipobj; // object actually used to display the tooltip in
132 CStr usedObjectName;
133 if (GUI<CStr>::GetSetting(tooltipobj, "use_object", usedObjectName) == PSRETURN_OK &&
134 !usedObjectName.empty())
136 usedobj = gui->FindObjectByName(usedObjectName);
137 if (!usedobj)
139 LOGERROR("Cannot find object named '%s' used by tooltip '%s'", usedObjectName.c_str(), style.c_str());
140 return;
143 else if (GUI<CPos>::SetSetting(usedobj, "_mousepos", pos) != PSRETURN_OK)
144 debug_warn(L"Failed to set tooltip mouse position");
146 GUI<bool>::SetSetting(usedobj, "hidden", false);
148 CStrW text;
149 if (m_IsIconTooltip)
151 if (GUI<CStrW>::GetSetting(obj, "_icon_tooltip", text) != PSRETURN_OK)
152 debug_warn(L"Failed to retrieve icon tooltip text");
154 else if (GUI<CStrW>::GetSetting(obj, "tooltip", text) != PSRETURN_OK)
155 debug_warn(L"Failed to retrieve tooltip text");
157 if (usedobj->SetSetting("caption", text) != PSRETURN_OK)
158 debug_warn(L"Failed to set tooltip caption");
160 SGUIMessage msg(GUIM_SETTINGS_UPDATED, "caption");
161 usedobj->HandleMessage(msg);
164 void GUITooltip::HideTooltip(const CStr& style, CGUI* gui)
166 if (style.empty())
167 return;
169 IGUIObject* tooltipobj = gui->FindObjectByName("__tooltip_"+style);
170 if (!tooltipobj)
172 LOGERROR("Cannot find tooltip named '%s'", style.c_str());
173 return;
176 CStr usedObjectName;
177 if (GUI<CStr>::GetSetting(tooltipobj, "use_object", usedObjectName) == PSRETURN_OK &&
178 !usedObjectName.empty())
180 IGUIObject* usedobj = gui->FindObjectByName(usedObjectName);
181 if (!usedobj)
183 LOGERROR("Cannot find object named '%s' used by tooltip '%s'", usedObjectName.c_str(), style.c_str());
184 return;
187 usedobj->SetSetting("caption", L"");
188 SGUIMessage msg(GUIM_SETTINGS_UPDATED, "caption");
189 usedobj->HandleMessage(msg);
191 bool hideobject = true;
192 GUI<bool>::GetSetting(tooltipobj, "hide_object", hideobject);
194 if (hideobject)
195 GUI<bool>::SetSetting(usedobj, "hidden", true);
197 else
198 GUI<bool>::SetSetting(tooltipobj, "hidden", true);
201 static int GetTooltipDelay(const CStr& style, CGUI* gui)
203 int delay = 500; // default value (in msec)
205 IGUIObject* tooltipobj = gui->FindObjectByName("__tooltip_"+style);
206 if (!tooltipobj)
208 LOGERROR("Cannot find tooltip object named '%s'", style.c_str());
209 return delay;
211 GUI<int>::GetSetting(tooltipobj, "delay", delay);
212 return delay;
215 void GUITooltip::Update(IGUIObject* Nearest, const CPos& MousePos, CGUI* GUI)
217 // Called once per frame, so efficiency isn't vital
218 double now = timer_Time();
220 CStr style;
221 int nextstate = -1;
223 switch (m_State)
225 case ST_IN_MOTION:
226 if (MousePos == m_PreviousMousePos)
228 if (GetTooltip(Nearest, style))
229 nextstate = ST_STATIONARY_TOOLTIP;
230 else
231 nextstate = ST_STATIONARY_NO_TOOLTIP;
233 else
235 // Check for movement onto a zero-delayed tooltip
236 if (GetTooltip(Nearest, style) && GetTooltipDelay(style, GUI)==0)
238 // Reset any previous tooltips completely
239 //m_Time = now + (double)GetTooltipDelay(style, GUI) / 1000.;
240 HideTooltip(m_PreviousTooltipName, GUI);
242 nextstate = ST_SHOWING;
245 break;
247 case ST_STATIONARY_NO_TOOLTIP:
248 if (MousePos != m_PreviousMousePos)
249 nextstate = ST_IN_MOTION;
250 break;
252 case ST_STATIONARY_TOOLTIP:
253 if (MousePos != m_PreviousMousePos)
254 nextstate = ST_IN_MOTION;
255 else if (now >= m_Time)
257 // Make sure the tooltip still exists
258 if (GetTooltip(Nearest, style))
259 nextstate = ST_SHOWING;
260 else
262 // Failed to retrieve style - the object has probably been
263 // altered, so just restart the process
264 nextstate = ST_IN_MOTION;
267 break;
269 case ST_SHOWING:
270 // Handle special case of icon tooltips
271 if (Nearest == m_PreviousObject && (!m_IsIconTooltip || Nearest->MouseOverIcon()))
273 // Still showing the same object's tooltip, but the text might have changed
274 if (GetTooltip(Nearest, style))
275 ShowTooltip(Nearest, MousePos, style, GUI);
277 else
279 // Mouse moved onto a new object
280 if (GetTooltip(Nearest, style))
282 CStr style_old;
284 // If we're displaying a tooltip with no delay, then we want to
285 // reset so that other object that should have delay can't
286 // "ride this tail", it have to wait.
287 // Notice that this doesn't apply to when you go from one delay=0
288 // to another delay=0
289 if (GetTooltip(m_PreviousObject, style_old) && GetTooltipDelay(style_old, GUI) == 0 &&
290 GetTooltipDelay(style, GUI) != 0)
292 HideTooltip(m_PreviousTooltipName, GUI);
293 nextstate = ST_IN_MOTION;
295 else
297 // Hide old scrollbar
298 HideTooltip(m_PreviousTooltipName, GUI);
299 nextstate = ST_SHOWING;
302 else
303 nextstate = ST_COOLING;
305 break;
307 case ST_COOLING:
308 if (GetTooltip(Nearest, style))
309 nextstate = ST_SHOWING;
310 else if (now >= m_Time)
311 nextstate = ST_IN_MOTION;
312 break;
315 // Handle state-entry code:
316 if (nextstate != -1)
318 switch (nextstate)
320 case ST_STATIONARY_TOOLTIP:
321 m_Time = now + (double)GetTooltipDelay(style, GUI) / 1000.;
322 break;
324 case ST_SHOWING:
325 ShowTooltip(Nearest, MousePos, style, GUI);
326 m_PreviousTooltipName = style;
327 break;
329 case ST_COOLING:
330 HideTooltip(m_PreviousTooltipName, GUI);
331 m_Time = now + CooldownTime;
332 break;
335 m_State = nextstate;
338 m_PreviousMousePos = MousePos;
339 m_PreviousObject = Nearest;