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