Index: ps/trunk/source/gui/Scripting/JSInterface_CText.cpp
===================================================================
--- ps/trunk/source/gui/Scripting/JSInterface_CText.cpp (revision 25224)
+++ ps/trunk/source/gui/Scripting/JSInterface_CText.cpp (nonexistent)
@@ -1,38 +0,0 @@
-/* Copyright (C) 2020 Wildfire Games.
- * This file is part of 0 A.D.
- *
- * 0 A.D. is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
- *
- * 0 A.D. is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with 0 A.D. If not, see .
- */
-
-#include "precompiled.h"
-
-#include "JSInterface_GUIProxy_impl.h"
-
-#include "gui/ObjectTypes/CText.h"
-
-using GUIObjectType = CText;
-
-template<>
-void JSI_GUIProxy::CreateFunctions(const ScriptRequest& rq, GUIProxyProps* cache)
-{
-#define func(class, func) &JSInterface_GUIProxy::apply_to
- cache->setFunction(rq, "toString", func(IGUIObject, toString), 0);
- cache->setFunction(rq, "focus", func(IGUIObject, focus), 0);
- cache->setFunction(rq, "blur", func(IGUIObject, blur), 0);
- cache->setFunction(rq, "getComputedSize", func(IGUIObject, getComputedSize), 0);
- cache->setFunction(rq, "getTextSize", func(GUIObjectType, getTextSize), 0);
-#undef func
-}
-
-template class JSI_GUIProxy;
Property changes on: ps/trunk/source/gui/Scripting/JSInterface_CText.cpp
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/source/gui/Scripting/JSInterface_CButton.cpp
===================================================================
--- ps/trunk/source/gui/Scripting/JSInterface_CButton.cpp (revision 25224)
+++ ps/trunk/source/gui/Scripting/JSInterface_CButton.cpp (nonexistent)
@@ -1,38 +0,0 @@
-/* Copyright (C) 2020 Wildfire Games.
- * This file is part of 0 A.D.
- *
- * 0 A.D. is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
- *
- * 0 A.D. is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with 0 A.D. If not, see .
- */
-
-#include "precompiled.h"
-
-#include "JSInterface_GUIProxy_impl.h"
-
-#include "gui/ObjectTypes/CButton.h"
-
-using GUIObjectType = CButton;
-
-template<>
-void JSI_GUIProxy::CreateFunctions(const ScriptRequest& rq, GUIProxyProps* cache)
-{
-#define func(class, func) &JSInterface_GUIProxy::apply_to
- cache->setFunction(rq, "toString", func(IGUIObject, toString), 0);
- cache->setFunction(rq, "focus", func(IGUIObject, focus), 0);
- cache->setFunction(rq, "blur", func(IGUIObject, blur), 0);
- cache->setFunction(rq, "getComputedSize", func(IGUIObject, getComputedSize), 0);
- cache->setFunction(rq, "getTextSize", func(GUIObjectType, getTextSize), 0);
-#undef func
-}
-
-template class JSI_GUIProxy;
Property changes on: ps/trunk/source/gui/Scripting/JSInterface_CButton.cpp
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/source/gui/Scripting/JSInterface_CList.cpp
===================================================================
--- ps/trunk/source/gui/Scripting/JSInterface_CList.cpp (revision 25224)
+++ ps/trunk/source/gui/Scripting/JSInterface_CList.cpp (nonexistent)
@@ -1,54 +0,0 @@
-/* Copyright (C) 2020 Wildfire Games.
- * This file is part of 0 A.D.
- *
- * 0 A.D. is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
- *
- * 0 A.D. is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with 0 A.D. If not, see .
- */
-
-#include "precompiled.h"
-
-#include "JSInterface_GUIProxy_impl.h"
-
-#include "gui/ObjectTypes/CList.h"
-
-bool CList_AddItem(JSContext* cx, uint argc, JS::Value* vp)
-{
- JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
- CList* e = static_cast(js::GetProxyPrivate(args.thisv().toObjectOrNull()).toPrivate());
- if (!e)
- return false;
-
- ScriptInterface& scriptInterface = *ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface;
- ScriptRequest rq(scriptInterface);
- CGUIString text;
- if (!ScriptInterface::FromJSVal(rq, args.get(0), text))
- return false;
- e->AddItem(text, text);
- return true;
-}
-
-using GUIObjectType = CList;
-
-template<>
-void JSI_GUIProxy::CreateFunctions(const ScriptRequest& rq, GUIProxyProps* cache)
-{
-#define func(class, func) &JSInterface_GUIProxy::apply_to
- cache->setFunction(rq, "toString", func(IGUIObject, toString), 0);
- cache->setFunction(rq, "focus", func(IGUIObject, focus), 0);
- cache->setFunction(rq, "blur", func(IGUIObject, blur), 0);
- cache->setFunction(rq, "getComputedSize", func(IGUIObject, getComputedSize), 0);
-#undef func
- cache->setFunction(rq, "addItem", CList_AddItem, 1);
-}
-
-template class JSI_GUIProxy;
Property changes on: ps/trunk/source/gui/Scripting/JSInterface_CList.cpp
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/source/gui/Scripting/JSInterface_IGUIObject.cpp
===================================================================
--- ps/trunk/source/gui/Scripting/JSInterface_IGUIObject.cpp (revision 25224)
+++ ps/trunk/source/gui/Scripting/JSInterface_IGUIObject.cpp (nonexistent)
@@ -1,35 +0,0 @@
-/* Copyright (C) 2020 Wildfire Games.
- * This file is part of 0 A.D.
- *
- * 0 A.D. is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
- *
- * 0 A.D. is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with 0 A.D. If not, see .
- */
-
-#include "precompiled.h"
-
-#include "JSInterface_GUIProxy_impl.h"
-
-using GUIObjectType = IGUIObject;
-
-template<>
-void JSI_GUIProxy::CreateFunctions(const ScriptRequest& rq, GUIProxyProps* cache)
-{
-#define func(class, func) &JSInterface_GUIProxy::apply_to
- cache->setFunction(rq, "toString", func(IGUIObject, toString), 0);
- cache->setFunction(rq, "focus", func(IGUIObject, focus), 0);
- cache->setFunction(rq, "blur", func(IGUIObject, blur), 0);
- cache->setFunction(rq, "getComputedSize", func(IGUIObject, getComputedSize), 0);
-#undef func
-}
-
-template class JSI_GUIProxy;
Property changes on: ps/trunk/source/gui/Scripting/JSInterface_IGUIObject.cpp
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/source/gui/ObjectBases/IGUIObject.cpp
===================================================================
--- ps/trunk/source/gui/ObjectBases/IGUIObject.cpp (revision 25224)
+++ ps/trunk/source/gui/ObjectBases/IGUIObject.cpp (revision 25225)
@@ -1,585 +1,565 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "IGUIObject.h"
#include "gui/CGUI.h"
#include "gui/CGUISetting.h"
#include "gui/Scripting/JSInterface_GUIProxy.h"
#include "js/Conversions.h"
#include "ps/CLogger.h"
#include "ps/GameSetup/Config.h"
#include "ps/Profile.h"
#include "scriptinterface/ScriptContext.h"
#include "scriptinterface/ScriptExtraHeaders.h"
#include "scriptinterface/ScriptInterface.h"
#include "soundmanager/ISoundManager.h"
#include
#include
const CStr IGUIObject::EventNameMouseEnter = "MouseEnter";
const CStr IGUIObject::EventNameMouseMove = "MouseMove";
const CStr IGUIObject::EventNameMouseLeave = "MouseLeave";
IGUIObject::IGUIObject(CGUI& pGUI)
: m_pGUI(pGUI),
m_pParent(),
m_MouseHovering(),
m_LastClickTime(),
m_Enabled(),
m_Hidden(),
m_Size(),
m_Style(),
m_Hotkey(),
m_Z(),
m_Absolute(),
m_Ghost(),
m_AspectRatio(),
m_Tooltip(),
m_TooltipStyle()
{
RegisterSetting("enabled", m_Enabled);
RegisterSetting("hidden", m_Hidden);
RegisterSetting("size", m_Size);
RegisterSetting("style", m_Style);
RegisterSetting("hotkey", m_Hotkey);
RegisterSetting("z", m_Z);
RegisterSetting("absolute", m_Absolute);
RegisterSetting("ghost", m_Ghost);
RegisterSetting("aspectratio", m_AspectRatio);
RegisterSetting("tooltip", m_Tooltip);
RegisterSetting("tooltip_style", m_TooltipStyle);
// Setup important defaults
// TODO: Should be in the default style?
SetSetting("hidden", false, true);
SetSetting("ghost", false, true);
SetSetting("enabled", true, true);
SetSetting("absolute", true, true);
}
IGUIObject::~IGUIObject()
{
for (const std::pair& p : m_Settings)
delete p.second;
if (!m_ScriptHandlers.empty())
JS_RemoveExtraGCRootsTracer(m_pGUI.GetScriptInterface()->GetGeneralJSContext(), Trace, this);
// m_Children is deleted along all other GUI Objects in the CGUI destructor
}
void IGUIObject::AddChild(IGUIObject& pChild)
{
pChild.SetParent(this);
m_Children.push_back(&pChild);
}
template
void IGUIObject::RegisterSetting(const CStr& Name, T& Value)
{
if (SettingExists(Name))
LOGERROR("The setting '%s' already exists on the object '%s'!", Name.c_str(), GetPresentableName().c_str());
else
m_Settings.emplace(Name, new CGUISetting(*this, Name, Value));
}
bool IGUIObject::SettingExists(const CStr& Setting) const
{
return m_Settings.find(Setting) != m_Settings.end();
}
template
T& IGUIObject::GetSetting(const CStr& Setting)
{
return static_cast* >(m_Settings.at(Setting))->m_pSetting;
}
template
const T& IGUIObject::GetSetting(const CStr& Setting) const
{
return static_cast* >(m_Settings.at(Setting))->m_pSetting;
}
bool IGUIObject::SetSettingFromString(const CStr& Setting, const CStrW& Value, const bool SendMessage)
{
const std::map::iterator it = m_Settings.find(Setting);
if (it == m_Settings.end())
{
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());
return false;
}
return it->second->FromString(Value, SendMessage);
}
template
void IGUIObject::SetSetting(const CStr& Setting, T& Value, const bool SendMessage)
{
PreSettingChange(Setting);
static_cast* >(m_Settings.at(Setting))->m_pSetting = std::move(Value);
SettingChanged(Setting, SendMessage);
}
template
void IGUIObject::SetSetting(const CStr& Setting, const T& Value, const bool SendMessage)
{
PreSettingChange(Setting);
static_cast* >(m_Settings.at(Setting))->m_pSetting = Value;
SettingChanged(Setting, SendMessage);
}
void IGUIObject::PreSettingChange(const CStr& Setting)
{
if (Setting == "hotkey")
m_pGUI.UnsetObjectHotkey(this, GetSetting(Setting));
}
void IGUIObject::SettingChanged(const CStr& Setting, const bool SendMessage)
{
if (Setting == "size")
{
// If setting was "size", we need to re-cache itself and all children
RecurseObject(nullptr, &IGUIObject::UpdateCachedSize);
}
else if (Setting == "hidden")
{
// Hiding an object requires us to reset it and all children
if (m_Hidden)
RecurseObject(nullptr, &IGUIObject::ResetStates);
}
else if (Setting == "hotkey")
m_pGUI.SetObjectHotkey(this, GetSetting(Setting));
else if (Setting == "style")
m_pGUI.SetObjectStyle(this, GetSetting(Setting));
if (SendMessage)
{
SGUIMessage msg(GUIM_SETTINGS_UPDATED, Setting);
HandleMessage(msg);
}
}
bool IGUIObject::IsMouseOver() const
{
return m_CachedActualSize.PointInside(m_pGUI.GetMousePos());
}
bool IGUIObject::MouseOverIcon()
{
return false;
}
void IGUIObject::UpdateMouseOver(IGUIObject* const& pMouseOver)
{
if (pMouseOver == this)
{
if (!m_MouseHovering)
SendMouseEvent(GUIM_MOUSE_ENTER,EventNameMouseEnter);
m_MouseHovering = true;
SendMouseEvent(GUIM_MOUSE_OVER, EventNameMouseMove);
}
else
{
if (m_MouseHovering)
{
m_MouseHovering = false;
SendMouseEvent(GUIM_MOUSE_LEAVE, EventNameMouseLeave);
}
}
}
void IGUIObject::ChooseMouseOverAndClosest(IGUIObject*& pObject)
{
if (!IsMouseOver())
return;
// Check if we've got competition at all
if (pObject == nullptr)
{
pObject = this;
return;
}
// Or if it's closer
if (GetBufferedZ() >= pObject->GetBufferedZ())
{
pObject = this;
return;
}
}
IGUIObject* IGUIObject::GetParent() const
{
// Important, we're not using GetParent() for these
// checks, that could screw it up
if (m_pParent && m_pParent->m_pParent == nullptr)
return nullptr;
return m_pParent;
}
void IGUIObject::ResetStates()
{
// Notify the gui that we aren't hovered anymore
UpdateMouseOver(nullptr);
}
void IGUIObject::UpdateCachedSize()
{
// If absolute="false" and the object has got a parent,
// use its cached size instead of the screen. Notice
// it must have just been cached for it to work.
if (!m_Absolute && m_pParent && !IsRootObject())
m_CachedActualSize = m_Size.GetSize(m_pParent->m_CachedActualSize);
else
m_CachedActualSize = m_Size.GetSize(CRect(0.f, 0.f, g_xres / g_GuiScale, g_yres / g_GuiScale));
// In a few cases, GUI objects have to resize to fill the screen
// but maintain a constant aspect ratio.
// Adjust the size to be the max possible, centered in the original size:
if (m_AspectRatio)
{
if (m_CachedActualSize.GetWidth() > m_CachedActualSize.GetHeight() * m_AspectRatio)
{
float delta = m_CachedActualSize.GetWidth() - m_CachedActualSize.GetHeight() * m_AspectRatio;
m_CachedActualSize.left += delta/2.f;
m_CachedActualSize.right -= delta/2.f;
}
else
{
float delta = m_CachedActualSize.GetHeight() - m_CachedActualSize.GetWidth() / m_AspectRatio;
m_CachedActualSize.bottom -= delta/2.f;
m_CachedActualSize.top += delta/2.f;
}
}
}
+CRect IGUIObject::GetComputedSize()
+{
+ UpdateCachedSize();
+ return m_CachedActualSize;
+}
+
+
bool IGUIObject::ApplyStyle(const CStr& StyleName)
{
if (!m_pGUI.HasStyle(StyleName))
{
LOGERROR("IGUIObject: Trying to use style '%s' that doesn't exist.", StyleName.c_str());
return false;
}
// The default style may specify settings for any GUI object.
// Other styles are reported if they specify a Setting that does not exist,
// so that the XML author is informed and can correct the style.
for (const std::pair& p : m_pGUI.GetStyle(StyleName).m_SettingsDefaults)
{
if (SettingExists(p.first))
SetSettingFromString(p.first, p.second, true);
else if (StyleName != "default")
LOGWARNING("GUI object has no setting \"%s\", but the style \"%s\" defines it", p.first, StyleName.c_str());
}
return true;
}
float IGUIObject::GetBufferedZ() const
{
if (m_Absolute)
return m_Z;
if (GetParent())
return GetParent()->GetBufferedZ() + m_Z;
// In philosophy, a parentless object shouldn't be able to have a relative sizing,
// but we'll accept it so that absolute can be used as default without a complaint.
// Also, you could consider those objects children to the screen resolution.
return m_Z;
}
void IGUIObject::RegisterScriptHandler(const CStr& eventName, const CStr& Code, CGUI& pGUI)
{
ScriptRequest rq(pGUI.GetScriptInterface());
const int paramCount = 1;
const char* paramNames[paramCount] = { "mouse" };
// Location to report errors from
CStr CodeName = GetName() + " " + eventName;
// Generate a unique name
static int x = 0;
char buf[64];
sprintf_s(buf, ARRAY_SIZE(buf), "__eventhandler%d (%s)", x++, eventName.c_str());
// TODO: this is essentially the same code as ScriptInterface::LoadScript (with a tweak for the argument).
JS::CompileOptions options(rq.cx);
options.setFileAndLine(CodeName.c_str(), 0);
options.setIsRunOnce(false);
JS::SourceText src;
ENSURE(src.init(rq.cx, Code.c_str(), Code.length(), JS::SourceOwnership::Borrowed));
JS::RootedObjectVector emptyScopeChain(rq.cx);
JS::RootedFunction func(rq.cx, JS::CompileFunction(rq.cx, emptyScopeChain, options, buf, paramCount, paramNames, src));
if (func == nullptr)
{
LOGERROR("RegisterScriptHandler: Failed to compile the script for %s", eventName.c_str());
return;
}
JS::RootedObject funcObj(rq.cx, JS_GetFunctionObject(func));
SetScriptHandler(eventName, funcObj);
}
void IGUIObject::SetScriptHandler(const CStr& eventName, JS::HandleObject Function)
{
if (m_ScriptHandlers.empty())
JS_AddExtraGCRootsTracer(m_pGUI.GetScriptInterface()->GetGeneralJSContext(), Trace, this);
m_ScriptHandlers[eventName] = JS::Heap(Function);
if (std::find(m_pGUI.m_EventObjects[eventName].begin(), m_pGUI.m_EventObjects[eventName].end(), this) == m_pGUI.m_EventObjects[eventName].end())
m_pGUI.m_EventObjects[eventName].emplace_back(this);
}
void IGUIObject::UnsetScriptHandler(const CStr& eventName)
{
std::map >::iterator it = m_ScriptHandlers.find(eventName);
if (it == m_ScriptHandlers.end())
return;
m_ScriptHandlers.erase(it);
if (m_ScriptHandlers.empty())
JS_RemoveExtraGCRootsTracer(m_pGUI.GetScriptInterface()->GetGeneralJSContext(), Trace, this);
std::unordered_map>::iterator it2 = m_pGUI.m_EventObjects.find(eventName);
if (it2 == m_pGUI.m_EventObjects.end())
return;
std::vector& handlers = it2->second;
handlers.erase(std::remove(handlers.begin(), handlers.end(), this), handlers.end());
if (handlers.empty())
m_pGUI.m_EventObjects.erase(it2);
}
InReaction IGUIObject::SendEvent(EGUIMessageType type, const CStr& eventName)
{
PROFILE2_EVENT("gui event");
PROFILE2_ATTR("type: %s", eventName.c_str());
PROFILE2_ATTR("object: %s", m_Name.c_str());
SGUIMessage msg(type);
HandleMessage(msg);
ScriptEvent(eventName);
return msg.skipped ? IN_PASS : IN_HANDLED;
}
InReaction IGUIObject::SendMouseEvent(EGUIMessageType type, const CStr& eventName)
{
PROFILE2_EVENT("gui mouse event");
PROFILE2_ATTR("type: %s", eventName.c_str());
PROFILE2_ATTR("object: %s", m_Name.c_str());
SGUIMessage msg(type);
HandleMessage(msg);
ScriptRequest rq(m_pGUI.GetScriptInterface());
// Set up the 'mouse' parameter
JS::RootedValue mouse(rq.cx);
const CVector2D& mousePos = m_pGUI.GetMousePos();
ScriptInterface::CreateObject(
rq,
&mouse,
"x", mousePos.X,
"y", mousePos.Y,
"buttons", m_pGUI.GetMouseButtons());
JS::RootedValueVector paramData(rq.cx);
ignore_result(paramData.append(mouse));
ScriptEvent(eventName, paramData);
return msg.skipped ? IN_PASS : IN_HANDLED;
}
void IGUIObject::ScriptEvent(const CStr& eventName)
{
ScriptEventWithReturn(eventName);
}
bool IGUIObject::ScriptEventWithReturn(const CStr& eventName)
{
if (m_ScriptHandlers.find(eventName) == m_ScriptHandlers.end())
return false;
ScriptRequest rq(m_pGUI.GetScriptInterface());
JS::RootedValueVector paramData(rq.cx);
return ScriptEventWithReturn(eventName, paramData);
}
void IGUIObject::ScriptEvent(const CStr& eventName, const JS::HandleValueArray& paramData)
{
ScriptEventWithReturn(eventName, paramData);
}
bool IGUIObject::ScriptEventWithReturn(const CStr& eventName, const JS::HandleValueArray& paramData)
{
std::map >::iterator it = m_ScriptHandlers.find(eventName);
if (it == m_ScriptHandlers.end())
return false;
ScriptRequest rq(m_pGUI.GetScriptInterface());
JS::RootedObject obj(rq.cx, GetJSObject());
JS::RootedValue handlerVal(rq.cx, JS::ObjectValue(*it->second));
JS::RootedValue result(rq.cx);
if (!JS_CallFunctionValue(rq.cx, obj, handlerVal, paramData, &result))
{
LOGERROR("Errors executing script event \"%s\"", eventName.c_str());
ScriptException::CatchPending(rq);
return false;
}
return JS::ToBoolean(result);
}
-void IGUIObject::CreateJSObject()
-{
- ScriptRequest rq(m_pGUI.GetScriptInterface());
- using ProxyHandler = JSI_GUIProxy>;
- ProxyHandler::CreateJSObject(rq, this, GetGUI().GetProxyData(&ProxyHandler::Singleton()), m_JSObject);
-}
-
JSObject* IGUIObject::GetJSObject()
{
// Cache the object when somebody first asks for it, because otherwise
// we end up doing far too much object allocation.
- if (!m_JSObject.initialized())
+ if (!m_JSObject)
CreateJSObject();
- return m_JSObject.get();
-}
-
-void IGUIObject::toString(ScriptInterface& scriptInterface, JS::MutableHandleValue ret)
-{
- ScriptRequest rq(scriptInterface);
- ScriptInterface::ToJSVal(rq, ret, "[GUIObject: " + GetName() + "]");
-}
-
-void IGUIObject::focus(ScriptInterface& UNUSED(scriptInterface), JS::MutableHandleValue ret)
-{
- GetGUI().SetFocusedObject(this);
- ret.setUndefined();
-}
-
-void IGUIObject::blur(ScriptInterface& UNUSED(scriptInterface), JS::MutableHandleValue ret)
-{
- GetGUI().SetFocusedObject(nullptr);
- ret.setUndefined();
-}
-
-void IGUIObject::getComputedSize(ScriptInterface& scriptInterface, JS::MutableHandleValue ret)
-{
- UpdateCachedSize();
- ScriptRequest rq(scriptInterface);
- ScriptInterface::ToJSVal(rq, ret, m_CachedActualSize);
+ return m_JSObject->Get();
}
bool IGUIObject::IsEnabled() const
{
return m_Enabled;
}
bool IGUIObject::IsHidden() const
{
return m_Hidden;
}
bool IGUIObject::IsHiddenOrGhost() const
{
return m_Hidden || m_Ghost;
}
void IGUIObject::PlaySound(const CStrW& soundPath) const
{
if (g_SoundManager && !soundPath.empty())
g_SoundManager->PlayAsUI(soundPath.c_str(), false);
}
CStr IGUIObject::GetPresentableName() const
{
// __internal(), must be at least 13 letters to be able to be
// an internal name
if (m_Name.length() <= 12)
return m_Name;
if (m_Name.substr(0, 10) == "__internal")
return CStr("[unnamed object]");
else
return m_Name;
}
void IGUIObject::SetFocus()
{
m_pGUI.SetFocusedObject(this);
}
+void IGUIObject::ReleaseFocus()
+{
+ m_pGUI.SetFocusedObject(nullptr);
+}
+
bool IGUIObject::IsFocused() const
{
return m_pGUI.GetFocusedObject() == this;
}
bool IGUIObject::IsBaseObject() const
{
return this == m_pGUI.GetBaseObject();
}
bool IGUIObject::IsRootObject() const
{
return m_pParent == m_pGUI.GetBaseObject();
}
void IGUIObject::TraceMember(JSTracer* trc)
{
// Please ensure to adapt the Tracer enabling and disabling in accordance with the GC things traced!
for (std::pair>& handler : m_ScriptHandlers)
JS::TraceEdge(trc, &handler.second, "IGUIObject::m_ScriptHandlers");
}
// Instantiate templated functions:
// These functions avoid copies by working with a reference and move semantics.
#define TYPE(T) \
template void IGUIObject::RegisterSetting(const CStr& Name, T& Value); \
template T& IGUIObject::GetSetting(const CStr& Setting); \
template const T& IGUIObject::GetSetting(const CStr& Setting) const; \
template void IGUIObject::SetSetting(const CStr& Setting, T& Value, const bool SendMessage); \
#include "gui/GUISettingTypes.h"
#undef TYPE
// Copying functions - discouraged except for primitives.
#define TYPE(T) \
template void IGUIObject::SetSetting(const CStr& Setting, const T& Value, const bool SendMessage); \
#define GUITYPE_IGNORE_NONCOPYABLE
#include "gui/GUISettingTypes.h"
#undef GUITYPE_IGNORE_NONCOPYABLE
#undef TYPE
Index: ps/trunk/source/gui/ObjectBases/IGUIObject.h
===================================================================
--- ps/trunk/source/gui/ObjectBases/IGUIObject.h (revision 25224)
+++ ps/trunk/source/gui/ObjectBases/IGUIObject.h (revision 25225)
@@ -1,556 +1,560 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
/*
* The base class of an object.
* All objects are derived from this class.
* It's an abstract data type, so it can't be used per se.
* Also contains a Dummy object which is used for completely blank objects.
*/
#ifndef INCLUDED_IGUIOBJECT
#define INCLUDED_IGUIOBJECT
#include "gui/SettingTypes/CGUISize.h"
#include "gui/SGUIMessage.h"
#include "lib/input.h" // just for IN_PASS
#include "ps/XML/Xeromyces.h"
#include "scriptinterface/ScriptTypes.h"
#include
#include
class CGUI;
class IGUIObject;
+class IGUIProxyObject;
class IGUISetting;
template
class JSI_GUIProxy;
#define GUI_OBJECT(obj) \
public: \
static IGUIObject* ConstructObject(CGUI& pGUI) \
{ return new obj(pGUI); }
/**
* GUI object such as a button or an input-box.
* Abstract data type !
*/
class IGUIObject
{
friend class CGUI;
// Allow getProperty to access things like GetParent()
template
friend class JSI_GUIProxy;
public:
NONCOPYABLE(IGUIObject);
IGUIObject(CGUI& pGUI);
virtual ~IGUIObject();
/**
* This function checks if the mouse is hovering the
* rectangle that the base setting "size" makes.
* Although it is virtual, so one could derive
* an object from CButton, which changes only this
* to checking the circle that "size" makes.
*
* This function also returns true if there is a different
* GUI object shown on top of this one.
*/
virtual bool IsMouseOver() const;
/**
* This function returns true if the mouse is hovering
* over this GUI object and if this GUI object is the
* topmost object in that screen location.
* For example when hovering dropdown list items, the
* buttons beneath the list won't return true here.
*/
virtual bool IsMouseHovering() const { return m_MouseHovering; }
/**
* Test if mouse position is over an icon
*/
virtual bool MouseOverIcon();
//--------------------------------------------------------
/** @name Leaf Functions */
//--------------------------------------------------------
//@{
/// Get object name, name is unique
const CStr& GetName() const { return m_Name; }
/// Get object name
void SetName(const CStr& Name) { m_Name = Name; }
// Get Presentable name.
// Will change all internally set names to something like ""
CStr GetPresentableName() const;
/**
* Builds the object hierarchy with references.
*/
void AddChild(IGUIObject& pChild);
/**
* Return all child objects of the current object.
*/
const std::vector& GetChildren() const { return m_Children; }
//@}
//--------------------------------------------------------
/** @name Settings Management */
//--------------------------------------------------------
//@{
/**
* Registers the given setting variables with the GUI object.
* Enable XML and JS to modify the given variable.
*
* @param Type Setting type
* @param Name Setting reference name
*/
template
void RegisterSetting(const CStr& Name, T& Value);
/**
* Returns whether there is a setting with the given name registered.
*
* @param Setting setting name
* @return True if settings exist.
*/
bool SettingExists(const CStr& Setting) const;
/**
* Get a mutable reference to the setting.
* If no such setting exists, an exception of type std::out_of_range is thrown.
* If the value is modified, there is no GUIM_SETTINGS_UPDATED message sent.
*/
template
T& GetSetting(const CStr& Setting);
template
const T& GetSetting(const CStr& Setting) const;
/**
* Set a setting by string, regardless of what type it is.
* Used to parse setting values from XML files.
* For example a CRect(10,10,20,20) is created from "10 10 20 20".
* Returns false if the conversion fails, otherwise true.
*/
bool SetSettingFromString(const CStr& Setting, const CStrW& Value, const bool SendMessage);
/**
* Assigns the given value to the setting identified by the given name.
* Uses move semantics, so do not read from Value after this call.
*
* @param SendMessage If true, a GUIM_SETTINGS_UPDATED message will be broadcasted to all GUI objects.
*/
template
void SetSetting(const CStr& Setting, T& Value, const bool SendMessage);
/**
* This variant will copy the value.
*/
template
void SetSetting(const CStr& Setting, const T& Value, const bool SendMessage);
/**
* Returns whether this object is set to be hidden or ghost.
*/
bool IsEnabled() const;
/**
* Returns whether this is object is set to be hidden.
*/
bool IsHidden() const;
/**
* Returns whether this object is set to be hidden or ghost.
*/
bool IsHiddenOrGhost() const;
/**
* Retrieves the configured sound filename from the given setting name and plays that once.
*/
void PlaySound(const CStrW& soundPath) const;
/**
* Send event to this GUI object (HandleMessage and ScriptEvent)
*
* @param type Type of GUI message to be handled
* @param eventName String representation of event name
* @return IN_HANDLED if event was handled, or IN_PASS if skipped
*/
InReaction SendEvent(EGUIMessageType type, const CStr& eventName);
/**
* Same as SendEvent, but passes mouse coordinates and button state as an argument.
*/
InReaction SendMouseEvent(EGUIMessageType type, const CStr& eventName);
/**
* All sizes are relative to resolution, and the calculation
* is not wanted in real time, therefore it is cached, update
* the cached size with this function.
*/
virtual void UpdateCachedSize();
/**
+ * Updates and returns the size of the object.
+ */
+ CRect GetComputedSize();
+
+ /**
* Reset internal state of this object.
*/
virtual void ResetStates();
/**
* Set the script handler for a particular object-specific action
*
* @param eventName Name of action
* @param Code Javascript code to execute when the action occurs
* @param pGUI GUI instance to associate the script with
*/
void RegisterScriptHandler(const CStr& eventName, const CStr& Code, CGUI& pGUI);
/**
* Retrieves the JSObject representing this GUI object.
*/
JSObject* GetJSObject();
- /**
- * The following functions are called from JS.
- */
- void toString(ScriptInterface& scriptInterface, JS::MutableHandleValue ret);
- void focus(ScriptInterface& scriptInterface, JS::MutableHandleValue ret);
- void blur(ScriptInterface& scriptInterface, JS::MutableHandleValue ret);
- void getComputedSize(ScriptInterface& scriptInterface, JS::MutableHandleValue ret);
-
//@}
protected:
//--------------------------------------------------------
/** @name Called by CGUI and friends
*
* Methods that the CGUI will call using
* its friendship, these should not
* be called by user.
* These functions' security are a lot
* what constitutes the GUI's
*/
//--------------------------------------------------------
//@{
public:
/**
* Called on every GUI tick unless the object or one of its parent is hidden/ghost.
*/
virtual void Tick() {};
/**
* This function is called with different messages
* for instance when the mouse enters the object.
*
* @param Message GUI Message
*/
virtual void HandleMessage(SGUIMessage& UNUSED(Message)) {}
/**
* Calls an IGUIObject member function recursively on this object and its children.
* Aborts recursion at IGUIObjects that have the isRestricted function return true.
* The arguments of the callback function must be references.
*/
template
void RecurseObject(bool(IGUIObject::*isRestricted)() const, void(IGUIObject::*callbackFunction)(Args... args), Args&&... args)
{
if (!IsBaseObject())
{
if (isRestricted && (this->*isRestricted)())
return;
(this->*callbackFunction)(args...);
}
for (IGUIObject* const& obj : m_Children)
obj->RecurseObject(isRestricted, callbackFunction, args...);
}
protected:
/**
* Draws the object.
*/
virtual void Draw() = 0;
/**
* Some objects need to be able to pre-emptively process SDL_Event_.
*
* Only the object with focus will have this function called.
*
* Returns either IN_PASS or IN_HANDLED. If IN_HANDLED, then
* the event won't be passed on and processed by other handlers.
*/
virtual InReaction PreemptEvent(const SDL_Event_* UNUSED(ev)) { return IN_PASS; }
/**
* Some objects need to handle the text-related SDL_Event_ manually.
* For instance the input box.
*
* Only the object with focus will have this function called.
*
* Returns either IN_PASS or IN_HANDLED. If IN_HANDLED, then
* the key won't be passed on and processed by other handlers.
* This is used for keys that the GUI uses.
*/
virtual InReaction ManuallyHandleKeys(const SDL_Event_* UNUSED(ev)) { return IN_PASS; }
/**
* Applies the given style to the object.
*
* Returns false if the style is not recognised (and thus has
* not been applied).
*/
bool ApplyStyle(const CStr& StyleName);
/**
* Returns not the Z value, but the actual buffered Z value, i.e. if it's
* defined relative, then it will check its parent's Z value and add
* the relativity.
*
* @return Actual Z value on the screen.
*/
virtual float GetBufferedZ() const;
/**
* Set parent of this object
*/
void SetParent(IGUIObject* pParent) { m_pParent = pParent; }
public:
CGUI& GetGUI() { return m_pGUI; }
const CGUI& GetGUI() const { return m_pGUI; }
/**
* Take focus!
*/
void SetFocus();
+ /**
+ * Release focus.
+ */
+ void ReleaseFocus();
+
protected:
/**
* Check if object is focused.
*/
bool IsFocused() const;
/**
* NOTE! This will not just return m_pParent, when that is
* need use it! There is one exception to it, when the parent is
* the top-node (the object that isn't a real object), this
* will return nullptr, so that the top-node's children are
* seemingly parentless.
*
* @return Pointer to parent
*/
IGUIObject* GetParent() const;
/**
* Handle additional children to the \-tag. In IGUIObject, this function does
* nothing. In CList and CDropDown, it handles the \- , used to build the data.
*
* Returning false means the object doesn't recognize the child. Should be reported.
* Notice 'false' is default, because an object not using this function, should not
* have any additional children (and this function should never be called).
*/
virtual bool HandleAdditionalChildren(const XMBElement& UNUSED(child), CXeromyces* UNUSED(pFile))
{
return false;
}
/**
* Allow the GUI object to process after all child items were handled.
* Useful to avoid iterator invalidation with push_back calls.
*/
virtual void AdditionalChildrenHandled() {}
/**
* Cached size, real size m_Size is actually dependent on resolution
* and can have different *real* outcomes, this is the real outcome
* cached to avoid slow calculations in real time.
*/
CRect m_CachedActualSize;
/**
* Execute the script for a particular action.
* Does nothing if no script has been registered for that action.
* The mouse coordinates will be passed as the first argument.
*
* @param eventName Name of action
*/
void ScriptEvent(const CStr& eventName);
/**
* Execute the script for a particular action.
* Does nothing if no script has been registered for that action.
* The mouse coordinates will be passed as the first argument.
*
* @param eventName Name of action
*
* @return True if the script returned something truthy.
*/
bool ScriptEventWithReturn(const CStr& eventName);
/**
* Execute the script for a particular action.
* Does nothing if no script has been registered for that action.
*
* @param eventName Name of action
* @param paramData JS::HandleValueArray arguments to pass to the event.
*/
void ScriptEvent(const CStr& eventName, const JS::HandleValueArray& paramData);
/**
* Execute the script for a particular action.
* Does nothing if no script has been registered for that action.
*
* @param eventName Name of action
* @param paramData JS::HandleValueArray arguments to pass to the event.
*
* @return True if the script returned something truthy.
*/
bool ScriptEventWithReturn(const CStr& eventName, const JS::HandleValueArray& paramData);
/**
* Assigns a JS function to the event name.
*/
void SetScriptHandler(const CStr& eventName, JS::HandleObject Function);
/**
* Deletes an event handler assigned to the given name, if such a handler exists.
*/
void UnsetScriptHandler(const CStr& eventName);
/**
* Inputes the object that is currently hovered, this function
* updates this object accordingly (i.e. if it's the object
* being inputted one thing happens, and not, another).
*
* @param pMouseOver Object that is currently hovered, can be nullptr too!
*/
void UpdateMouseOver(IGUIObject* const& pMouseOver);
//@}
private:
//--------------------------------------------------------
/** @name Internal functions */
//--------------------------------------------------------
//@{
/**
* Creates the JS object representing this page upon first use.
+ * This function (and its derived versions) are defined in the GUIProxy implementation file for convenience.
*/
virtual void CreateJSObject();
/**
* Updates some internal data depending on the setting changed.
*/
void PreSettingChange(const CStr& Setting);
void SettingChanged(const CStr& Setting, const bool SendMessage);
/**
* Inputs a reference pointer, checks if the new inputted object
* if hovered, if so, then check if this's Z value is greater
* than the inputted object... If so then the object is closer
* and we'll replace the pointer with this.
* Also Notice input can be nullptr, which means the Z value demand
* is out. NOTICE you can't input nullptr as const so you'll have
* to set an object to nullptr.
*
* @param pObject Object pointer, can be either the old one, or
* the new one.
*/
void ChooseMouseOverAndClosest(IGUIObject*& pObject);
/**
* Returns whether this is the object all other objects are descendants of.
*/
bool IsBaseObject() const;
/**
* Returns whether this object is a child of the base object.
*/
bool IsRootObject() const;
static void Trace(JSTracer* trc, void* data)
{
reinterpret_cast
(data)->TraceMember(trc);
}
void TraceMember(JSTracer* trc);
// Variables
protected:
static const CStr EventNameMouseEnter;
static const CStr EventNameMouseMove;
static const CStr EventNameMouseLeave;
// Name of object
CStr m_Name;
// Constructed on the heap, will be destroyed along with the the CGUI
std::vector m_Children;
// Pointer to parent
IGUIObject* m_pParent;
//This represents the last click time for each mouse button
double m_LastClickTime[6];
/**
* This variable is true if the mouse is hovering this object and
* this object is the topmost object shown in this location.
*/
bool m_MouseHovering;
/**
* Settings pool, all an object's settings are located here
*/
std::map m_Settings;
// An object can't function stand alone
CGUI& m_pGUI;
// Internal storage for registered script handlers.
std::map > m_ScriptHandlers;
- // Cached JSObject representing this GUI object
- JS::PersistentRootedObject m_JSObject;
+ // Cached JSObject representing this GUI object.
+ std::unique_ptr m_JSObject;
// Cache references to settings for performance
bool m_Enabled;
bool m_Hidden;
CGUISize m_Size;
CStr m_Style;
CStr m_Hotkey;
float m_Z;
bool m_Absolute;
bool m_Ghost;
float m_AspectRatio;
CStrW m_Tooltip;
CStr m_TooltipStyle;
};
#endif // INCLUDED_IGUIOBJECT
Index: ps/trunk/source/gui/ObjectTypes/CButton.cpp
===================================================================
--- ps/trunk/source/gui/ObjectTypes/CButton.cpp (revision 25224)
+++ ps/trunk/source/gui/ObjectTypes/CButton.cpp (revision 25225)
@@ -1,131 +1,122 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "CButton.h"
#include "gui/CGUI.h"
#include "gui/CGUIText.h"
-#include "gui/Scripting/JSInterface_GUIProxy.h"
#include "gui/SettingTypes/CGUIColor.h"
CButton::CButton(CGUI& pGUI)
: IGUIObject(pGUI),
IGUIButtonBehavior(*static_cast(this)),
IGUITextOwner(*static_cast(this)),
m_BufferZone(),
m_Caption(),
m_Font(),
m_Sprite(),
m_SpriteOver(),
m_SpritePressed(),
m_SpriteDisabled(),
m_TextAlign(),
m_TextVAlign(),
m_TextColor(),
m_TextColorOver(),
m_TextColorPressed(),
m_TextColorDisabled()
{
RegisterSetting("buffer_zone", m_BufferZone);
RegisterSetting("caption", m_Caption);
RegisterSetting("font", m_Font);
RegisterSetting("sprite", m_Sprite);
RegisterSetting("sprite_over", m_SpriteOver);
RegisterSetting("sprite_pressed", m_SpritePressed);
RegisterSetting("sprite_disabled", m_SpriteDisabled);
RegisterSetting("text_align", m_TextAlign);
RegisterSetting("text_valign", m_TextVAlign);
RegisterSetting("textcolor", m_TextColor);
RegisterSetting("textcolor_over", m_TextColorOver);
RegisterSetting("textcolor_pressed", m_TextColorPressed);
RegisterSetting("textcolor_disabled", m_TextColorDisabled);
AddText();
}
CButton::~CButton()
{
}
void CButton::SetupText()
{
ENSURE(m_GeneratedTexts.size() == 1);
m_GeneratedTexts[0] = CGUIText(m_pGUI, m_Caption, m_Font, m_CachedActualSize.GetWidth(), m_BufferZone, this);
CalculateTextPosition(m_CachedActualSize, m_TextPos, m_GeneratedTexts[0]);
}
void CButton::ResetStates()
{
IGUIObject::ResetStates();
IGUIButtonBehavior::ResetStates();
}
void CButton::UpdateCachedSize()
{
IGUIObject::UpdateCachedSize();
IGUITextOwner::UpdateCachedSize();
}
+CSize2D CButton::GetTextSize()
+{
+ UpdateText();
+ return m_GeneratedTexts[0].GetSize();
+}
+
void CButton::HandleMessage(SGUIMessage& Message)
{
IGUIObject::HandleMessage(Message);
IGUIButtonBehavior::HandleMessage(Message);
IGUITextOwner::HandleMessage(Message);
}
void CButton::Draw()
{
const float bz = GetBufferedZ();
m_pGUI.DrawSprite(
GetButtonSprite(m_Sprite, m_SpriteOver, m_SpritePressed, m_SpriteDisabled),
bz,
m_CachedActualSize);
DrawText(0, ChooseColor(), m_TextPos, bz + 0.1f);
}
const CGUIColor& CButton::ChooseColor()
{
if (!m_Enabled)
return m_TextColorDisabled ? m_TextColorDisabled : m_TextColor;
if (!m_MouseHovering)
return m_TextColor;
if (m_Pressed)
return m_TextColorPressed ? m_TextColorPressed : m_TextColor;
return m_TextColorOver ? m_TextColorOver : m_TextColor;
}
-
-void CButton::getTextSize(ScriptInterface& scriptInterface, JS::MutableHandleValue ret)
-{
- ScriptRequest rq(scriptInterface);
- UpdateText();
- ScriptInterface::ToJSVal(rq, ret, m_GeneratedTexts[0].GetSize());
-}
-
-void CButton::CreateJSObject()
-{
- ScriptRequest rq(m_pGUI.GetScriptInterface());
- using ProxyHandler = JSI_GUIProxy>;
- ProxyHandler::CreateJSObject(rq, this, GetGUI().GetProxyData(&ProxyHandler::Singleton()), m_JSObject);
-}
Index: ps/trunk/source/gui/ObjectTypes/CButton.h
===================================================================
--- ps/trunk/source/gui/ObjectTypes/CButton.h (revision 25224)
+++ ps/trunk/source/gui/ObjectTypes/CButton.h (revision 25225)
@@ -1,95 +1,95 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_CBUTTON
#define INCLUDED_CBUTTON
#include "gui/CGUISprite.h"
#include "gui/ObjectBases/IGUIButtonBehavior.h"
#include "gui/ObjectBases/IGUIObject.h"
#include "gui/ObjectBases/IGUITextOwner.h"
#include "gui/SettingTypes/CGUIString.h"
#include "maths/Vector2D.h"
class CButton : public IGUIObject, public IGUITextOwner, public IGUIButtonBehavior
{
GUI_OBJECT(CButton)
public:
CButton(CGUI& pGUI);
virtual ~CButton();
/**
* @see IGUIObject#ResetStates()
*/
virtual void ResetStates();
/**
* @see IGUIObject#UpdateCachedSize()
*/
virtual void UpdateCachedSize();
/**
+ * @return the object text size.
+ */
+ CSize2D GetTextSize();
+
+ /**
* @see IGUIObject#HandleMessage()
*/
virtual void HandleMessage(SGUIMessage& Message);
/**
* Draws the Button
*/
virtual void Draw();
- /**
- * Populate @param ret with the object's text size.
- */
- void getTextSize(ScriptInterface& scriptInterface, JS::MutableHandleValue ret);
-
protected:
/**
* Sets up text, should be called every time changes has been
* made that can change the visual.
*/
void SetupText();
/**
* Picks the text color depending on current object settings.
*/
const CGUIColor& ChooseColor();
/**
* Placement of text.
*/
CVector2D m_TextPos;
virtual void CreateJSObject();
// Settings
float m_BufferZone;
CGUIString m_Caption;
CStrW m_Font;
CGUISpriteInstance m_Sprite;
CGUISpriteInstance m_SpriteOver;
CGUISpriteInstance m_SpritePressed;
CGUISpriteInstance m_SpriteDisabled;
EAlign m_TextAlign;
EVAlign m_TextVAlign;
CGUIColor m_TextColor;
CGUIColor m_TextColorOver;
CGUIColor m_TextColorPressed;
CGUIColor m_TextColorDisabled;
};
#endif // INCLUDED_CBUTTON
Index: ps/trunk/source/gui/ObjectTypes/CList.cpp
===================================================================
--- ps/trunk/source/gui/ObjectTypes/CList.cpp (revision 25224)
+++ ps/trunk/source/gui/ObjectTypes/CList.cpp (revision 25225)
@@ -1,508 +1,504 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "CList.h"
#include "gui/CGUI.h"
#include "gui/CGUIScrollBarVertical.h"
#include "gui/SettingTypes/CGUIColor.h"
#include "gui/SettingTypes/CGUIList.h"
-#include "gui/Scripting/JSInterface_GUIProxy.h"
#include "lib/external_libraries/libsdl.h"
#include "lib/timer.h"
const CStr CList::EventNameSelectionChange = "SelectionChange";
const CStr CList::EventNameHoverChange = "HoverChange";
const CStr CList::EventNameMouseLeftClickItem = "MouseLeftClickItem";
const CStr CList::EventNameMouseLeftDoubleClickItem = "MouseLeftDoubleClickItem";
CList::CList(CGUI& pGUI)
: IGUIObject(pGUI),
IGUITextOwner(*static_cast(this)),
IGUIScrollBarOwner(*static_cast(this)),
m_Modified(false),
m_PrevSelectedItem(-1),
m_LastItemClickTime(0),
m_BufferZone(),
m_Font(),
m_ScrollBar(),
m_ScrollBarStyle(),
m_ScrollBottom(false),
m_SoundDisabled(),
m_SoundSelected(),
m_Sprite(),
m_SpriteSelectArea(),
m_TextAlign(),
m_TextColor(),
m_TextColorSelected(),
m_Selected(),
m_AutoScroll(),
m_Hovered(),
m_List(),
m_ListData()
{
RegisterSetting("buffer_zone", m_BufferZone);
RegisterSetting("font", m_Font);
RegisterSetting("scrollbar", m_ScrollBar);
RegisterSetting("scrollbar_style", m_ScrollBarStyle);
RegisterSetting("scroll_bottom", m_ScrollBottom);
RegisterSetting("sound_disabled", m_SoundDisabled);
RegisterSetting("sound_selected", m_SoundSelected);
RegisterSetting("sprite", m_Sprite);
// Add sprite_disabled! TODO
RegisterSetting("sprite_selectarea", m_SpriteSelectArea);
RegisterSetting("text_align", m_TextAlign);
RegisterSetting("textcolor", m_TextColor);
RegisterSetting("textcolor_selected", m_TextColorSelected);
RegisterSetting("selected", m_Selected); // Index selected. -1 is none.
RegisterSetting("auto_scroll", m_AutoScroll);
RegisterSetting("hovered", m_Hovered);
// Each list item has both a name (in 'list') and an associated data string (in 'list_data')
RegisterSetting("list", m_List);
RegisterSetting("list_data", m_ListData);
SetSetting("scrollbar", false, true);
SetSetting("selected", -1, true);
SetSetting("hovered", -1, true);
SetSetting("auto_scroll", false, true);
// Add scroll-bar
CGUIScrollBarVertical* bar = new CGUIScrollBarVertical(pGUI);
bar->SetRightAligned(true);
AddScrollBar(bar);
}
CList::~CList()
{
}
void CList::SetupText()
{
SetupText(false);
}
void CList::SetupText(bool append)
{
m_Modified = true;
if (!append)
// Delete all generated texts.
// TODO: try to be cleverer if we want to update items before the end.
m_GeneratedTexts.clear();
float width = GetListRect().GetWidth();
bool bottom = false;
if (m_ScrollBar && GetScrollBar(0).GetStyle())
{
if (m_ScrollBottom && GetScrollBar(0).GetPos() > GetScrollBar(0).GetMaxPos() - 1.5f)
bottom = true;
// remove scrollbar if applicable
width -= GetScrollBar(0).GetStyle()->m_Width;
}
// Generate texts
float buffered_y = 0.f;
if (append && !m_ItemsYPositions.empty())
buffered_y = m_ItemsYPositions.back();
m_ItemsYPositions.resize(m_List.m_Items.size() + 1);
for (size_t i = append ? m_List.m_Items.size() - 1 : 0; i < m_List.m_Items.size(); ++i)
{
CGUIText* text;
if (!m_List.m_Items[i].GetOriginalString().empty())
text = &AddText(m_List.m_Items[i], m_Font, width, m_BufferZone);
else
{
// Minimum height of a space character of the current font size
CGUIString align_string;
align_string.SetValue(L" ");
text = &AddText(align_string, m_Font, width, m_BufferZone);
}
m_ItemsYPositions[i] = buffered_y;
buffered_y += text->GetSize().Height;
}
m_ItemsYPositions[m_List.m_Items.size()] = buffered_y;
// Setup scrollbar
if (m_ScrollBar)
{
GetScrollBar(0).SetScrollRange(m_ItemsYPositions.back());
GetScrollBar(0).SetScrollSpace(GetListRect().GetHeight());
CRect rect = GetListRect();
GetScrollBar(0).SetX(rect.right);
GetScrollBar(0).SetY(rect.top);
GetScrollBar(0).SetZ(GetBufferedZ());
GetScrollBar(0).SetLength(rect.bottom - rect.top);
if (bottom)
GetScrollBar(0).SetPos(GetScrollBar(0).GetMaxPos());
}
}
void CList::ResetStates()
{
IGUIObject::ResetStates();
IGUIScrollBarOwner::ResetStates();
}
void CList::UpdateCachedSize()
{
IGUIObject::UpdateCachedSize();
IGUITextOwner::UpdateCachedSize();
}
void CList::HandleMessage(SGUIMessage& Message)
{
IGUIObject::HandleMessage(Message);
IGUIScrollBarOwner::HandleMessage(Message);
//IGUITextOwner::HandleMessage(Message); <== placed it after the switch instead!
m_Modified = false;
switch (Message.type)
{
case GUIM_SETTINGS_UPDATED:
if (Message.value == "list")
SetupText();
// If selected is changed, call "SelectionChange"
if (Message.value == "selected")
{
// TODO: Check range
if (m_AutoScroll)
UpdateAutoScroll();
ScriptEvent(EventNameSelectionChange);
}
if (Message.value == "scrollbar")
SetupText();
// Update scrollbar
if (Message.value == "scrollbar_style")
{
GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle);
SetupText();
}
break;
case GUIM_MOUSE_PRESS_LEFT:
{
if (!m_Enabled)
{
PlaySound(m_SoundDisabled);
break;
}
int hovered = GetHoveredItem();
if (hovered == -1)
break;
SetSetting("selected", hovered, true);
UpdateAutoScroll();
PlaySound(m_SoundSelected);
if (timer_Time() - m_LastItemClickTime < SELECT_DBLCLICK_RATE && hovered == m_PrevSelectedItem)
this->SendMouseEvent(GUIM_MOUSE_DBLCLICK_LEFT_ITEM, EventNameMouseLeftDoubleClickItem);
else
this->SendMouseEvent(GUIM_MOUSE_PRESS_LEFT_ITEM, EventNameMouseLeftClickItem);
m_LastItemClickTime = timer_Time();
m_PrevSelectedItem = hovered;
break;
}
case GUIM_MOUSE_LEAVE:
{
if (m_Hovered == -1)
break;
SetSetting("hovered", -1, true);
ScriptEvent(EventNameHoverChange);
break;
}
case GUIM_MOUSE_OVER:
{
int hovered = GetHoveredItem();
if (hovered == m_Hovered)
break;
SetSetting("hovered", hovered, true);
ScriptEvent(EventNameHoverChange);
break;
}
case GUIM_LOAD:
{
GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle);
break;
}
default:
break;
}
IGUITextOwner::HandleMessage(Message);
}
InReaction CList::ManuallyHandleKeys(const SDL_Event_* ev)
{
InReaction result = IN_PASS;
if (ev->ev.type == SDL_KEYDOWN)
{
int szChar = ev->ev.key.keysym.sym;
switch (szChar)
{
case SDLK_HOME:
SelectFirstElement();
UpdateAutoScroll();
result = IN_HANDLED;
break;
case SDLK_END:
SelectLastElement();
UpdateAutoScroll();
result = IN_HANDLED;
break;
case SDLK_UP:
SelectPrevElement();
UpdateAutoScroll();
result = IN_HANDLED;
break;
case SDLK_DOWN:
SelectNextElement();
UpdateAutoScroll();
result = IN_HANDLED;
break;
case SDLK_PAGEUP:
GetScrollBar(0).ScrollMinusPlenty();
result = IN_HANDLED;
break;
case SDLK_PAGEDOWN:
GetScrollBar(0).ScrollPlusPlenty();
result = IN_HANDLED;
break;
default: // Do nothing
result = IN_PASS;
}
}
return result;
}
void CList::Draw()
{
DrawList(m_Selected, m_Sprite, m_SpriteSelectArea, m_TextColor);
}
void CList::DrawList(const int& selected, const CGUISpriteInstance& sprite, const CGUISpriteInstance& sprite_selectarea, const CGUIColor& textcolor)
{
float bz = GetBufferedZ();
// First call draw on ScrollBarOwner
if (m_ScrollBar)
IGUIScrollBarOwner::Draw();
{
CRect rect = GetListRect();
m_pGUI.DrawSprite(sprite, bz, rect);
float scroll = 0.f;
if (m_ScrollBar)
scroll = GetScrollBar(0).GetPos();
if (selected >= 0 && selected+1 < (int)m_ItemsYPositions.size())
{
// Get rectangle of selection:
CRect rect_sel(rect.left, rect.top + m_ItemsYPositions[selected] - scroll,
rect.right, rect.top + m_ItemsYPositions[selected+1] - scroll);
if (rect_sel.top <= rect.bottom &&
rect_sel.bottom >= rect.top)
{
if (rect_sel.bottom > rect.bottom)
rect_sel.bottom = rect.bottom;
if (rect_sel.top < rect.top)
rect_sel.top = rect.top;
if (m_ScrollBar)
{
// Remove any overlapping area of the scrollbar.
if (rect_sel.right > GetScrollBar(0).GetOuterRect().left &&
rect_sel.right <= GetScrollBar(0).GetOuterRect().right)
rect_sel.right = GetScrollBar(0).GetOuterRect().left;
if (rect_sel.left >= GetScrollBar(0).GetOuterRect().left &&
rect_sel.left < GetScrollBar(0).GetOuterRect().right)
rect_sel.left = GetScrollBar(0).GetOuterRect().right;
}
m_pGUI.DrawSprite(sprite_selectarea, bz + 0.05f, rect_sel);
}
}
for (size_t i = 0; i < m_List.m_Items.size(); ++i)
{
if (m_ItemsYPositions[i+1] - scroll < 0 ||
m_ItemsYPositions[i] - scroll > rect.GetHeight())
continue;
// Clipping area (we'll have to substract the scrollbar)
CRect cliparea = GetListRect();
if (m_ScrollBar)
{
if (cliparea.right > GetScrollBar(0).GetOuterRect().left &&
cliparea.right <= GetScrollBar(0).GetOuterRect().right)
cliparea.right = GetScrollBar(0).GetOuterRect().left;
if (cliparea.left >= GetScrollBar(0).GetOuterRect().left &&
cliparea.left < GetScrollBar(0).GetOuterRect().right)
cliparea.left = GetScrollBar(0).GetOuterRect().right;
}
DrawText(i, textcolor, rect.TopLeft() - CVector2D(0.f, scroll - m_ItemsYPositions[i]), bz + 0.1f, cliparea);
}
}
}
void CList::AddItem(const CGUIString& str, const CGUIString& data)
{
// Do not send a settings-changed message
m_List.m_Items.push_back(str);
m_ListData.m_Items.push_back(data);
SetupText(true);
}
+void CList::AddItem(const CGUIString& strAndData)
+{
+ AddItem(strAndData, strAndData);
+}
+
bool CList::HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile)
{
int elmt_item = pFile->GetElementID("item");
if (child.GetNodeName() == elmt_item)
{
CGUIString vlist;
vlist.SetValue(child.GetText().FromUTF8());
AddItem(vlist, vlist);
return true;
}
return false;
}
void CList::SelectNextElement()
{
if (m_Selected != static_cast(m_List.m_Items.size()) - 1)
{
SetSetting("selected", m_Selected + 1, true);
PlaySound(m_SoundSelected);
}
}
void CList::SelectPrevElement()
{
if (m_Selected > 0)
{
SetSetting("selected", m_Selected - 1, true);
PlaySound(m_SoundSelected);
}
}
void CList::SelectFirstElement()
{
if (m_Selected >= 0)
SetSetting("selected", 0, true);
}
void CList::SelectLastElement()
{
const int index = static_cast(m_List.m_Items.size()) - 1;
if (m_Selected != index)
SetSetting("selected", index, true);
}
void CList::UpdateAutoScroll()
{
// No scrollbar, no scrolling (at least it's not made to work properly).
if (!m_ScrollBar || m_Selected < 0 || static_cast(m_Selected) >= m_ItemsYPositions.size())
return;
float scroll = GetScrollBar(0).GetPos();
// Check upper boundary
if (m_ItemsYPositions[m_Selected] < scroll)
{
GetScrollBar(0).SetPos(m_ItemsYPositions[m_Selected]);
return; // this means, if it wants to align both up and down at the same time
// this will have precedence.
}
// Check lower boundary
CRect rect = GetListRect();
if (m_ItemsYPositions[m_Selected+1]-rect.GetHeight() > scroll)
GetScrollBar(0).SetPos(m_ItemsYPositions[m_Selected+1]-rect.GetHeight());
}
int CList::GetHoveredItem()
{
const float scroll = m_ScrollBar ? GetScrollBar(0).GetPos() : 0.f;
const CRect& rect = GetListRect();
CVector2D mouse = m_pGUI.GetMousePos();
mouse.Y += scroll;
// Mouse is over scrollbar
if (m_ScrollBar && GetScrollBar(0).IsVisible() &&
mouse.X >= GetScrollBar(0).GetOuterRect().left &&
mouse.X <= GetScrollBar(0).GetOuterRect().right)
return -1;
for (size_t i = 0; i < m_List.m_Items.size(); ++i)
if (mouse.Y >= rect.top + m_ItemsYPositions[i] &&
mouse.Y < rect.top + m_ItemsYPositions[i + 1])
return i;
return -1;
}
-
-void CList::CreateJSObject()
-{
- ScriptRequest rq(m_pGUI.GetScriptInterface());
- using ProxyHandler = JSI_GUIProxy>;
- ProxyHandler::CreateJSObject(rq, this, GetGUI().GetProxyData(&ProxyHandler::Singleton()), m_JSObject);
-}
-
Index: ps/trunk/source/gui/ObjectTypes/CList.h
===================================================================
--- ps/trunk/source/gui/ObjectTypes/CList.h (revision 25224)
+++ ps/trunk/source/gui/ObjectTypes/CList.h (revision 25225)
@@ -1,159 +1,164 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_CLIST
#define INCLUDED_CLIST
#include "gui/CGUISprite.h"
#include "gui/ObjectBases/IGUIObject.h"
#include "gui/ObjectBases/IGUIScrollBarOwner.h"
#include "gui/ObjectBases/IGUITextOwner.h"
#include "gui/SettingTypes/CGUIList.h"
#include
/**
* Create a list of elements, where one can be selected
* by the user. The control will use a pre-processed
* text-object for each element, which will be managed
* by the IGUITextOwner structure.
*
* A scroll-bar will appear when needed. This will be
* achieved with the IGUIScrollBarOwner structure.
*/
class CList : public IGUIObject, public IGUIScrollBarOwner, public IGUITextOwner
{
GUI_OBJECT(CList)
public:
CList(CGUI& pGUI);
virtual ~CList();
/**
* @see IGUIObject#ResetStates()
*/
virtual void ResetStates();
/**
* @see IGUIObject#UpdateCachedSize()
*/
virtual void UpdateCachedSize();
/**
* Adds an item last to the list.
*/
virtual void AddItem(const CGUIString& str, const CGUIString& data);
+ /**
+ * Add an item where both parameters are identical.
+ */
+ void AddItem(const CGUIString& strAndData);
+
protected:
/**
* Sets up text, should be called every time changes has been
* made that can change the visual.
* @param append - if true, we assume we only need to render the new element at the end of the list.
*/
virtual void SetupText();
virtual void SetupText(bool append);
/**
* @see IGUIObject#HandleMessage()
*/
virtual void HandleMessage(SGUIMessage& Message);
/**
* Handle events manually to catch keyboard inputting.
*/
virtual InReaction ManuallyHandleKeys(const SDL_Event_* ev);
/**
* Draws the List box
*/
virtual void Draw();
virtual void CreateJSObject();
/**
* Easy select elements functions
*/
virtual void SelectNextElement();
virtual void SelectPrevElement();
virtual void SelectFirstElement();
virtual void SelectLastElement();
/**
* Handle the \- tag.
*/
virtual bool HandleAdditionalChildren(const XMBElement& child, CXeromyces* pFile);
// Called every time the auto-scrolling should be checked.
void UpdateAutoScroll();
// Extended drawing interface, this is so that classes built on the this one
// can use other sprite names.
virtual void DrawList(const int& selected, const CGUISpriteInstance& sprite, const CGUISpriteInstance& sprite_selected, const CGUIColor& textcolor);
// Get the area of the list. This is so that it can easily be changed, like in CDropDown
// where the area is not equal to m_CachedActualSize.
virtual CRect GetListRect() const { return m_CachedActualSize; }
// Returns whether SetupText() has run since the last message was received
// (and thus whether list items have possibly changed).
virtual bool GetModified() const { return m_Modified; }
/**
* List of each element's relative y position. Will be
* one larger than m_Items, because it will end with the
* bottom of the last element. First element will always
* be zero, but still stored for easy handling.
*/
std::vector
m_ItemsYPositions;
virtual int GetHoveredItem();
// Settings
float m_BufferZone;
CStrW m_Font;
bool m_ScrollBar;
CStr m_ScrollBarStyle;
bool m_ScrollBottom;
CStrW m_SoundDisabled;
CStrW m_SoundSelected;
CGUISpriteInstance m_Sprite;
CGUISpriteInstance m_SpriteSelectArea;
EAlign m_TextAlign;
CGUIColor m_TextColor;
CGUIColor m_TextColorSelected;
i32 m_Selected;
bool m_AutoScroll;
i32 m_Hovered;
CGUIList m_List;
CGUIList m_ListData;
private:
static const CStr EventNameSelectionChange;
static const CStr EventNameHoverChange;
static const CStr EventNameMouseLeftClickItem;
static const CStr EventNameMouseLeftDoubleClickItem;
// Whether the list's items have been modified since last handling a message.
bool m_Modified;
// Used for doubleclick registration
int m_PrevSelectedItem;
// Last time a click on an item was issued
double m_LastItemClickTime;
};
#endif // INCLUDED_CLIST
Index: ps/trunk/source/gui/ObjectTypes/CText.cpp
===================================================================
--- ps/trunk/source/gui/ObjectTypes/CText.cpp (revision 25224)
+++ ps/trunk/source/gui/ObjectTypes/CText.cpp (revision 25225)
@@ -1,267 +1,257 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "CText.h"
#include "gui/CGUI.h"
#include "gui/CGUIScrollBarVertical.h"
#include "gui/CGUIText.h"
-#include "gui/Scripting/JSInterface_GUIProxy.h"
#include "scriptinterface/ScriptInterface.h"
CText::CText(CGUI& pGUI)
: IGUIObject(pGUI),
IGUIScrollBarOwner(*static_cast(this)),
IGUITextOwner(*static_cast(this)),
m_BufferZone(),
m_Caption(),
m_Clip(),
m_Font(),
m_ScrollBar(),
m_ScrollBarStyle(),
m_ScrollBottom(),
m_ScrollTop(),
m_Sprite(),
m_TextAlign(),
m_TextVAlign(),
m_TextColor(),
m_TextColorDisabled(),
m_IconTooltip(),
m_IconTooltipStyle()
{
RegisterSetting("buffer_zone", m_BufferZone);
RegisterSetting("caption", m_Caption);
RegisterSetting("clip", m_Clip);
RegisterSetting("font", m_Font);
RegisterSetting("scrollbar", m_ScrollBar);
RegisterSetting("scrollbar_style", m_ScrollBarStyle);
RegisterSetting("scroll_bottom", m_ScrollBottom);
RegisterSetting("scroll_top", m_ScrollTop);
RegisterSetting("sprite", m_Sprite);
RegisterSetting("text_align", m_TextAlign);
RegisterSetting("text_valign", m_TextVAlign);
RegisterSetting("textcolor", m_TextColor);
RegisterSetting("textcolor_disabled", m_TextColorDisabled);
// Private settings
RegisterSetting("_icon_tooltip", m_IconTooltip);
RegisterSetting("_icon_tooltip_style", m_IconTooltipStyle);
//SetSetting("ghost", true, true);
SetSetting("scrollbar", false, true);
SetSetting("clip", true, true);
// Add scroll-bar
CGUIScrollBarVertical* bar = new CGUIScrollBarVertical(pGUI);
bar->SetRightAligned(true);
AddScrollBar(bar);
// Add text
AddText();
}
CText::~CText()
{
}
void CText::SetupText()
{
if (m_GeneratedTexts.empty())
return;
float width = m_CachedActualSize.GetWidth();
// remove scrollbar if applicable
if (m_ScrollBar && GetScrollBar(0).GetStyle())
width -= GetScrollBar(0).GetStyle()->m_Width;
m_GeneratedTexts[0] = CGUIText(m_pGUI, m_Caption, m_Font, width, m_BufferZone, this);
if (!m_ScrollBar)
CalculateTextPosition(m_CachedActualSize, m_TextPos, m_GeneratedTexts[0]);
// Setup scrollbar
if (m_ScrollBar)
{
// If we are currently scrolled to the bottom of the text,
// then add more lines of text, update the scrollbar so we
// stick to the bottom.
// (Use 1.5px delta so this triggers the first time caption is set)
bool bottom = false;
if (m_ScrollBottom && GetScrollBar(0).GetPos() > GetScrollBar(0).GetMaxPos() - 1.5f)
bottom = true;
GetScrollBar(0).SetScrollRange(m_GeneratedTexts[0].GetSize().Height);
GetScrollBar(0).SetScrollSpace(m_CachedActualSize.GetHeight());
GetScrollBar(0).SetX(m_CachedActualSize.right);
GetScrollBar(0).SetY(m_CachedActualSize.top);
GetScrollBar(0).SetZ(GetBufferedZ());
GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top);
if (bottom)
GetScrollBar(0).SetPos(GetScrollBar(0).GetMaxPos());
if (m_ScrollTop)
GetScrollBar(0).SetPos(0.0f);
}
}
void CText::ResetStates()
{
IGUIObject::ResetStates();
IGUIScrollBarOwner::ResetStates();
}
void CText::UpdateCachedSize()
{
IGUIObject::UpdateCachedSize();
IGUITextOwner::UpdateCachedSize();
}
+CSize2D CText::GetTextSize()
+{
+ UpdateText();
+ return m_GeneratedTexts[0].GetSize();
+}
+
void CText::HandleMessage(SGUIMessage& Message)
{
IGUIObject::HandleMessage(Message);
IGUIScrollBarOwner::HandleMessage(Message);
//IGUITextOwner::HandleMessage(Message); <== placed it after the switch instead!
switch (Message.type)
{
case GUIM_SETTINGS_UPDATED:
if (Message.value == "scrollbar")
SetupText();
// Update scrollbar
if (Message.value == "scrollbar_style")
{
GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle);
SetupText();
}
break;
case GUIM_MOUSE_WHEEL_DOWN:
{
GetScrollBar(0).ScrollPlus();
// Since the scroll was changed, let's simulate a mouse movement
// to check if scrollbar now is hovered
SGUIMessage msg(GUIM_MOUSE_MOTION);
HandleMessage(msg);
break;
}
case GUIM_MOUSE_WHEEL_UP:
{
GetScrollBar(0).ScrollMinus();
// Since the scroll was changed, let's simulate a mouse movement
// to check if scrollbar now is hovered
SGUIMessage msg(GUIM_MOUSE_MOTION);
HandleMessage(msg);
break;
}
case GUIM_LOAD:
{
GetScrollBar(0).SetX(m_CachedActualSize.right);
GetScrollBar(0).SetY(m_CachedActualSize.top);
GetScrollBar(0).SetZ(GetBufferedZ());
GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top);
GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle);
break;
}
default:
break;
}
IGUITextOwner::HandleMessage(Message);
}
void CText::Draw()
{
float bz = GetBufferedZ();
if (m_ScrollBar)
IGUIScrollBarOwner::Draw();
m_pGUI.DrawSprite(m_Sprite, bz, m_CachedActualSize);
float scroll = 0.f;
if (m_ScrollBar)
scroll = GetScrollBar(0).GetPos();
// Clipping area (we'll have to subtract the scrollbar)
CRect cliparea;
if (m_Clip)
{
cliparea = m_CachedActualSize;
if (m_ScrollBar)
{
// subtract scrollbar from cliparea
if (cliparea.right > GetScrollBar(0).GetOuterRect().left &&
cliparea.right <= GetScrollBar(0).GetOuterRect().right)
cliparea.right = GetScrollBar(0).GetOuterRect().left;
if (cliparea.left >= GetScrollBar(0).GetOuterRect().left &&
cliparea.left < GetScrollBar(0).GetOuterRect().right)
cliparea.left = GetScrollBar(0).GetOuterRect().right;
}
}
const CGUIColor& color = m_Enabled ? m_TextColor : m_TextColorDisabled;
if (m_ScrollBar)
DrawText(0, color, m_CachedActualSize.TopLeft() - CVector2D(0.f, scroll), bz + 0.1f, cliparea);
else
DrawText(0, color, m_TextPos, bz + 0.1f, cliparea);
}
bool CText::MouseOverIcon()
{
for (const CGUIText& guitext : m_GeneratedTexts)
for (const CGUIText::SSpriteCall& spritecall : guitext.GetSpriteCalls())
{
// Check mouse over sprite
if (!spritecall.m_Area.PointInside(m_pGUI.GetMousePos() - m_CachedActualSize.TopLeft()))
continue;
// If tooltip exists, set the property
if (!spritecall.m_Tooltip.empty())
{
SetSettingFromString("_icon_tooltip_style", spritecall.m_TooltipStyle, true);
SetSettingFromString("_icon_tooltip", spritecall.m_Tooltip, true);
}
return true;
}
return false;
}
-
-
-void CText::CreateJSObject()
-{
- ScriptRequest rq(m_pGUI.GetScriptInterface());
- using ProxyHandler = JSI_GUIProxy>;
- ProxyHandler::CreateJSObject(rq, this, GetGUI().GetProxyData(&ProxyHandler::Singleton()), m_JSObject);
-}
-
-void CText::getTextSize(ScriptInterface& scriptInterface, JS::MutableHandleValue ret)
-{
- ScriptRequest rq(scriptInterface);
- UpdateText();
- ScriptInterface::ToJSVal(rq, ret, m_GeneratedTexts[0].GetSize());
-}
Index: ps/trunk/source/gui/ObjectTypes/CText.h
===================================================================
--- ps/trunk/source/gui/ObjectTypes/CText.h (revision 25224)
+++ ps/trunk/source/gui/ObjectTypes/CText.h (revision 25225)
@@ -1,98 +1,98 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_CTEXT
#define INCLUDED_CTEXT
#include "gui/CGUISprite.h"
#include "gui/ObjectBases/IGUIObject.h"
#include "gui/ObjectBases/IGUIScrollBarOwner.h"
#include "gui/ObjectBases/IGUITextOwner.h"
#include "gui/SettingTypes/CGUIString.h"
/**
* Text field that just displays static text.
*/
class CText : public IGUIObject, public IGUIScrollBarOwner, public IGUITextOwner
{
GUI_OBJECT(CText)
public:
CText(CGUI& pGUI);
virtual ~CText();
/**
* @see IGUIObject#ResetStates()
*/
virtual void ResetStates();
/**
* @see IGUIObject#UpdateCachedSize()
*/
virtual void UpdateCachedSize();
/**
- * Test if mouse position is over an icon
+ * @return the object text size.
*/
- virtual bool MouseOverIcon();
+ CSize2D GetTextSize();
/**
- * Populate @param ret with the object's text size.
+ * Test if mouse position is over an icon
*/
- void getTextSize(ScriptInterface& scriptInterface, JS::MutableHandleValue ret);
+ virtual bool MouseOverIcon();
protected:
/**
* Sets up text, should be called every time changes has been
* made that can change the visual.
*/
void SetupText();
/**
* @see IGUIObject#HandleMessage()
*/
virtual void HandleMessage(SGUIMessage& Message);
/**
* Draws the Text
*/
virtual void Draw();
virtual void CreateJSObject();
/**
* Placement of text. Ignored when scrollbars are active.
*/
CVector2D m_TextPos;
// Settings
float m_BufferZone;
CGUIString m_Caption;
bool m_Clip;
CStrW m_Font;
bool m_ScrollBar;
CStr m_ScrollBarStyle;
bool m_ScrollBottom;
bool m_ScrollTop;
CGUISpriteInstance m_Sprite;
EAlign m_TextAlign;
EVAlign m_TextVAlign;
CGUIColor m_TextColor;
CGUIColor m_TextColorDisabled;
CStrW m_IconTooltip;
CStr m_IconTooltipStyle;
};
#endif // INCLUDED_CTEXT
Index: ps/trunk/source/gui/Scripting/JSInterface_GUIProxy.cpp
===================================================================
--- ps/trunk/source/gui/Scripting/JSInterface_GUIProxy.cpp (nonexistent)
+++ ps/trunk/source/gui/Scripting/JSInterface_GUIProxy.cpp (revision 25225)
@@ -0,0 +1,60 @@
+/* Copyright (C) 2021 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "JSInterface_GUIProxy_impl.h"
+
+#include "gui/ObjectBases/IGUIObject.h"
+#include "gui/ObjectTypes/CButton.h"
+#include "gui/ObjectTypes/CList.h"
+#include "gui/ObjectTypes/CText.h"
+
+// Called for every specialization - adds the common interface.
+template<>
+void JSI_GUIProxy::CreateFunctions(const ScriptRequest& rq, GUIProxyProps* cache)
+{
+ CreateFunction<&IGUIObject::GetName>(rq, cache, "toString");
+ CreateFunction<&IGUIObject::GetName>(rq, cache, "toSource");
+ CreateFunction<&IGUIObject::SetFocus>(rq, cache, "focus");
+ CreateFunction<&IGUIObject::ReleaseFocus>(rq, cache, "blur");
+ CreateFunction<&IGUIObject::GetComputedSize>(rq, cache, "getComputedSize");
+}
+DECLARE_GUIPROXY(IGUIObject);
+
+// Implement derived types below.
+
+// CButton
+template<> void JSI_GUIProxy::CreateFunctions(const ScriptRequest& rq, GUIProxyProps* cache)
+{
+ CreateFunction<&CButton::GetTextSize>(rq, cache, "getTextSize");
+}
+DECLARE_GUIPROXY(CButton);
+
+// CText
+template<> void JSI_GUIProxy::CreateFunctions(const ScriptRequest& rq, GUIProxyProps* cache)
+{
+ CreateFunction<&CText::GetTextSize>(rq, cache, "getTextSize");
+}
+DECLARE_GUIPROXY(CText);
+
+// CList
+template<> void JSI_GUIProxy::CreateFunctions(const ScriptRequest& rq, GUIProxyProps* cache)
+{
+ CreateFunction(&CList::AddItem)>(rq, cache, "addItem");
+}
+DECLARE_GUIPROXY(CList);
Property changes on: ps/trunk/source/gui/Scripting/JSInterface_GUIProxy.cpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/source/gui/Scripting/JSInterface_GUIProxy.h
===================================================================
--- ps/trunk/source/gui/Scripting/JSInterface_GUIProxy.h (revision 25224)
+++ ps/trunk/source/gui/Scripting/JSInterface_GUIProxy.h (revision 25225)
@@ -1,171 +1,214 @@
-/* Copyright (C) 2019 Wildfire Games.
+/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_JSI_GUIPROXY
#define INCLUDED_JSI_GUIPROXY
#include "scriptinterface/ScriptExtraHeaders.h"
+#include
#include
class ScriptInterface;
class ScriptRequest;
+template
+class JSI_GUIProxy;
+
// See JSI_GuiProxy below
#if GCC_VERSION
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
#elif MSC_VERSION
# pragma warning(push, 1)
# pragma warning(disable: 4265)
#endif
/**
+ * JS GUI proxies need to store some private data.
+ * This class is responsible for deleting that data.
+ */
+class IGUIProxyObject final
+{
+ template
+ friend class JSI_GUIProxy;
+ friend std::unique_ptr std::make_unique();
+public:
+ JSObject* Get() const
+ {
+ return m_Object.get();
+ }
+
+ using PrivateData = IGUIObject*;
+ template
+ static T* FromPrivateSlot(JSObject* obj)
+ {
+ return static_cast(static_cast(js::GetProxyPrivate(obj).toPrivate()));
+ }
+
+protected:
+ IGUIProxyObject() = default;
+ IGUIProxyObject(const IGUIProxyObject&) = delete;
+ IGUIProxyObject(IGUIProxyObject&&) = delete;
+
+ JS::PersistentRootedObject m_Object;
+ PrivateData m_Ptr;
+};
+
+/**
* Proxies need to store some data whose lifetime is tied to an interface.
* This is the virtual interface of that data.
*/
class GUIProxyProps
{
public:
virtual ~GUIProxyProps() {};
// @return true if @param name exists in this cache.
virtual bool has(const std::string& name) const = 0;
// @return the JSFunction matching @param name. Must call has() first as it can assume existence.
virtual JSObject* get(const std::string& name) const = 0;
- virtual bool setFunction(const ScriptRequest& rq, const std::string& name, JSNative function, int nargs) = 0;
+ virtual bool setFunction(const ScriptRequest& rq, const std::string& name, JSFunction* function) = 0;
};
/**
* Handles the js interface with C++ GUI objects.
* Proxy handlers must live for at least as long as the JS runtime
* where a proxy object with that handler was created. The reason is that
* proxy handlers are called during GC, such as on runtime destruction.
* In practical terms, this means "just keep them static and store no data".
*
* GUI Objects only exist in C++ and have no JS-only properties.
* As such, there is no "target" JS object that this proxy could point to,
* and thus we should inherit from BaseProxyHandler and not js::Wrapper.
*
* Proxies can be used with prototypes and almost treated like regular JS objects.
* However, the default implementation embarks a lot of code that we don't really need here,
* since the only fanciness is that we cache JSFunction* properties.
* As such, these GUI proxies don't have one and instead override get/set directly.
*
* To add a new JSI_GUIProxy, you'll need to:
- * - overload the GUI Object's CreateJSObject with the correct types and pointers
- * - change the CGUI::AddObjectTypes method
- * - explicitly instantiate the template.
+ * - overload CreateJSObject in your class header.
+ * - change the CGUI::AddObjectTypes method.
+ * - explicitly instantiate the template & CreateJSObject via DECLARE_GUIPROXY.
*
*/
template
class JSI_GUIProxy : public js::BaseProxyHandler
{
+ // Need to friend other specializations so CreateFunctions() can call the IGUIObject version in all codepaths.
+ template
+ friend class JSI_GUIProxy;
public:
// Access the js::Class of the Proxy.
static JSClass& ClassDefinition();
// For convenience, this is the single instantiated JSI_GUIProxy.
static JSI_GUIProxy& Singleton();
// Call this in CGUI::AddObjectTypes.
static std::pair CreateData(ScriptInterface& scriptInterface);
- static void CreateJSObject(const ScriptRequest& rq, GUIObjectType* ptr, GUIProxyProps* data, JS::PersistentRootedObject& val);
+ // Create the JS object, the proxy, the data and wrap it in a convenient unique_ptr.
+ static std::unique_ptr CreateJSObject(const ScriptRequest& rq, GUIObjectType* ptr, GUIProxyProps* data);
protected:
// @param family can't be nullptr because that's used for some DOM object and it crashes.
JSI_GUIProxy() : BaseProxyHandler(this, false, false) {};
// Note: SM provides no virtual destructor for baseProxyHandler.
// This also enforces making proxy handlers dataless static variables.
~JSI_GUIProxy() {};
// The default implementations need to know the type of the GUIProxyProps for this proxy type.
// This is done by specializing this struct's alias type.
struct PropCache;
// Specialize this to define the custom properties of this type.
static void CreateFunctions(const ScriptRequest& rq, GUIProxyProps* cache);
+ // Convenience helper for the above.
+ template
+ static void CreateFunction(const ScriptRequest& rq, GUIProxyProps* cache, const std::string& name);
+
// This handles returning custom properties. Specialize this if needed.
bool PropGetter(JS::HandleObject proxy, const std::string& propName, JS::MutableHandleValue vp) const;
protected:
// BaseProxyHandler interface below
// Handler for `object.x`
virtual bool get(JSContext* cx, JS::HandleObject proxy, JS::HandleValue receiver, JS::HandleId id, JS::MutableHandleValue vp) const override final;
// Handler for `object.x = y;`
virtual bool set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue vp,
JS::HandleValue receiver, JS::ObjectOpResult& result) const final;
// Handler for `delete object.x;`
virtual bool delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::ObjectOpResult& result) const override final;
// The following methods are not provided by BaseProxyHandler.
// We provide defaults that do nothing (some raise JS exceptions).
// The JS code will see undefined when querying a property descriptor.
virtual bool getOwnPropertyDescriptor(JSContext* UNUSED(cx), JS::HandleObject UNUSED(proxy), JS::HandleId UNUSED(id),
JS::MutableHandle UNUSED(desc)) const override
{
return true;
}
// Throw an exception is JS code attempts defining a property.
virtual bool defineProperty(JSContext* UNUSED(cx), JS::HandleObject UNUSED(proxy), JS::HandleId UNUSED(id),
JS::Handle UNUSED(desc), JS::ObjectOpResult& UNUSED(result)) const override
{
return false;
}
// No accessible properties.
virtual bool ownPropertyKeys(JSContext* UNUSED(cx), JS::HandleObject UNUSED(proxy), JS::MutableHandleIdVector UNUSED(props)) const override
{
return true;
}
// Nothing to enumerate.
virtual bool enumerate(JSContext* UNUSED(cx), JS::HandleObject UNUSED(proxy), JS::MutableHandleIdVector UNUSED(props)) const override
{
return true;
}
// Throw an exception is JS attempts to query the prototype.
virtual bool getPrototypeIfOrdinary(JSContext* UNUSED(cx), JS::HandleObject UNUSED(proxy), bool* UNUSED(isOrdinary), JS::MutableHandleObject UNUSED(protop)) const override
{
return false;
}
// Throw an exception - no prototype to set.
virtual bool setImmutablePrototype(JSContext* UNUSED(cx), JS::HandleObject UNUSED(proxy), bool* UNUSED(succeeded)) const override
{
return false;
}
// We are not extensible.
virtual bool preventExtensions(JSContext* UNUSED(cx), JS::HandleObject UNUSED(proxy), JS::ObjectOpResult& UNUSED(result)) const override
{
return true;
}
virtual bool isExtensible(JSContext* UNUSED(cx), JS::HandleObject UNUSED(proxy), bool* extensible) const override
{
*extensible = false;
return true;
}
};
#if GCC_VERSION
# pragma GCC diagnostic pop
#elif MSC_VERSION
# pragma warning(pop)
#endif
#endif // INCLUDED_JSI_GUIPROXY
Index: ps/trunk/source/gui/Scripting/JSInterface_GUIProxy_impl.h
===================================================================
--- ps/trunk/source/gui/Scripting/JSInterface_GUIProxy_impl.h (revision 25224)
+++ ps/trunk/source/gui/Scripting/JSInterface_GUIProxy_impl.h (revision 25225)
@@ -1,291 +1,309 @@
-/* Copyright (C) 2020 Wildfire Games.
+/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
// This file is included directly into actual implementation files.
#include "JSInterface_GUIProxy.h"
#include "gui/CGUI.h"
#include "gui/CGUISetting.h"
#include "gui/ObjectBases/IGUIObject.h"
#include "ps/CLogger.h"
+#include "scriptinterface/FunctionWrapper.h"
#include "scriptinterface/ScriptExtraHeaders.h"
#include "scriptinterface/ScriptInterface.h"
#include
template
JSClass& JSI_GUIProxy::ClassDefinition()
{
static JSClass c = PROXY_CLASS_DEF("GUIObjectProxy", JSCLASS_HAS_CACHED_PROTO(JSProto_Proxy) | JSCLASS_HAS_RESERVED_SLOTS(1));
return c;
}
template
JSI_GUIProxy& JSI_GUIProxy::Singleton()
{
static JSI_GUIProxy s;
return s;
}
+// Call this for every specialised type. You will need to override IGUIObject::CreateJSObject() in your class interface.
+#define DECLARE_GUIPROXY(Type) \
+void Type::CreateJSObject() \
+{ \
+ ScriptRequest rq(m_pGUI.GetScriptInterface()); \
+ using ProxyHandler = JSI_GUIProxy>; \
+ m_JSObject = ProxyHandler::CreateJSObject(rq, this, GetGUI().GetProxyData(&ProxyHandler::Singleton())); \
+} \
+template class JSI_GUIProxy;
+
// Use a common namespace to avoid duplicating the symbols un-necessarily.
namespace JSInterface_GUIProxy
{
-/**
- * Conveniently wrap a simple C++ function to a JSNative.
- */
-template
-inline bool apply_to(JSContext* cx, uint argc, JS::Value* vp)
+template
+inline T* GuiObjectGetter(const ScriptRequest&, JS::CallArgs& args)
{
- JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
- OG* e = static_cast(js::GetProxyPrivate(args.thisv().toObjectOrNull()).toPrivate());
- if (!e)
- return false;
-
- (static_cast(e)->*(funcptr))(*(ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface), args.rval());
-
- return true;
+ return IGUIProxyObject::FromPrivateSlot(args.thisv().toObjectOrNull());
}
// Default implementation of the cache via unordered_map
class MapCache : public GUIProxyProps
{
public:
virtual ~MapCache() {};
virtual bool has(const std::string& name) const override
{
return m_Functions.find(name) != m_Functions.end();
}
virtual JSObject* get(const std::string& name) const override
{
return m_Functions.at(name).get();
}
- virtual bool setFunction(const ScriptRequest& rq, const std::string& name, JSNative function, int nargs) override
+ virtual bool setFunction(const ScriptRequest& rq, const std::string& name, JSFunction* function) override
{
- m_Functions[name].init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, function, nargs, 0, name.c_str())));
+ m_Functions[name].init(rq.cx, JS_GetFunctionObject(function));
return true;
}
protected:
std::unordered_map m_Functions;
};
}
// The default propcache is a MapCache.
template
struct JSI_GUIProxy::PropCache
{
using type = JSInterface_GUIProxy::MapCache;
};
template
bool JSI_GUIProxy::PropGetter(JS::HandleObject proxy, const std::string& propName, JS::MutableHandleValue vp) const
{
using PropertyCache = typename PropCache::type;
// Since we know at compile time what the type actually is, avoid the virtual call.
const PropertyCache* data = static_cast(static_cast(js::GetProxyReservedSlot(proxy, 0).toPrivate()));
if (data->has(propName))
{
vp.setObjectOrNull(data->get(propName));
return true;
}
return false;
}
template
std::pair JSI_GUIProxy::CreateData(ScriptInterface& scriptInterface)
{
using PropertyCache = typename PropCache::type;
PropertyCache* data = new PropertyCache();
ScriptRequest rq(scriptInterface);
- CreateFunctions(rq, data);
+
+ // Functions common to all children of IGUIObject.
+ JSI_GUIProxy::CreateFunctions(rq, data);
+
+ // Let derived classes register their own interface.
+ if constexpr (!std::is_same_v)
+ CreateFunctions(rq, data);
return { &Singleton(), data };
}
template
-void JSI_GUIProxy::CreateJSObject(const ScriptRequest& rq, T* ptr, GUIProxyProps* dataPtr, JS::PersistentRootedObject& val)
+template
+void JSI_GUIProxy::CreateFunction(const ScriptRequest& rq, GUIProxyProps* cache, const std::string& name)
+{
+ cache->setFunction(rq, name, ScriptFunction::Create>(rq, name.c_str()));
+}
+
+template
+std::unique_ptr JSI_GUIProxy::CreateJSObject(const ScriptRequest& rq, T* ptr, GUIProxyProps* dataPtr)
{
js::ProxyOptions options;
options.setClass(&ClassDefinition());
+ auto ret = std::make_unique();
+ ret->m_Ptr = static_cast(ptr);
+
JS::RootedValue cppObj(rq.cx), data(rq.cx);
- cppObj.get().setPrivate(ptr);
+ cppObj.get().setPrivate(ret->m_Ptr);
data.get().setPrivate(static_cast(dataPtr));
- val.init(rq.cx, js::NewProxyObject(rq.cx, &Singleton(), cppObj, nullptr, options));
- js::SetProxyReservedSlot(val, 0, data);
+ ret->m_Object.init(rq.cx, js::NewProxyObject(rq.cx, &Singleton(), cppObj, nullptr, options));
+ js::SetProxyReservedSlot(ret->m_Object, 0, data);
+ return ret;
}
template
bool JSI_GUIProxy::get(JSContext* cx, JS::HandleObject proxy, JS::HandleValue UNUSED(receiver), JS::HandleId id, JS::MutableHandleValue vp) const
{
ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface;
ScriptRequest rq(*pScriptInterface);
- T* e = static_cast(js::GetProxyPrivate(proxy.get()).toPrivate());
+ T* e = IGUIProxyObject::FromPrivateSlot(proxy.get());
if (!e)
return false;
JS::RootedValue idval(rq.cx);
if (!JS_IdToValue(rq.cx, id, &idval))
return false;
std::string propName;
if (!ScriptInterface::FromJSVal(rq, idval, propName))
return false;
// Return function properties. Specializable.
if (PropGetter(proxy, propName, vp))
return true;
// Use onWhatever to access event handlers
if (propName.substr(0, 2) == "on")
{
CStr eventName(propName.substr(2));
std::map>::iterator it = e->m_ScriptHandlers.find(eventName);
if (it == e->m_ScriptHandlers.end())
vp.setNull();
else
vp.setObject(*it->second.get());
return true;
}
if (propName == "parent")
{
IGUIObject* parent = e->GetParent();
if (parent)
vp.set(JS::ObjectValue(*parent->GetJSObject()));
else
vp.set(JS::NullValue());
return true;
}
else if (propName == "children")
{
ScriptInterface::CreateArray(rq, vp);
for (size_t i = 0; i < e->m_Children.size(); ++i)
pScriptInterface->SetPropertyInt(vp, i, e->m_Children[i]);
return true;
}
else if (propName == "name")
{
ScriptInterface::ToJSVal(rq, vp, e->GetName());
return true;
}
else if (e->SettingExists(propName))
{
e->m_Settings[propName]->ToJSVal(rq, vp);
return true;
}
LOGERROR("Property '%s' does not exist!", propName.c_str());
return false;
}
template
bool JSI_GUIProxy::set(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::HandleValue vp,
JS::HandleValue UNUSED(receiver), JS::ObjectOpResult& result) const
{
- T* e = static_cast(js::GetProxyPrivate(proxy.get()).toPrivate());
+ T* e = IGUIProxyObject::FromPrivateSlot(proxy.get());
if (!e)
{
LOGERROR("C++ GUI Object could not be found");
return result.fail(JSMSG_OBJECT_REQUIRED);
}
ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface);
JS::RootedValue idval(rq.cx);
if (!JS_IdToValue(rq.cx, id, &idval))
return result.fail(JSMSG_BAD_PROP_ID);
std::string propName;
if (!ScriptInterface::FromJSVal(rq, idval, propName))
return result.fail(JSMSG_BAD_PROP_ID);
if (propName == "name")
{
std::string value;
if (!ScriptInterface::FromJSVal(rq, vp, value))
return result.fail(JSMSG_BAD_PROP_ID);
e->SetName(value);
return result.succeed();
}
JS::RootedObject vpObj(cx);
if (vp.isObject())
vpObj = &vp.toObject();
// Use onWhatever to set event handlers
if (propName.substr(0, 2) == "on")
{
if (vp.isPrimitive() || vp.isNull() || !JS_ObjectIsFunction(&vp.toObject()))
{
LOGERROR("on- event-handlers must be functions");
return result.fail(JSMSG_NOT_FUNCTION);
}
CStr eventName(propName.substr(2));
e->SetScriptHandler(eventName, vpObj);
return result.succeed();
}
if (e->SettingExists(propName))
return e->m_Settings[propName]->FromJSVal(rq, vp, true) ? result.succeed() : result.fail(JSMSG_USER_DEFINED_ERROR);
LOGERROR("Property '%s' does not exist!", propName.c_str());
return result.fail(JSMSG_BAD_PROP_ID);
}
template
bool JSI_GUIProxy::delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, JS::ObjectOpResult& result) const
{
- T* e = static_cast(js::GetProxyPrivate(proxy.get()).toPrivate());
+ T* e = IGUIProxyObject::FromPrivateSlot(proxy.get());
if (!e)
{
LOGERROR("C++ GUI Object could not be found");
return result.fail(JSMSG_OBJECT_REQUIRED);
}
ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface);
JS::RootedValue idval(rq.cx);
if (!JS_IdToValue(rq.cx, id, &idval))
return result.fail(JSMSG_BAD_PROP_ID);
std::string propName;
if (!ScriptInterface::FromJSVal(rq, idval, propName))
return result.fail(JSMSG_BAD_PROP_ID);
// event handlers
if (propName.substr(0, 2) == "on")
{
CStr eventName(propName.substr(2));
e->UnsetScriptHandler(eventName);
return result.succeed();
}
LOGERROR("Only event handlers can be deleted from GUI objects!");
return result.fail(JSMSG_BAD_PROP_ID);
}
Index: ps/trunk/source/scriptinterface/FunctionWrapper.h
===================================================================
--- ps/trunk/source/scriptinterface/FunctionWrapper.h (revision 25224)
+++ ps/trunk/source/scriptinterface/FunctionWrapper.h (revision 25225)
@@ -1,321 +1,330 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_FUNCTIONWRAPPER
#define INCLUDED_FUNCTIONWRAPPER
#include "ScriptInterface.h"
#include "ScriptExceptions.h"
/**
* This class introduces templates to conveniently wrap C++ functions in JSNative functions.
* This _is_ rather template heavy, so compilation times beware.
* The C++ code can have arbitrary arguments and arbitrary return types, so long
* as they can be converted to/from JS using ScriptInterface::ToJSVal (FromJSVal respectively),
* and they are default-constructible (TODO: that can probably changed).
* (This could be a namespace, but I like being able to specify public/private).
*/
class ScriptFunction {
private:
ScriptFunction() = delete;
ScriptFunction(const ScriptFunction&) = delete;
ScriptFunction(ScriptFunction&&) = delete;
/**
* In JS->C++ calls, types are converted using FromJSVal,
* and this requires them to be default-constructible (as that function takes an out parameter)
* thus constref needs to be removed when defining the tuple.
* Exceptions are:
* - const ScriptRequest& (as the first argument only, for implementation simplicity).
* - const ScriptInterface& (as the first argument only, for implementation simplicity).
* - JS::HandleValue
*/
template
using type_transform = std::conditional_t<
std::is_same_v || std::is_same_v,
T,
std::remove_const_t>
>;
/**
* Convenient struct to get info on a [class] [const] function pointer.
* TODO VS19: I ran into a really weird bug with an auto specialisation on this taking function pointers.
* It'd be good to add it back once we upgrade.
*/
template struct args_info;
template
struct args_info
{
static constexpr const size_t nb_args = sizeof...(Types);
using return_type = R;
using object_type = void;
using arg_types = std::tuple...>;
};
template
struct args_info : public args_info { using object_type = C; };
template
struct args_info : public args_info {};
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
/**
* DoConvertFromJS takes a type, a JS argument, and converts.
* The type T must be default constructible (except for HandleValue, which is handled specially).
* (possible) TODO: this could probably be changed if FromJSVal had a different signature.
* @param went_ok - true if the conversion succeeded and went_ok was true before, false otherwise.
*/
template
static std::tuple DoConvertFromJS(const ScriptRequest& rq, JS::CallArgs& args, bool& went_ok)
{
// No need to convert JS values.
if constexpr (std::is_same_v)
{
// Default-construct values that aren't passed by JS.
// TODO: this should perhaps be removed, as it's distinct from C++ default values and kind of tricky.
if (idx >= args.length())
return std::forward_as_tuple(JS::UndefinedHandleValue);
else
{
// GCC (at least < 9) & VS17 prints warnings if arguments are not used in some constexpr branch.
UNUSED2(rq); UNUSED2(args); UNUSED2(went_ok);
return std::forward_as_tuple(args[idx]); // This passes the null handle value if idx is beyond the length of args.
}
}
else
{
// Default-construct values that aren't passed by JS.
// TODO: this should perhaps be removed, as it's distinct from C++ default values and kind of tricky.
if (idx >= args.length())
return std::forward_as_tuple(T{});
else
{
T ret;
went_ok &= ScriptInterface::FromJSVal(rq, args[idx], ret);
return std::forward_as_tuple(ret);
}
}
}
/**
* Recursive wrapper: calls DoConvertFromJS for type T and recurses.
*/
template
static std::tuple DoConvertFromJS(const ScriptRequest& rq, JS::CallArgs& args, bool& went_ok)
{
return std::tuple_cat(DoConvertFromJS(rq, args, went_ok), DoConvertFromJS(rq, args, went_ok));
}
/**
* ConvertFromJS is a wrapper around DoConvertFromJS, and serves to:
* - unwrap the tuple types as a parameter pack
* - handle specific cases for the first argument (cmptPrivate, ScriptRequest).
*
* Trick: to unpack the types of the tuple as a parameter pack, we deduce them from the function signature.
* To do that, we want the tuple in the arguments, but we don't want to actually have to default-instantiate,
* so we'll pass a nullptr that's static_cast to what we want.
*/
template
static std::tuple ConvertFromJS(ScriptInterface::CmptPrivate*, const ScriptRequest& rq, JS::CallArgs& args, bool& went_ok, std::tuple*)
{
if constexpr (sizeof...(Types) == 0)
{
// GCC (at least < 9) & VS17 prints warnings if arguments are not used in some constexpr branch.
UNUSED2(rq); UNUSED2(args); UNUSED2(went_ok);
return {};
}
else
return DoConvertFromJS<0, Types...>(rq, args, went_ok);
}
// Overloads for CmptPrivate* first argument.
template
static std::tuple ConvertFromJS(ScriptInterface::CmptPrivate* cmptPrivate, const ScriptRequest& rq, JS::CallArgs& args, bool& went_ok, std::tuple*)
{
if constexpr (sizeof...(Types) == 0)
{
// GCC (at least < 9) & VS17 prints warnings if arguments are not used in some constexpr branch.
UNUSED2(rq); UNUSED2(args); UNUSED2(went_ok);
return std::forward_as_tuple(cmptPrivate);
}
else
return std::tuple_cat(std::forward_as_tuple(cmptPrivate), DoConvertFromJS<0, Types...>(rq, args, went_ok));
}
// Overloads for ScriptRequest& first argument.
template
static std::tuple ConvertFromJS(ScriptInterface::CmptPrivate*, const ScriptRequest& rq, JS::CallArgs& args, bool& went_ok, std::tuple*)
{
if constexpr (sizeof...(Types) == 0)
{
// GCC (at least < 9) & VS17 prints warnings if arguments are not used in some constexpr branch.
UNUSED2(args); UNUSED2(went_ok);
return std::forward_as_tuple(rq);
}
else
return std::tuple_cat(std::forward_as_tuple(rq), DoConvertFromJS<0, Types...>(rq, args, went_ok));
}
// Overloads for ScriptInterface& first argument.
template
static std::tuple ConvertFromJS(ScriptInterface::CmptPrivate* cmptPrivate, const ScriptRequest& rq, JS::CallArgs& args, bool& went_ok, std::tuple*)
{
if constexpr (sizeof...(Types) == 0)
{
// GCC (at least < 9) & VS17 prints warnings if arguments are not used in some constexpr branch.
UNUSED2(rq); UNUSED2(args); UNUSED2(went_ok);
return std::forward_as_tuple(*cmptPrivate->pScriptInterface);
}
else
return std::tuple_cat(std::forward_as_tuple(*cmptPrivate->pScriptInterface), DoConvertFromJS<0, Types...>(rq, args, went_ok));
}
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
/**
* Wrap std::apply for the case where we have an object method or a regular function.
*/
template
static typename args_info::return_type call(T* object, tuple& args)
{
if constexpr(std::is_same_v)
{
// GCC (at least < 9) & VS17 prints warnings if arguments are not used in some constexpr branch.
UNUSED2(object);
return std::apply(callable, args);
}
else
return std::apply(callable, std::tuple_cat(std::forward_as_tuple(*object), args));
}
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
public:
template
using ObjectGetter = T*(*)(const ScriptRequest&, JS::CallArgs&);
// TODO: the fact that this takes class and not auto is to work around an odd VS17 bug.
// It can be removed with VS19.
template
using GetterFor = ObjectGetter::object_type>;
/**
* The meat of this file. This wraps a C++ function into a JSNative,
* so that it can be called from JS and manipulated in Spidermonkey.
* Most C++ functions can be directly wrapped, so long as their arguments are
* convertible from JS::Value and their return value is convertible to JS::Value (or void)
* The C++ function may optionally take const ScriptRequest& or CmptPrivate* as its first argument.
* The function may be an object method, in which case you need to pass an appropriate getter
*
* Optimisation note: the ScriptRequest object is created even without arguments,
* as it's necessary for IsExceptionPending.
*
* @param thisGetter to get the object, if necessary.
*/
template thisGetter = nullptr>
static bool ToJSNative(JSContext* cx, unsigned argc, JS::Value* vp)
{
using ObjType = typename args_info::object_type;
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
ScriptInterface* scriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface;
ScriptRequest rq(*scriptInterface);
// If the callable is an object method, we must specify how to fetch the object.
static_assert(std::is_same_v::object_type, void> || thisGetter != nullptr,
"ScriptFunction::Register - No getter specified for object method");
// GCC 7 triggers spurious warnings
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Waddress"
#endif
ObjType* obj = nullptr;
if constexpr (thisGetter != nullptr)
{
obj = thisGetter(rq, args);
if (!obj)
return false;
}
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
bool went_ok = true;
typename args_info::arg_types outs = ConvertFromJS(ScriptInterface::GetScriptInterfaceAndCBData(cx), rq, args, went_ok, static_cast::arg_types*>(nullptr));
if (!went_ok)
return false;
/**
* TODO: error handling isn't standard, and since this can call any C++ function,
* there's no simple obvious way to deal with it.
* For now we check for pending JS exceptions, but it would probably be nicer
* to standardise on something, or perhaps provide an "errorHandler" here.
*/
if constexpr (std::is_same_v::return_type>)
call(obj, outs);
else if constexpr (std::is_same_v::return_type>)
args.rval().set(call(obj, outs));
else
ScriptInterface::ToJSVal(rq, args.rval(), call(obj, outs));
return !ScriptException::IsPending(rq);
}
/**
* Return a function spec from a C++ function.
*/
template thisGetter = nullptr, u16 flags = JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT>
static JSFunctionSpec Wrap(const char* name)
{
- return JS_FN(name, (&ToJSNative), args_info::nb_args, flags);
+ return JS_FN(name, (&ToJSNative), args_info::nb_args, flags);
+ }
+
+ /**
+ * Return a JSFunction from a C++ function.
+ */
+ template thisGetter = nullptr, u16 flags = JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT>
+ static JSFunction* Create(const ScriptRequest& rq, const char* name)
+ {
+ return JS_NewFunction(rq.cx, &ToJSNative, args_info::nb_args, flags, name);
}
/**
* Register a function on the native scope (usually 'Engine').
*/
template thisGetter = nullptr, u16 flags = JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT>
static void Register(const ScriptRequest& rq, const char* name)
{
JS_DefineFunction(rq.cx, rq.nativeScope, name, &ToJSNative, args_info::nb_args, flags);
}
/**
* Register a function on @param scope.
* Prefer the version taking ScriptRequest unless you have a good reason not to.
* @see Register
*/
template thisGetter = nullptr, u16 flags = JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT>
static void Register(JSContext* cx, JS::HandleObject scope, const char* name)
{
JS_DefineFunction(cx, scope, name, &ToJSNative, args_info::nb_args, flags);
}
/**
* Convert the CmptPrivate callback data to T*
*/
template
static T* ObjectFromCBData(const ScriptRequest& rq, JS::CallArgs&)
{
return static_cast(ScriptInterface::GetScriptInterfaceAndCBData(rq.cx)->pCBData);
}
};
#endif // INCLUDED_FUNCTIONWRAPPER