1 /* Copyright (C) 2019 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 "IGUIObject.h"
23 #include "gui/CGUISetting.h"
24 #include "ps/CLogger.h"
25 #include "ps/GameSetup/Config.h"
26 #include "ps/Profile.h"
27 #include "scriptinterface/ScriptInterface.h"
28 #include "soundmanager/ISoundManager.h"
30 IGUIObject::IGUIObject(CGUI
& pGUI
)
47 RegisterSetting("enabled", m_Enabled
);
48 RegisterSetting("hidden", m_Hidden
);
49 RegisterSetting("size", m_Size
);
50 RegisterSetting("style", m_Style
);
51 RegisterSetting("hotkey", m_Hotkey
);
52 RegisterSetting("z", m_Z
);
53 RegisterSetting("absolute", m_Absolute
);
54 RegisterSetting("ghost", m_Ghost
);
55 RegisterSetting("aspectratio", m_AspectRatio
);
56 RegisterSetting("tooltip", m_Tooltip
);
57 RegisterSetting("tooltip_style", m_TooltipStyle
);
59 // Setup important defaults
60 // TODO: Should be in the default style?
61 SetSetting
<bool>("hidden", false, true);
62 SetSetting
<bool>("ghost", false, true);
63 SetSetting
<bool>("enabled", true, true);
64 SetSetting
<bool>("absolute", true, true);
67 IGUIObject::~IGUIObject()
69 for (const std::pair
<CStr
, IGUISetting
*>& p
: m_Settings
)
72 if (!m_ScriptHandlers
.empty())
73 JS_RemoveExtraGCRootsTracer(m_pGUI
.GetScriptInterface()->GetJSRuntime(), Trace
, this);
75 // m_Children is deleted along all other GUI Objects in the CGUI destructor
78 void IGUIObject::AddChild(IGUIObject
& pChild
)
80 pChild
.SetParent(this);
81 m_Children
.push_back(&pChild
);
85 void IGUIObject::RegisterSetting(const CStr
& Name
, T
& Value
)
87 if (SettingExists(Name
))
88 LOGERROR("The setting '%s' already exists on the object '%s'!", Name
.c_str(), GetPresentableName().c_str());
90 m_Settings
.emplace(Name
, new CGUISetting
<T
>(*this, Name
, Value
));
93 bool IGUIObject::SettingExists(const CStr
& Setting
) const
95 return m_Settings
.find(Setting
) != m_Settings
.end();
99 T
& IGUIObject::GetSetting(const CStr
& Setting
)
101 return static_cast<CGUISetting
<T
>* >(m_Settings
.at(Setting
))->m_pSetting
;
104 template <typename T
>
105 const T
& IGUIObject::GetSetting(const CStr
& Setting
) const
107 return static_cast<CGUISetting
<T
>* >(m_Settings
.at(Setting
))->m_pSetting
;
110 bool IGUIObject::SetSettingFromString(const CStr
& Setting
, const CStrW
& Value
, const bool SendMessage
)
112 const std::map
<CStr
, IGUISetting
*>::iterator it
= m_Settings
.find(Setting
);
113 if (it
== m_Settings
.end())
115 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());
118 return it
->second
->FromString(Value
, SendMessage
);
121 template <typename T
>
122 void IGUIObject::SetSetting(const CStr
& Setting
, T
& Value
, const bool SendMessage
)
124 PreSettingChange(Setting
);
125 static_cast<CGUISetting
<T
>* >(m_Settings
.at(Setting
))->m_pSetting
= std::move(Value
);
126 SettingChanged(Setting
, SendMessage
);
129 template <typename T
>
130 void IGUIObject::SetSetting(const CStr
& Setting
, const T
& Value
, const bool SendMessage
)
132 PreSettingChange(Setting
);
133 static_cast<CGUISetting
<T
>* >(m_Settings
.at(Setting
))->m_pSetting
= Value
;
134 SettingChanged(Setting
, SendMessage
);
137 void IGUIObject::PreSettingChange(const CStr
& Setting
)
139 if (Setting
== "hotkey")
140 m_pGUI
.UnsetObjectHotkey(this, GetSetting
<CStr
>(Setting
));
143 void IGUIObject::SettingChanged(const CStr
& Setting
, const bool SendMessage
)
145 if (Setting
== "size")
147 // If setting was "size", we need to re-cache itself and all children
148 RecurseObject(nullptr, &IGUIObject::UpdateCachedSize
);
150 else if (Setting
== "hidden")
152 // Hiding an object requires us to reset it and all children
154 RecurseObject(nullptr, &IGUIObject::ResetStates
);
156 else if (Setting
== "hotkey")
157 m_pGUI
.SetObjectHotkey(this, GetSetting
<CStr
>(Setting
));
161 SGUIMessage
msg(GUIM_SETTINGS_UPDATED
, Setting
);
166 bool IGUIObject::IsMouseOver() const
168 return m_CachedActualSize
.PointInside(m_pGUI
.GetMousePos());
171 bool IGUIObject::MouseOverIcon()
176 void IGUIObject::UpdateMouseOver(IGUIObject
* const& pMouseOver
)
178 if (pMouseOver
== this)
180 if (!m_MouseHovering
)
181 SendEvent(GUIM_MOUSE_ENTER
, "mouseenter");
183 m_MouseHovering
= true;
185 SendEvent(GUIM_MOUSE_OVER
, "mousemove");
191 m_MouseHovering
= false;
192 SendEvent(GUIM_MOUSE_LEAVE
, "mouseleave");
197 void IGUIObject::ChooseMouseOverAndClosest(IGUIObject
*& pObject
)
202 // Check if we've got competition at all
203 if (pObject
== nullptr)
210 if (GetBufferedZ() >= pObject
->GetBufferedZ())
217 IGUIObject
* IGUIObject::GetParent() const
219 // Important, we're not using GetParent() for these
220 // checks, that could screw it up
221 if (m_pParent
&& m_pParent
->m_pParent
== nullptr)
227 void IGUIObject::ResetStates()
229 // Notify the gui that we aren't hovered anymore
230 UpdateMouseOver(nullptr);
233 void IGUIObject::UpdateCachedSize()
235 // If absolute="false" and the object has got a parent,
236 // use its cached size instead of the screen. Notice
237 // it must have just been cached for it to work.
238 if (!m_Absolute
&& m_pParent
&& !IsRootObject())
239 m_CachedActualSize
= m_Size
.GetSize(m_pParent
->m_CachedActualSize
);
241 m_CachedActualSize
= m_Size
.GetSize(CRect(0.f
, 0.f
, g_xres
/ g_GuiScale
, g_yres
/ g_GuiScale
));
243 // In a few cases, GUI objects have to resize to fill the screen
244 // but maintain a constant aspect ratio.
245 // Adjust the size to be the max possible, centered in the original size:
248 if (m_CachedActualSize
.GetWidth() > m_CachedActualSize
.GetHeight() * m_AspectRatio
)
250 float delta
= m_CachedActualSize
.GetWidth() - m_CachedActualSize
.GetHeight() * m_AspectRatio
;
251 m_CachedActualSize
.left
+= delta
/2.f
;
252 m_CachedActualSize
.right
-= delta
/2.f
;
256 float delta
= m_CachedActualSize
.GetHeight() - m_CachedActualSize
.GetWidth() / m_AspectRatio
;
257 m_CachedActualSize
.bottom
-= delta
/2.f
;
258 m_CachedActualSize
.top
+= delta
/2.f
;
263 void IGUIObject::LoadStyle(const CStr
& StyleName
)
265 if (!m_pGUI
.HasStyle(StyleName
))
266 debug_warn(L
"IGUIObject::LoadStyle failed");
268 // The default style may specify settings for any GUI object.
269 // Other styles are reported if they specify a Setting that does not exist,
270 // so that the XML author is informed and can correct the style.
272 for (const std::pair
<CStr
, CStrW
>& p
: m_pGUI
.GetStyle(StyleName
).m_SettingsDefaults
)
274 if (SettingExists(p
.first
))
275 SetSettingFromString(p
.first
, p
.second
, true);
276 else if (StyleName
!= "default")
277 LOGWARNING("GUI object has no setting \"%s\", but the style \"%s\" defines it", p
.first
, StyleName
.c_str());
281 float IGUIObject::GetBufferedZ() const
287 return GetParent()->GetBufferedZ() + m_Z
;
289 // In philosophy, a parentless object shouldn't be able to have a relative sizing,
290 // but we'll accept it so that absolute can be used as default without a complaint.
291 // Also, you could consider those objects children to the screen resolution.
295 void IGUIObject::RegisterScriptHandler(const CStr
& Action
, const CStr
& Code
, CGUI
& pGUI
)
297 JSContext
* cx
= pGUI
.GetScriptInterface()->GetContext();
298 JSAutoRequest
rq(cx
);
299 JS::RootedValue
globalVal(cx
, pGUI
.GetGlobalObject());
300 JS::RootedObject
globalObj(cx
, &globalVal
.toObject());
302 const int paramCount
= 1;
303 const char* paramNames
[paramCount
] = { "mouse" };
305 // Location to report errors from
306 CStr CodeName
= GetName()+" "+Action
;
308 // Generate a unique name
311 sprintf_s(buf
, ARRAY_SIZE(buf
), "__eventhandler%d (%s)", x
++, Action
.c_str());
313 JS::CompileOptions
options(cx
);
314 options
.setFileAndLine(CodeName
.c_str(), 0);
315 options
.setIsRunOnce(false);
317 JS::RootedFunction
func(cx
);
318 JS::AutoObjectVector
emptyScopeChain(cx
);
319 if (!JS::CompileFunction(cx
, emptyScopeChain
, options
, buf
, paramCount
, paramNames
, Code
.c_str(), Code
.length(), &func
))
321 LOGERROR("RegisterScriptHandler: Failed to compile the script for %s", Action
.c_str());
325 JS::RootedObject
funcObj(cx
, JS_GetFunctionObject(func
));
326 SetScriptHandler(Action
, funcObj
);
329 void IGUIObject::SetScriptHandler(const CStr
& Action
, JS::HandleObject Function
)
331 if (m_ScriptHandlers
.empty())
332 JS_AddExtraGCRootsTracer(m_pGUI
.GetScriptInterface()->GetJSRuntime(), Trace
, this);
334 m_ScriptHandlers
[Action
] = JS::Heap
<JSObject
*>(Function
);
337 void IGUIObject::UnsetScriptHandler(const CStr
& Action
)
339 std::map
<CStr
, JS::Heap
<JSObject
*> >::iterator it
= m_ScriptHandlers
.find(Action
);
341 if (it
== m_ScriptHandlers
.end())
344 m_ScriptHandlers
.erase(it
);
346 if (m_ScriptHandlers
.empty())
347 JS_RemoveExtraGCRootsTracer(m_pGUI
.GetScriptInterface()->GetJSRuntime(), Trace
, this);
350 InReaction
IGUIObject::SendEvent(EGUIMessageType type
, const CStr
& EventName
)
352 PROFILE2_EVENT("gui event");
353 PROFILE2_ATTR("type: %s", EventName
.c_str());
354 PROFILE2_ATTR("object: %s", m_Name
.c_str());
356 SGUIMessage
msg(type
);
359 ScriptEvent(EventName
);
361 return (msg
.skipped
? IN_PASS
: IN_HANDLED
);
364 void IGUIObject::ScriptEvent(const CStr
& Action
)
366 std::map
<CStr
, JS::Heap
<JSObject
*> >::iterator it
= m_ScriptHandlers
.find(Action
);
367 if (it
== m_ScriptHandlers
.end())
370 JSContext
* cx
= m_pGUI
.GetScriptInterface()->GetContext();
371 JSAutoRequest
rq(cx
);
373 // Set up the 'mouse' parameter
374 JS::RootedValue
mouse(cx
);
376 const CPos
& mousePos
= m_pGUI
.GetMousePos();
378 ScriptInterface::CreateObject(
383 "buttons", m_pGUI
.GetMouseButtons());
385 JS::AutoValueVector
paramData(cx
);
386 paramData
.append(mouse
);
387 JS::RootedObject
obj(cx
, GetJSObject());
388 JS::RootedValue
handlerVal(cx
, JS::ObjectValue(*it
->second
));
389 JS::RootedValue
result(cx
);
390 bool ok
= JS_CallFunctionValue(cx
, obj
, handlerVal
, paramData
, &result
);
393 // We have no way to propagate the script exception, so just ignore it
394 // and hope the caller checks JS_IsExceptionPending
398 void IGUIObject::ScriptEvent(const CStr
& Action
, const JS::HandleValueArray
& paramData
)
400 std::map
<CStr
, JS::Heap
<JSObject
*> >::iterator it
= m_ScriptHandlers
.find(Action
);
401 if (it
== m_ScriptHandlers
.end())
404 JSContext
* cx
= m_pGUI
.GetScriptInterface()->GetContext();
405 JSAutoRequest
rq(cx
);
406 JS::RootedObject
obj(cx
, GetJSObject());
407 JS::RootedValue
handlerVal(cx
, JS::ObjectValue(*it
->second
));
408 JS::RootedValue
result(cx
);
410 if (!JS_CallFunctionValue(cx
, obj
, handlerVal
, paramData
, &result
))
411 JS_ReportError(cx
, "Errors executing script action \"%s\"", Action
.c_str());
414 void IGUIObject::CreateJSObject()
416 JSContext
* cx
= m_pGUI
.GetScriptInterface()->GetContext();
417 JSAutoRequest
rq(cx
);
419 m_JSObject
.init(cx
, m_pGUI
.GetScriptInterface()->CreateCustomObject("GUIObject"));
420 JS_SetPrivate(m_JSObject
.get(), this);
422 RegisterScriptFunctions();
425 JSObject
* IGUIObject::GetJSObject()
427 // Cache the object when somebody first asks for it, because otherwise
428 // we end up doing far too much object allocation.
429 if (!m_JSObject
.initialized())
432 return m_JSObject
.get();
435 bool IGUIObject::IsEnabled() const
440 bool IGUIObject::IsHidden() const
445 bool IGUIObject::IsHiddenOrGhost() const
447 return m_Hidden
|| m_Ghost
;
450 void IGUIObject::PlaySound(const CStrW
& soundPath
) const
452 if (g_SoundManager
&& !soundPath
.empty())
453 g_SoundManager
->PlayAsUI(soundPath
.c_str(), false);
456 CStr
IGUIObject::GetPresentableName() const
458 // __internal(), must be at least 13 letters to be able to be
460 if (m_Name
.length() <= 12)
463 if (m_Name
.substr(0, 10) == "__internal")
464 return CStr("[unnamed object]");
469 void IGUIObject::SetFocus()
471 m_pGUI
.SetFocusedObject(this);
474 bool IGUIObject::IsFocused() const
476 return m_pGUI
.GetFocusedObject() == this;
479 bool IGUIObject::IsBaseObject() const
481 return this == &m_pGUI
.GetBaseObject();
484 bool IGUIObject::IsRootObject() const
486 return m_pParent
== &m_pGUI
.GetBaseObject();
489 void IGUIObject::TraceMember(JSTracer
* trc
)
491 // Please ensure to adapt the Tracer enabling and disabling in accordance with the GC things traced!
493 for (std::pair
<const CStr
, JS::Heap
<JSObject
*>>& handler
: m_ScriptHandlers
)
494 JS_CallObjectTracer(trc
, &handler
.second
, "IGUIObject::m_ScriptHandlers");
497 // Instantiate templated functions:
498 // These functions avoid copies by working with a reference and move semantics.
500 template void IGUIObject::RegisterSetting<T>(const CStr& Name, T& Value); \
501 template T& IGUIObject::GetSetting<T>(const CStr& Setting); \
502 template const T& IGUIObject::GetSetting<T>(const CStr& Setting) const; \
503 template void IGUIObject::SetSetting<T>(const CStr& Setting, T& Value, const bool SendMessage); \
505 #include "gui/GUISettingTypes.h"
508 // Copying functions - discouraged except for primitives.
510 template void IGUIObject::SetSetting<T>(const CStr& Setting, const T& Value, const bool SendMessage); \
512 #define GUITYPE_IGNORE_NONCOPYABLE
513 #include "gui/GUISettingTypes.h"
514 #undef GUITYPE_IGNORE_NONCOPYABLE