Index: ps/trunk/source/gui/CTooltip.cpp
===================================================================
--- ps/trunk/source/gui/CTooltip.cpp (revision 22572)
+++ ps/trunk/source/gui/CTooltip.cpp (revision 22573)
@@ -1,174 +1,175 @@
/* Copyright (C) 2019 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 "CTooltip.h"
#include "CGUI.h"
#include
CTooltip::CTooltip()
{
// If the tooltip is an object by itself:
AddSetting(GUIST_float, "buffer_zone");
AddSetting(GUIST_CGUIString, "caption");
AddSetting(GUIST_CStrW, "font");
AddSetting(GUIST_CGUISpriteInstance, "sprite");
AddSetting(GUIST_int, "delay");
AddSetting(GUIST_CGUIColor, "textcolor");
AddSetting(GUIST_float, "maxwidth");
AddSetting(GUIST_CPos, "offset");
AddSetting(GUIST_EVAlign, "anchor");
AddSetting(GUIST_EAlign, "text_align");
// This is used for tooltips that are hidden/revealed manually by scripts, rather than through the standard tooltip display mechanism
AddSetting(GUIST_bool, "independent");
// If the tooltip is just a reference to another object:
AddSetting(GUIST_CStr, "use_object");
AddSetting(GUIST_bool, "hide_object");
// Private settings:
+ // This is set by GUITooltip
AddSetting(GUIST_CPos, "_mousepos");
// Defaults
GUI::SetSetting(this, "delay", 500);
GUI::SetSetting(this, "anchor", EVAlign_Bottom);
GUI::SetSetting(this, "text_align", EAlign_Left);
// Set up a blank piece of text, to be replaced with a more
// interesting message later
AddText(new SGUIText());
}
CTooltip::~CTooltip()
{
}
void CTooltip::SetupText()
{
if (!GetGUI())
return;
ENSURE(m_GeneratedTexts.size() == 1);
CStrW font;
if (GUI::GetSetting(this, "font", font) != PSRETURN_OK || font.empty())
font = L"default";
float buffer_zone = 0.f;
GUI::GetSetting(this, "buffer_zone", buffer_zone);
CGUIString caption;
GUI::GetSetting(this, "caption", caption);
float max_width = 0.f;
GUI::GetSetting(this, "maxwidth", max_width);
*m_GeneratedTexts[0] = GetGUI()->GenerateText(caption, font, max_width, buffer_zone, this);
// Position the tooltip relative to the mouse:
CPos mousepos, offset;
EVAlign anchor;
bool independent;
GUI::GetSetting(this, "independent", independent);
if (independent)
mousepos = GetMousePos();
else
GUI::GetSetting(this, "_mousepos", mousepos);
GUI::GetSetting(this, "offset", offset);
GUI::GetSetting(this, "anchor", anchor);
float textwidth = m_GeneratedTexts[0]->m_Size.cx;
float textheight = m_GeneratedTexts[0]->m_Size.cy;
CClientArea size;
size.pixel.left = mousepos.x + offset.x;
size.pixel.right = size.pixel.left + textwidth;
switch (anchor)
{
case EVAlign_Top:
size.pixel.top = mousepos.y + offset.y;
size.pixel.bottom = size.pixel.top + textheight;
break;
case EVAlign_Bottom:
size.pixel.bottom = mousepos.y + offset.y;
size.pixel.top = size.pixel.bottom - textheight;
break;
case EVAlign_Center:
size.pixel.top = mousepos.y + offset.y - textheight/2.f;
size.pixel.bottom = size.pixel.top + textwidth;
break;
default:
debug_warn(L"Invalid EVAlign!");
}
// Reposition the tooltip if it's falling off the screen:
extern int g_xres, g_yres;
extern float g_GuiScale;
float screenw = g_xres / g_GuiScale;
float screenh = g_yres / g_GuiScale;
if (size.pixel.top < 0.f)
size.pixel.bottom -= size.pixel.top, size.pixel.top = 0.f;
else if (size.pixel.bottom > screenh)
size.pixel.top -= (size.pixel.bottom-screenh), size.pixel.bottom = screenh;
if (size.pixel.left < 0.f)
size.pixel.right -= size.pixel.left, size.pixel.left = 0.f;
else if (size.pixel.right > screenw)
size.pixel.left -= (size.pixel.right-screenw), size.pixel.right = screenw;
GUI::SetSetting(this, "size", size);
}
void CTooltip::HandleMessage(SGUIMessage& Message)
{
IGUITextOwner::HandleMessage(Message);
}
void CTooltip::Draw()
{
if (!GetGUI())
return;
float z = 900.f; // TODO: Find a nicer way of putting the tooltip on top of everything else
CGUISpriteInstance* sprite;
GUI::GetSettingPointer(this, "sprite", sprite);
// Normally IGUITextOwner will handle this updating but since SetupText can modify the position
// we need to call it now *before* we do the rest of the drawing
if (!m_GeneratedTextsValid)
{
SetupText();
m_GeneratedTextsValid = true;
}
GetGUI()->DrawSprite(*sprite, 0, z, m_CachedActualSize);
CGUIColor color;
GUI::GetSetting(this, "textcolor", color);
DrawText(0, color, m_CachedActualSize.TopLeft(), z+0.1f);
}
Index: ps/trunk/source/gui/scripting/GuiScriptConversions.cpp
===================================================================
--- ps/trunk/source/gui/scripting/GuiScriptConversions.cpp (revision 22572)
+++ ps/trunk/source/gui/scripting/GuiScriptConversions.cpp (revision 22573)
@@ -1,303 +1,333 @@
/* Copyright (C) 2019 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 "gui/CGUIColor.h"
#include "gui/CGUIList.h"
#include "gui/CGUISeries.h"
#include "gui/GUIbase.h"
#include "gui/IGUIObject.h"
#include "lib/external_libraries/libsdl.h"
#include "maths/Vector2D.h"
#include "ps/Hotkey.h"
#include "scriptinterface/ScriptConversions.h"
#define SET(obj, name, value) STMT(JS::RootedValue v_(cx); AssignOrToJSVal(cx, &v_, (value)); JS_SetProperty(cx, obj, (name), v_))
// ignore JS_SetProperty return value, because errors should be impossible
// and we can't do anything useful in the case of errors anyway
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, SDL_Event_ const& val)
{
JSAutoRequest rq(cx);
const char* typeName;
switch (val.ev.type)
{
case SDL_WINDOWEVENT: typeName = "windowevent"; break;
case SDL_KEYDOWN: typeName = "keydown"; break;
case SDL_KEYUP: typeName = "keyup"; break;
case SDL_MOUSEMOTION: typeName = "mousemotion"; break;
case SDL_MOUSEBUTTONDOWN: typeName = "mousebuttondown"; break;
case SDL_MOUSEBUTTONUP: typeName = "mousebuttonup"; break;
case SDL_QUIT: typeName = "quit"; break;
case SDL_HOTKEYDOWN: typeName = "hotkeydown"; break;
case SDL_HOTKEYUP: typeName = "hotkeyup"; break;
default: typeName = "(unknown)"; break;
}
JS::RootedObject obj(cx, JS_NewPlainObject(cx));
if (!obj)
{
ret.setUndefined();
return;
}
SET(obj, "type", typeName);
switch (val.ev.type)
{
case SDL_KEYDOWN:
case SDL_KEYUP:
{
// SET(obj, "which", (int)val.ev.key.which); // (not in wsdl.h)
// SET(obj, "state", (int)val.ev.key.state); // (not in wsdl.h)
JS::RootedObject keysym(cx, JS_NewPlainObject(cx));
if (!keysym)
{
ret.setUndefined();
return;
}
JS::RootedValue keysymVal(cx, JS::ObjectValue(*keysym));
JS_SetProperty(cx, obj, "keysym", keysymVal);
// SET(keysym, "scancode", (int)val.ev.key.keysym.scancode); // (not in wsdl.h)
SET(keysym, "sym", (int)val.ev.key.keysym.sym);
// SET(keysym, "mod", (int)val.ev.key.keysym.mod); // (not in wsdl.h)
{
SET(keysym, "unicode", JS::UndefinedHandleValue);
}
// TODO: scripts have no idea what all the key/mod enum values are;
// we should probably expose them as constants if we expect scripts to use them
break;
}
case SDL_MOUSEMOTION:
{
// SET(obj, "which", (int)val.ev.motion.which); // (not in wsdl.h)
// SET(obj, "state", (int)val.ev.motion.state); // (not in wsdl.h)
SET(obj, "x", (int)val.ev.motion.x);
SET(obj, "y", (int)val.ev.motion.y);
// SET(obj, "xrel", (int)val.ev.motion.xrel); // (not in wsdl.h)
// SET(obj, "yrel", (int)val.ev.motion.yrel); // (not in wsdl.h)
break;
}
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
{
// SET(obj, "which", (int)val.ev.button.which); // (not in wsdl.h)
SET(obj, "button", (int)val.ev.button.button);
SET(obj, "state", (int)val.ev.button.state);
SET(obj, "x", (int)val.ev.button.x);
SET(obj, "y", (int)val.ev.button.y);
SET(obj, "clicks", (int)val.ev.button.clicks);
break;
}
case SDL_HOTKEYDOWN:
case SDL_HOTKEYUP:
{
SET(obj, "hotkey", static_cast(val.ev.user.data1));
break;
}
}
ret.setObject(*obj);
}
template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::MutableHandleValue ret, IGUIObject* const& val)
{
if (val == NULL)
ret.setNull();
else
ret.setObject(*val->GetJSObject());
}
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CGUIString& val)
{
ScriptInterface::ToJSVal(cx, ret, val.GetOriginalString());
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CGUIString& out)
{
std::wstring val;
if (!FromJSVal(cx, v, val))
return false;
out.SetValue(val);
return true;
}
JSVAL_VECTOR(CVector2D)
JSVAL_VECTOR(std::vector)
JSVAL_VECTOR(CGUIString)
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CGUIColor& val)
{
ToJSVal(cx, ret, val);
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CGUIColor& out)
{
if (v.isString())
{
CStr name;
if (!FromJSVal(cx, v, name))
return false;
if (!out.ParseString(name))
{
JS_ReportError(cx, "Invalid color '%s'", name.c_str());
return false;
}
return true;
}
// Parse as object
return FromJSVal(cx, v, out);
}
+template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CPos& val)
+{
+ ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface->CreateObject(ret, "x", val.x, "y", val.y);
+}
+
+template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CPos& out)
+{
+ if (!v.isObject())
+ {
+ JS_ReportError(cx, "CPos value must be an object!");
+ return false;
+ }
+
+ if (!FromJSProperty(cx, v, "x", out.x))
+ {
+ JS_ReportError(cx, "Failed to get CPos.x property");
+ return false;
+ }
+
+ if (!FromJSProperty(cx, v, "y", out.y))
+ {
+ JS_ReportError(cx, "Failed to get CPos.y property");
+ return false;
+ }
+
+ return true;
+}
+
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CClientArea& val)
{
val.ToJSVal(cx, ret);
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CClientArea& out)
{
return out.FromJSVal(cx, v);
}
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CGUIList& val)
{
ToJSVal(cx, ret, val.m_Items);
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CGUIList& out)
{
return FromJSVal(cx, v, out.m_Items);
}
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CGUISeries& val)
{
ToJSVal(cx, ret, val.m_Series);
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CGUISeries& out)
{
return FromJSVal(cx, v, out.m_Series);
}
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const EVAlign& val)
{
std::string word;
switch (val)
{
case EVAlign_Top:
word = "top";
break;
case EVAlign_Bottom:
word = "bottom";
break;
case EVAlign_Center:
word = "center";
break;
default:
word = "error";
JS_ReportError(cx, "Invalid EVAlign");
break;
}
ToJSVal(cx, ret, word);
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, EVAlign& out)
{
std::string word;
FromJSVal(cx, v, word);
if (word == "top")
out = EVAlign_Top;
else if (word == "bottom")
out = EVAlign_Bottom;
else if (word == "center")
out = EVAlign_Center;
else
{
out = EVAlign_Top;
JS_ReportError(cx, "Invalid alignment (should be 'left', 'right' or 'center')");
return false;
}
return true;
}
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const EAlign& val)
{
std::string word;
switch (val)
{
case EAlign_Left:
word = "left";
break;
case EAlign_Right:
word = "right";
break;
case EAlign_Center:
word = "center";
break;
default:
word = "error";
JS_ReportError(cx, "Invalid alignment (should be 'left', 'right' or 'center')");
break;
}
ToJSVal(cx, ret, word);
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, EAlign& out)
{
std::string word;
FromJSVal(cx, v, word);
if (word == "left")
out = EAlign_Left;
else if (word == "right")
out = EAlign_Right;
else if (word == "center")
out = EAlign_Center;
else
{
out = EAlign_Left;
JS_ReportError(cx, "Invalid alignment (should be 'left', 'right' or 'center')");
return false;
}
return true;
}
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CGUISpriteInstance& val)
{
ToJSVal(cx, ret, val.GetName());
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CGUISpriteInstance& out)
{
std::string name;
if (!FromJSVal(cx, v, name))
return false;
out.SetName(name);
return true;
}
+
+#undef SET
Index: ps/trunk/source/gui/scripting/JSInterface_IGUIObject.cpp
===================================================================
--- ps/trunk/source/gui/scripting/JSInterface_IGUIObject.cpp (revision 22572)
+++ ps/trunk/source/gui/scripting/JSInterface_IGUIObject.cpp (revision 22573)
@@ -1,625 +1,643 @@
/* Copyright (C) 2019 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_IGUIObject.h"
#include "gui/CGUI.h"
#include "gui/CGUIColor.h"
#include "gui/CList.h"
#include "gui/GUIManager.h"
#include "gui/IGUIObject.h"
#include "gui/IGUIScrollBar.h"
#include "gui/scripting/JSInterface_GUITypes.h"
#include "ps/CLogger.h"
#include "scriptinterface/ScriptExtraHeaders.h"
#include "scriptinterface/ScriptInterface.h"
JSClass JSI_IGUIObject::JSI_class = {
"GUIObject", JSCLASS_HAS_PRIVATE,
nullptr, nullptr,
JSI_IGUIObject::getProperty, JSI_IGUIObject::setProperty,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr
};
JSFunctionSpec JSI_IGUIObject::JSI_methods[] =
{
JS_FN("toString", JSI_IGUIObject::toString, 0, 0),
JS_FN("focus", JSI_IGUIObject::focus, 0, 0),
JS_FN("blur", JSI_IGUIObject::blur, 0, 0),
JS_FN("getComputedSize", JSI_IGUIObject::getComputedSize, 0, 0),
JS_FN("getTextSize", JSI_IGUIObject::getTextSize, 0, 0),
JS_FS_END
};
bool JSI_IGUIObject::getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, JS::MutableHandleValue vp)
{
JSAutoRequest rq(cx);
ScriptInterface* pScriptInterface = ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface;
IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, obj, &JSI_IGUIObject::JSI_class, NULL);
if (!e)
return false;
JS::RootedValue idval(cx);
if (!JS_IdToValue(cx, id, &idval))
return false;
std::string propName;
if (!ScriptInterface::FromJSVal(cx, idval, propName))
return false;
// Skip registered functions and inherited properties
if (propName == "constructor" ||
propName == "prototype" ||
propName == "toString" ||
propName == "toJSON" ||
propName == "focus" ||
propName == "blur" ||
propName == "getTextSize" ||
propName == "getComputedSize"
)
return true;
// Use onWhatever to access event handlers
if (propName.substr(0, 2) == "on")
{
CStr eventName(CStr(propName.substr(2)).LowerCase());
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")
{
pScriptInterface->CreateArray(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(cx, vp, e->GetName());
return true;
}
else
{
// Retrieve the setting's type (and make sure it actually exists)
EGUISettingType Type;
if (e->GetSettingType(propName, Type) != PSRETURN_OK)
{
JS_ReportError(cx, "Invalid GUIObject property '%s'", propName.c_str());
return false;
}
// (All the cases are in {...} to avoid scoping problems)
switch (Type)
{
case GUIST_bool:
{
bool value;
GUI::GetSetting(e, propName, value);
ScriptInterface::ToJSVal(cx, vp, value);
break;
}
case GUIST_int:
{
i32 value;
GUI::GetSetting(e, propName, value);
ScriptInterface::ToJSVal(cx, vp, value);
break;
}
case GUIST_uint:
{
u32 value;
GUI::GetSetting(e, propName, value);
ScriptInterface::ToJSVal(cx, vp, value);
break;
}
case GUIST_float:
{
float value;
GUI::GetSetting(e, propName, value);
ScriptInterface::ToJSVal(cx, vp, value);
break;
}
case GUIST_CGUIColor:
{
CGUIColor value;
GUI::GetSetting(e, propName, value);
ScriptInterface::ToJSVal(cx, vp, value);
break;
}
+ case GUIST_CPos:
+ {
+ CPos value;
+ GUI::GetSetting(e, propName, value);
+ ScriptInterface::ToJSVal(cx, vp, value);
+ break;
+ }
+
case GUIST_CClientArea:
{
CClientArea value;
GUI::GetSetting(e, propName, value);
ScriptInterface::ToJSVal(cx, vp, value);
break;
}
case GUIST_CGUIString:
{
CGUIString value;
GUI::GetSetting(e, propName, value);
ScriptInterface::ToJSVal(cx, vp, value);
break;
}
case GUIST_CStr:
{
CStr value;
GUI::GetSetting(e, propName, value);
ScriptInterface::ToJSVal(cx, vp, value);
break;
}
case GUIST_CStrW:
{
CStrW value;
GUI::GetSetting(e, propName, value);
ScriptInterface::ToJSVal(cx, vp, value);
break;
}
case GUIST_CGUISpriteInstance:
{
CGUISpriteInstance* value;
GUI::GetSettingPointer(e, propName, value);
ScriptInterface::ToJSVal(cx, vp, *value);
break;
}
case GUIST_EAlign:
{
EAlign value;
GUI::GetSetting(e, propName, value);
ScriptInterface::ToJSVal(cx, vp, value);
break;
}
case GUIST_EVAlign:
{
EVAlign value;
GUI::GetSetting(e, propName, value);
ScriptInterface::ToJSVal(cx, vp, value);
break;
}
case GUIST_CGUIList:
{
CGUIList value;
GUI::GetSetting(e, propName, value);
ScriptInterface::ToJSVal(cx, vp, value);
break;
}
case GUIST_CGUISeries:
{
CGUISeries value;
GUI::GetSetting(e, propName, value);
ScriptInterface::ToJSVal(cx, vp, value);
break;
}
default:
JS_ReportError(cx, "Setting '%s' uses an unimplemented type", propName.c_str());
DEBUG_WARN_ERR(ERR::LOGIC);
return false;
}
return true;
}
}
bool JSI_IGUIObject::setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, bool UNUSED(strict), JS::MutableHandleValue vp)
{
IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, obj, &JSI_IGUIObject::JSI_class, NULL);
if (!e)
return false;
JSAutoRequest rq(cx);
JS::RootedValue idval(cx);
if (!JS_IdToValue(cx, id, &idval))
return false;
std::string propName;
if (!ScriptInterface::FromJSVal(cx, idval, propName))
return false;
if (propName == "name")
{
std::string value;
if (!ScriptInterface::FromJSVal(cx, vp, value))
return false;
e->SetName(value);
return true;
}
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(cx, &vp.toObject()))
{
JS_ReportError(cx, "on- event-handlers must be functions");
return false;
}
CStr eventName(CStr(propName.substr(2)).LowerCase());
e->SetScriptHandler(eventName, vpObj);
return true;
}
// Retrieve the setting's type (and make sure it actually exists)
EGUISettingType Type;
if (e->GetSettingType(propName, Type) != PSRETURN_OK)
{
JS_ReportError(cx, "Invalid setting '%s'", propName.c_str());
return true;
}
switch (Type)
{
case GUIST_CStr:
{
CStr value;
if (!ScriptInterface::FromJSVal(cx, vp, value))
return false;
GUI::SetSetting(e, propName, value);
break;
}
case GUIST_CStrW:
{
CStrW value;
if (!ScriptInterface::FromJSVal(cx, vp, value))
return false;
GUI::SetSetting(e, propName, value);
break;
}
case GUIST_CGUISpriteInstance:
{
CGUISpriteInstance value;
if (!ScriptInterface::FromJSVal(cx, vp, value))
return false;
GUI::SetSetting(e, propName, value);
break;
}
case GUIST_CGUIString:
{
CGUIString value;
if (!ScriptInterface::FromJSVal(cx, vp, value))
return false;
GUI::SetSetting(e, propName, value);
break;
}
case GUIST_EAlign:
{
EAlign a;
if (!ScriptInterface::FromJSVal(cx, vp, a))
return false;
GUI::SetSetting(e, propName, a);
break;
}
case GUIST_EVAlign:
{
EVAlign a;
if (!ScriptInterface::FromJSVal(cx, vp, a))
return false;
GUI::SetSetting(e, propName, a);
break;
}
case GUIST_int:
{
i32 value;
if (ScriptInterface::FromJSVal(cx, vp, value))
GUI::SetSetting(e, propName, value);
else
{
JS_ReportError(cx, "Cannot convert value to i32");
return false;
}
break;
}
case GUIST_uint:
{
u32 value;
if (ScriptInterface::FromJSVal(cx, vp, value))
GUI::SetSetting(e, propName, value);
else
{
JS_ReportError(cx, "Cannot convert value to u32");
return false;
}
break;
}
case GUIST_float:
{
float value;
if (ScriptInterface::FromJSVal(cx, vp, value))
GUI::SetSetting(e, propName, value);
else
{
JS_ReportError(cx, "Cannot convert value to float");
return false;
}
break;
}
case GUIST_bool:
{
bool value;
if (!ScriptInterface::FromJSVal(cx, vp, value))
return false;
GUI::SetSetting(e, propName, value);
break;
}
case GUIST_CClientArea:
{
CClientArea value;
if (!ScriptInterface::FromJSVal(cx, vp, value))
return false;
GUI::SetSetting(e, propName, value);
break;
}
+ case GUIST_CPos:
+ {
+ CPos value;
+ if (!ScriptInterface::FromJSVal(cx, vp, value))
+ return false;
+
+ GUI::SetSetting(e, propName, value);
+ break;
+ }
+
case GUIST_CGUIColor:
{
CGUIColor value;
if (!ScriptInterface::FromJSVal(cx, vp, value))
return false;
GUI::SetSetting(e, propName, value);
break;
}
case GUIST_CGUIList:
{
CGUIList list;
if (ScriptInterface::FromJSVal(cx, vp, list))
GUI::SetSetting(e, propName, list);
else
{
JS_ReportError(cx, "Failed to get list '%s'", propName.c_str());
return false;
}
break;
}
case GUIST_CGUISeries:
{
CGUISeries series;
if (ScriptInterface::FromJSVal(cx, vp, series))
GUI::SetSetting(e, propName, series);
else
{
JS_ReportError(cx, "Invalid value for chart series '%s'", propName.c_str());
return false;
}
break;
}
default:
JS_ReportError(cx, "Setting '%s' uses an unimplemented type", propName.c_str());
break;
}
return !JS_IsExceptionPending(cx);
}
void JSI_IGUIObject::init(ScriptInterface& scriptInterface)
{
scriptInterface.DefineCustomObjectType(&JSI_class, nullptr, 1, nullptr, JSI_methods, nullptr, nullptr);
}
bool JSI_IGUIObject::toString(JSContext* cx, uint UNUSED(argc), JS::Value* vp)
{
JSAutoRequest rq(cx);
JS::CallReceiver rec = JS::CallReceiverFromVp(vp);
JS::RootedObject thisObj(cx, JS_THIS_OBJECT(cx, vp));
IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, thisObj, &JSI_IGUIObject::JSI_class, NULL);
if (!e)
return false;
ScriptInterface::ToJSVal(cx, rec.rval(), "[GUIObject: " + e->GetName() + "]");
return true;
}
bool JSI_IGUIObject::focus(JSContext* cx, uint UNUSED(argc), JS::Value* vp)
{
JSAutoRequest rq(cx);
JS::CallReceiver rec = JS::CallReceiverFromVp(vp);
JS::RootedObject thisObj(cx, JS_THIS_OBJECT(cx, vp));
IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, thisObj, &JSI_IGUIObject::JSI_class, NULL);
if (!e)
return false;
e->GetGUI()->SetFocusedObject(e);
rec.rval().setUndefined();
return true;
}
bool JSI_IGUIObject::blur(JSContext* cx, uint UNUSED(argc), JS::Value* vp)
{
JSAutoRequest rq(cx);
JS::CallReceiver rec = JS::CallReceiverFromVp(vp);
JS::RootedObject thisObj(cx, JS_THIS_OBJECT(cx, vp));
IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, thisObj, &JSI_IGUIObject::JSI_class, NULL);
if (!e)
return false;
e->GetGUI()->SetFocusedObject(NULL);
rec.rval().setUndefined();
return true;
}
bool JSI_IGUIObject::getTextSize(JSContext* cx, uint argc, JS::Value* vp)
{
JSAutoRequest rq(cx);
JS::CallReceiver rec = JS::CallReceiverFromVp(vp);
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
JS::RootedObject thisObj(cx, &args.thisv().toObject());
IGUIObject* obj = (IGUIObject*)JS_GetInstancePrivate(cx, thisObj, &JSI_IGUIObject::JSI_class, NULL);
if (!obj || !obj->SettingExists("caption"))
return false;
CStrW font;
if (GUI::GetSetting(obj, "font", font) != PSRETURN_OK || font.empty())
font = L"default";
CGUIString caption;
EGUISettingType Type;
obj->GetSettingType("caption", Type);
if (Type == GUIST_CGUIString)
// CText, CButton, CCheckBox, CRadioButton
GUI::GetSetting(obj, "caption", caption);
else if (Type == GUIST_CStrW)
{
// CInput
CStrW captionStr;
GUI::GetSetting(obj, "caption", captionStr);
caption.SetValue(captionStr);
}
else
return false;
obj->UpdateCachedSize();
float width = obj->m_CachedActualSize.GetWidth();
if (obj->SettingExists("scrollbar"))
{
bool scrollbar;
GUI::GetSetting(obj, "scrollbar", scrollbar);
if (scrollbar)
{
CStr scrollbar_style;
GUI::GetSetting(obj, "scrollbar_style", scrollbar_style);
const SGUIScrollBarStyle* scrollbar_style_object = obj->GetGUI()->GetScrollBarStyle(scrollbar_style);
if (scrollbar_style_object)
width -= scrollbar_style_object->m_Width;
}
}
float buffer_zone = 0.f;
GUI::GetSetting(obj, "buffer_zone", buffer_zone);
SGUIText text = obj->GetGUI()->GenerateText(caption, font, width, buffer_zone, obj);
JS::RootedValue objVal(cx);
try
{
ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface->CreateObject(
&objVal,
"width", text.m_Size.cx,
"height", text.m_Size.cy);
}
catch (PSERROR_Scripting_ConversionFailed&)
{
debug_warn(L"Error creating size object!");
return false;
}
rec.rval().set(objVal);
return true;
}
bool JSI_IGUIObject::getComputedSize(JSContext* cx, uint UNUSED(argc), JS::Value* vp)
{
JSAutoRequest rq(cx);
JS::CallReceiver rec = JS::CallReceiverFromVp(vp);
JS::RootedObject thisObj(cx, JS_THIS_OBJECT(cx, vp));
IGUIObject* e = (IGUIObject*)JS_GetInstancePrivate(cx, thisObj, &JSI_IGUIObject::JSI_class, NULL);
if (!e)
return false;
e->UpdateCachedSize();
CRect size = e->m_CachedActualSize;
JS::RootedValue objVal(cx);
try
{
ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface->CreateObject(
&objVal,
"left", size.left,
"right", size.right,
"top", size.top,
"bottom", size.bottom);
}
catch (PSERROR_Scripting_ConversionFailed&)
{
debug_warn(L"Error creating size object!");
return false;
}
rec.rval().set(objVal);
return true;
}
Index: ps/trunk/source/scriptinterface/ScriptConversions.cpp
===================================================================
--- ps/trunk/source/scriptinterface/ScriptConversions.cpp (revision 22572)
+++ ps/trunk/source/scriptinterface/ScriptConversions.cpp (revision 22573)
@@ -1,347 +1,350 @@
-/* Copyright (C) 2018 Wildfire Games.
+/* Copyright (C) 2019 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 "ScriptConversions.h"
#include "graphics/Entity.h"
#include "maths/Vector2D.h"
#include "ps/utf16string.h"
#include "ps/CStr.h"
#define FAIL(msg) STMT(JS_ReportError(cx, msg); return false)
// Implicit type conversions often hide bugs, so warn about them
#define WARN_IF_NOT(c, v) STMT(if (!(c)) { JS_ReportWarning(cx, "Script value conversion check failed: %s (got type %s)", #c, InformalValueTypeName(v)); })
// TODO: SpiderMonkey: Follow upstream progresses about JS_InformalValueTypeName in the API
// https://bugzilla.mozilla.org/show_bug.cgi?id=1285917
static const char* InformalValueTypeName(const JS::Value& v)
{
if (v.isObject())
return "object";
if (v.isString())
return "string";
if (v.isSymbol())
return "symbol";
if (v.isNumber())
return "number";
if (v.isBoolean())
return "boolean";
if (v.isNull())
return "null";
if (v.isUndefined())
return "undefined";
return "value";
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, bool& out)
{
JSAutoRequest rq(cx);
WARN_IF_NOT(v.isBoolean(), v);
out = JS::ToBoolean(v);
return true;
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, float& out)
{
JSAutoRequest rq(cx);
double tmp;
WARN_IF_NOT(v.isNumber(), v);
if (!JS::ToNumber(cx, v, &tmp))
return false;
out = tmp;
return true;
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, double& out)
{
JSAutoRequest rq(cx);
WARN_IF_NOT(v.isNumber(), v);
if (!JS::ToNumber(cx, v, &out))
return false;
return true;
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, i32& out)
{
JSAutoRequest rq(cx);
WARN_IF_NOT(v.isNumber(), v);
if (!JS::ToInt32(cx, v, &out))
return false;
return true;
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, u32& out)
{
JSAutoRequest rq(cx);
WARN_IF_NOT(v.isNumber(), v);
if (!JS::ToUint32(cx, v, &out))
return false;
return true;
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, u16& out)
{
JSAutoRequest rq(cx);
WARN_IF_NOT(v.isNumber(), v);
if (!JS::ToUint16(cx, v, &out))
return false;
return true;
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, u8& out)
{
JSAutoRequest rq(cx);
u16 tmp;
WARN_IF_NOT(v.isNumber(), v);
if (!JS::ToUint16(cx, v, &tmp))
return false;
out = (u8)tmp;
return true;
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, std::wstring& out)
{
JSAutoRequest rq(cx);
WARN_IF_NOT(v.isString() || v.isNumber(), v); // allow implicit number conversions
JS::RootedString str(cx, JS::ToString(cx, v));
if (!str)
FAIL("Argument must be convertible to a string");
if (JS_StringHasLatin1Chars(str))
{
size_t length;
JS::AutoCheckCannotGC nogc;
const JS::Latin1Char* ch = JS_GetLatin1StringCharsAndLength(cx, nogc, str, &length);
if (!ch)
FAIL("JS_GetLatin1StringCharsAndLength failed");
out.assign(ch, ch + length);
}
else
{
size_t length;
JS::AutoCheckCannotGC nogc;
const char16_t* ch = JS_GetTwoByteStringCharsAndLength(cx, nogc, str, &length);
if (!ch)
FAIL("JS_GetTwoByteStringsCharsAndLength failed"); // out of memory
out.assign(ch, ch + length);
}
return true;
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, Path& out)
{
std::wstring string;
if (!FromJSVal(cx, v, string))
return false;
out = string;
return true;
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, std::string& out)
{
JSAutoRequest rq(cx);
WARN_IF_NOT(v.isString() || v.isNumber(), v); // allow implicit number conversions
JS::RootedString str(cx, JS::ToString(cx, v));
if (!str)
FAIL("Argument must be convertible to a string");
char* ch = JS_EncodeString(cx, str); // chops off high byte of each char16_t
if (!ch)
FAIL("JS_EncodeString failed"); // out of memory
out.assign(ch, ch + JS_GetStringLength(str));
JS_free(cx, ch);
return true;
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CStr8& out)
{
return ScriptInterface::FromJSVal(cx, v, static_cast(out));
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CStrW& out)
{
return ScriptInterface::FromJSVal(cx, v, static_cast(out));
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, Entity& out)
{
JSAutoRequest rq(cx);
if (!v.isObject())
FAIL("Argument must be an object");
JS::RootedObject obj(cx, &v.toObject());
JS::RootedValue templateName(cx);
JS::RootedValue id(cx);
JS::RootedValue player(cx);
JS::RootedValue position(cx);
JS::RootedValue rotation(cx);
// TODO: Report type errors
if (!JS_GetProperty(cx, obj, "player", &player) || !FromJSVal(cx, player, out.playerID))
FAIL("Failed to read Entity.player property");
if (!JS_GetProperty(cx, obj, "templateName", &templateName) || !FromJSVal(cx, templateName, out.templateName))
FAIL("Failed to read Entity.templateName property");
if (!JS_GetProperty(cx, obj, "id", &id) || !FromJSVal(cx, id, out.entityID))
FAIL("Failed to read Entity.id property");
if (!JS_GetProperty(cx, obj, "position", &position) || !FromJSVal(cx, position, out.position))
FAIL("Failed to read Entity.position property");
if (!JS_GetProperty(cx, obj, "rotation", &rotation) || !FromJSVal(cx, rotation, out.rotation))
FAIL("Failed to read Entity.rotation property");
return true;
}
////////////////////////////////////////////////////////////////
// Primitive types:
template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::MutableHandleValue ret, const bool& val)
{
ret.setBoolean(val);
}
template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::MutableHandleValue ret, const float& val)
{
ret.set(JS::NumberValue(val));
}
template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::MutableHandleValue ret, const double& val)
{
ret.set(JS::NumberValue(val));
}
template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::MutableHandleValue ret, const i32& val)
{
ret.set(JS::NumberValue(val));
}
template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::MutableHandleValue ret, const u16& val)
{
ret.set(JS::NumberValue(val));
}
template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::MutableHandleValue ret, const u8& val)
{
ret.set(JS::NumberValue(val));
}
template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::MutableHandleValue ret, const u32& val)
{
ret.set(JS::NumberValue(val));
}
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const std::wstring& val)
{
JSAutoRequest rq(cx);
utf16string utf16(val.begin(), val.end());
JS::RootedString str(cx, JS_NewUCStringCopyN(cx, reinterpret_cast (utf16.c_str()), utf16.length()));
if (str)
ret.setString(str);
else
ret.setUndefined();
}
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const Path& val)
{
ToJSVal(cx, ret, val.string());
}
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const std::string& val)
{
JSAutoRequest rq(cx);
JS::RootedString str(cx, JS_NewStringCopyN(cx, val.c_str(), val.length()));
if (str)
ret.setString(str);
else
ret.setUndefined();
}
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const wchar_t* const& val)
{
ToJSVal(cx, ret, std::wstring(val));
}
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const char* const& val)
{
JSAutoRequest rq(cx);
JS::RootedString str(cx, JS_NewStringCopyZ(cx, val));
if (str)
ret.setString(str);
else
ret.setUndefined();
}
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CStrW& val)
{
ToJSVal(cx, ret, static_cast(val));
}
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CStr8& val)
{
ToJSVal(cx, ret, static_cast(val));
}
////////////////////////////////////////////////////////////////
// Compound types
// Instantiate various vector types:
JSVAL_VECTOR(int)
JSVAL_VECTOR(u32)
JSVAL_VECTOR(u16)
JSVAL_VECTOR(std::string)
JSVAL_VECTOR(std::wstring)
JSVAL_VECTOR(std::vector)
JSVAL_VECTOR(CStr8)
JSVAL_VECTOR(std::vector)
JSVAL_VECTOR(std::vector)
class IComponent;
template<> void ScriptInterface::ToJSVal >(JSContext* cx, JS::MutableHandleValue ret, const std::vector& val)
{
ToJSVal_vector(cx, ret, val);
}
template<> bool ScriptInterface::FromJSVal >(JSContext* cx, JS::HandleValue v, std::vector& out)
{
return FromJSVal_vector(cx, v, out);
}
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CVector2D& val)
{
std::vector vec = {val.X, val.Y};
ToJSVal_vector(cx, ret, vec);
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CVector2D& out)
{
std::vector vec;
if (!FromJSVal_vector(cx, v, vec))
return false;
if (vec.size() != 2)
return false;
out.X = vec[0];
out.Y = vec[1];
return true;
}
+
+#undef FAIL
+#undef WARN_IF_NOT
Index: ps/trunk/source/simulation2/scripting/EngineScriptConversions.cpp
===================================================================
--- ps/trunk/source/simulation2/scripting/EngineScriptConversions.cpp (revision 22572)
+++ ps/trunk/source/simulation2/scripting/EngineScriptConversions.cpp (revision 22573)
@@ -1,363 +1,366 @@
/* Copyright (C) 2019 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 "scriptinterface/ScriptConversions.h"
#include "graphics/Color.h"
#include "maths/Fixed.h"
#include "maths/FixedVector2D.h"
#include "maths/FixedVector3D.h"
#include "ps/CLogger.h"
#include "ps/Shapes.h"
#include "ps/utf16string.h"
#include "simulation2/helpers/CinemaPath.h"
#include "simulation2/helpers/Grid.h"
#include "simulation2/system/IComponent.h"
#include "simulation2/system/ParamNode.h"
#define FAIL(msg) STMT(JS_ReportError(cx, msg); return false)
#define FAIL_VOID(msg) STMT(JS_ReportError(cx, msg); return)
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, IComponent* const& val)
{
JSAutoRequest rq(cx);
if (val == NULL)
{
ret.setNull();
return;
}
// If this is a scripted component, just return the JS object directly
JS::RootedValue instance(cx, val->GetJSInstance());
if (!instance.isNull())
{
ret.set(instance);
return;
}
// Otherwise we need to construct a wrapper object
// (TODO: cache wrapper objects?)
JS::RootedObject obj(cx);
if (!val->NewJSObject(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface, &obj))
{
// Report as an error, since scripts really shouldn't try to use unscriptable interfaces
LOGERROR("IComponent does not have a scriptable interface");
ret.setUndefined();
return;
}
JS_SetPrivate(obj, static_cast(val));
ret.setObject(*obj);
}
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, CParamNode const& val)
{
JSAutoRequest rq(cx);
val.ToJSVal(cx, true, ret);
// Prevent modifications to the object, so that it's safe to share between
// components and to reconstruct on deserialization
if (ret.isObject())
{
JS::RootedObject obj(cx, &ret.toObject());
JS_DeepFreezeObject(cx, obj);
}
}
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CParamNode* const& val)
{
if (val)
ToJSVal(cx, ret, *val);
else
ret.setUndefined();
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CColor& out)
{
if (!v.isObject())
FAIL("JS::HandleValue not an object");
JSAutoRequest rq(cx);
JS::RootedObject obj(cx, &v.toObject());
JS::RootedValue r(cx);
JS::RootedValue g(cx);
JS::RootedValue b(cx);
JS::RootedValue a(cx);
if (!JS_GetProperty(cx, obj, "r", &r) || !FromJSVal(cx, r, out.r))
FAIL("Failed to get property CColor.r");
if (!JS_GetProperty(cx, obj, "g", &g) || !FromJSVal(cx, g, out.g))
FAIL("Failed to get property CColor.g");
if (!JS_GetProperty(cx, obj, "b", &b) || !FromJSVal(cx, b, out.b))
FAIL("Failed to get property CColor.b");
if (!JS_GetProperty(cx, obj, "a", &a) || !FromJSVal(cx, a, out.a))
FAIL("Failed to get property CColor.a");
return true;
}
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, CColor const& val)
{
JSAutoRequest rq(cx);
JS::RootedObject obj(cx, JS_NewPlainObject(cx));
if (!obj)
{
ret.setUndefined();
return;
}
JS::RootedValue r(cx);
JS::RootedValue g(cx);
JS::RootedValue b(cx);
JS::RootedValue a(cx);
ToJSVal(cx, &r, val.r);
ToJSVal(cx, &g, val.g);
ToJSVal(cx, &b, val.b);
ToJSVal(cx, &a, val.a);
JS_SetProperty(cx, obj, "r", r);
JS_SetProperty(cx, obj, "g", g);
JS_SetProperty(cx, obj, "b", b);
JS_SetProperty(cx, obj, "a", a);
ret.setObject(*obj);
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, fixed& out)
{
JSAutoRequest rq(cx);
double ret;
if (!JS::ToNumber(cx, v, &ret))
return false;
out = fixed::FromDouble(ret);
// double can precisely represent the full range of fixed, so this is a non-lossy conversion
return true;
}
template<> void ScriptInterface::ToJSVal(JSContext* UNUSED(cx), JS::MutableHandleValue ret, const fixed& val)
{
ret.set(JS::NumberValue(val.ToDouble()));
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CFixedVector3D& out)
{
if (!v.isObject())
return false; // TODO: report type error
JSAutoRequest rq(cx);
JS::RootedObject obj(cx, &v.toObject());
JS::RootedValue p(cx);
if (!JS_GetProperty(cx, obj, "x", &p)) return false; // TODO: report type errors
if (!FromJSVal(cx, p, out.X)) return false;
if (!JS_GetProperty(cx, obj, "y", &p)) return false;
if (!FromJSVal(cx, p, out.Y)) return false;
if (!JS_GetProperty(cx, obj, "z", &p)) return false;
if (!FromJSVal(cx, p, out.Z)) return false;
return true;
}
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CFixedVector3D& val)
{
JSAutoRequest rq(cx);
ScriptInterface::CxPrivate* pCxPrivate = ScriptInterface::GetScriptInterfaceAndCBData(cx);
JS::RootedObject global(cx, &pCxPrivate->pScriptInterface->GetGlobalObject().toObject());
JS::RootedValue valueVector3D(cx);
if (!JS_GetProperty(cx, global, "Vector3D", &valueVector3D))
FAIL_VOID("Failed to get Vector3D constructor");
JS::AutoValueArray<3> args(cx);
args[0].setNumber(val.X.ToDouble());
args[1].setNumber(val.Y.ToDouble());
args[2].setNumber(val.Z.ToDouble());
if (!JS::Construct(cx, valueVector3D, args, ret))
FAIL_VOID("Failed to construct Vector3D object");
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CFixedVector2D& out)
{
JSAutoRequest rq(cx);
if (!v.isObject())
return false; // TODO: report type error
JS::RootedObject obj(cx, &v.toObject());
JS::RootedValue p(cx);
if (!JS_GetProperty(cx, obj, "x", &p)) return false; // TODO: report type errors
if (!FromJSVal(cx, p, out.X)) return false;
if (!JS_GetProperty(cx, obj, "y", &p)) return false;
if (!FromJSVal(cx, p, out.Y)) return false;
return true;
}
template<> void ScriptInterface::ToJSVal(JSContext* cx, JS::MutableHandleValue ret, const CFixedVector2D& val)
{
JSAutoRequest rq(cx);
ScriptInterface::CxPrivate* pCxPrivate = ScriptInterface::GetScriptInterfaceAndCBData(cx);
JS::RootedObject global(cx, &pCxPrivate->pScriptInterface->GetGlobalObject().toObject());
JS::RootedValue valueVector2D(cx);
if (!JS_GetProperty(cx, global, "Vector2D", &valueVector2D))
FAIL_VOID("Failed to get Vector2D constructor");
JS::AutoValueArray<2> args(cx);
args[0].setNumber(val.X.ToDouble());
args[1].setNumber(val.Y.ToDouble());
if (!JS::Construct(cx, valueVector2D, args, ret))
FAIL_VOID("Failed to construct Vector2D object");
}
template<> void ScriptInterface::ToJSVal >(JSContext* cx, JS::MutableHandleValue ret, const Grid& val)
{
JSAutoRequest rq(cx);
u32 length = (u32)(val.m_W * val.m_H);
u32 nbytes = (u32)(length * sizeof(u8));
JS::RootedObject objArr(cx, JS_NewUint8Array(cx, length));
// Copy the array data and then remove the no-GC check to allow further changes to the JS data
{
JS::AutoCheckCannotGC nogc;
memcpy((void*)JS_GetUint8ArrayData(objArr, nogc), val.m_Data, nbytes);
}
JS::RootedValue data(cx, JS::ObjectValue(*objArr));
JS::RootedValue w(cx);
JS::RootedValue h(cx);
ScriptInterface::ToJSVal(cx, &w, val.m_W);
ScriptInterface::ToJSVal(cx, &h, val.m_H);
JS::RootedObject obj(cx, JS_NewPlainObject(cx));
JS_SetProperty(cx, obj, "width", w);
JS_SetProperty(cx, obj, "height", h);
JS_SetProperty(cx, obj, "data", data);
ret.setObject(*obj);
}
template<> void ScriptInterface::ToJSVal >(JSContext* cx, JS::MutableHandleValue ret, const Grid& val)
{
JSAutoRequest rq(cx);
u32 length = (u32)(val.m_W * val.m_H);
u32 nbytes = (u32)(length * sizeof(u16));
JS::RootedObject objArr(cx, JS_NewUint16Array(cx, length));
// Copy the array data and then remove the no-GC check to allow further changes to the JS data
{
JS::AutoCheckCannotGC nogc;
memcpy((void*)JS_GetUint16ArrayData(objArr, nogc), val.m_Data, nbytes);
}
JS::RootedValue data(cx, JS::ObjectValue(*objArr));
JS::RootedValue w(cx);
JS::RootedValue h(cx);
ScriptInterface::ToJSVal(cx, &w, val.m_W);
ScriptInterface::ToJSVal(cx, &h, val.m_H);
JS::RootedObject obj(cx, JS_NewPlainObject(cx));
JS_SetProperty(cx, obj, "width", w);
JS_SetProperty(cx, obj, "height", h);
JS_SetProperty(cx, obj, "data", data);
ret.setObject(*obj);
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, TNSpline& out)
{
if (!v.isObject())
FAIL("Argument must be an object");
JSAutoRequest rq(cx);
JS::RootedObject obj(cx, &v.toObject());
if (!JS_IsArrayObject(cx, obj))
FAIL("Argument must be an array");
u32 numberOfNodes = 0;
if (!JS_GetArrayLength(cx, obj, &numberOfNodes))
FAIL("Failed to get array length");
for (u32 i = 0; i < numberOfNodes; ++i)
{
JS::RootedValue node(cx);
if (!JS_GetElement(cx, obj, i, &node))
FAIL("Failed to read array element");
fixed deltaTime;
if (!FromJSProperty(cx, node, "deltaTime", deltaTime))
FAIL("Failed to read Spline.deltaTime property");
CFixedVector3D position;
if (!FromJSProperty(cx, node, "position", position))
FAIL("Failed to read Spline.position property");
out.AddNode(position, CFixedVector3D(), deltaTime);
}
if (out.GetAllNodes().empty())
FAIL("Spline must contain at least one node");
return true;
}
template<> bool ScriptInterface::FromJSVal(JSContext* cx, JS::HandleValue v, CCinemaPath& out)
{
if (!v.isObject())
FAIL("Argument must be an object");
JSAutoRequest rq(cx);
JS::RootedObject obj(cx, &v.toObject());
CCinemaData pathData;
TNSpline positionSpline, targetSpline;
if (!FromJSProperty(cx, v, "name", pathData.m_Name))
FAIL("Failed to get CCinemaPath.name property");
if (!FromJSProperty(cx, v, "orientation", pathData.m_Orientation))
FAIL("Failed to get CCinemaPath.orientation property");
if (!FromJSProperty(cx, v, "positionNodes", positionSpline))
FAIL("Failed to get CCinemaPath.positionNodes property");
if (pathData.m_Orientation == L"target" && !FromJSProperty(cx, v, "targetNodes", targetSpline))
FAIL("Failed to get CCinemaPath.targetNodes property");
// Other properties are not necessary to be defined
if (!FromJSProperty(cx, v, "timescale", pathData.m_Timescale))
pathData.m_Timescale = fixed::FromInt(1);
if (!FromJSProperty(cx, v, "mode", pathData.m_Mode))
pathData.m_Mode = L"ease_inout";
if (!FromJSProperty(cx, v, "style", pathData.m_Style))
pathData.m_Style = L"default";
out = CCinemaPath(pathData, positionSpline, targetSpline);
return true;
}
// define vectors
JSVAL_VECTOR(CFixedVector2D)
+
+#undef FAIL
+#undef FAIL_VOID