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