LCOV - code coverage report
Current view: top level - source/gui - GUITooltip.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 25 121 20.7 %
Date: 2023-01-19 00:18:29 Functions: 3 6 50.0 %

          Line data    Source code
       1             : /* Copyright (C) 2021 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       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.
       8             :  *
       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.
      13             :  *
      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/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "GUITooltip.h"
      21             : 
      22             : #include "gui/CGUI.h"
      23             : #include "gui/ObjectTypes/CTooltip.h"
      24             : #include "lib/timer.h"
      25             : #include "ps/CLogger.h"
      26             : 
      27             : /*
      28             :     Tooltips:
      29             :     When holding the mouse stationary over an object for some amount of time,
      30             :     the tooltip is displayed. If the mouse moves off that object, the tooltip
      31             :     disappears. If the mouse re-enters an object within a short time, the new
      32             :     tooltip is displayed immediately. (This lets you run the mouse across a
      33             :     series of buttons, without waiting ages for the text to pop up every time.)
      34             : 
      35             :     See Visual Studio's toolbar buttons for an example.
      36             : 
      37             : 
      38             :     Implemented as a state machine:
      39             : 
      40             :     (where "*" lines are checked constantly, and "<" lines are handled
      41             :     on entry to that state)
      42             : 
      43             :     IN MOTION
      44             :     * If the mouse stops, check whether it should have a tooltip and move to
      45             :       'STATIONARY, NO TOOLTIP' or 'STATIONARY, TOOLIP'
      46             :     * If the mouse enters an object with a tooltip delay of 0, switch to 'SHOWING'
      47             : 
      48             :     STATIONARY, NO TOOLTIP
      49             :     * If the mouse moves, switch to 'IN MOTION'
      50             : 
      51             :     STATIONARY, TOOLTIP
      52             :     < Set target time = now + tooltip time
      53             :     * If the mouse moves, switch to 'IN MOTION'
      54             :     * If now > target time, switch to 'SHOWING'
      55             : 
      56             :     SHOWING
      57             :     < Start displaying the tooltip
      58             :     * If the mouse leaves the object, check whether the new object has a tooltip
      59             :       and switch to 'SHOWING' or 'COOLING'
      60             : 
      61             :     COOLING  (since I can't think of a better name)
      62             :     < Stop displaying the tooltip
      63             :     < Set target time = now + cooldown time
      64             :     * If the mouse has moved and is over a tooltipped object, switch to 'SHOWING'
      65             :     * If now > target time, switch to 'STATIONARY, NO TOOLTIP'
      66             : */
      67             : 
      68             : enum
      69             : {
      70             :     ST_IN_MOTION,
      71             :     ST_STATIONARY_NO_TOOLTIP,
      72             :     ST_STATIONARY_TOOLTIP,
      73             :     ST_SHOWING,
      74             :     ST_COOLING
      75             : };
      76             : 
      77          12 : GUITooltip::GUITooltip()
      78          12 : : m_State(ST_IN_MOTION), m_PreviousObject(nullptr), m_PreviousTooltipName()
      79             : {
      80          12 : }
      81             : 
      82             : const double CooldownTime = 0.25; // TODO: Don't hard-code this value
      83             : 
      84           1 : bool GUITooltip::GetTooltip(IGUIObject* obj, CStr& style)
      85             : {
      86           1 :     if (!obj)
      87           1 :         return false;
      88             : 
      89           0 :     if (obj->GetTooltipText().empty())
      90           0 :         return false;
      91             : 
      92           0 :     style = obj->GetTooltipStyle();
      93           0 :     if (style.empty())
      94           0 :         style = "default";
      95           0 :     return true;
      96             : }
      97             : 
      98           0 : void GUITooltip::ShowTooltip(IGUIObject* obj, const CVector2D& pos, const CStr& style, CGUI& pGUI)
      99             : {
     100           0 :     ENSURE(obj);
     101             : 
     102           0 :     if (style.empty())
     103           0 :         return;
     104             : 
     105             :     // Objects in __tooltip_ are guaranteed to be CTooltip* by the engine.
     106           0 :     CTooltip* tooltipobj = static_cast<CTooltip*>(pGUI.FindObjectByName("__tooltip_" + style));
     107           0 :     if (!tooltipobj || !tooltipobj->SettingExists("use_object"))
     108             :     {
     109           0 :         LOGERROR("Cannot find tooltip named '%s'", style.c_str());
     110           0 :         return;
     111             :     }
     112             : 
     113             :     IGUIObject* usedobj; // object actually used to display the tooltip in.
     114             : 
     115           0 :     const CStr& usedObjectName = tooltipobj->GetUsedObject();
     116           0 :     if (usedObjectName.empty())
     117             :     {
     118           0 :         usedobj = tooltipobj;
     119           0 :         tooltipobj->SetMousePos(pos);
     120             :     }
     121             :     else
     122             :     {
     123           0 :         usedobj = pGUI.FindObjectByName(usedObjectName);
     124           0 :         if (!usedobj)
     125             :         {
     126           0 :             LOGERROR("Cannot find object named '%s' used by tooltip '%s'", usedObjectName.c_str(), style.c_str());
     127           0 :             return;
     128             :         }
     129             :     }
     130             : 
     131           0 :     if (usedobj->SettingExists("caption"))
     132             :     {
     133           0 :         const CStrW& text = obj->GetTooltipText();
     134           0 :         usedobj->SetSettingFromString("caption", text, true);
     135             :     }
     136             :     else
     137             :     {
     138           0 :         LOGERROR("Object '%s' used by tooltip '%s' must have a caption setting!", usedobj->GetPresentableName().c_str(), style.c_str());
     139           0 :         return;
     140             :     }
     141             : 
     142           0 :     usedobj->SetHidden(false);
     143             : }
     144             : 
     145           0 : void GUITooltip::HideTooltip(const CStr& style, CGUI& pGUI)
     146             : {
     147           0 :     if (style.empty())
     148           0 :         return;
     149             : 
     150             :     // Objects in __tooltip_ are guaranteed to be CTooltip* by the engine.
     151           0 :     CTooltip* tooltipobj = static_cast<CTooltip*>(pGUI.FindObjectByName("__tooltip_" + style));
     152           0 :     if (!tooltipobj || !tooltipobj->SettingExists("use_object") || !tooltipobj->SettingExists("hide_object"))
     153             :     {
     154           0 :         LOGERROR("Cannot find tooltip named '%s' or it is not a tooltip", style.c_str());
     155           0 :         return;
     156             :     }
     157           0 :     const CStr& usedObjectName = tooltipobj->GetUsedObject();
     158           0 :     if (!usedObjectName.empty())
     159             :     {
     160           0 :         IGUIObject* usedobj = pGUI.FindObjectByName(usedObjectName);
     161           0 :         if (usedobj && usedobj->SettingExists("caption"))
     162             :         {
     163           0 :             usedobj->SetSettingFromString("caption", L"", true);
     164             :         }
     165             :         else
     166             :         {
     167           0 :             LOGERROR("Object named '%s' used by tooltip '%s' does not exist or does not have a caption setting!", usedObjectName.c_str(), style.c_str());
     168           0 :             return;
     169             :         }
     170             : 
     171           0 :         if (tooltipobj->ShouldHideObject())
     172           0 :             usedobj->SetHidden(true);
     173             :     }
     174             :     else
     175           0 :         tooltipobj->SetHidden(true);
     176             : }
     177             : 
     178           0 : static i32 GetTooltipDelay(const CStr& style, CGUI& pGUI)
     179             : {
     180             :     // Objects in __tooltip_ are guaranteed to be CTooltip* by the engine.
     181           0 :     CTooltip* tooltipobj = static_cast<CTooltip*>(pGUI.FindObjectByName("__tooltip_" + style));
     182             : 
     183           0 :     if (!tooltipobj)
     184             :     {
     185           0 :         LOGERROR("Cannot find tooltip object named '%s'", style.c_str());
     186           0 :         return 500;
     187             :     }
     188             : 
     189           0 :     return tooltipobj->GetTooltipDelay();
     190             : }
     191             : 
     192           2 : void GUITooltip::Update(IGUIObject* Nearest, const CVector2D& MousePos, CGUI& GUI)
     193             : {
     194             :     // Called once per frame, so efficiency isn't vital
     195           2 :     double now = timer_Time();
     196             : 
     197           4 :     CStr style;
     198           2 :     int nextstate = -1;
     199             : 
     200           2 :     switch (m_State)
     201             :     {
     202           1 :     case ST_IN_MOTION:
     203           1 :         if (MousePos == m_PreviousMousePos)
     204             :         {
     205           1 :             if (GetTooltip(Nearest, style))
     206           0 :                 nextstate = ST_STATIONARY_TOOLTIP;
     207             :             else
     208           1 :                 nextstate = ST_STATIONARY_NO_TOOLTIP;
     209             :         }
     210             :         else
     211             :         {
     212             :             // Check for movement onto a zero-delayed tooltip
     213           0 :             if (GetTooltip(Nearest, style) && GetTooltipDelay(style, GUI)==0)
     214             :             {
     215             :                 // Reset any previous tooltips completely
     216             :                 //m_Time = now + (double)GetTooltipDelay(style, GUI) / 1000.;
     217           0 :                 HideTooltip(m_PreviousTooltipName, GUI);
     218             : 
     219           0 :                 nextstate = ST_SHOWING;
     220             :             }
     221             :         }
     222           1 :         break;
     223             : 
     224           1 :     case ST_STATIONARY_NO_TOOLTIP:
     225           1 :         if (MousePos != m_PreviousMousePos)
     226           0 :             nextstate = ST_IN_MOTION;
     227           1 :         break;
     228             : 
     229           0 :     case ST_STATIONARY_TOOLTIP:
     230           0 :         if (MousePos != m_PreviousMousePos)
     231           0 :             nextstate = ST_IN_MOTION;
     232           0 :         else if (now >= m_Time)
     233             :         {
     234             :             // Make sure the tooltip still exists
     235           0 :             if (GetTooltip(Nearest, style))
     236           0 :                 nextstate = ST_SHOWING;
     237             :             else
     238             :             {
     239             :                 // Failed to retrieve style - the object has probably been
     240             :                 // altered, so just restart the process
     241           0 :                 nextstate = ST_IN_MOTION;
     242             :             }
     243             :         }
     244           0 :         break;
     245             : 
     246           0 :     case ST_SHOWING:
     247             :         // Handle sub-object tooltips.
     248           0 :         if (Nearest == m_PreviousObject)
     249             :         {
     250             :             // Still showing the same object's tooltip, but the text might have changed
     251           0 :             if (GetTooltip(Nearest, style))
     252           0 :                 ShowTooltip(Nearest, MousePos, style, GUI);
     253             :             else
     254           0 :                 nextstate = ST_COOLING;
     255             :         }
     256             :         else
     257             :         {
     258             :             // Mouse moved onto a new object
     259           0 :             if (GetTooltip(Nearest, style))
     260             :             {
     261           0 :                 CStr style_old;
     262             : 
     263             :                 // If we're displaying a tooltip with no delay, then we want to
     264             :                 //  reset so that other object that should have delay can't
     265             :                 //  "ride this tail", it have to wait.
     266             :                 // Notice that this doesn't apply to when you go from one delay=0
     267             :                 //  to another delay=0
     268           0 :                 if (GetTooltip(m_PreviousObject, style_old) && GetTooltipDelay(style_old, GUI) == 0 &&
     269           0 :                     GetTooltipDelay(style, GUI) != 0)
     270             :                 {
     271           0 :                     HideTooltip(m_PreviousTooltipName, GUI);
     272           0 :                     nextstate = ST_IN_MOTION;
     273             :                 }
     274             :                 else
     275             :                 {
     276             :                     // Hide old scrollbar
     277           0 :                     HideTooltip(m_PreviousTooltipName, GUI);
     278           0 :                     nextstate = ST_SHOWING;
     279             :                 }
     280             :             }
     281             :             else
     282           0 :                 nextstate = ST_COOLING;
     283             :         }
     284           0 :         break;
     285             : 
     286           0 :     case ST_COOLING:
     287           0 :         if (GetTooltip(Nearest, style))
     288           0 :             nextstate = ST_SHOWING;
     289           0 :         else if (now >= m_Time)
     290           0 :             nextstate = ST_IN_MOTION;
     291           0 :         break;
     292             :     }
     293             : 
     294             :     // Handle state-entry code:
     295           2 :     if (nextstate != -1)
     296             :     {
     297           1 :         switch (nextstate)
     298             :         {
     299           0 :         case ST_STATIONARY_TOOLTIP:
     300           0 :             m_Time = now + (double)GetTooltipDelay(style, GUI) / 1000.;
     301           0 :             break;
     302             : 
     303           0 :         case ST_SHOWING:
     304           0 :             ShowTooltip(Nearest, MousePos, style, GUI);
     305           0 :             m_PreviousTooltipName = style;
     306           0 :             break;
     307             : 
     308           0 :         case ST_COOLING:
     309           0 :             HideTooltip(m_PreviousTooltipName, GUI);
     310           0 :             m_Time = now + CooldownTime;
     311           0 :             break;
     312             :         }
     313             : 
     314           1 :         m_State = nextstate;
     315             :     }
     316             : 
     317           2 :     m_PreviousMousePos = MousePos;
     318           2 :     m_PreviousObject = Nearest;
     319           2 : }

Generated by: LCOV version 1.13