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"
24 #include "IGUIObject.h"
26 #include "lib/timer.h"
27 #include "ps/CLogger.h"
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)
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'
54 < Set target time = now + tooltip time
55 * If the mouse moves, switch to 'IN MOTION'
56 * If now > target time, switch to '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'
73 ST_STATIONARY_NO_TOOLTIP
,
74 ST_STATIONARY_TOOLTIP
,
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
)
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())
97 m_IsIconTooltip
= 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
&&
109 m_IsIconTooltip
= false;
116 void GUITooltip::ShowTooltip(IGUIObject
* obj
, const CPos
& pos
, const CStr
& style
, CGUI
* gui
)
123 IGUIObject
* tooltipobj
= gui
->FindObjectByName("__tooltip_"+style
);
126 LOGERROR("Cannot find tooltip named '%s'", style
.c_str());
130 IGUIObject
* usedobj
= tooltipobj
; // object actually used to display the tooltip in
133 if (GUI
<CStr
>::GetSetting(tooltipobj
, "use_object", usedObjectName
) == PSRETURN_OK
&&
134 !usedObjectName
.empty())
136 usedobj
= gui
->FindObjectByName(usedObjectName
);
139 LOGERROR("Cannot find object named '%s' used by tooltip '%s'", usedObjectName
.c_str(), style
.c_str());
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);
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
)
169 IGUIObject
* tooltipobj
= gui
->FindObjectByName("__tooltip_"+style
);
172 LOGERROR("Cannot find tooltip named '%s'", style
.c_str());
177 if (GUI
<CStr
>::GetSetting(tooltipobj
, "use_object", usedObjectName
) == PSRETURN_OK
&&
178 !usedObjectName
.empty())
180 IGUIObject
* usedobj
= gui
->FindObjectByName(usedObjectName
);
183 LOGERROR("Cannot find object named '%s' used by tooltip '%s'", usedObjectName
.c_str(), style
.c_str());
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
);
195 GUI
<bool>::SetSetting(usedobj
, "hidden", true);
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
);
208 LOGERROR("Cannot find tooltip object named '%s'", style
.c_str());
211 GUI
<int>::GetSetting(tooltipobj
, "delay", 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();
226 if (MousePos
== m_PreviousMousePos
)
228 if (GetTooltip(Nearest
, style
))
229 nextstate
= ST_STATIONARY_TOOLTIP
;
231 nextstate
= ST_STATIONARY_NO_TOOLTIP
;
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
;
247 case ST_STATIONARY_NO_TOOLTIP
:
248 if (MousePos
!= m_PreviousMousePos
)
249 nextstate
= ST_IN_MOTION
;
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
;
262 // Failed to retrieve style - the object has probably been
263 // altered, so just restart the process
264 nextstate
= ST_IN_MOTION
;
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
);
279 // Mouse moved onto a new object
280 if (GetTooltip(Nearest
, style
))
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
;
297 // Hide old scrollbar
298 HideTooltip(m_PreviousTooltipName
, GUI
);
299 nextstate
= ST_SHOWING
;
303 nextstate
= ST_COOLING
;
308 if (GetTooltip(Nearest
, style
))
309 nextstate
= ST_SHOWING
;
310 else if (now
>= m_Time
)
311 nextstate
= ST_IN_MOTION
;
315 // Handle state-entry code:
320 case ST_STATIONARY_TOOLTIP
:
321 m_Time
= now
+ (double)GetTooltipDelay(style
, GUI
) / 1000.;
325 ShowTooltip(Nearest
, MousePos
, style
, GUI
);
326 m_PreviousTooltipName
= style
;
330 HideTooltip(m_PreviousTooltipName
, GUI
);
331 m_Time
= now
+ CooldownTime
;
338 m_PreviousMousePos
= MousePos
;
339 m_PreviousObject
= Nearest
;