Index: ps/trunk/source/gui/ObjectTypes/CList.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/CList.cpp (revision 24311) +++ ps/trunk/source/gui/ObjectTypes/CList.cpp (revision 24312) @@ -1,532 +1,517 @@ /* 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 "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_CellID(), 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("cell_id", m_CellID); 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; - m_ItemsYPositions.resize(m_List.m_Items.size() + 1); - 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) - buffered_y = m_ItemsYPositions[m_List.m_Items.size() - 1]; + 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().cy; } 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, m_CellID, 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, m_CellID, 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() - CPos(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); - // TODO Temp - SetupText(); + SetupText(true); } 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(); CPos 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()); js::ProxyOptions options; options.setClass(&JSI_GUIProxy::ClassDefinition()); JS::RootedValue cppObj(rq.cx), data(rq.cx); cppObj.get().setPrivate(this); data.get().setPrivate(GetGUI().GetProxyData(&JSI_GUIProxy::Singleton())); m_JSObject.init(rq.cx, js::NewProxyObject(rq.cx, &JSI_GUIProxy::Singleton(), cppObj, nullptr, options)); js::SetProxyReservedSlot(m_JSObject, 0, data); } -bool CList::Script_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; -} Index: ps/trunk/source/gui/ObjectTypes/CList.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CList.h (revision 24311) +++ ps/trunk/source/gui/ObjectTypes/CList.h (revision 24312) @@ -1,165 +1,162 @@ /* 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 . */ #ifndef INCLUDED_CLIST #define INCLUDED_CLIST #include "gui/CGUISprite.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) friend JSI_GUIProxy; 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); protected: - - static bool Script_AddItem(JSContext* cx, uint argc, JS::Value* vp); - /** * 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; i32 m_CellID; 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/Scripting/JSInterface_CList.cpp =================================================================== --- ps/trunk/source/gui/Scripting/JSInterface_CList.cpp (revision 24311) +++ ps/trunk/source/gui/Scripting/JSInterface_CList.cpp (revision 24312) @@ -1,80 +1,97 @@ /* 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.h" #include "gui/CGUI.h" #include "gui/CGUISetting.h" #include "gui/ObjectBases/IGUIObject.h" #include "gui/ObjectTypes/CList.h" #include "ps/CLogger.h" #include "scriptinterface/ScriptExtraHeaders.h" #include "scriptinterface/ScriptInterface.h" #include // Include the definition of the generic templates. #include "JSInterface_GUIProxy_impl.h" -namespace { - struct SData - { - JS::PersistentRootedObject m_ToString; - JS::PersistentRootedObject m_Focus; - JS::PersistentRootedObject m_Blur; - JS::PersistentRootedObject m_GetComputedSize; - JS::PersistentRootedObject m_AddItem; - }; +namespace +{ +struct SData +{ + JS::PersistentRootedObject m_ToString; + JS::PersistentRootedObject m_Focus; + JS::PersistentRootedObject m_Blur; + JS::PersistentRootedObject m_GetComputedSize; + JS::PersistentRootedObject m_AddItem; +}; + +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; +} } template <> -bool JSI_GUIProxy::funcGetter(JS::HandleObject proxy, const std::string& propName, JS::MutableHandleValue vp) const +bool JSI_GUIProxy::FuncGetter(JS::HandleObject proxy, const std::string& propName, JS::MutableHandleValue vp) const { const SData& data = *static_cast(js::GetProxyReservedSlot(proxy, 0).toPrivate()); if (propName == "toString") return vp.setObjectOrNull(data.m_ToString), true; if (propName == "focus") return vp.setObjectOrNull(data.m_Focus), true; if (propName == "blur") return vp.setObjectOrNull(data.m_Blur), true; if (propName == "getComputedSize") return vp.setObjectOrNull(data.m_GetComputedSize), true; if (propName == "addItem") return vp.setObjectOrNull(data.m_AddItem), true; return false; } template <> std::pair JSI_GUIProxy::CreateData(ScriptInterface& scriptInterface) { SData* data = new SData(); ScriptRequest rq(scriptInterface); #define func(class, func) &apply_to data->m_ToString.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, toString), 0, 0, "toString"))); data->m_Focus.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, focus), 0, 0, "focus"))); data->m_Blur.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, blur), 0, 0, "blur"))); data->m_GetComputedSize.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, getComputedSize), 0, 0, "getComputedSize"))); #undef func - data->m_AddItem.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, CList::Script_AddItem, 0, 0, "addItem"))); + data->m_AddItem.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, CList_AddItem, 1, 0, "addItem"))); return { &Singleton(), data }; } template class JSI_GUIProxy; Index: ps/trunk/source/gui/Scripting/JSInterface_CText.cpp =================================================================== --- ps/trunk/source/gui/Scripting/JSInterface_CText.cpp (revision 24311) +++ ps/trunk/source/gui/Scripting/JSInterface_CText.cpp (revision 24312) @@ -1,80 +1,81 @@ /* 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.h" #include "gui/CGUI.h" #include "gui/CGUISetting.h" #include "gui/ObjectBases/IGUIObject.h" #include "gui/ObjectTypes/CText.h" #include "ps/CLogger.h" #include "scriptinterface/ScriptExtraHeaders.h" #include "scriptinterface/ScriptInterface.h" #include // Include the definition of the generic templates. #include "JSInterface_GUIProxy_impl.h" -namespace { - struct SData - { - JS::PersistentRootedObject m_ToString; - JS::PersistentRootedObject m_Focus; - JS::PersistentRootedObject m_Blur; - JS::PersistentRootedObject m_GetComputedSize; - JS::PersistentRootedObject m_GetTextSize; - }; +namespace +{ +struct SData +{ + JS::PersistentRootedObject m_ToString; + JS::PersistentRootedObject m_Focus; + JS::PersistentRootedObject m_Blur; + JS::PersistentRootedObject m_GetComputedSize; + JS::PersistentRootedObject m_GetTextSize; +}; } template <> -bool JSI_GUIProxy::funcGetter(JS::HandleObject proxy, const std::string& propName, JS::MutableHandleValue vp) const +bool JSI_GUIProxy::FuncGetter(JS::HandleObject proxy, const std::string& propName, JS::MutableHandleValue vp) const { const SData& data = *static_cast(js::GetProxyReservedSlot(proxy, 0).toPrivate()); if (propName == "toString") return vp.setObjectOrNull(data.m_ToString), true; if (propName == "focus") return vp.setObjectOrNull(data.m_Focus), true; if (propName == "blur") return vp.setObjectOrNull(data.m_Blur), true; if (propName == "getComputedSize") return vp.setObjectOrNull(data.m_GetComputedSize), true; if (propName == "getTextSize") return vp.setObjectOrNull(data.m_GetTextSize), true; return false; } template <> std::pair JSI_GUIProxy::CreateData(ScriptInterface& scriptInterface) { SData* data = new SData(); ScriptRequest rq(scriptInterface); #define func(class, func) &apply_to data->m_ToString.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, toString), 0, 0, "toString"))); data->m_Focus.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, focus), 0, 0, "focus"))); data->m_Blur.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, blur), 0, 0, "blur"))); data->m_GetComputedSize.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, getComputedSize), 0, 0, "getComputedSize"))); data->m_GetTextSize.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(CText, getTextSize), 0, 0, "getTextSize"))); #undef func return { &Singleton(), data }; } template class JSI_GUIProxy; Index: ps/trunk/source/gui/Scripting/JSInterface_GUIProxy.h =================================================================== --- ps/trunk/source/gui/Scripting/JSInterface_GUIProxy.h (revision 24311) +++ ps/trunk/source/gui/Scripting/JSInterface_GUIProxy.h (revision 24312) @@ -1,133 +1,133 @@ /* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_JSI_GUIPROXY #define INCLUDED_JSI_GUIPROXY #include "scriptinterface/ScriptExtraHeaders.h" #include class ScriptInterface; // 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 /** * 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. */ template class JSI_GUIProxy : public js::BaseProxyHandler { public: // Access the js::Class of the Proxy. static js::Class& ClassDefinition(); // For convenience, this is the single instantiated JSI_GUIProxy. static JSI_GUIProxy& Singleton(); static std::pair CreateData(ScriptInterface& scriptInterface); 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() {}; // This handles returning function properties. // Specialize this. - bool funcGetter(JS::HandleObject proxy, const std::string& propName, JS::MutableHandleValue vp) const; + bool FuncGetter(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 24311) +++ ps/trunk/source/gui/Scripting/JSInterface_GUIProxy_impl.h (revision 24312) @@ -1,200 +1,200 @@ /* 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 . */ // This file is included directly into actual implementation files. template js::Class& JSI_GUIProxy::ClassDefinition() { static js::Class 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; } namespace { template inline bool apply_to(JSContext* cx, uint argc, JS::Value* vp) { 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; } } 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()); 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 (funcGetter(proxy, propName, vp)) + if (FuncGetter(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()); if (!e) return result.fail(JSMSG_NOT_NONNULL_OBJECT); ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); JS::RootedValue idval(rq.cx); if (!JS_IdToValue(rq.cx, id, &idval)) return result.fail(JSMSG_NOT_NONNULL_OBJECT); std::string propName; if (!ScriptInterface::FromJSVal(rq, idval, propName)) return result.fail(JSMSG_UNDEFINED_PROP); if (propName == "name") { std::string value; if (!ScriptInterface::FromJSVal(rq, vp, value)) return result.fail(JSMSG_UNDEFINED_PROP); 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_UNDEFINED_PROP); } 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()); if (!e) return result.fail(JSMSG_NOT_NONNULL_OBJECT); ScriptRequest rq(*ScriptInterface::GetScriptInterfaceAndCBData(cx)->pScriptInterface); JS::RootedValue idval(rq.cx); if (!JS_IdToValue(rq.cx, id, &idval)) return result.fail(JSMSG_NOT_NONNULL_OBJECT); std::string propName; if (!ScriptInterface::FromJSVal(rq, idval, propName)) return result.fail(JSMSG_UNDEFINED_PROP); // 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_UNDEFINED_PROP); } Index: ps/trunk/source/gui/Scripting/JSInterface_IGUIObject.cpp =================================================================== --- ps/trunk/source/gui/Scripting/JSInterface_IGUIObject.cpp (revision 24311) +++ ps/trunk/source/gui/Scripting/JSInterface_IGUIObject.cpp (revision 24312) @@ -1,74 +1,75 @@ /* 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.h" #include "gui/CGUI.h" #include "gui/CGUISetting.h" #include "gui/ObjectBases/IGUIObject.h" #include "gui/ObjectTypes/CText.h" #include "ps/CLogger.h" #include "scriptinterface/ScriptExtraHeaders.h" #include "scriptinterface/ScriptInterface.h" #include // Include the definition of the generic templates. #include "JSInterface_GUIProxy_impl.h" -namespace { - struct SData - { - JS::PersistentRootedObject m_ToString; - JS::PersistentRootedObject m_Focus; - JS::PersistentRootedObject m_Blur; - JS::PersistentRootedObject m_GetComputedSize; - }; +namespace +{ +struct SData +{ + JS::PersistentRootedObject m_ToString; + JS::PersistentRootedObject m_Focus; + JS::PersistentRootedObject m_Blur; + JS::PersistentRootedObject m_GetComputedSize; +}; } template <> -bool JSI_GUIProxy::funcGetter(JS::HandleObject proxy, const std::string& propName, JS::MutableHandleValue vp) const +bool JSI_GUIProxy::FuncGetter(JS::HandleObject proxy, const std::string& propName, JS::MutableHandleValue vp) const { const SData& data = *static_cast(js::GetProxyReservedSlot(proxy, 0).toPrivate()); if (propName == "toString") return vp.setObjectOrNull(data.m_ToString), true; if (propName == "focus") return vp.setObjectOrNull(data.m_Focus), true; if (propName == "blur") return vp.setObjectOrNull(data.m_Blur), true; if (propName == "getComputedSize") return vp.setObjectOrNull(data.m_GetComputedSize), true; return false; } template <> std::pair JSI_GUIProxy::CreateData(ScriptInterface& scriptInterface) { SData* data = new SData(); ScriptRequest rq(scriptInterface); #define func(class, func) &apply_to data->m_ToString.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, toString), 0, 0, "toString"))); data->m_Focus.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, focus), 0, 0, "focus"))); data->m_Blur.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, blur), 0, 0, "blur"))); data->m_GetComputedSize.init(rq.cx, JS_GetFunctionObject(JS_NewFunction(rq.cx, func(IGUIObject, getComputedSize), 0, 0, "getComputedSize"))); #undef func return { &Singleton(), data }; } template class JSI_GUIProxy;