LCOV - code coverage report
Current view: top level - source/gui/ObjectBases - IGUIObject.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 87 233 37.3 %
Date: 2023-01-19 00:18:29 Functions: 20 42 47.6 %

          Line data    Source code
       1             : /* Copyright (C) 2022 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 "IGUIObject.h"
      21             : 
      22             : #include "gui/CGUI.h"
      23             : #include "gui/CGUISetting.h"
      24             : #include "gui/ObjectBases/IGUIObject.h"
      25             : #include "gui/Scripting/JSInterface_GUIProxy.h"
      26             : #include "js/Conversions.h"
      27             : #include "ps/CLogger.h"
      28             : #include "ps/Profile.h"
      29             : #include "scriptinterface/Object.h"
      30             : #include "scriptinterface/ScriptContext.h"
      31             : #include "scriptinterface/ScriptExtraHeaders.h"
      32             : #include "scriptinterface/ScriptConversions.h"
      33             : #include "soundmanager/ISoundManager.h"
      34             : 
      35             : #include <algorithm>
      36             : #include <string_view>
      37             : #include <unordered_map>
      38             : 
      39           1 : const CStr IGUIObject::EventNameMouseEnter = "MouseEnter";
      40           1 : const CStr IGUIObject::EventNameMouseMove = "MouseMove";
      41           1 : const CStr IGUIObject::EventNameMouseLeave = "MouseLeave";
      42             : 
      43          16 : IGUIObject::IGUIObject(CGUI& pGUI)
      44             :     : m_pGUI(pGUI),
      45             :       m_pParent(),
      46             :       m_MouseHovering(),
      47             :       m_LastClickTime(),
      48             :       m_Enabled(this, "enabled", true),
      49             :       m_Hidden(this, "hidden", false),
      50             :       m_Size(this, "size"),
      51             :       m_Style(this, "style"),
      52             :       m_Hotkey(this, "hotkey"),
      53             :       m_Z(this, "z"),
      54             :       m_Absolute(this, "absolute", true),
      55             :       m_Ghost(this, "ghost", false),
      56             :       m_AspectRatio(this, "aspectratio"),
      57             :       m_Tooltip(this, "tooltip"),
      58          16 :       m_TooltipStyle(this, "tooltip_style")
      59             : {
      60          16 : }
      61             : 
      62          32 : IGUIObject::~IGUIObject()
      63             : {
      64          16 :     if (!m_ScriptHandlers.empty())
      65           2 :         JS_RemoveExtraGCRootsTracer(ScriptRequest(m_pGUI.GetScriptInterface()).cx, Trace, this);
      66             : 
      67             :     // m_Children is deleted along all other GUI Objects in the CGUI destructor
      68          16 : }
      69             : 
      70           4 : void IGUIObject::RegisterChild(IGUIObject* child)
      71             : {
      72           4 :     child->SetParent(this);
      73           4 :     m_Children.push_back(child);
      74           4 : }
      75             : 
      76           0 : void IGUIObject::UnregisterChild(IGUIObject* child)
      77             : {
      78           0 :     std::vector<IGUIObject*>::iterator it = std::find(m_Children.begin(), m_Children.end(), child);
      79           0 :     if (it != m_Children.end())
      80             :     {
      81           0 :         (*it)->m_pParent = nullptr;
      82           0 :         m_Children.erase(it);
      83             :     }
      84           0 : }
      85             : 
      86         176 : void IGUIObject::RegisterSetting(const CStr& Name, IGUISetting* setting)
      87             : {
      88         176 :     if (SettingExists(Name))
      89           0 :         LOGERROR("The setting '%s' already exists on the object '%s'!", Name.c_str(), GetPresentableName().c_str());
      90             :     else
      91         176 :         m_Settings.emplace(Name, setting);
      92         176 : }
      93             : 
      94           0 : void IGUIObject::ReregisterSetting(const CStr& Name, IGUISetting* setting)
      95             : {
      96           0 :     if (!SettingExists(Name))
      97           0 :         LOGERROR("The setting '%s' must already exist on the object '%s'!", Name.c_str(), GetPresentableName().c_str());
      98             :     else
      99           0 :         m_Settings.at(Name) = setting;
     100           0 : }
     101             : 
     102         176 : bool IGUIObject::SettingExists(const CStr& Setting) const
     103             : {
     104         176 :     return m_Settings.find(Setting) != m_Settings.end();
     105             : }
     106             : 
     107           0 : bool IGUIObject::SetSettingFromString(const CStr& Setting, const CStrW& Value, const bool SendMessage)
     108             : {
     109           0 :     const std::map<CStr, IGUISetting*>::iterator it = m_Settings.find(Setting);
     110           0 :     if (it == m_Settings.end())
     111             :     {
     112           0 :         LOGERROR("GUI object '%s' has no property called '%s', can't set parse and set value '%s'", GetPresentableName().c_str(), Setting.c_str(), Value.ToUTF8().c_str());
     113           0 :         return false;
     114             :     }
     115           0 :     return it->second->FromString(Value, SendMessage);
     116             : }
     117             : 
     118           4 : void IGUIObject::SettingChanged(const CStr& Setting, const bool SendMessage)
     119             : {
     120           4 :     if (Setting == "size")
     121             :     {
     122             :         // If setting was "size", we need to re-cache itself and all children
     123           0 :         RecurseObject(nullptr, &IGUIObject::UpdateCachedSize);
     124             :     }
     125           4 :     else if (Setting == "hidden")
     126             :     {
     127             :         // Hiding an object requires us to reset it and all children
     128           0 :         if (m_Hidden)
     129           0 :             RecurseObject(nullptr, &IGUIObject::ResetStates);
     130             :     }
     131           4 :     else if (Setting == "style")
     132           0 :         m_pGUI.SetObjectStyle(this, m_Style);
     133             : 
     134           4 :     if (SendMessage)
     135             :     {
     136           0 :         SGUIMessage msg(GUIM_SETTINGS_UPDATED, Setting);
     137           0 :         HandleMessage(msg);
     138             :     }
     139           4 : }
     140             : 
     141           0 : bool IGUIObject::IsMouseOver() const
     142             : {
     143           0 :     return m_CachedActualSize.PointInside(m_pGUI.GetMousePos());
     144             : }
     145             : 
     146           0 : void IGUIObject::UpdateMouseOver(IGUIObject* const& pMouseOver)
     147             : {
     148           0 :     if (pMouseOver == this)
     149             :     {
     150           0 :         if (!m_MouseHovering)
     151           0 :             SendMouseEvent(GUIM_MOUSE_ENTER,EventNameMouseEnter);
     152             : 
     153           0 :         m_MouseHovering = true;
     154             : 
     155           0 :         SendMouseEvent(GUIM_MOUSE_OVER, EventNameMouseMove);
     156             :     }
     157             :     else
     158             :     {
     159           0 :         if (m_MouseHovering)
     160             :         {
     161           0 :             m_MouseHovering = false;
     162           0 :             SendMouseEvent(GUIM_MOUSE_LEAVE, EventNameMouseLeave);
     163             :         }
     164             :     }
     165           0 : }
     166             : 
     167           8 : void IGUIObject::ChooseMouseOverAndClosest(IGUIObject*& pObject)
     168             : {
     169           8 :     if (!IsMouseOver())
     170           8 :         return;
     171             : 
     172             :     // Check if we've got competition at all
     173           0 :     if (pObject == nullptr)
     174             :     {
     175           0 :         pObject = this;
     176           0 :         return;
     177             :     }
     178             : 
     179             :     // Or if it's closer
     180           0 :     if (GetBufferedZ() >= pObject->GetBufferedZ())
     181             :     {
     182           0 :         pObject = this;
     183           0 :         return;
     184             :     }
     185             : }
     186             : 
     187           0 : IGUIObject* IGUIObject::GetParent() const
     188             : {
     189             :     // Important, we're not using GetParent() for these
     190             :     //  checks, that could screw it up
     191           0 :     if (m_pParent && m_pParent->m_pParent == nullptr)
     192           0 :         return nullptr;
     193             : 
     194           0 :     return m_pParent;
     195             : }
     196             : 
     197           0 : void IGUIObject::ResetStates()
     198             : {
     199             :     // Notify the gui that we aren't hovered anymore
     200           0 :     UpdateMouseOver(nullptr);
     201           0 : }
     202             : 
     203           4 : void IGUIObject::UpdateCachedSize()
     204             : {
     205             :     // If absolute="false" and the object has got a parent,
     206             :     //  use its cached size instead of the screen. Notice
     207             :     //  it must have just been cached for it to work.
     208           4 :     if (!m_Absolute && m_pParent && !IsRootObject())
     209           0 :         m_CachedActualSize = m_Size->GetSize(m_pParent->m_CachedActualSize);
     210             :     else
     211           4 :         m_CachedActualSize = m_Size->GetSize(CRect(m_pGUI.GetWindowSize()));
     212             : 
     213             :     // In a few cases, GUI objects have to resize to fill the screen
     214             :     // but maintain a constant aspect ratio.
     215             :     // Adjust the size to be the max possible, centered in the original size:
     216           4 :     if (m_AspectRatio)
     217             :     {
     218           0 :         if (m_CachedActualSize.GetWidth() > m_CachedActualSize.GetHeight() * m_AspectRatio)
     219             :         {
     220           0 :             float delta = m_CachedActualSize.GetWidth() - m_CachedActualSize.GetHeight() * m_AspectRatio;
     221           0 :             m_CachedActualSize.left += delta/2.f;
     222           0 :             m_CachedActualSize.right -= delta/2.f;
     223             :         }
     224             :         else
     225             :         {
     226           0 :             float delta = m_CachedActualSize.GetHeight() - m_CachedActualSize.GetWidth() / m_AspectRatio;
     227           0 :             m_CachedActualSize.bottom -= delta/2.f;
     228           0 :             m_CachedActualSize.top += delta/2.f;
     229             :         }
     230             :     }
     231           4 : }
     232             : 
     233           0 : CRect IGUIObject::GetComputedSize()
     234             : {
     235           0 :     UpdateCachedSize();
     236           0 :     return m_CachedActualSize;
     237             : }
     238             : 
     239             : 
     240           4 : bool IGUIObject::ApplyStyle(const CStr& StyleName)
     241             : {
     242           4 :     if (!m_pGUI.HasStyle(StyleName))
     243             :     {
     244           0 :         LOGERROR("IGUIObject: Trying to use style '%s' that doesn't exist.", StyleName.c_str());
     245           0 :         return false;
     246             :     }
     247             : 
     248             :     // The default style may specify settings for any GUI object.
     249             :     // Other styles are reported if they specify a Setting that does not exist,
     250             :     // so that the XML author is informed and can correct the style.
     251             : 
     252           4 :     for (const std::pair<const CStr, CStrW>& p : m_pGUI.GetStyle(StyleName).m_SettingsDefaults)
     253             :     {
     254           0 :         if (SettingExists(p.first))
     255           0 :             m_Settings.at(p.first)->FromString(p.second, true);
     256           0 :         else if (StyleName != "default")
     257           0 :             LOGWARNING("GUI object has no setting \"%s\", but the style \"%s\" defines it", p.first, StyleName.c_str());
     258             :     }
     259           4 :     return true;
     260             : }
     261             : 
     262           4 : float IGUIObject::GetBufferedZ() const
     263             : {
     264           4 :     if (m_Absolute)
     265           4 :         return m_Z;
     266             : 
     267           0 :     if (GetParent())
     268           0 :         return GetParent()->GetBufferedZ() + m_Z;
     269             : 
     270             :     // In philosophy, a parentless object shouldn't be able to have a relative sizing,
     271             :     //  but we'll accept it so that absolute can be used as default without a complaint.
     272             :     //  Also, you could consider those objects children to the screen resolution.
     273           0 :     return m_Z;
     274             : }
     275             : 
     276           0 : void IGUIObject::RegisterScriptHandler(const CStr& eventName, const CStr& Code, CGUI& pGUI)
     277             : {
     278           0 :     ScriptRequest rq(pGUI.GetScriptInterface());
     279             : 
     280           0 :     const int paramCount = 1;
     281           0 :     const char* paramNames[paramCount] = { "mouse" };
     282             : 
     283             :     // Location to report errors from
     284           0 :     CStr CodeName = GetName() + " " + eventName;
     285             : 
     286             :     // Generate a unique name
     287             :     static int x = 0;
     288             :     char buf[64];
     289           0 :     sprintf_s(buf, ARRAY_SIZE(buf), "__eventhandler%d (%s)", x++, eventName.c_str());
     290             : 
     291             :     // TODO: this is essentially the same code as ScriptInterface::LoadScript (with a tweak for the argument).
     292           0 :     JS::CompileOptions options(rq.cx);
     293           0 :     options.setFileAndLine(CodeName.c_str(), 0);
     294           0 :     options.setIsRunOnce(false);
     295             : 
     296           0 :     JS::SourceText<mozilla::Utf8Unit> src;
     297           0 :     ENSURE(src.init(rq.cx, Code.c_str(), Code.length(), JS::SourceOwnership::Borrowed));
     298           0 :     JS::RootedObjectVector emptyScopeChain(rq.cx);
     299           0 :     JS::RootedFunction func(rq.cx, JS::CompileFunction(rq.cx, emptyScopeChain, options, buf, paramCount, paramNames, src));
     300           0 :     if (func == nullptr)
     301             :     {
     302           0 :         LOGERROR("RegisterScriptHandler: Failed to compile the script for %s", eventName.c_str());
     303           0 :         return;
     304             :     }
     305             : 
     306           0 :     JS::RootedObject funcObj(rq.cx, JS_GetFunctionObject(func));
     307           0 :     SetScriptHandler(eventName, funcObj);
     308             : }
     309             : 
     310           5 : void IGUIObject::SetScriptHandler(const CStr& eventName, JS::HandleObject Function)
     311             : {
     312           5 :     if (m_ScriptHandlers.empty())
     313           4 :         JS_AddExtraGCRootsTracer(ScriptRequest(m_pGUI.GetScriptInterface()).cx, Trace, this);
     314             : 
     315           5 :     m_ScriptHandlers[eventName] = JS::Heap<JSObject*>(Function);
     316             : 
     317           5 :     if (std::find(m_pGUI.m_EventObjects[eventName].begin(), m_pGUI.m_EventObjects[eventName].end(), this) == m_pGUI.m_EventObjects[eventName].end())
     318           4 :         m_pGUI.m_EventObjects[eventName].emplace_back(this);
     319           5 : }
     320             : 
     321           4 : void IGUIObject::UnsetScriptHandler(const CStr& eventName)
     322             : {
     323           4 :     std::map<CStr, JS::Heap<JSObject*> >::iterator it = m_ScriptHandlers.find(eventName);
     324             : 
     325           4 :     if (it == m_ScriptHandlers.end())
     326           4 :         return;
     327             : 
     328           2 :     m_ScriptHandlers.erase(it);
     329             : 
     330           2 :     if (m_ScriptHandlers.empty())
     331           2 :         JS_RemoveExtraGCRootsTracer(ScriptRequest(m_pGUI.GetScriptInterface()).cx, Trace, this);
     332             : 
     333           2 :     std::unordered_map<CStr, std::vector<IGUIObject*>>::iterator it2 = m_pGUI.m_EventObjects.find(eventName);
     334           2 :     if (it2 == m_pGUI.m_EventObjects.end())
     335           0 :         return;
     336             : 
     337           2 :     std::vector<IGUIObject*>& handlers = it2->second;
     338           2 :     handlers.erase(std::remove(handlers.begin(), handlers.end(), this), handlers.end());
     339             : 
     340           2 :     if (handlers.empty())
     341           0 :         m_pGUI.m_EventObjects.erase(it2);
     342             : }
     343             : 
     344           0 : InReaction IGUIObject::SendEvent(EGUIMessageType type, const CStr& eventName)
     345             : {
     346           0 :     PROFILE2_EVENT("gui event");
     347           0 :     PROFILE2_ATTR("type: %s", eventName.c_str());
     348           0 :     PROFILE2_ATTR("object: %s", m_Name.c_str());
     349             : 
     350           0 :     SGUIMessage msg(type);
     351           0 :     HandleMessage(msg);
     352             : 
     353           0 :     ScriptEvent(eventName);
     354             : 
     355           0 :     return msg.skipped ? IN_PASS : IN_HANDLED;
     356             : }
     357             : 
     358           0 : InReaction IGUIObject::SendMouseEvent(EGUIMessageType type, const CStr& eventName)
     359             : {
     360           0 :     PROFILE2_EVENT("gui mouse event");
     361           0 :     PROFILE2_ATTR("type: %s", eventName.c_str());
     362           0 :     PROFILE2_ATTR("object: %s", m_Name.c_str());
     363             : 
     364           0 :     SGUIMessage msg(type);
     365           0 :     HandleMessage(msg);
     366             : 
     367           0 :     ScriptRequest rq(m_pGUI.GetScriptInterface());
     368             : 
     369             :     // Set up the 'mouse' parameter
     370           0 :     JS::RootedValue mouse(rq.cx);
     371             : 
     372           0 :     const CVector2D& mousePos = m_pGUI.GetMousePos();
     373             : 
     374           0 :     Script::CreateObject(
     375             :         rq,
     376             :         &mouse,
     377             :         "x", mousePos.X,
     378             :         "y", mousePos.Y,
     379           0 :         "buttons", m_pGUI.GetMouseButtons());
     380           0 :     JS::RootedValueVector paramData(rq.cx);
     381           0 :     ignore_result(paramData.append(mouse));
     382           0 :     ScriptEvent(eventName, paramData);
     383             : 
     384           0 :     return msg.skipped ? IN_PASS : IN_HANDLED;
     385             : }
     386             : 
     387           5 : void IGUIObject::ScriptEvent(const CStr& eventName)
     388             : {
     389           5 :     ScriptEventWithReturn(eventName);
     390           5 : }
     391             : 
     392           5 : bool IGUIObject::ScriptEventWithReturn(const CStr& eventName)
     393             : {
     394           5 :     if (m_ScriptHandlers.find(eventName) == m_ScriptHandlers.end())
     395           1 :         return false;
     396             : 
     397           8 :     ScriptRequest rq(m_pGUI.GetScriptInterface());
     398           8 :     JS::RootedValueVector paramData(rq.cx);
     399           4 :     return ScriptEventWithReturn(eventName, paramData);
     400             : }
     401             : 
     402           0 : void IGUIObject::ScriptEvent(const CStr& eventName, const JS::HandleValueArray& paramData)
     403             : {
     404           0 :     ScriptEventWithReturn(eventName, paramData);
     405           0 : }
     406             : 
     407           4 : bool IGUIObject::ScriptEventWithReturn(const CStr& eventName, const JS::HandleValueArray& paramData)
     408             : {
     409           4 :     std::map<CStr, JS::Heap<JSObject*> >::iterator it = m_ScriptHandlers.find(eventName);
     410           4 :     if (it == m_ScriptHandlers.end())
     411           0 :         return false;
     412             : 
     413           8 :     ScriptRequest rq(m_pGUI.GetScriptInterface());
     414           8 :     JS::RootedObject obj(rq.cx, GetJSObject());
     415           8 :     JS::RootedValue handlerVal(rq.cx, JS::ObjectValue(*it->second));
     416           8 :     JS::RootedValue result(rq.cx);
     417             : 
     418           4 :     if (!JS_CallFunctionValue(rq.cx, obj, handlerVal, paramData, &result))
     419             :     {
     420           0 :         LOGERROR("Errors executing script event \"%s\"", eventName.c_str());
     421           0 :         ScriptException::CatchPending(rq);
     422           0 :         return false;
     423             :     }
     424           4 :     return JS::ToBoolean(result);
     425             : }
     426             : 
     427           9 : JSObject* IGUIObject::GetJSObject()
     428             : {
     429             :     // Cache the object when somebody first asks for it, because otherwise
     430             :     // we end up doing far too much object allocation.
     431           9 :     if (!m_JSObject)
     432           4 :         CreateJSObject();
     433             : 
     434           9 :     return m_JSObject->Get();
     435             : }
     436             : 
     437           0 : bool IGUIObject::IsEnabled() const
     438             : {
     439           0 :     return m_Enabled;
     440             : }
     441             : 
     442           0 : bool IGUIObject::IsHidden() const
     443             : {
     444           0 :     return m_Hidden;
     445             : }
     446             : 
     447          16 : bool IGUIObject::IsHiddenOrGhost() const
     448             : {
     449          16 :     return m_Hidden || m_Ghost;
     450             : }
     451             : 
     452           0 : void IGUIObject::PlaySound(const CStrW& soundPath) const
     453             : {
     454           0 :     if (g_SoundManager && !soundPath.empty())
     455           0 :         g_SoundManager->PlayAsUI(soundPath.c_str(), false);
     456           0 : }
     457             : 
     458           0 : CStr IGUIObject::GetPresentableName() const
     459             : {
     460             :     // __internal(), must be at least 13 letters to be able to be
     461             :     //  an internal name
     462           0 :     if (m_Name.length() <= 12)
     463           0 :         return m_Name;
     464             : 
     465           0 :     if (std::string_view{m_Name}.substr(0, 10) == "__internal")
     466           0 :         return CStr("[unnamed object]");
     467             :     else
     468           0 :         return m_Name;
     469             : }
     470             : 
     471           0 : void IGUIObject::SetFocus()
     472             : {
     473           0 :     m_pGUI.SetFocusedObject(this);
     474           0 : }
     475             : 
     476           0 : void IGUIObject::ReleaseFocus()
     477             : {
     478           0 :     m_pGUI.SetFocusedObject(nullptr);
     479           0 : }
     480             : 
     481           0 : bool IGUIObject::IsFocused() const
     482             : {
     483           0 :     return m_pGUI.GetFocusedObject() == this;
     484             : }
     485             : 
     486          61 : bool IGUIObject::IsBaseObject() const
     487             : {
     488          61 :     return this == m_pGUI.GetBaseObject();
     489             : }
     490             : 
     491           0 : bool IGUIObject::IsRootObject() const
     492             : {
     493           0 :     return m_pParent == m_pGUI.GetBaseObject();
     494             : }
     495             : 
     496           0 : void IGUIObject::TraceMember(JSTracer* trc)
     497             : {
     498             :     // Please ensure to adapt the Tracer enabling and disabling in accordance with the GC things traced!
     499             : 
     500           0 :     for (std::pair<const CStr, JS::Heap<JSObject*>>& handler : m_ScriptHandlers)
     501           0 :         JS::TraceEdge(trc, &handler.second, "IGUIObject::m_ScriptHandlers");
     502           3 : }

Generated by: LCOV version 1.13