/* Copyright (C) 2026 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/Conversions.h" #include "gui/CGUISprite.h" #include "gui/ObjectBases/IGUIObject.h" #include "gui/Scripting/JSInterface_GUIProxy.h" #include "gui/SettingTypes/CGUIColor.h" #include "gui/SettingTypes/CGUIList.h" #include "gui/SettingTypes/CGUISeries.h" #include "gui/SettingTypes/CGUIString.h" #include "gui/SettingTypes/EAlign.h" #include "gui/SettingTypes/EScrollOrientation.h" #include "lib/code_generation.h" #include "lib/external_libraries/libsdl.h" #include "maths/Rect.h" #include "maths/Size2D.h" #include "maths/Vector2D.h" #include "ps/CLogger.h" #include "ps/Hotkey.h" #include "scriptinterface/Object.h" #include "scriptinterface/Exceptions.h" #include "scriptinterface/Request.h" #include #include #include #include #include #include #include #include #include struct CColor; #define SET(obj, name, value) STMT(JS::RootedValue v_(rq.cx); Script::ToJSVal(rq, &v_, (value)); JS_SetProperty(rq.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 Script::ToJSVal(const Script::Request& rq, JS::MutableHandleValue ret, SDL_Event_ const& val) { const char* typeName; switch (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_HOTKEYPRESS: typeName = "hotkeypress"; break; case SDL_HOTKEYDOWN: typeName = "hotkeydown"; break; case SDL_HOTKEYUP: typeName = "hotkeyup"; break; case SDL_HOTKEYPRESS_SILENT: typeName = "hotkeypresssilent"; break; case SDL_HOTKEYUP_SILENT: typeName = "hotkeyupsilent"; break; default: typeName = "(unknown)"; break; } JS::RootedObject obj(rq.cx, JS_NewPlainObject(rq.cx)); if (!obj) { ret.setUndefined(); return; } SET(obj, "type", typeName); switch (ev.type) { case SDL_KEYDOWN: case SDL_KEYUP: { // SET(obj, "which", static_cast(ev.key.which)); // (not in wsdl.h) // SET(obj, "state", static_cast(ev.key.state)); // (not in wsdl.h) JS::RootedObject keysym(rq.cx, JS_NewPlainObject(rq.cx)); if (!keysym) { ret.setUndefined(); return; } JS::RootedValue keysymVal(rq.cx, JS::ObjectValue(*keysym)); JS_SetProperty(rq.cx, obj, "keysym", keysymVal); // SET(keysym, "scancode", static_cast(ev.key.keysym.scancode)); // (not in wsdl.h) SET(keysym, "sym", static_cast(ev.key.keysym.sym)); // SET(keysym, "mod", static_cast(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", static_cast(ev.motion.which)); // (not in wsdl.h) // SET(obj, "state", static_cast(ev.motion.state)); // (not in wsdl.h) SET(obj, "x", static_cast(ev.motion.x)); SET(obj, "y", static_cast(ev.motion.y)); // SET(obj, "xrel", static_cast(ev.motion.xrel)); // (not in wsdl.h) // SET(obj, "yrel", static_cast(ev.motion.yrel)); // (not in wsdl.h) break; } case SDL_MOUSEBUTTONDOWN: case SDL_MOUSEBUTTONUP: { // SET(obj, "which", static_cast(ev.button.which)); // (not in wsdl.h) SET(obj, "button", static_cast(ev.button.button)); SET(obj, "state", static_cast(ev.button.state)); SET(obj, "x", static_cast(ev.button.x)); SET(obj, "y", static_cast(ev.button.y)); SET(obj, "clicks", static_cast(ev.button.clicks)); break; } case SDL_HOTKEYPRESS: case SDL_HOTKEYDOWN: case SDL_HOTKEYUP: case SDL_HOTKEYPRESS_SILENT: case SDL_HOTKEYUP_SILENT: { SET(obj, "hotkey", static_cast(ev.user.data1)); break; } } ret.setObject(*obj); } template<> void Script::ToJSVal(const Script::Request&, JS::MutableHandleValue ret, IGUIObject* const& val) { if (val == nullptr) ret.setNull(); else ret.setObject(*val->GetJSObject()); } template<> bool Script::FromJSVal(const Script::Request& rq, JS::HandleValue v, IGUIObject*& out) { if (!v.isObject()) { Script::Exception::Raise(rq, "Value is not an IGUIObject."); return false; } out = IGUIProxyObject::FromPrivateSlot(v.toObjectOrNull()); if (!out) { Script::Exception::Raise(rq, "Value is not an IGUIObject."); return false; } return true; } template<> void Script::ToJSVal(const Script::Request& rq, JS::MutableHandleValue ret, const CGUIString& val) { Script::ToJSVal(rq, ret, val.GetOriginalString()); } template<> bool Script::FromJSVal(const Script::Request& rq, JS::HandleValue v, CGUIString& out) { std::wstring val; if (!FromJSVal(rq, v, val)) return false; out.SetValue(val); return true; } JSVAL_VECTOR(CVector2D) JSVAL_VECTOR(std::vector) JSVAL_VECTOR(CGUIString) template<> void Script::ToJSVal(const Script::Request& rq, JS::MutableHandleValue ret, const CGUIColor& val) { ToJSVal(rq, ret, val); } /** * The color depends on the predefined color database stored in the current GUI page. */ template<> bool Script::FromJSVal(const Script::Request& rq, JS::HandleValue v, CGUIColor& out) = delete; template<> void Script::ToJSVal(const Script::Request& rq, JS::MutableHandleValue ret, const CRect& val) { Script::CreateObject( rq, ret, "left", val.left, "right", val.right, "top", val.top, "bottom", val.bottom); } template<> void Script::ToJSVal(const Script::Request& rq, JS::MutableHandleValue ret, const CGUIList& val) { ToJSVal(rq, ret, val.m_Items); } template<> bool Script::FromJSVal(const Script::Request& rq, JS::HandleValue v, CGUIList& out) { return FromJSVal(rq, v, out.m_Items); } template<> void Script::ToJSVal(const Script::Request& rq, JS::MutableHandleValue ret, const CGUISeries& val) { ToJSVal(rq, ret, val.m_Series); } template<> bool Script::FromJSVal(const Script::Request& rq, JS::HandleValue v, CGUISeries& out) { return FromJSVal(rq, v, out.m_Series); } template<> void Script::ToJSVal(const Script::Request& rq, 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"; Script::Exception::Raise(rq, "Invalid EVAlign"); break; } ToJSVal(rq, ret, word); } template<> bool Script::FromJSVal(const Script::Request& rq, JS::HandleValue v, EVAlign& out) { std::string word; FromJSVal(rq, 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; LOGERROR("Invalid alignment (should be 'left', 'right' or 'center')"); return false; } return true; } template<> void Script::ToJSVal(const Script::Request& rq, 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"; Script::Exception::Raise(rq, "Invalid alignment (should be 'left', 'right' or 'center')"); break; } ToJSVal(rq, ret, word); } template<> bool Script::FromJSVal(const Script::Request& rq, JS::HandleValue v, EAlign& out) { std::string word; FromJSVal(rq, 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; LOGERROR("Invalid alignment (should be 'left', 'right' or 'center')"); return false; } return true; } template<> void Script::ToJSVal(const Script::Request& rq, JS::MutableHandleValue ret, const EScrollOrientation& val) { std::string word; switch (val) { case EScrollOrientation::HORIZONTAL: word = "horizontal"; break; case EScrollOrientation::VERTICAL: word = "vertical"; break; case EScrollOrientation::BOTH: word = "both"; break; default: word = "error"; Script::Exception::Raise(rq, "Invalid scroll orientation (should be 'vertical', 'horizontal' or 'both')"); break; } ToJSVal(rq, ret, word); } template <> bool Script::FromJSVal(const Script::Request& rq, JS::HandleValue v, EScrollOrientation& out) { std::string word; FromJSVal(rq, v, word); if (word == "horizontal") out = EScrollOrientation::HORIZONTAL; else if (word == "vertical") out = EScrollOrientation::VERTICAL; else if (word == "both") out = EScrollOrientation::BOTH; else { out = EScrollOrientation::VERTICAL; LOGERROR("Invalid scroll orientation (should be 'vertical', 'horizontal' or 'both')"); return false; } return true; } template<> void Script::ToJSVal(const Script::Request& rq, JS::MutableHandleValue ret, const CGUISpriteInstance& val) { ToJSVal(rq, ret, val.GetName()); } template<> bool Script::FromJSVal(const Script::Request& rq, JS::HandleValue v, CGUISpriteInstance& out) { std::string name; if (!FromJSVal(rq, v, name)) return false; out.SetName(name); return true; } template<> void Script::ToJSVal(const Script::Request& rq, JS::MutableHandleValue ret, const CSize2D& val) { Script::CreateObject(rq, ret, "width", val.Width, "height", val.Height); } template<> bool Script::FromJSVal(const Script::Request& rq, JS::HandleValue v, CSize2D& out) { if (!v.isObject()) { LOGERROR("CSize2D value must be an object!"); return false; } if (!FromJSProperty(rq, v, "width", out.Width)) { LOGERROR("Failed to get CSize2D.Width property"); return false; } if (!FromJSProperty(rq, v, "height", out.Height)) { LOGERROR("Failed to get CSize2D.Height property"); return false; } return true; } template<> void Script::ToJSVal(const Script::Request& rq, JS::MutableHandleValue ret, const CVector2D& val) { Script::CreateObject(rq, ret, "x", val.X, "y", val.Y); } template<> bool Script::FromJSVal(const Script::Request& rq, JS::HandleValue v, CVector2D& out) { if (!v.isObject()) { LOGERROR("CVector2D value must be an object!"); return false; } if (!FromJSProperty(rq, v, "x", out.X)) { LOGERROR("Failed to get CVector2D.X property"); return false; } if (!FromJSProperty(rq, v, "y", out.Y)) { LOGERROR("Failed to get CVector2D.Y property"); return false; } return true; } #undef SET