Index: ps/trunk/source/gui/GUISettingTypes.h =================================================================== --- ps/trunk/source/gui/GUISettingTypes.h (revision 25391) +++ ps/trunk/source/gui/GUISettingTypes.h (nonexistent) @@ -1,50 +0,0 @@ -/* 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 used by all bits of GUI code that need to repeat some code -for a variety of types (to avoid repeating the list of types in half a dozen -places, and to make it much easier to add a new type). Just do - #define TYPE(T) your_code_involving_T; - #include "gui/SettingTypes/GUISettingTypes.h" - #undef TYPE -to handle every possible type. -*/ - -#ifndef GUITYPE_IGNORE_COPYABLE -#include "gui/SettingTypes/EAlign.h" -TYPE(bool) -TYPE(i32) -TYPE(u32) -TYPE(float) -TYPE(EAlign) -TYPE(EVAlign) -TYPE(CVector2D) -#endif - -#ifndef GUITYPE_IGNORE_NONCOPYABLE -#include "gui/SettingTypes/CGUIList.h" -#include "gui/SettingTypes/CGUISeries.h" -TYPE(CGUISize) -TYPE(CGUIColor) -TYPE(CGUIList) -TYPE(CGUISeries) -TYPE(CGUISpriteInstance) -TYPE(CGUIString) -TYPE(CStr) -TYPE(CStrW) -#endif Property changes on: ps/trunk/source/gui/GUISettingTypes.h ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/source/gui/ObjectTypes/CRadioButton.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/CRadioButton.cpp (revision 25391) +++ ps/trunk/source/gui/ObjectTypes/CRadioButton.cpp (revision 25392) @@ -1,48 +1,48 @@ -/* 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 . */ #include "precompiled.h" #include "CRadioButton.h" CRadioButton::CRadioButton(CGUI& pGUI) : CCheckBox(pGUI) { } void CRadioButton::HandleMessage(SGUIMessage& Message) { IGUIButtonBehavior::HandleMessage(Message); - switch (Message.type) { case GUIM_PRESSED: for (IGUIObject* const& obj : GetParent()->GetChildren()) { // Notice, if you use other objects within the parent object that has got // the setting "checked", it too will change. Hence NO OTHER OBJECTS THAN // RADIO BUTTONS SHOULD BE WITHIN IT! - obj->SetSetting("checked", false, true); + // TODO: this should be enforced in the engine, and then we could cast IGUIObject* to CRadioButton*. + obj->SetSettingFromString("checked", L"false", true); } - SetSetting("checked", true, true); + m_Checked.Set(true, true); break; default: break; } } Index: ps/trunk/source/gui/ObjectTypes/CInput.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CInput.h (revision 25391) +++ ps/trunk/source/gui/ObjectTypes/CInput.h (revision 25392) @@ -1,239 +1,238 @@ /* 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_CINPUT #define INCLUDED_CINPUT #include "gui/CGUISprite.h" #include "gui/ObjectBases/IGUIObject.h" #include "gui/ObjectBases/IGUIScrollBarOwner.h" #include "gui/SettingTypes/CGUIString.h" #include "lib/external_libraries/libsdl.h" #include #include /** * Text field where you can input and edit the text. * * It doesn't use IGUITextOwner, because we don't need * any other features than word-wrapping, and we need to be * able to rapidly change the string. */ class CInput : public IGUIObject, public IGUIScrollBarOwner { GUI_OBJECT(CInput) protected: // forwards struct SRow; public: CInput(CGUI& pGUI); virtual ~CInput(); /** * @see IGUIObject#ResetStates() */ virtual void ResetStates(); // Check where the mouse is hovering, and get the appropriate text position. // return is the text-position index. int GetMouseHoveringTextPosition() const; // Same as above, but only on one row in X, and a given value, not the mouse's. // wanted is filled with x if the row didn't extend as far as the mouse pos. int GetXTextPosition(const std::list::const_iterator& c, const float& x, float& wanted) const; protected: void SetupGeneratedPlaceholderText(); /** * @see IGUIObject#HandleMessage() */ virtual void HandleMessage(SGUIMessage& Message); /** * Handle events manually to catch keyboard inputting. */ virtual InReaction ManuallyHandleKeys(const SDL_Event_* ev); /** * Handle events manually to catch keys which change the text. */ virtual void ManuallyMutableHandleKeyDownEvent(const SDL_Keycode keyCode); /** * Handle events manually to catch keys which don't change the text. */ virtual void ManuallyImmutableHandleKeyDownEvent(const SDL_Keycode keyCode); /** * Handle hotkey events (called by ManuallyHandleKeys) */ virtual InReaction ManuallyHandleHotkeyEvent(const SDL_Event_* ev); /** * @see IGUIObject#UpdateCachedSize() */ virtual void UpdateCachedSize(); /** * Draws the Text */ virtual void Draw(); /** * Calculate m_CharacterPosition * the main task for this function is to perfom word-wrapping * You input from which character it has been changed, because * if we add a character to the very last end, we don't want * process everything all over again! Also notice you can * specify a 'to' also, it will only be used though if a '\n' * appears, because then the word-wrapping won't change after * that. */ void UpdateText(int from = 0, int to_before = -1, int to_after = -1); /** * Draws the text generated for placeholder. * * @param z Z value * @param clipping Clipping rectangle, don't even add a parameter * to get no clipping. */ virtual void DrawPlaceholderText(float z, const CRect& clipping = CRect()); /** * Delete the current selection. Also places the pointer at the * crack between the two segments kept. */ void DeleteCurSelection(); /** * Is text selected? It can be denote two ways, m_iBufferPos_Tail * being -1 or the same as m_iBufferPos. This makes for clearer * code. */ bool SelectingText() const; /// Get area of where text can be drawn. float GetTextAreaWidth(); /// Called every time the auto-scrolling should be checked. void UpdateAutoScroll(); /// Clear composed IME input when supported (SDL2 only). void ClearComposedText(); /// Updates the buffer (cursor) position exposed to JS. void UpdateBufferPositionSetting(); protected: /// Cursor position int m_iBufferPos; /// Cursor position we started to select from. (-1 if not selecting) /// (NB: Can be larger than m_iBufferPos if selecting from back to front.) int m_iBufferPos_Tail; /// If we're composing text with an IME bool m_ComposingText; /// The length and position of the current IME composition int m_iComposedLength, m_iComposedPos; /// The position to insert committed text int m_iInsertPos; // the outer vector is lines, and the inner is X positions // in a row. So that we can determine where characters are // placed. It's important because we need to know where the // pointer should be placed when the input control is pressed. struct SRow { // Where the Row starts int m_ListStart; // List of X values for each character. std::vector m_ListOfX; }; /** * List of rows to ease changing its size, so iterators stay valid. * For one-liners only one row is used. */ std::list m_CharacterPositions; // *** Things for a multi-lined input control *** // /** * When you change row with up/down, and the row you jump to does * not have anything at that X position, then it will keep the * m_WantedX position in mind when switching to the next row. * It will keep on being used until it reach a row which meets the * requirements. * 0.0f means not in use. */ float m_WantedX; /** * If we are in the process of selecting a larger selection of text * using the mouse click (hold) and drag, this is true. */ bool m_SelectingText; /** * Whether the cached text is currently valid (if not then SetupText will be called by Draw) */ bool m_GeneratedPlaceholderTextValid; CGUIText m_GeneratedPlaceholderText; // *** Things for one-line input control *** // float m_HorizontalScroll; /// Used to store the previous time for flashing the cursor. double m_PrevTime; /// Cursor blink rate in seconds, if greater than 0.0. double m_CursorBlinkRate; /// If the cursor should be drawn or not. bool m_CursorVisState; static const CStr EventNameTextEdit; static const CStr EventNamePress; static const CStr EventNameTab; - // Settings - i32 m_BufferPosition; - float m_BufferZone; - CStrW m_Caption; - CGUIString m_PlaceholderText; - CStrW m_Font; - CStrW m_MaskChar; - bool m_Mask; - i32 m_MaxLength; - bool m_MultiLine; - bool m_Readonly; - bool m_ScrollBar; - CStr m_ScrollBarStyle; - CGUISpriteInstance m_Sprite; - CGUISpriteInstance m_SpriteSelectArea; - CGUIColor m_TextColor; - CGUIColor m_TextColorSelected; - CGUIColor m_PlaceholderColor; + CGUISimpleSetting m_BufferPosition; + CGUISimpleSetting m_BufferZone; + CGUISimpleSetting m_Caption; + CGUISimpleSetting m_PlaceholderText; + CGUISimpleSetting m_Font; + CGUISimpleSetting m_MaskChar; + CGUISimpleSetting m_Mask; + CGUISimpleSetting m_MaxLength; + CGUISimpleSetting m_MultiLine; + CGUISimpleSetting m_Readonly; + CGUISimpleSetting m_ScrollBar; + CGUISimpleSetting m_ScrollBarStyle; + CGUISimpleSetting m_Sprite; + CGUISimpleSetting m_SpriteSelectArea; + CGUISimpleSetting m_TextColor; + CGUISimpleSetting m_TextColorSelected; + CGUISimpleSetting m_PlaceholderColor; }; #endif // INCLUDED_CINPUT Index: ps/trunk/source/gui/CGUI.cpp =================================================================== --- ps/trunk/source/gui/CGUI.cpp (revision 25391) +++ ps/trunk/source/gui/CGUI.cpp (revision 25392) @@ -1,1325 +1,1330 @@ /* 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 "CGUI.h" #include "gui/IGUIScrollBar.h" #include "gui/ObjectTypes/CGUIDummyObject.h" #include "gui/ObjectTypes/CTooltip.h" #include "gui/Scripting/ScriptFunctions.h" #include "gui/Scripting/JSInterface_GUIProxy.h" #include "i18n/L10n.h" #include "lib/allocators/DynamicArena.h" #include "lib/allocators/STLAllocators.h" #include "lib/bits.h" #include "lib/input.h" #include "lib/sysdep/sysdep.h" #include "lib/timer.h" #include "lib/utf8.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/GameSetup/Config.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Profile.h" #include "ps/Pyrogenesis.h" #include "ps/XML/Xeromyces.h" #include "renderer/Renderer.h" #include "scriptinterface/ScriptContext.h" #include "scriptinterface/ScriptInterface.h" #include #include #include extern int g_yres; const double SELECT_DBLCLICK_RATE = 0.5; const u32 MAX_OBJECT_DEPTH = 100; // Max number of nesting for GUI includes. Used to detect recursive inclusion const CStr CGUI::EventNameLoad = "Load"; const CStr CGUI::EventNameTick = "Tick"; const CStr CGUI::EventNamePress = "Press"; const CStr CGUI::EventNameKeyDown = "KeyDown"; const CStr CGUI::EventNameRelease = "Release"; const CStr CGUI::EventNameMouseRightPress = "MouseRightPress"; const CStr CGUI::EventNameMouseLeftPress = "MouseLeftPress"; const CStr CGUI::EventNameMouseWheelDown = "MouseWheelDown"; const CStr CGUI::EventNameMouseWheelUp = "MouseWheelUp"; const CStr CGUI::EventNameMouseLeftDoubleClick = "MouseLeftDoubleClick"; const CStr CGUI::EventNameMouseLeftRelease = "MouseLeftRelease"; const CStr CGUI::EventNameMouseRightDoubleClick = "MouseRightDoubleClick"; const CStr CGUI::EventNameMouseRightRelease = "MouseRightRelease"; namespace { struct VisibleObject { IGUIObject* object; // Index of the object in a depth-first search inside GUI tree. size_t index; // Cached value of GetBufferedZ to avoid recursive calls in a deep hierarchy. float bufferedZ; }; template void CollectVisibleObjectsRecursively(const std::vector& objects, Container* visibleObjects) { for (IGUIObject* const& object : objects) { if (!object->IsHidden()) { visibleObjects->emplace_back(VisibleObject{object, visibleObjects->size(), 0.0f}); CollectVisibleObjectsRecursively(object->GetChildren(), visibleObjects); } } } } // anonynous namespace CGUI::CGUI(const shared_ptr& context) : m_BaseObject(std::make_unique(*this)), m_FocusedObject(nullptr), m_InternalNameNumber(0), m_MouseButtons(0) { m_ScriptInterface = std::make_shared("Engine", "GUIPage", context); m_ScriptInterface->SetCallbackData(this); GuiScriptingInit(*m_ScriptInterface); m_ScriptInterface->LoadGlobalScripts(); } CGUI::~CGUI() { for (const std::pair& p : m_pAllObjects) delete p.second; for (const std::pair& p : m_Sprites) delete p.second; } InReaction CGUI::HandleEvent(const SDL_Event_* ev) { InReaction ret = IN_PASS; if (ev->ev.type == SDL_HOTKEYDOWN || ev->ev.type == SDL_HOTKEYPRESS || ev->ev.type == SDL_HOTKEYUP) { const char* hotkey = static_cast(ev->ev.user.data1); const CStr& eventName = ev->ev.type == SDL_HOTKEYPRESS ? EventNamePress : ev->ev.type == SDL_HOTKEYDOWN ? EventNameKeyDown : EventNameRelease; if (m_GlobalHotkeys.find(hotkey) != m_GlobalHotkeys.end() && m_GlobalHotkeys[hotkey].find(eventName) != m_GlobalHotkeys[hotkey].end()) { ret = IN_HANDLED; ScriptRequest rq(m_ScriptInterface); JS::RootedObject globalObj(rq.cx, rq.glob); JS::RootedValue result(rq.cx); if (!JS_CallFunctionValue(rq.cx, globalObj, m_GlobalHotkeys[hotkey][eventName], JS::HandleValueArray::empty(), &result)) ScriptException::CatchPending(rq); } std::map >::iterator it = m_HotkeyObjects.find(hotkey); if (it != m_HotkeyObjects.end()) for (IGUIObject* const& obj : it->second) { if (ev->ev.type == SDL_HOTKEYPRESS) ret = obj->SendEvent(GUIM_PRESSED, EventNamePress); else if (ev->ev.type == SDL_HOTKEYDOWN) ret = obj->SendEvent(GUIM_KEYDOWN, EventNameKeyDown); else ret = obj->SendEvent(GUIM_RELEASED, EventNameRelease); } } else if (ev->ev.type == SDL_MOUSEMOTION) { // Yes the mouse position is stored as float to avoid // constant conversions when operating in a // float-based environment. m_MousePos = CVector2D((float)ev->ev.motion.x / g_GuiScale, (float)ev->ev.motion.y / g_GuiScale); SGUIMessage msg(GUIM_MOUSE_MOTION); m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::HandleMessage, msg); } // Update m_MouseButtons. (BUTTONUP is handled later.) else if (ev->ev.type == SDL_MOUSEBUTTONDOWN) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: case SDL_BUTTON_RIGHT: case SDL_BUTTON_MIDDLE: m_MouseButtons |= Bit(ev->ev.button.button); break; default: break; } } // Update m_MousePos (for delayed mouse button events) CVector2D oldMousePos = m_MousePos; if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP) { m_MousePos = CVector2D((float)ev->ev.button.x / g_GuiScale, (float)ev->ev.button.y / g_GuiScale); } // Allow the focused object to pre-empt regular GUI events. if (GetFocusedObject()) ret = GetFocusedObject()->PreemptEvent(ev); // Only one object can be hovered // pNearest will after this point at the hovered object, possibly nullptr IGUIObject* pNearest = FindObjectUnderMouse(); if (ret == IN_PASS) { // Now we'll call UpdateMouseOver on *all* objects, // we'll input the one hovered, and they will each // update their own data and send messages accordingly m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::UpdateMouseOver, static_cast(pNearest)); if (ev->ev.type == SDL_MOUSEBUTTONDOWN) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: // Focus the clicked object (or focus none if nothing clicked on) SetFocusedObject(pNearest); if (pNearest) ret = pNearest->SendMouseEvent(GUIM_MOUSE_PRESS_LEFT, EventNameMouseLeftPress); break; case SDL_BUTTON_RIGHT: if (pNearest) ret = pNearest->SendMouseEvent(GUIM_MOUSE_PRESS_RIGHT, EventNameMouseRightPress); break; default: break; } } else if (ev->ev.type == SDL_MOUSEWHEEL && pNearest) { if (ev->ev.wheel.y < 0) ret = pNearest->SendMouseEvent(GUIM_MOUSE_WHEEL_DOWN, EventNameMouseWheelDown); else if (ev->ev.wheel.y > 0) ret = pNearest->SendMouseEvent(GUIM_MOUSE_WHEEL_UP, EventNameMouseWheelUp); } else if (ev->ev.type == SDL_MOUSEBUTTONUP) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: if (pNearest) { double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_LEFT]; pNearest->m_LastClickTime[SDL_BUTTON_LEFT] = timer_Time(); if (timeElapsed < SELECT_DBLCLICK_RATE) ret = pNearest->SendMouseEvent(GUIM_MOUSE_DBLCLICK_LEFT, EventNameMouseLeftDoubleClick); else ret = pNearest->SendMouseEvent(GUIM_MOUSE_RELEASE_LEFT, EventNameMouseLeftRelease); } break; case SDL_BUTTON_RIGHT: if (pNearest) { double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_RIGHT]; pNearest->m_LastClickTime[SDL_BUTTON_RIGHT] = timer_Time(); if (timeElapsed < SELECT_DBLCLICK_RATE) ret = pNearest->SendMouseEvent(GUIM_MOUSE_DBLCLICK_RIGHT, EventNameMouseRightDoubleClick); else ret = pNearest->SendMouseEvent(GUIM_MOUSE_RELEASE_RIGHT, EventNameMouseRightRelease); } break; } // Reset all states on all visible objects m_BaseObject->RecurseObject(&IGUIObject::IsHidden, &IGUIObject::ResetStates); // Since the hover state will have been reset, we reload it. m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::UpdateMouseOver, static_cast(pNearest)); } } // BUTTONUP's effect on m_MouseButtons is handled after // everything else, so that e.g. 'press' handlers (activated // on button up) see which mouse button had been pressed. if (ev->ev.type == SDL_MOUSEBUTTONUP) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: case SDL_BUTTON_RIGHT: case SDL_BUTTON_MIDDLE: m_MouseButtons &= ~Bit(ev->ev.button.button); break; default: break; } } // Restore m_MousePos (for delayed mouse button events) if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP) m_MousePos = oldMousePos; // Let GUI items handle keys after everything else, e.g. for input boxes. if (ret == IN_PASS && GetFocusedObject()) { if (ev->ev.type == SDL_KEYUP || ev->ev.type == SDL_KEYDOWN || ev->ev.type == SDL_HOTKEYUP || ev->ev.type == SDL_HOTKEYDOWN || ev->ev.type == SDL_TEXTINPUT || ev->ev.type == SDL_TEXTEDITING) ret = GetFocusedObject()->ManuallyHandleKeys(ev); // else will return IN_PASS because we never used the button. } return ret; } void CGUI::TickObjects() { m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::Tick); SendEventToAll(EventNameTick); m_Tooltip.Update(FindObjectUnderMouse(), m_MousePos, *this); } void CGUI::SendEventToAll(const CStr& eventName) { std::unordered_map>::iterator it = m_EventObjects.find(eventName); if (it == m_EventObjects.end()) return; std::vector copy = it->second; for (IGUIObject* object : copy) object->ScriptEvent(eventName); } void CGUI::SendEventToAll(const CStr& eventName, const JS::HandleValueArray& paramData) { std::unordered_map>::iterator it = m_EventObjects.find(eventName); if (it == m_EventObjects.end()) return; std::vector copy = it->second; for (IGUIObject* object : copy) object->ScriptEvent(eventName, paramData); } void CGUI::Draw() { using Arena = Allocators::DynamicArena<128 * KiB>; using ObjectListAllocator = ProxyAllocator; Arena arena; std::vector visibleObjects((ObjectListAllocator(arena))); CollectVisibleObjectsRecursively(m_BaseObject->GetChildren(), &visibleObjects); for (VisibleObject& visibleObject : visibleObjects) visibleObject.bufferedZ = visibleObject.object->GetBufferedZ(); std::sort(visibleObjects.begin(), visibleObjects.end(), [](const VisibleObject& visibleObject1, const VisibleObject& visibleObject2) -> bool { if (visibleObject1.bufferedZ != visibleObject2.bufferedZ) return visibleObject1.bufferedZ < visibleObject2.bufferedZ; return visibleObject1.index < visibleObject2.index; }); for (const VisibleObject& visibleObject : visibleObjects) visibleObject.object->Draw(); } void CGUI::DrawSprite(const CGUISpriteInstance& Sprite, const float& Z, const CRect& Rect, const CRect& UNUSED(Clipping)) { // If the sprite doesn't exist (name == ""), don't bother drawing anything if (!Sprite) return; // TODO: Clipping? Sprite.Draw(*this, Rect, m_Sprites, Z); } void CGUI::UpdateResolution() { m_BaseObject->RecurseObject(nullptr, &IGUIObject::UpdateCachedSize); } IGUIObject* CGUI::ConstructObject(const CStr& str) { std::map::iterator it = m_ObjectTypes.find(str); if (it == m_ObjectTypes.end()) return nullptr; return (*it->second)(*this); } bool CGUI::AddObject(IGUIObject& parent, IGUIObject& child) { if (child.m_Name.empty()) { LOGERROR("Can't register an object without name!"); return false; } if (m_pAllObjects.find(child.m_Name) != m_pAllObjects.end()) { LOGERROR("Can't register more than one object of the name %s", child.m_Name.c_str()); return false; } m_pAllObjects[child.m_Name] = &child; parent.RegisterChild(&child); return true; } IGUIObject* CGUI::GetBaseObject() { return m_BaseObject.get(); }; bool CGUI::ObjectExists(const CStr& Name) const { return m_pAllObjects.find(Name) != m_pAllObjects.end(); } IGUIObject* CGUI::FindObjectByName(const CStr& Name) const { map_pObjects::const_iterator it = m_pAllObjects.find(Name); if (it == m_pAllObjects.end()) return nullptr; return it->second; } IGUIObject* CGUI::FindObjectUnderMouse() { IGUIObject* pNearest = nullptr; m_BaseObject->RecurseObject(&IGUIObject::IsHiddenOrGhost, &IGUIObject::ChooseMouseOverAndClosest, pNearest); return pNearest; } void CGUI::SetFocusedObject(IGUIObject* pObject) { if (pObject == m_FocusedObject) return; if (m_FocusedObject) { SGUIMessage msg(GUIM_LOST_FOCUS); m_FocusedObject->HandleMessage(msg); } m_FocusedObject = pObject; if (m_FocusedObject) { SGUIMessage msg(GUIM_GOT_FOCUS); m_FocusedObject->HandleMessage(msg); } } void CGUI::SetObjectStyle(IGUIObject* pObject, const CStr& styleName) { // If the style is not recognised (or an empty string) then ApplyStyle will // emit an error message. Thus we don't need to handle it here. - if (pObject->ApplyStyle(styleName)) - pObject->m_Style = styleName; + pObject->ApplyStyle(styleName); } void CGUI::UnsetObjectStyle(IGUIObject* pObject) { SetObjectStyle(pObject, "default"); } void CGUI::SetObjectHotkey(IGUIObject* pObject, const CStr& hotkeyTag) { if (!hotkeyTag.empty()) m_HotkeyObjects[hotkeyTag].push_back(pObject); } void CGUI::UnsetObjectHotkey(IGUIObject* pObject, const CStr& hotkeyTag) { if (hotkeyTag.empty()) return; std::vector& assignment = m_HotkeyObjects[hotkeyTag]; assignment.erase( std::remove_if( assignment.begin(), assignment.end(), [&pObject](const IGUIObject* hotkeyObject) { return pObject == hotkeyObject; }), assignment.end()); } void CGUI::SetGlobalHotkey(const CStr& hotkeyTag, const CStr& eventName, JS::HandleValue function) { ScriptRequest rq(*m_ScriptInterface); if (hotkeyTag.empty()) { ScriptException::Raise(rq, "Cannot assign a function to an empty hotkey identifier!"); return; } // Only support "Press", "Keydown" and "Release" events. if (eventName != EventNamePress && eventName != EventNameKeyDown && eventName != EventNameRelease) { ScriptException::Raise(rq, "Cannot assign a function to an unsupported event!"); return; } if (!function.isObject() || !JS_ObjectIsFunction(&function.toObject())) { ScriptException::Raise(rq, "Cannot assign non-function value to global hotkey '%s'", hotkeyTag.c_str()); return; } UnsetGlobalHotkey(hotkeyTag, eventName); m_GlobalHotkeys[hotkeyTag][eventName].init(rq.cx, function); } void CGUI::UnsetGlobalHotkey(const CStr& hotkeyTag, const CStr& eventName) { std::map>::iterator it = m_GlobalHotkeys.find(hotkeyTag); if (it == m_GlobalHotkeys.end()) return; m_GlobalHotkeys[hotkeyTag].erase(eventName); if (m_GlobalHotkeys.count(hotkeyTag) == 0) m_GlobalHotkeys.erase(it); } const SGUIScrollBarStyle* CGUI::GetScrollBarStyle(const CStr& style) const { std::map::const_iterator it = m_ScrollBarStyles.find(style); if (it == m_ScrollBarStyles.end()) return nullptr; return &it->second; } /** * @callgraph */ void CGUI::LoadXmlFile(const VfsPath& Filename, std::unordered_set& Paths) { Paths.insert(Filename); CXeromyces xeroFile; if (xeroFile.Load(g_VFS, Filename, "gui") != PSRETURN_OK) return; XMBElement node = xeroFile.GetRoot(); std::string_view root_name(xeroFile.GetElementStringView(node.GetNodeName())); if (root_name == "objects") Xeromyces_ReadRootObjects(xeroFile, node, Paths); else if (root_name == "sprites") Xeromyces_ReadRootSprites(xeroFile, node); else if (root_name == "styles") Xeromyces_ReadRootStyles(xeroFile, node); else if (root_name == "setup") Xeromyces_ReadRootSetup(xeroFile, node); else LOGERROR("CGUI::LoadXmlFile encountered an unknown XML root node type: %s", root_name.data()); } void CGUI::LoadedXmlFiles() { m_BaseObject->RecurseObject(nullptr, &IGUIObject::UpdateCachedSize); SGUIMessage msg(GUIM_LOAD); m_BaseObject->RecurseObject(nullptr, &IGUIObject::HandleMessage, msg); SendEventToAll(EventNameLoad); } //=================================================================== // XML Reading Xeromyces Specific Sub-Routines //=================================================================== void CGUI::Xeromyces_ReadRootObjects(const XMBData& xmb, XMBElement element, std::unordered_set& Paths) { int el_script = xmb.GetElementID("script"); std::vector > subst; // Iterate main children // they should all be or