Don't use the default metal/stone mines of the random biome system in the independent...
[0ad.git] / source / gui / IGUIObject.cpp
blob9113b4ab19455dd4e65a572717cc2af57fce1532
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 "GUI.h"
22 #include "gui/scripting/JSInterface_GUITypes.h"
23 #include "gui/scripting/JSInterface_IGUIObject.h"
25 #include "ps/GameSetup/Config.h"
26 #include "ps/CLogger.h"
27 #include "ps/Profile.h"
28 #include "scriptinterface/ScriptInterface.h"
31 IGUIObject::IGUIObject()
32 : m_pGUI(NULL), m_pParent(NULL), m_MouseHovering(false), m_LastClickTime()
34 AddSetting(GUIST_bool, "enabled");
35 AddSetting(GUIST_bool, "hidden");
36 AddSetting(GUIST_CClientArea, "size");
37 AddSetting(GUIST_CStr, "style");
38 AddSetting(GUIST_CStr, "hotkey");
39 AddSetting(GUIST_float, "z");
40 AddSetting(GUIST_bool, "absolute");
41 AddSetting(GUIST_bool, "ghost");
42 AddSetting(GUIST_float, "aspectratio");
43 AddSetting(GUIST_CStrW, "tooltip");
44 AddSetting(GUIST_CStr, "tooltip_style");
46 // Setup important defaults
47 GUI<bool>::SetSetting(this, "hidden", false);
48 GUI<bool>::SetSetting(this, "ghost", false);
49 GUI<bool>::SetSetting(this, "enabled", true);
50 GUI<bool>::SetSetting(this, "absolute", true);
53 IGUIObject::~IGUIObject()
55 for (const std::pair<CStr, SGUISetting>& p : m_Settings)
56 switch (p.second.m_Type)
58 // delete() needs to know the type of the variable - never delete a void*
59 #define TYPE(t) case GUIST_##t: delete (t*)p.second.m_pSetting; break;
60 #include "GUItypes.h"
61 #undef TYPE
62 default:
63 debug_warn(L"Invalid setting type");
66 if (m_pGUI)
67 JS_RemoveExtraGCRootsTracer(m_pGUI->GetScriptInterface()->GetJSRuntime(), Trace, this);
70 //-------------------------------------------------------------------
71 // Functions
72 //-------------------------------------------------------------------
73 void IGUIObject::SetGUI(CGUI* const& pGUI)
75 if (!m_pGUI)
76 JS_AddExtraGCRootsTracer(pGUI->GetScriptInterface()->GetJSRuntime(), Trace, this);
77 m_pGUI = pGUI;
80 void IGUIObject::AddChild(IGUIObject* pChild)
82 // ENSURE(pChild);
84 pChild->SetParent(this);
86 m_Children.push_back(pChild);
88 // If this (not the child) object is already attached
89 // to a CGUI, it pGUI pointer will be non-null.
90 // This will mean we'll have to check if we're using
91 // names already used.
92 if (pChild->GetGUI())
94 try
96 // Atomic function, if it fails it won't
97 // have changed anything
98 //UpdateObjects();
99 pChild->GetGUI()->UpdateObjects();
101 catch (PSERROR_GUI&)
103 // If anything went wrong, reverse what we did and throw
104 // an exception telling it never added a child
105 m_Children.erase(m_Children.end()-1);
107 throw;
110 // else do nothing
113 void IGUIObject::AddToPointersMap(map_pObjects& ObjectMap)
115 // Just don't do anything about the top node
116 if (m_pParent == NULL)
117 return;
119 // Now actually add this one
120 // notice we won't add it if it's doesn't have any parent
121 // (i.e. being the base object)
122 if (m_Name.empty())
124 throw PSERROR_GUI_ObjectNeedsName();
126 if (ObjectMap.count(m_Name) > 0)
128 throw PSERROR_GUI_NameAmbiguity(m_Name.c_str());
130 else
132 ObjectMap[m_Name] = this;
136 void IGUIObject::Destroy()
138 // Is there anything besides the children to destroy?
141 void IGUIObject::AddSetting(const EGUISettingType& Type, const CStr& Name)
143 // Is name already taken?
144 if (m_Settings.count(Name) >= 1)
145 return;
147 // Construct, and set type
148 m_Settings[Name].m_Type = Type;
150 switch (Type)
152 #define TYPE(type) \
153 case GUIST_##type: \
154 m_Settings[Name].m_pSetting = new type(); \
155 break;
157 // Construct the setting.
158 #include "GUItypes.h"
160 #undef TYPE
162 default:
163 debug_warn(L"IGUIObject::AddSetting failed, type not recognized!");
164 break;
169 bool IGUIObject::MouseOver()
171 if (!GetGUI())
172 throw PSERROR_GUI_OperationNeedsGUIObject();
174 return m_CachedActualSize.PointInside(GetMousePos());
177 bool IGUIObject::MouseOverIcon()
179 return false;
182 CPos IGUIObject::GetMousePos() const
184 if (GetGUI())
185 return GetGUI()->m_MousePos;
187 return CPos();
190 void IGUIObject::UpdateMouseOver(IGUIObject* const& pMouseOver)
192 if (pMouseOver == this)
194 if (!m_MouseHovering)
195 SendEvent(GUIM_MOUSE_ENTER, "mouseenter");
197 m_MouseHovering = true;
199 SendEvent(GUIM_MOUSE_OVER, "mousemove");
201 else
203 if (m_MouseHovering)
205 m_MouseHovering = false;
206 SendEvent(GUIM_MOUSE_LEAVE, "mouseleave");
211 bool IGUIObject::SettingExists(const CStr& Setting) const
213 // Because GetOffsets will direct dynamically defined
214 // classes with polymorphism to respective ms_SettingsInfo
215 // we need to make no further updates on this function
216 // in derived classes.
217 //return (GetSettingsInfo().count(Setting) >= 1);
218 return (m_Settings.count(Setting) >= 1);
221 PSRETURN IGUIObject::SetSetting(const CStr& Setting, const CStrW& Value, const bool& SkipMessage)
223 if (!SettingExists(Setting))
224 return PSRETURN_GUI_InvalidSetting;
226 SGUISetting set = m_Settings[Setting];
228 #define TYPE(type) \
229 else if (set.m_Type == GUIST_##type) \
231 type _Value; \
232 if (!GUI<type>::ParseString(Value, _Value)) \
233 return PSRETURN_GUI_UnableToParse; \
234 GUI<type>::SetSetting(this, Setting, _Value, SkipMessage); \
237 if (0)
239 #include "GUItypes.h"
240 #undef TYPE
241 else
243 // Why does it always fail?
244 //return PS_FAIL;
245 return LogInvalidSettings(Setting);
247 return PSRETURN_OK;
252 PSRETURN IGUIObject::GetSettingType(const CStr& Setting, EGUISettingType& Type) const
254 if (!SettingExists(Setting))
255 return LogInvalidSettings(Setting);
257 if (m_Settings.find(Setting) == m_Settings.end())
258 return LogInvalidSettings(Setting);
260 Type = m_Settings.find(Setting)->second.m_Type;
262 return PSRETURN_OK;
266 void IGUIObject::ChooseMouseOverAndClosest(IGUIObject*& pObject)
268 if (!MouseOver())
269 return;
271 // Check if we've got competition at all
272 if (pObject == NULL)
274 pObject = this;
275 return;
278 // Or if it's closer
279 if (GetBufferedZ() >= pObject->GetBufferedZ())
281 pObject = this;
282 return;
286 IGUIObject* IGUIObject::GetParent() const
288 // Important, we're not using GetParent() for these
289 // checks, that could screw it up
290 if (m_pParent)
292 if (m_pParent->m_pParent == NULL)
293 return NULL;
296 return m_pParent;
299 void IGUIObject::UpdateCachedSize()
301 bool absolute;
302 GUI<bool>::GetSetting(this, "absolute", absolute);
304 float aspectratio = 0.f;
305 GUI<float>::GetSetting(this, "aspectratio", aspectratio);
307 CClientArea ca;
308 GUI<CClientArea>::GetSetting(this, "size", ca);
310 // If absolute="false" and the object has got a parent,
311 // use its cached size instead of the screen. Notice
312 // it must have just been cached for it to work.
313 if (absolute == false && m_pParent && !IsRootObject())
314 m_CachedActualSize = ca.GetClientArea(m_pParent->m_CachedActualSize);
315 else
316 m_CachedActualSize = ca.GetClientArea(CRect(0.f, 0.f, g_xres / g_GuiScale, g_yres / g_GuiScale));
318 // In a few cases, GUI objects have to resize to fill the screen
319 // but maintain a constant aspect ratio.
320 // Adjust the size to be the max possible, centered in the original size:
321 if (aspectratio)
323 if (m_CachedActualSize.GetWidth() > m_CachedActualSize.GetHeight()*aspectratio)
325 float delta = m_CachedActualSize.GetWidth() - m_CachedActualSize.GetHeight()*aspectratio;
326 m_CachedActualSize.left += delta/2.f;
327 m_CachedActualSize.right -= delta/2.f;
329 else
331 float delta = m_CachedActualSize.GetHeight() - m_CachedActualSize.GetWidth()/aspectratio;
332 m_CachedActualSize.bottom -= delta/2.f;
333 m_CachedActualSize.top += delta/2.f;
338 void IGUIObject::LoadStyle(CGUI& GUIinstance, const CStr& StyleName)
340 // Fetch style
341 if (GUIinstance.m_Styles.count(StyleName) == 1)
343 LoadStyle(GUIinstance.m_Styles[StyleName]);
345 else
347 debug_warn(L"IGUIObject::LoadStyle failed");
351 void IGUIObject::LoadStyle(const SGUIStyle& Style)
353 // Iterate settings, it won't be able to set them all probably, but that doesn't matter
354 for (const std::pair<CStr, CStrW>& p : Style.m_SettingsDefaults)
356 // Try set setting in object
357 SetSetting(p.first, p.second);
359 // It doesn't matter if it fail, it's not suppose to be able to set every setting.
360 // since it's generic.
362 // The beauty with styles is that it can contain more settings
363 // than exists for the objects using it. So if the SetSetting
364 // fails, don't care.
368 float IGUIObject::GetBufferedZ() const
370 bool absolute;
371 GUI<bool>::GetSetting(this, "absolute", absolute);
373 float Z;
374 GUI<float>::GetSetting(this, "z", Z);
376 if (absolute)
377 return Z;
378 else
380 if (GetParent())
381 return GetParent()->GetBufferedZ() + Z;
382 else
384 // In philosophy, a parentless object shouldn't be able to have a relative sizing,
385 // but we'll accept it so that absolute can be used as default without a complaint.
386 // Also, you could consider those objects children to the screen resolution.
387 return Z;
392 void IGUIObject::RegisterScriptHandler(const CStr& Action, const CStr& Code, CGUI* pGUI)
394 if(!GetGUI())
395 throw PSERROR_GUI_OperationNeedsGUIObject();
397 JSContext* cx = pGUI->GetScriptInterface()->GetContext();
398 JSAutoRequest rq(cx);
399 JS::RootedValue globalVal(cx, pGUI->GetGlobalObject());
400 JS::RootedObject globalObj(cx, &globalVal.toObject());
402 const int paramCount = 1;
403 const char* paramNames[paramCount] = { "mouse" };
405 // Location to report errors from
406 CStr CodeName = GetName()+" "+Action;
408 // Generate a unique name
409 static int x = 0;
410 char buf[64];
411 sprintf_s(buf, ARRAY_SIZE(buf), "__eventhandler%d (%s)", x++, Action.c_str());
413 JS::CompileOptions options(cx);
414 options.setFileAndLine(CodeName.c_str(), 0);
415 options.setCompileAndGo(true);
417 JS::RootedFunction func(cx);
418 JS::AutoObjectVector emptyScopeChain(cx);
419 if (!JS::CompileFunction(cx, emptyScopeChain, options, buf, paramCount, paramNames, Code.c_str(), Code.length(), &func))
421 LOGERROR("RegisterScriptHandler: Failed to compile the script for %s", Action.c_str());
422 return;
425 JS::RootedObject funcObj(cx, JS_GetFunctionObject(func));
426 SetScriptHandler(Action, funcObj);
429 void IGUIObject::SetScriptHandler(const CStr& Action, JS::HandleObject Function)
431 // m_ScriptHandlers is only rooted after SetGUI() has been called (which sets up the GC trace callbacks),
432 // so we can't safely store objects in it if the GUI hasn't been set yet.
433 ENSURE(m_pGUI && "A GUI must be associated with the GUIObject before adding ScriptHandlers!");
434 m_ScriptHandlers[Action] = JS::Heap<JSObject*>(Function);
437 InReaction IGUIObject::SendEvent(EGUIMessageType type, const CStr& EventName)
439 PROFILE2_EVENT("gui event");
440 PROFILE2_ATTR("type: %s", EventName.c_str());
441 PROFILE2_ATTR("object: %s", m_Name.c_str());
443 SGUIMessage msg(type);
444 HandleMessage(msg);
446 ScriptEvent(EventName);
448 return (msg.skipped ? IN_PASS : IN_HANDLED);
451 void IGUIObject::ScriptEvent(const CStr& Action)
453 std::map<CStr, JS::Heap<JSObject*>>::iterator it = m_ScriptHandlers.find(Action);
454 if (it == m_ScriptHandlers.end())
455 return;
457 JSContext* cx = m_pGUI->GetScriptInterface()->GetContext();
458 JSAutoRequest rq(cx);
460 // Set up the 'mouse' parameter
461 JS::RootedValue mouse(cx);
462 m_pGUI->GetScriptInterface()->Eval("({})", &mouse);
463 m_pGUI->GetScriptInterface()->SetProperty(mouse, "x", m_pGUI->m_MousePos.x, false);
464 m_pGUI->GetScriptInterface()->SetProperty(mouse, "y", m_pGUI->m_MousePos.y, false);
465 m_pGUI->GetScriptInterface()->SetProperty(mouse, "buttons", m_pGUI->m_MouseButtons, false);
467 JS::AutoValueVector paramData(cx);
468 paramData.append(mouse);
469 JS::RootedObject obj(cx, GetJSObject());
470 JS::RootedValue handlerVal(cx, JS::ObjectValue(*it->second));
471 JS::RootedValue result(cx);
472 bool ok = JS_CallFunctionValue(cx, obj, handlerVal, paramData, &result);
473 if (!ok)
475 // We have no way to propagate the script exception, so just ignore it
476 // and hope the caller checks JS_IsExceptionPending
480 void IGUIObject::ScriptEvent(const CStr& Action, JS::HandleValue Argument)
482 std::map<CStr, JS::Heap<JSObject*>>::iterator it = m_ScriptHandlers.find(Action);
483 if (it == m_ScriptHandlers.end())
484 return;
486 JSContext* cx = m_pGUI->GetScriptInterface()->GetContext();
487 JSAutoRequest rq(cx);
488 JS::AutoValueVector paramData(cx);
489 paramData.append(Argument.get());
490 JS::RootedObject obj(cx, GetJSObject());
491 JS::RootedValue handlerVal(cx, JS::ObjectValue(*it->second));
492 JS::RootedValue result(cx);
493 bool ok = JS_CallFunctionValue(cx, obj, handlerVal, paramData, &result);
494 if (!ok)
496 JS_ReportError(cx, "Errors executing script action \"%s\"", Action.c_str());
500 JSObject* IGUIObject::GetJSObject()
502 JSContext* cx = m_pGUI->GetScriptInterface()->GetContext();
503 JSAutoRequest rq(cx);
504 // Cache the object when somebody first asks for it, because otherwise
505 // we end up doing far too much object allocation. TODO: Would be nice to
506 // not have these objects hang around forever using up memory, though.
507 if (!m_JSObject.initialized())
509 m_JSObject.init(cx, m_pGUI->GetScriptInterface()->CreateCustomObject("GUIObject"));
510 JS_SetPrivate(m_JSObject.get(), this);
512 return m_JSObject.get();
515 CStr IGUIObject::GetPresentableName() const
517 // __internal(), must be at least 13 letters to be able to be
518 // an internal name
519 if (m_Name.length() <= 12)
520 return m_Name;
522 if (m_Name.substr(0, 10) == "__internal")
523 return CStr("[unnamed object]");
524 else
525 return m_Name;
528 void IGUIObject::SetFocus()
530 GetGUI()->m_FocusedObject = this;
533 bool IGUIObject::IsFocused() const
535 return GetGUI()->m_FocusedObject == this;
538 bool IGUIObject::IsRootObject() const
540 return GetGUI() != 0 && m_pParent == GetGUI()->m_BaseObject;
543 void IGUIObject::TraceMember(JSTracer* trc)
545 for (std::pair<const CStr, JS::Heap<JSObject*>>& handler : m_ScriptHandlers)
546 JS_CallObjectTracer(trc, &handler.second, "IGUIObject::m_ScriptHandlers");
549 PSRETURN IGUIObject::LogInvalidSettings(const CStr8& Setting) const
551 LOGWARNING("IGUIObject: setting %s was not found on an object", Setting.c_str());
552 return PSRETURN_GUI_InvalidSetting;