From 3664624628e987048785e19cbca6c8e95185cb16 Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Tue, 14 Jan 2025 23:26:00 +0000 Subject: [PATCH 01/20] Add basic 'modern' skin method todo: autoload the skin changes from a separate source, instead of editing the default skin files --- Client/gui/CGUIWindow_Impl.cpp | 2 +- Client/gui/CGUI_Impl.cpp | 8 + Client/gui/CGUI_Impl.h | 5 + .../skins/Default 2023/CGUI.lnf.xml | 199 ++++++++++++++++++ .../skins/Default 2023/CGUI.xml | 7 +- 5 files changed, 219 insertions(+), 2 deletions(-) diff --git a/Client/gui/CGUIWindow_Impl.cpp b/Client/gui/CGUIWindow_Impl.cpp index 6936cbdbc92..7d6231674a6 100644 --- a/Client/gui/CGUIWindow_Impl.cpp +++ b/Client/gui/CGUIWindow_Impl.cpp @@ -32,7 +32,7 @@ CGUIWindow_Impl::CGUIWindow_Impl(CGUI_Impl* pGUI, CGUIElement* pParent, const ch if (!m_pWindow) { // Create new here - m_pWindow = pGUI->GetWindowManager()->createWindow(CGUIWINDOW_NAME, szUnique); + m_pWindow = pGUI->GetWindowManager()->createWindow(pGUI->ResolveSkin(CGUIWINDOW_NAME), szUnique); m_pWindow->setRect(CEGUI::Relative, CEGUI::Rect(0.10f, 0.10f, 0.60f, 0.90f)); m_pWindow->setAlpha(0.8f); diff --git a/Client/gui/CGUI_Impl.cpp b/Client/gui/CGUI_Impl.cpp index 67d625ed5ec..2ca4d23bdab 100644 --- a/Client/gui/CGUI_Impl.cpp +++ b/Client/gui/CGUI_Impl.cpp @@ -100,6 +100,8 @@ CGUI_Impl::CGUI_Impl(IDirect3DDevice9* pDevice) : m_HasSchemeLoaded(false), m_fC SString strMessage = e.getMessage().c_str(); BrowseToSolution("create-fonts", EXIT_GAME_FIRST | ASK_GO_ONLINE, SString("Error loading fonts!\n\n%s", *strMessage)); } + + SetModernSkinEnabled(true); } CGUI_Impl::~CGUI_Impl() @@ -1700,3 +1702,9 @@ CEGUI::Window* CGUI_Impl::GetMasterWindow(CEGUI::Window* wnd) } return wnd; } + +const char* CGUI_Impl::ResolveSkin(const char* szSkin) +{ + const std::string name = m_bUseModernSkin ? std::string(szSkin) + "Modern" : szSkin; + return name.c_str(); +} diff --git a/Client/gui/CGUI_Impl.h b/Client/gui/CGUI_Impl.h index 30153808665..97843079f4b 100644 --- a/Client/gui/CGUI_Impl.h +++ b/Client/gui/CGUI_Impl.h @@ -279,6 +279,9 @@ class CGUI_Impl : public CGUI, public CGUITabList CGUIWindow* LoadLayout(CGUIElement* pParent, const SString& strFilename); bool LoadImageset(const SString& strFilename); + void SetModernSkinEnabled(bool bEnabled) { m_bUseModernSkin = bEnabled; } + const char* ResolveSkin(const char* szSkin); + private: CGUIButton* _CreateButton(CGUIElement_Impl* pParent = NULL, const char* szCaption = ""); CGUICheckBox* _CreateCheckBox(CGUIElement_Impl* pParent = NULL, const char* szCaption = "", bool bChecked = false); @@ -350,4 +353,6 @@ class CGUI_Impl : public CGUI, public CGUITabList bool m_HasSchemeLoaded; SString m_CurrentSchemeName; CElapsedTime m_RenderOkTimer; + + bool m_bUseModernSkin = false; }; diff --git a/Shared/data/MTA San Andreas/skins/Default 2023/CGUI.lnf.xml b/Shared/data/MTA San Andreas/skins/Default 2023/CGUI.lnf.xml index 05c3f8fa23f..a6e9b773fa0 100644 --- a/Shared/data/MTA San Andreas/skins/Default 2023/CGUI.lnf.xml +++ b/Shared/data/MTA San Andreas/skins/Default 2023/CGUI.lnf.xml @@ -594,6 +594,205 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + diff --git a/Shared/data/MTA San Andreas/skins/Default 2023/CGUI.xml b/Shared/data/MTA San Andreas/skins/Default 2023/CGUI.xml index 9a758e843ea..f02bebc9f1a 100644 --- a/Shared/data/MTA San Andreas/skins/Default 2023/CGUI.xml +++ b/Shared/data/MTA San Andreas/skins/Default 2023/CGUI.xml @@ -5,7 +5,6 @@ - @@ -35,4 +34,10 @@ --> + + + + + + From 67541e72c93518b8a1363e59e7ba2c295ed5887b Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Tue, 14 Jan 2025 23:29:26 +0000 Subject: [PATCH 02/20] Move modern skin toggling for better PoC example --- Client/core/CSettings.cpp | 4 ++++ Client/gui/CGUI_Impl.cpp | 2 -- Client/sdk/gui/CGUI.h | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Client/core/CSettings.cpp b/Client/core/CSettings.cpp index 566a374247b..ee3f9b9b96e 100644 --- a/Client/core/CSettings.cpp +++ b/Client/core/CSettings.cpp @@ -59,6 +59,8 @@ void CSettings::CreateGUI() CGUITab *pTabMultiplayer, *pTabVideo, *pTabAudio, *pTabBinds, *pTabControls, *pTabAdvanced; CGUI* pManager = g_pCore->GetGUI(); + pManager->SetModernSkinEnabled(true); + // Init m_bIsModLoaded = false; m_bCaptureKey = false; @@ -1379,6 +1381,8 @@ void CSettings::CreateGUI() // Load the load of skins LoadSkins(); + + pManager->SetModernSkinEnabled(false); } void CSettings::DestroyGUI() diff --git a/Client/gui/CGUI_Impl.cpp b/Client/gui/CGUI_Impl.cpp index 2ca4d23bdab..5286cb260d2 100644 --- a/Client/gui/CGUI_Impl.cpp +++ b/Client/gui/CGUI_Impl.cpp @@ -100,8 +100,6 @@ CGUI_Impl::CGUI_Impl(IDirect3DDevice9* pDevice) : m_HasSchemeLoaded(false), m_fC SString strMessage = e.getMessage().c_str(); BrowseToSolution("create-fonts", EXIT_GAME_FIRST | ASK_GO_ONLINE, SString("Error loading fonts!\n\n%s", *strMessage)); } - - SetModernSkinEnabled(true); } CGUI_Impl::~CGUI_Impl() diff --git a/Client/sdk/gui/CGUI.h b/Client/sdk/gui/CGUI.h index d6e27bd3da5..9d63f9d8bac 100644 --- a/Client/sdk/gui/CGUI.h +++ b/Client/sdk/gui/CGUI.h @@ -169,4 +169,6 @@ class CGUI virtual CGUIWindow* LoadLayout(CGUIElement* pParent, const SString& strFilename) = 0; virtual bool LoadImageset(const SString& strFilename) = 0; + + virtual void SetModernSkinEnabled(bool bEnabled) = 0; }; From 880ca95fb554bc5e1b072ec9a656f20c7490b762 Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Wed, 15 Jan 2025 03:04:27 +0000 Subject: [PATCH 03/20] Add auto skin injection functionality --- Client/core/CCore.cpp | 1 + Client/gui/CGUI_Impl.cpp | 208 ++++++++++++++++-- Client/gui/CGUI_Impl.h | 6 + Client/sdk/gui/CGUI.h | 2 + Shared/XML/CXMLNodeImpl.cpp | 17 +- Shared/XML/CXMLNodeImpl.h | 2 +- .../MTA San Andreas/MTA/cgui/modern/.version | 1 + .../MTA/cgui/modern/templates/looknfeel.xml | 201 +++++++++++++++++ .../MTA/cgui/modern/templates/scheme.xml | 4 + .../skins/Default 2023/CGUI.lnf.xml | 199 ----------------- .../skins/Default 2023/CGUI.xml | 7 +- Shared/sdk/xml/CXMLNode.h | 3 +- 12 files changed, 425 insertions(+), 226 deletions(-) create mode 100644 Shared/data/MTA San Andreas/MTA/cgui/modern/.version create mode 100644 Shared/data/MTA San Andreas/MTA/cgui/modern/templates/looknfeel.xml create mode 100644 Shared/data/MTA San Andreas/MTA/cgui/modern/templates/scheme.xml diff --git a/Client/core/CCore.cpp b/Client/core/CCore.cpp index 12e8cf81d54..746b7245d42 100644 --- a/Client/core/CCore.cpp +++ b/Client/core/CCore.cpp @@ -1020,6 +1020,7 @@ void CCore::DeinitGUI() void CCore::InitGUI(IDirect3DDevice9* pDevice) { m_pGUI = InitModule(m_GUIModule, "GUI", "InitGUIInterface", pDevice); + m_pGUI->SetXMLParser(m_pXML); } void CCore::CreateGUI() diff --git a/Client/gui/CGUI_Impl.cpp b/Client/gui/CGUI_Impl.cpp index 5286cb260d2..7afa3f98ac7 100644 --- a/Client/gui/CGUI_Impl.cpp +++ b/Client/gui/CGUI_Impl.cpp @@ -11,24 +11,28 @@ #include "StdInc.h" #include "CEGUIExceptions.h" +#include +#include +#include +#include using std::list; -#define CGUI_MTA_DEFAULT_FONT "tahoma.ttf" // %WINDIR%/font/<...> -#define CGUI_MTA_DEFAULT_FONT_BOLD "tahomabd.ttf" // %WINDIR%/font/<...> -#define CGUI_MTA_CLEAR_FONT "verdana.ttf" // %WINDIR%/font/<...> +#define CGUI_MTA_DEFAULT_FONT "tahoma.ttf" // %WINDIR%/font/<...> +#define CGUI_MTA_DEFAULT_FONT_BOLD "tahomabd.ttf" // %WINDIR%/font/<...> +#define CGUI_MTA_CLEAR_FONT "verdana.ttf" // %WINDIR%/font/<...> -#define CGUI_MTA_DEFAULT_REG "Tahoma (TrueType)" -#define CGUI_MTA_DEFAULT_REG_BOLD "Tahoma Bold (TrueType)" -#define CGUI_MTA_CLEAR_REG "Verdana (TrueType)" +#define CGUI_MTA_DEFAULT_REG "Tahoma (TrueType)" +#define CGUI_MTA_DEFAULT_REG_BOLD "Tahoma Bold (TrueType)" +#define CGUI_MTA_CLEAR_REG "Verdana (TrueType)" -#define CGUI_MTA_SUBSTITUTE_FONT "cgui/unifont.ttf" // GTA/MTA/<...> -#define CGUI_MTA_SANS_FONT "cgui/sans.ttf" // GTA/MTA/<...> -#define CGUI_SA_HEADER_FONT "cgui/saheader.ttf" // GTA/MTA/<...> -#define CGUI_SA_GOTHIC_FONT "cgui/sagothic.ttf" // GTA/MTA/<...> -#define CGUI_SA_HEADER_SIZE 26 -#define CGUI_SA_GOTHIC_SIZE 47 -#define CGUI_MTA_SANS_FONT_SIZE 9 +#define CGUI_MTA_SUBSTITUTE_FONT "cgui/unifont.ttf" // GTA/MTA/<...> +#define CGUI_MTA_SANS_FONT "cgui/sans.ttf" // GTA/MTA/<...> +#define CGUI_SA_HEADER_FONT "cgui/saheader.ttf" // GTA/MTA/<...> +#define CGUI_SA_GOTHIC_FONT "cgui/sagothic.ttf" // GTA/MTA/<...> +#define CGUI_SA_HEADER_SIZE 26 +#define CGUI_SA_GOTHIC_SIZE 47 +#define CGUI_MTA_SANS_FONT_SIZE 9 CGUI_Impl::CGUI_Impl(IDirect3DDevice9* pDevice) : m_HasSchemeLoaded(false), m_fCurrentServerCursorAlpha(1.0f) { @@ -115,7 +119,15 @@ void CGUI_Impl::SetSkin(const char* szName) CEGUI::SchemeManager::getSingleton().unloadScheme(m_CurrentSchemeName); } - PushGuiWorkingDirectory(CalcMTASAPath(PathJoin("skins", szName))); + bool bConvertSkin = ConvertToModernSkin(szName); + + if (!bConvertSkin) + { + BrowseToSolution("gui-skin", EXIT_GAME_FIRST, SString("Error converting skin '%s' to modern", szName)); + return; + } + + PushGuiWorkingDirectory(CalcMTASAPath(PathJoin("MTA", "cgui", "modern", "autogenerated"))); CEGUI::Scheme* scheme = CEGUI::SchemeManager::getSingleton().loadScheme("CGUI.xml"); m_CurrentSchemeName = scheme->getName().c_str(); @@ -150,6 +162,174 @@ void CGUI_Impl::SetSkin(const char* szName) m_eInputMode = INPUTMODE_NO_BINDS_ON_EDIT; } +bool CGUI_Impl::ConvertToModernSkin(const char* szSkin) +{ + if (!m_pXML) + return false; + + // Duplicate all the CGUI files in the skin folder to a temporary folder + SString strSkinPath = CalcMTASAPath(PathJoin("skins", szSkin)); + + if (!DirectoryExists(strSkinPath)) + return false; + + SString tempPath = CalcMTASAPath(PathJoin("MTA", "cgui", "modern", "autogenerated")); + + if (!DirectoryExists(tempPath)) + { + CreateDirectory(tempPath, 0); + } + else + { + // Verify if the modern skin version has changed since the skin was last converted + SString strModernVersionPath = CalcMTASAPath(PathJoin("MTA", "cgui", "modern", ".version")); + std::ifstream pModernVersionFile(strModernVersionPath); + std::string modernVersion; + + if (pModernVersionFile.is_open()) + { + std::getline(pModernVersionFile, modernVersion); + pModernVersionFile.close(); + } + + SString strSkinVersionPath = PathJoin(tempPath, ".version"); + std::ifstream pSkinVersionFile(strSkinVersionPath); + std::string skinVersion; + + if (pSkinVersionFile.is_open()) + { + std::getline(pSkinVersionFile, skinVersion); + pSkinVersionFile.close(); + } + + if (!modernVersion.empty() && !skinVersion.empty() && modernVersion == skinVersion) + { + // Verify if the skin has changed, or if files already up to date + SString strHashesPath = PathJoin(tempPath, ".integrity"); + + if (FileExists(strHashesPath)) + { + std::ifstream pHashesFile(strHashesPath); + + if (pHashesFile.good()) + { + std::string skinName; + std::getline(pHashesFile, skinName); + + if (skinName == szSkin) + { + SString lookNFeelXml = PathJoin(strSkinPath, "CGUI.lnf.xml"); + SString imagesetXml = PathJoin(strSkinPath, "CGUI.is.xml"); + SString imagesetPng = PathJoin(strSkinPath, "CGUI.png"); + SString schemeXml = PathJoin(strSkinPath, "CGUI.xml"); + + SString lookNFeelXmlHash = CMD5Hasher::CalculateHexString(lookNFeelXml); + SString imagesetXmlHash = CMD5Hasher::CalculateHexString(imagesetXml); + SString imagesetPngHash = CMD5Hasher::CalculateHexString(imagesetPng); + SString schemeXmlHash = CMD5Hasher::CalculateHexString(schemeXml); + + std::string hashes; + std::getline(pHashesFile, hashes); + + if (hashes == lookNFeelXmlHash + imagesetXmlHash + imagesetPngHash + schemeXmlHash) + { + pHashesFile.close(); + return true; + } + } + } + } + } + } + + // Copy all files + SString lookNFeelXml = PathJoin(strSkinPath, "CGUI.lnf.xml"); + SString imagesetXml = PathJoin(strSkinPath, "CGUI.is.xml"); + SString imagesetPng = PathJoin(strSkinPath, "CGUI.png"); + SString schemeXml = PathJoin(strSkinPath, "CGUI.xml"); + + if (!FileExists(lookNFeelXml) || !FileExists(imagesetXml) || !FileExists(imagesetPng) || !FileExists(schemeXml)) + return false; + + CopyFile(lookNFeelXml, PathJoin(tempPath, "CGUI.lnf.xml"), 0); + CopyFile(imagesetXml, PathJoin(tempPath, "CGUI.is.xml"), 0); + CopyFile(imagesetPng, PathJoin(tempPath, "CGUI.png"), 0); + CopyFile(schemeXml, PathJoin(tempPath, "CGUI.xml"), 0); + + // Store the hashes of each file under .skinname + SString strHashesPath = PathJoin(tempPath, ".integrity"); + std::ofstream pHashesFile(strHashesPath, std::ofstream::out | std::ofstream::trunc); + + if (!pHashesFile.is_open()) + return false; + + SString lookNFeelXmlHash = CMD5Hasher::CalculateHexString(lookNFeelXml); + SString imagesetXmlHash = CMD5Hasher::CalculateHexString(imagesetXml); + SString imagesetPngHash = CMD5Hasher::CalculateHexString(imagesetPng); + SString schemeXmlHash = CMD5Hasher::CalculateHexString(schemeXml); + + pHashesFile << szSkin << std::endl; + pHashesFile << lookNFeelXmlHash << imagesetXmlHash << imagesetPngHash << schemeXmlHash; + pHashesFile.close(); + + // Open the modern XML templates (using TinyXML) + SString strModernTemplatesPath = CalcMTASAPath(PathJoin("MTA", "cgui", "modern", "templates")); + SString strModernLookNFeel = PathJoin(strModernTemplatesPath, "looknfeel.xml"); + SString strModernScheme = PathJoin(strModernTemplatesPath, "scheme.xml"); + + if (!FileExists(strModernLookNFeel) || !FileExists(strModernScheme)) + return false; + + // Load the modern LookNFeel, read-only + CXMLFile* pModernLookNFeel = m_pXML->CreateXML(strModernLookNFeel, false, true); + + if (!pModernLookNFeel || !pModernLookNFeel->Parse()) + return false; + + // Load the requested skin's LookNFeel, read-write + CXMLFile* pSkinLookNFeel = m_pXML->CreateXML(PathJoin(tempPath, "CGUI.lnf.xml")); + + if (!pSkinLookNFeel || !pSkinLookNFeel->Parse()) + return false; + + CXMLNode* modernRootNode = pModernLookNFeel->GetRootNode(); + CXMLNode* skinRootNode = pSkinLookNFeel->GetRootNode(); + modernRootNode->CopyChildrenInto(skinRootNode, true, false); + + if (!pSkinLookNFeel->Write()) + return false; + + delete pModernLookNFeel; + delete pSkinLookNFeel; + + // Load the modern scheme, read-only + CXMLFile* pModernScheme = m_pXML->CreateXML(strModernScheme, false, true); + + if (!pModernScheme || !pModernScheme->Parse()) + return false; + + // Load the requested skin's scheme, read-write + CXMLFile* pSkinScheme = m_pXML->CreateXML(PathJoin(tempPath, "CGUI.xml")); + + if (!pSkinScheme || !pSkinScheme->Parse()) + return false; + + modernRootNode = pModernScheme->GetRootNode(); + skinRootNode = pSkinScheme->GetRootNode(); + modernRootNode->CopyChildrenInto(skinRootNode, true, false); + + if (!pSkinScheme->Write()) + return false; + + // Copy the modern version file + CopyFile(CalcMTASAPath(PathJoin("MTA", "cgui", "modern", ".version")), PathJoin(tempPath, ".version"), 0); + + delete pModernScheme; + delete pSkinScheme; + + return true; +} + void CGUI_Impl::SetBidiEnabled(bool bEnabled) { m_pSystem->SetBidiEnabled(bEnabled); diff --git a/Client/gui/CGUI_Impl.h b/Client/gui/CGUI_Impl.h index 97843079f4b..5f761e25388 100644 --- a/Client/gui/CGUI_Impl.h +++ b/Client/gui/CGUI_Impl.h @@ -16,6 +16,7 @@ class CGUI_Impl; #include #include #include +#include #define CGUI_CHAR_SIZE 6 @@ -282,6 +283,8 @@ class CGUI_Impl : public CGUI, public CGUITabList void SetModernSkinEnabled(bool bEnabled) { m_bUseModernSkin = bEnabled; } const char* ResolveSkin(const char* szSkin); + void SetXMLParser(CXML* pXML) { m_pXML = pXML; } + private: CGUIButton* _CreateButton(CGUIElement_Impl* pParent = NULL, const char* szCaption = ""); CGUICheckBox* _CreateCheckBox(CGUIElement_Impl* pParent = NULL, const char* szCaption = "", bool bChecked = false); @@ -303,6 +306,8 @@ class CGUI_Impl : public CGUI, public CGUITabList bool bAutoScale = false); void ApplyGuiWorkingDirectory(); + bool ConvertToModernSkin(const char* szSkin); + IDirect3DDevice9* m_pDevice; CEGUI::Renderer* m_pRenderer; @@ -355,4 +360,5 @@ class CGUI_Impl : public CGUI, public CGUITabList CElapsedTime m_RenderOkTimer; bool m_bUseModernSkin = false; + CXML* m_pXML; }; diff --git a/Client/sdk/gui/CGUI.h b/Client/sdk/gui/CGUI.h index 9d63f9d8bac..65ea5f479e9 100644 --- a/Client/sdk/gui/CGUI.h +++ b/Client/sdk/gui/CGUI.h @@ -36,6 +36,7 @@ class CGUI; #include "CGUITabPanel.h" #include "CGUIComboBox.h" #include "CGUITypes.h" +#include // Path defines for CGUI #define CGUI_ICON_MESSAGEBOX_INFO "cgui\\images\\info.png" @@ -171,4 +172,5 @@ class CGUI virtual bool LoadImageset(const SString& strFilename) = 0; virtual void SetModernSkinEnabled(bool bEnabled) = 0; + virtual void SetXMLParser(CXML* pXML) = 0; }; diff --git a/Shared/XML/CXMLNodeImpl.cpp b/Shared/XML/CXMLNodeImpl.cpp index 3c83fb76064..4ab9b43a523 100644 --- a/Shared/XML/CXMLNodeImpl.cpp +++ b/Shared/XML/CXMLNodeImpl.cpp @@ -365,10 +365,13 @@ CXMLNode* CXMLNodeImpl::CopyNode(CXMLNode* pParent) return dynamic_cast(pNew); } -bool CXMLNodeImpl::CopyChildrenInto(CXMLNode* pDestination, bool bRecursive) +bool CXMLNodeImpl::CopyChildrenInto(CXMLNode* pDestination, bool bRecursive, bool bDelete) { - // Delete all the children of the target - pDestination->DeleteAllSubNodes(); + if (bDelete) + { + // Delete all the children of the target + pDestination->DeleteAllSubNodes(); + } // Iterate through our children if we have. Otherwize just copy the content if (m_Children.size() > 0) @@ -405,14 +408,18 @@ bool CXMLNodeImpl::CopyChildrenInto(CXMLNode* pDestination, bool bRecursive) { if (!pMyChildNode->CopyChildrenInto(pNewChildNode, true)) { - pDestination->DeleteAllSubNodes(); + if (bDelete) + pDestination->DeleteAllSubNodes(); + return false; } } } else { - pDestination->DeleteAllSubNodes(); + if (bDelete) + pDestination->DeleteAllSubNodes(); + return false; } } diff --git a/Shared/XML/CXMLNodeImpl.h b/Shared/XML/CXMLNodeImpl.h index 369d23d79ca..550bf1030a6 100644 --- a/Shared/XML/CXMLNodeImpl.h +++ b/Shared/XML/CXMLNodeImpl.h @@ -71,7 +71,7 @@ class CXMLNodeImpl : public CXMLNode bool IsUsingIDs() { return m_bUsingIDs; }; CXMLNode* CopyNode(CXMLNode* pParent = NULL); - bool CopyChildrenInto(CXMLNode* pDestination, bool bRecursive); + bool CopyChildrenInto(CXMLNode* pDestination, bool bRecursive, bool bDelete = true); TiXmlElement* GetNode(); void DeleteWrapper(); diff --git a/Shared/data/MTA San Andreas/MTA/cgui/modern/.version b/Shared/data/MTA San Andreas/MTA/cgui/modern/.version new file mode 100644 index 00000000000..8a9ecc2ea99 --- /dev/null +++ b/Shared/data/MTA San Andreas/MTA/cgui/modern/.version @@ -0,0 +1 @@ +0.0.1 \ No newline at end of file diff --git a/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/looknfeel.xml b/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/looknfeel.xml new file mode 100644 index 00000000000..4e4a4deb1d8 --- /dev/null +++ b/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/looknfeel.xml @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + diff --git a/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/scheme.xml b/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/scheme.xml new file mode 100644 index 00000000000..7302893b2c3 --- /dev/null +++ b/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/scheme.xml @@ -0,0 +1,4 @@ + + + + diff --git a/Shared/data/MTA San Andreas/skins/Default 2023/CGUI.lnf.xml b/Shared/data/MTA San Andreas/skins/Default 2023/CGUI.lnf.xml index a6e9b773fa0..05c3f8fa23f 100644 --- a/Shared/data/MTA San Andreas/skins/Default 2023/CGUI.lnf.xml +++ b/Shared/data/MTA San Andreas/skins/Default 2023/CGUI.lnf.xml @@ -594,205 +594,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - -
- - - - -
- - - - -
- - - - -
- - - - -
- - - - -
- - - - -
- - - - -
- - - - -
- - - - -
- - - - -
- - - - diff --git a/Shared/data/MTA San Andreas/skins/Default 2023/CGUI.xml b/Shared/data/MTA San Andreas/skins/Default 2023/CGUI.xml index f02bebc9f1a..9a758e843ea 100644 --- a/Shared/data/MTA San Andreas/skins/Default 2023/CGUI.xml +++ b/Shared/data/MTA San Andreas/skins/Default 2023/CGUI.xml @@ -5,6 +5,7 @@ + @@ -34,10 +35,4 @@ --> - - - - - - diff --git a/Shared/sdk/xml/CXMLNode.h b/Shared/sdk/xml/CXMLNode.h index 80051930e63..016175928f2 100644 --- a/Shared/sdk/xml/CXMLNode.h +++ b/Shared/sdk/xml/CXMLNode.h @@ -53,7 +53,7 @@ class CXMLNode : public CXMLCommon virtual void SetTagContentf(const char* szFormat, ...) = 0; virtual CXMLNode* CopyNode(CXMLNode* pParent) = 0; - virtual bool CopyChildrenInto(CXMLNode* pDestination, bool bRecursive) = 0; + virtual bool CopyChildrenInto(CXMLNode* pDestination, bool bRecursive, bool bDelete = true) = 0; virtual bool IsValid() = 0; @@ -62,4 +62,5 @@ class CXMLNode : public CXMLCommon virtual void SetCommentText(const char* szCommentText, bool bLeadingBlankLine = false) = 0; virtual std::string ToString() = 0; + virtual void AddToList(CXMLNode* pNode) = 0; }; From 47034c566b7f99ca5500a13bb6fa54ec4536693c Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Wed, 15 Jan 2025 23:46:35 +0000 Subject: [PATCH 04/20] Add imageset conversion support, cleanup code --- Client/core/CCore.cpp | 1 + Client/gui/CGUI_Impl.cpp | 315 +++++++++++++----- Client/gui/CGUI_Impl.h | 6 +- Client/sdk/core/CGraphicsInterface.h | 6 + Client/sdk/gui/CGUI.h | 5 +- .../MTA/cgui/modern/templates/imageset.png | Bin 0 -> 38269 bytes .../MTA/cgui/modern/templates/imageset.xml | 108 ++++++ 7 files changed, 360 insertions(+), 81 deletions(-) create mode 100644 Shared/data/MTA San Andreas/MTA/cgui/modern/templates/imageset.png create mode 100644 Shared/data/MTA San Andreas/MTA/cgui/modern/templates/imageset.xml diff --git a/Client/core/CCore.cpp b/Client/core/CCore.cpp index 746b7245d42..fdd3df84e39 100644 --- a/Client/core/CCore.cpp +++ b/Client/core/CCore.cpp @@ -1021,6 +1021,7 @@ void CCore::InitGUI(IDirect3DDevice9* pDevice) { m_pGUI = InitModule(m_GUIModule, "GUI", "InitGUIInterface", pDevice); m_pGUI->SetXMLParser(m_pXML); + m_pGUI->SetGraphics(m_pGraphics); } void CCore::CreateGUI() diff --git a/Client/gui/CGUI_Impl.cpp b/Client/gui/CGUI_Impl.cpp index 7afa3f98ac7..b41e2a32399 100644 --- a/Client/gui/CGUI_Impl.cpp +++ b/Client/gui/CGUI_Impl.cpp @@ -13,6 +13,8 @@ #include "CEGUIExceptions.h" #include #include +#include +#include #include #include @@ -162,18 +164,33 @@ void CGUI_Impl::SetSkin(const char* szName) m_eInputMode = INPUTMODE_NO_BINDS_ON_EDIT; } -bool CGUI_Impl::ConvertToModernSkin(const char* szSkin) +bool CGUI_Impl::ConvertToModernSkin(const char* skinName) { if (!m_pXML) + { + AddReportLog(1337, "ConvertToModernSkin: XML interface not initialized"); + return false; + } + + if (!m_pGraphics) + { + AddReportLog(1337, "ConvertToModernSkin: Graphics interface not initialized"); return false; + } // Duplicate all the CGUI files in the skin folder to a temporary folder - SString strSkinPath = CalcMTASAPath(PathJoin("skins", szSkin)); + SString skinPath = CalcMTASAPath(PathJoin("skins", skinName)); - if (!DirectoryExists(strSkinPath)) + if (!DirectoryExists(skinPath)) + { + AddReportLog(1337, "ConvertToModernSkin: Skin folder not found"); return false; + } - SString tempPath = CalcMTASAPath(PathJoin("MTA", "cgui", "modern", "autogenerated")); + SString modernPath = PathJoin("MTA", "cgui", "modern"); + SString tempPath = CalcMTASAPath(PathJoin(modernPath, "autogenerated")); + SString modernVersionPath = CalcMTASAPath(PathJoin(modernPath, ".version")); + SString tempVersionPath = PathJoin(tempPath, ".version"); if (!DirectoryExists(tempPath)) { @@ -182,58 +199,56 @@ bool CGUI_Impl::ConvertToModernSkin(const char* szSkin) else { // Verify if the modern skin version has changed since the skin was last converted - SString strModernVersionPath = CalcMTASAPath(PathJoin("MTA", "cgui", "modern", ".version")); - std::ifstream pModernVersionFile(strModernVersionPath); + std::ifstream modernVersionFile(modernVersionPath); std::string modernVersion; - if (pModernVersionFile.is_open()) + if (modernVersionFile.is_open()) { - std::getline(pModernVersionFile, modernVersion); - pModernVersionFile.close(); + std::getline(modernVersionFile, modernVersion); + modernVersionFile.close(); } + + std::ifstream tempVersionFile(tempVersionPath); + std::string tempVersion; - SString strSkinVersionPath = PathJoin(tempPath, ".version"); - std::ifstream pSkinVersionFile(strSkinVersionPath); - std::string skinVersion; - - if (pSkinVersionFile.is_open()) + if (tempVersionFile.is_open()) { - std::getline(pSkinVersionFile, skinVersion); - pSkinVersionFile.close(); + std::getline(tempVersionFile, tempVersion); + tempVersionFile.close(); } - if (!modernVersion.empty() && !skinVersion.empty() && modernVersion == skinVersion) + if (!modernVersion.empty() && !tempVersion.empty() && modernVersion == tempVersion) { // Verify if the skin has changed, or if files already up to date - SString strHashesPath = PathJoin(tempPath, ".integrity"); + SString hashesPath = PathJoin(tempPath, ".integrity"); - if (FileExists(strHashesPath)) + if (FileExists(hashesPath)) { - std::ifstream pHashesFile(strHashesPath); + std::ifstream hashesFile(hashesPath); - if (pHashesFile.good()) + if (hashesFile.good()) { - std::string skinName; - std::getline(pHashesFile, skinName); + std::string checkSkinName; + std::getline(hashesFile, checkSkinName); - if (skinName == szSkin) + if (checkSkinName == skinName) { - SString lookNFeelXml = PathJoin(strSkinPath, "CGUI.lnf.xml"); - SString imagesetXml = PathJoin(strSkinPath, "CGUI.is.xml"); - SString imagesetPng = PathJoin(strSkinPath, "CGUI.png"); - SString schemeXml = PathJoin(strSkinPath, "CGUI.xml"); + SString lookNFeelXmlPath = PathJoin(skinPath, "CGUI.lnf.xml"); + SString imagesetXmlPath = PathJoin(skinPath, "CGUI.is.xml"); + SString imagesetPngPath = PathJoin(skinPath, "CGUI.png"); + SString schemeXmlPath = PathJoin(skinPath, "CGUI.xml"); - SString lookNFeelXmlHash = CMD5Hasher::CalculateHexString(lookNFeelXml); - SString imagesetXmlHash = CMD5Hasher::CalculateHexString(imagesetXml); - SString imagesetPngHash = CMD5Hasher::CalculateHexString(imagesetPng); - SString schemeXmlHash = CMD5Hasher::CalculateHexString(schemeXml); + SString lookNFeelXmlHash = CMD5Hasher::CalculateHexString(lookNFeelXmlPath); + SString imagesetXmlHash = CMD5Hasher::CalculateHexString(imagesetXmlPath); + SString imagesetPngHash = CMD5Hasher::CalculateHexString(imagesetPngPath); + SString schemeXmlHash = CMD5Hasher::CalculateHexString(schemeXmlPath); std::string hashes; - std::getline(pHashesFile, hashes); + std::getline(hashesFile, hashes); if (hashes == lookNFeelXmlHash + imagesetXmlHash + imagesetPngHash + schemeXmlHash) { - pHashesFile.close(); + hashesFile.close(); return true; } } @@ -242,90 +257,232 @@ bool CGUI_Impl::ConvertToModernSkin(const char* szSkin) } } - // Copy all files - SString lookNFeelXml = PathJoin(strSkinPath, "CGUI.lnf.xml"); - SString imagesetXml = PathJoin(strSkinPath, "CGUI.is.xml"); - SString imagesetPng = PathJoin(strSkinPath, "CGUI.png"); - SString schemeXml = PathJoin(strSkinPath, "CGUI.xml"); + // Copy all files from requested skin to 'temporary' folder + SString skinLookNFeelXmlPath = PathJoin(skinPath, "CGUI.lnf.xml"); + SString skinImagesetXmlPath = PathJoin(skinPath, "CGUI.is.xml"); + SString skinImagesetPngPath = PathJoin(skinPath, "CGUI.png"); + SString skinSchemeXmlPath = PathJoin(skinPath, "CGUI.xml"); - if (!FileExists(lookNFeelXml) || !FileExists(imagesetXml) || !FileExists(imagesetPng) || !FileExists(schemeXml)) + if (!FileExists(skinLookNFeelXmlPath) || !FileExists(skinImagesetXmlPath) || !FileExists(skinImagesetPngPath) || !FileExists(skinSchemeXmlPath)) + { + AddReportLog(1337, "ConvertToModernSkin: Skin files not found"); return false; + } - CopyFile(lookNFeelXml, PathJoin(tempPath, "CGUI.lnf.xml"), 0); - CopyFile(imagesetXml, PathJoin(tempPath, "CGUI.is.xml"), 0); - CopyFile(imagesetPng, PathJoin(tempPath, "CGUI.png"), 0); - CopyFile(schemeXml, PathJoin(tempPath, "CGUI.xml"), 0); + SString tempLookNFeelXmlPath = PathJoin(tempPath, "CGUI.lnf.xml"); + SString tempImagesetXmlPath = PathJoin(tempPath, "CGUI.is.xml"); + SString tempImagesetPngPath = PathJoin(tempPath, "CGUI.png"); + SString tempSchemeXmlPath = PathJoin(tempPath, "CGUI.xml"); + + CopyFile(skinLookNFeelXmlPath, tempLookNFeelXmlPath, 0); + CopyFile(skinImagesetXmlPath, tempImagesetXmlPath, 0); + CopyFile(skinImagesetPngPath, tempImagesetPngPath, 0); + CopyFile(skinSchemeXmlPath, tempSchemeXmlPath, 0); + + if (!FileExists(tempLookNFeelXmlPath) || !FileExists(tempImagesetXmlPath) || !FileExists(tempImagesetPngPath) || !FileExists(tempSchemeXmlPath)) + { + AddReportLog(1337, "ConvertToModernSkin: Failed to copy skin files"); + return false; + } // Store the hashes of each file under .skinname - SString strHashesPath = PathJoin(tempPath, ".integrity"); - std::ofstream pHashesFile(strHashesPath, std::ofstream::out | std::ofstream::trunc); + SString integrityFilePath = PathJoin(tempPath, ".integrity"); + std::ofstream integrityFileStream(integrityFilePath, std::ofstream::out | std::ofstream::trunc); - if (!pHashesFile.is_open()) + if (!integrityFileStream.is_open()) + { + AddReportLog(1337, "ConvertToModernSkin: Failed to open integrity file stream"); return false; + } - SString lookNFeelXmlHash = CMD5Hasher::CalculateHexString(lookNFeelXml); - SString imagesetXmlHash = CMD5Hasher::CalculateHexString(imagesetXml); - SString imagesetPngHash = CMD5Hasher::CalculateHexString(imagesetPng); - SString schemeXmlHash = CMD5Hasher::CalculateHexString(schemeXml); + SString lookNFeelXmlHash = CMD5Hasher::CalculateHexString(skinLookNFeelXmlPath); + SString imagesetXmlHash = CMD5Hasher::CalculateHexString(skinImagesetXmlPath); + SString imagesetPngHash = CMD5Hasher::CalculateHexString(skinImagesetPngPath); + SString schemeXmlHash = CMD5Hasher::CalculateHexString(skinSchemeXmlPath); - pHashesFile << szSkin << std::endl; - pHashesFile << lookNFeelXmlHash << imagesetXmlHash << imagesetPngHash << schemeXmlHash; - pHashesFile.close(); + integrityFileStream << skinName << std::endl; + integrityFileStream << lookNFeelXmlHash << imagesetXmlHash << imagesetPngHash << schemeXmlHash; + integrityFileStream.close(); // Open the modern XML templates (using TinyXML) - SString strModernTemplatesPath = CalcMTASAPath(PathJoin("MTA", "cgui", "modern", "templates")); - SString strModernLookNFeel = PathJoin(strModernTemplatesPath, "looknfeel.xml"); - SString strModernScheme = PathJoin(strModernTemplatesPath, "scheme.xml"); + SString modernTemplatesPath = CalcMTASAPath(PathJoin(modernPath, "templates")); + SString modernLookNFeelPath = PathJoin(modernTemplatesPath, "looknfeel.xml"); + SString modernSchemePath = PathJoin(modernTemplatesPath, "scheme.xml"); + SString modernImagesetXmlPath = PathJoin(modernTemplatesPath, "imageset.xml"); + SString modernImagesetPngPath = PathJoin(modernTemplatesPath, "imageset.png"); - if (!FileExists(strModernLookNFeel) || !FileExists(strModernScheme)) + if (!FileExists(modernLookNFeelPath) || !FileExists(modernSchemePath) || !FileExists(modernImagesetXmlPath) || !FileExists(modernImagesetPngPath)) + { + AddReportLog(1337, "ConvertToModernSkin: Modern skin templates not found"); return false; + } // Load the modern LookNFeel, read-only - CXMLFile* pModernLookNFeel = m_pXML->CreateXML(strModernLookNFeel, false, true); + CXMLFile* modernLookNFeelFile = m_pXML->CreateXML(modernLookNFeelPath, false, true); - if (!pModernLookNFeel || !pModernLookNFeel->Parse()) + if (!modernLookNFeelFile || !modernLookNFeelFile->Parse()) + { + AddReportLog(1337, "ConvertToModernSkin: Failed to open/parse modern LookNFeel"); return false; + } // Load the requested skin's LookNFeel, read-write - CXMLFile* pSkinLookNFeel = m_pXML->CreateXML(PathJoin(tempPath, "CGUI.lnf.xml")); + CXMLFile* skinLookNFeelFile = m_pXML->CreateXML(tempLookNFeelXmlPath); - if (!pSkinLookNFeel || !pSkinLookNFeel->Parse()) + if (!skinLookNFeelFile || !skinLookNFeelFile->Parse()) + { + AddReportLog(1337, "ConvertToModernSkin: Failed to open/parse skin LookNFeel"); return false; + } - CXMLNode* modernRootNode = pModernLookNFeel->GetRootNode(); - CXMLNode* skinRootNode = pSkinLookNFeel->GetRootNode(); - modernRootNode->CopyChildrenInto(skinRootNode, true, false); + // Copy the modern LookNFeel into the skin LookNFeel + CXMLNode* modernRootNode = modernLookNFeelFile->GetRootNode(); + modernRootNode->CopyChildrenInto(skinLookNFeelFile->GetRootNode(), true, false); - if (!pSkinLookNFeel->Write()) + // Save the skin LookNFeel + if (!skinLookNFeelFile->Write()) + { + AddReportLog(1337, "ConvertToModernSkin: Failed to write skin LookNFeel"); return false; + } - delete pModernLookNFeel; - delete pSkinLookNFeel; + delete modernLookNFeelFile; + delete skinLookNFeelFile; // Load the modern scheme, read-only - CXMLFile* pModernScheme = m_pXML->CreateXML(strModernScheme, false, true); + CXMLFile* modernSchemeFile = m_pXML->CreateXML(modernSchemePath, false, true); - if (!pModernScheme || !pModernScheme->Parse()) + if (!modernSchemeFile || !modernSchemeFile->Parse()) + { + AddReportLog(1337, "ConvertToModernSkin: Failed to open/parse modern scheme"); return false; + } // Load the requested skin's scheme, read-write - CXMLFile* pSkinScheme = m_pXML->CreateXML(PathJoin(tempPath, "CGUI.xml")); + CXMLFile* skinSchemeFile = m_pXML->CreateXML(tempSchemeXmlPath); - if (!pSkinScheme || !pSkinScheme->Parse()) + if (!skinSchemeFile || !skinSchemeFile->Parse()) + { + AddReportLog(1337, "ConvertToModernSkin: Failed to open/parse skin scheme"); return false; + } - modernRootNode = pModernScheme->GetRootNode(); - skinRootNode = pSkinScheme->GetRootNode(); - modernRootNode->CopyChildrenInto(skinRootNode, true, false); + // Copy the modern scheme into the skin scheme + modernRootNode = modernSchemeFile->GetRootNode(); + modernRootNode->CopyChildrenInto(skinSchemeFile->GetRootNode(), true, false); - if (!pSkinScheme->Write()) + // Save the skin scheme + if (!skinSchemeFile->Write()) + { + AddReportLog(1337, "ConvertToModernSkin: Failed to write skin scheme"); return false; + } + + delete modernSchemeFile; + delete skinSchemeFile; + + // Load the imagesets as CTextureItem + auto renderManager = m_pGraphics->GetRenderItemManager(); + auto modernImagesetTexture = renderManager->CreateTexture(modernImagesetPngPath); + auto skinImagesetTexture = renderManager->CreateTexture(skinImagesetPngPath); + + if (!modernImagesetTexture || !skinImagesetTexture) + { + AddReportLog(1337, "ConvertToModernSkin: Failed to create imageset textures"); + return false; + } + + // Create a render target to hold both imagesets + int width = modernImagesetTexture->m_uiSizeX + skinImagesetTexture->m_uiSizeX; + auto renderTarget = + renderManager->CreateRenderTarget(width, std::max(modernImagesetTexture->m_uiSizeY, skinImagesetTexture->m_uiSizeY), false, true, 0, true); + + auto device = m_pGraphics->GetDevice(); + if (device->BeginScene() != D3D_OK) + { + AddReportLog(1337, "ConvertToModernSkin: Failed to begin scene for imageset drawing"); + return false; + } + + // Start draw batch + renderManager->SetRenderTarget(renderTarget, true); + + // Draw the imagesets side-by-side + m_pGraphics->DrawTexture(skinImagesetTexture, 0, 0, 1, 1, 0, 0, 0, 0xFFFFFFFF, 0, 0, 1, 1, true); + m_pGraphics->DrawTexture(modernImagesetTexture, skinImagesetTexture->m_uiSizeX, 0, 1, 1, 0, 0, 0, 0xFFFFFFFF, 0, 0, 1, 1, true); + + // Stop draw batch + renderManager->RestoreDefaultRenderTarget(); + + D3DXSaveTextureToFile(tempImagesetPngPath, D3DXIFF_PNG, renderTarget->m_pD3DTexture, 0); + + if (!FileExists(tempImagesetPngPath)) + { + AddReportLog(1337, "ConvertToModernSkin: Failed to save imageset texture"); + return false; + } + + // Load the requested skin's imageset, read-write + CXMLFile* skinImagesetFile = m_pXML->CreateXML(tempImagesetXmlPath); + + if (!skinImagesetFile || !skinImagesetFile->Parse()) + { + AddReportLog(1337, "ConvertToModernSkin: Failed to open/parse skin imageset"); + return false; + } + + // Load the modern imageset, read-only + CXMLFile* modernImagesetFile = m_pXML->CreateXML(modernImagesetXmlPath, false, true); + + if (!modernImagesetFile || !modernImagesetFile->Parse()) + { + AddReportLog(1337, "ConvertToModernSkin: Failed to open/parse modern imageset"); + return false; + } + + // Adjust each Image in the modern imageset to have the correct offset (XPos, YPos) + modernRootNode = modernImagesetFile->GetRootNode(); + + for (uint i = 0; i < modernRootNode->GetSubNodeCount(); i++) + { + CXMLNode* node = modernRootNode->GetSubNode(i); + CXMLAttributes& attributes = node->GetAttributes(); + + auto xPos = attributes.Find("XPos"); + + if (xPos) + { + try + { + auto x = std::stoi(xPos->GetValue()); + xPos->SetValue((uint)x + skinImagesetTexture->m_uiSizeX); + } + catch (std::invalid_argument) + { + AddReportLog(1337, "ConvertToModernSkin: Invalid XPos value in imageset"); + return false; + } + } + } + + // Copy the modern imageset into the skin imageset + modernRootNode->CopyChildrenInto(skinImagesetFile->GetRootNode(), true, false); + + // Save the skin imageset + if (!skinImagesetFile->Write()) + { + AddReportLog(1337, "ConvertToModernSkin: Failed to write skin imageset"); + return false; + } // Copy the modern version file - CopyFile(CalcMTASAPath(PathJoin("MTA", "cgui", "modern", ".version")), PathJoin(tempPath, ".version"), 0); + CopyFile(modernVersionPath, tempVersionPath, 0); - delete pModernScheme; - delete pSkinScheme; + if (!FileExists(tempVersionPath)) + { + AddReportLog(1337, "ConvertToModernSkin: Failed to copy modern version file"); + return false; + } return true; } diff --git a/Client/gui/CGUI_Impl.h b/Client/gui/CGUI_Impl.h index 5f761e25388..f06797feadf 100644 --- a/Client/gui/CGUI_Impl.h +++ b/Client/gui/CGUI_Impl.h @@ -17,6 +17,7 @@ class CGUI_Impl; #include #include #include +#include #define CGUI_CHAR_SIZE 6 @@ -284,6 +285,7 @@ class CGUI_Impl : public CGUI, public CGUITabList const char* ResolveSkin(const char* szSkin); void SetXMLParser(CXML* pXML) { m_pXML = pXML; } + void SetGraphics(CGraphicsInterface* pGraphicsInterface) { m_pGraphics = pGraphicsInterface; } private: CGUIButton* _CreateButton(CGUIElement_Impl* pParent = NULL, const char* szCaption = ""); @@ -306,7 +308,7 @@ class CGUI_Impl : public CGUI, public CGUITabList bool bAutoScale = false); void ApplyGuiWorkingDirectory(); - bool ConvertToModernSkin(const char* szSkin); + bool ConvertToModernSkin(const char* skinName); IDirect3DDevice9* m_pDevice; @@ -360,5 +362,7 @@ class CGUI_Impl : public CGUI, public CGUITabList CElapsedTime m_RenderOkTimer; bool m_bUseModernSkin = false; + CGraphicsInterface* m_pGraphics; CXML* m_pXML; + }; diff --git a/Client/sdk/core/CGraphicsInterface.h b/Client/sdk/core/CGraphicsInterface.h index b8e0ef744b1..ebdf479e6b9 100644 --- a/Client/sdk/core/CGraphicsInterface.h +++ b/Client/sdk/core/CGraphicsInterface.h @@ -103,6 +103,12 @@ enum class eRenderStage POST_GUI }; +class CTextureItem; +class CMaterialItem; +class CRenderItemManagerInterface; +class CScreenGrabberInterface; +class CPixelsManagerInterface; + class CGraphicsInterface { public: diff --git a/Client/sdk/gui/CGUI.h b/Client/sdk/gui/CGUI.h index 65ea5f479e9..d4d40c2f09a 100644 --- a/Client/sdk/gui/CGUI.h +++ b/Client/sdk/gui/CGUI.h @@ -36,7 +36,9 @@ class CGUI; #include "CGUITabPanel.h" #include "CGUIComboBox.h" #include "CGUITypes.h" -#include + +class CXML; +class CGraphicsInterface; // Path defines for CGUI #define CGUI_ICON_MESSAGEBOX_INFO "cgui\\images\\info.png" @@ -173,4 +175,5 @@ class CGUI virtual void SetModernSkinEnabled(bool bEnabled) = 0; virtual void SetXMLParser(CXML* pXML) = 0; + virtual void SetGraphics(CGraphicsInterface* pGraphicsInterface) = 0; }; diff --git a/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/imageset.png b/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/imageset.png new file mode 100644 index 0000000000000000000000000000000000000000..55bea772430d42f5f0c40b41dfb46836cd236a07 GIT binary patch literal 38269 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 J?epu-{{w1nv~>Ug literal 0 HcmV?d00001 diff --git a/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/imageset.xml b/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/imageset.xml new file mode 100644 index 00000000000..aa65d63cf60 --- /dev/null +++ b/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/imageset.xml @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From bb6c88281b9cdfdbcdb35bbaa2e75a995586fa18 Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Thu, 16 Jan 2025 00:21:53 +0000 Subject: [PATCH 05/20] Limit scene creation context, ensure scene end --- Client/gui/CGUI_Impl.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Client/gui/CGUI_Impl.cpp b/Client/gui/CGUI_Impl.cpp index b41e2a32399..81705887294 100644 --- a/Client/gui/CGUI_Impl.cpp +++ b/Client/gui/CGUI_Impl.cpp @@ -398,7 +398,8 @@ bool CGUI_Impl::ConvertToModernSkin(const char* skinName) renderManager->CreateRenderTarget(width, std::max(modernImagesetTexture->m_uiSizeY, skinImagesetTexture->m_uiSizeY), false, true, 0, true); auto device = m_pGraphics->GetDevice(); - if (device->BeginScene() != D3D_OK) + + if (!m_HasSchemeLoaded && device->BeginScene() != D3D_OK) { AddReportLog(1337, "ConvertToModernSkin: Failed to begin scene for imageset drawing"); return false; @@ -414,6 +415,12 @@ bool CGUI_Impl::ConvertToModernSkin(const char* skinName) // Stop draw batch renderManager->RestoreDefaultRenderTarget(); + if (!m_HasSchemeLoaded && device->EndScene() != D3D_OK) + { + AddReportLog(1337, "ConvertToModernSkin: Failed to end scene for imageset drawing"); + return false; + } + D3DXSaveTextureToFile(tempImagesetPngPath, D3DXIFF_PNG, renderTarget->m_pD3DTexture, 0); if (!FileExists(tempImagesetPngPath)) From 528244a90b38f5ce935bfa63d5e858bdf12a213d Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Thu, 16 Jan 2025 03:04:36 +0000 Subject: [PATCH 06/20] Implement all CGUI element types & enable modern skin for entire menu Plus other small fixes. Note: 'host game' window will still use the selected/non-modern skin, since it's created after CLocalGUI (which is wrapped by modern skin toggle) --- Client/core/CGUI.cpp | 3 + Client/core/CSettings.cpp | 4 - Client/gui/CGUIButton_Impl.cpp | 2 +- Client/gui/CGUICheckBox_Impl.cpp | 2 +- Client/gui/CGUIComboBox_Impl.cpp | 2 +- Client/gui/CGUIEdit_Impl.cpp | 2 +- Client/gui/CGUIGridList_Impl.cpp | 4 +- Client/gui/CGUILabel_Impl.cpp | 2 +- Client/gui/CGUIListItem_Impl.cpp | 4 +- Client/gui/CGUIMemo_Impl.cpp | 2 +- Client/gui/CGUIProgressBar_Impl.cpp | 2 +- Client/gui/CGUIRadioButton_Impl.cpp | 2 +- Client/gui/CGUIScrollBar_Impl.cpp | 2 +- Client/gui/CGUIScrollPane_Impl.cpp | 2 +- Client/gui/CGUIStaticImage_Impl.cpp | 2 +- Client/gui/CGUITabPanel_Impl.cpp | 2 +- Client/gui/CGUIWebBrowser_Impl.cpp | 2 +- Client/gui/CGUIWindow_Impl.cpp | 2 +- Client/gui/CGUI_Impl.cpp | 33 +- Client/gui/CGUI_Impl.h | 9 +- .../MTA/cgui/modern/templates/looknfeel.xml | 2047 ++++++++++++++++- .../MTA/cgui/modern/templates/scheme.xml | 26 +- 22 files changed, 2085 insertions(+), 73 deletions(-) diff --git a/Client/core/CGUI.cpp b/Client/core/CGUI.cpp index 62735363475..e73d711ef6c 100644 --- a/Client/core/CGUI.cpp +++ b/Client/core/CGUI.cpp @@ -137,6 +137,7 @@ void CLocalGUI::ChangeLocale(const char* szName) void CLocalGUI::CreateWindows(bool bGameIsAlreadyLoaded) { CGUI* pGUI = CCore::GetSingleton().GetGUI(); + pGUI->SetModernSkinEnabled(true); // Create chatbox m_pChat = new CChat(pGUI, CVector2D(0.0125f, 0.015f)); @@ -171,6 +172,8 @@ void CLocalGUI::CreateWindows(bool bGameIsAlreadyLoaded) // Create our news headlines if we're already ingame if (bGameIsAlreadyLoaded) m_pMainMenu->GetNewsBrowser()->CreateHeadlines(); + + pGUI->SetModernSkinEnabled(false); } void CLocalGUI::CreateObjects(IUnknown* pDevice) diff --git a/Client/core/CSettings.cpp b/Client/core/CSettings.cpp index ee3f9b9b96e..566a374247b 100644 --- a/Client/core/CSettings.cpp +++ b/Client/core/CSettings.cpp @@ -59,8 +59,6 @@ void CSettings::CreateGUI() CGUITab *pTabMultiplayer, *pTabVideo, *pTabAudio, *pTabBinds, *pTabControls, *pTabAdvanced; CGUI* pManager = g_pCore->GetGUI(); - pManager->SetModernSkinEnabled(true); - // Init m_bIsModLoaded = false; m_bCaptureKey = false; @@ -1381,8 +1379,6 @@ void CSettings::CreateGUI() // Load the load of skins LoadSkins(); - - pManager->SetModernSkinEnabled(false); } void CSettings::DestroyGUI() diff --git a/Client/gui/CGUIButton_Impl.cpp b/Client/gui/CGUIButton_Impl.cpp index ac48dc86f5f..b34426d268d 100644 --- a/Client/gui/CGUIButton_Impl.cpp +++ b/Client/gui/CGUIButton_Impl.cpp @@ -22,7 +22,7 @@ CGUIButton_Impl::CGUIButton_Impl(CGUI_Impl* pGUI, CGUIElement* pParent, const ch pGUI->GetUniqueName(szUnique); // Create the window and set default settings - m_pWindow = pGUI->GetWindowManager()->createWindow(CGUIBUTTON_NAME, szUnique); + m_pWindow = pGUI->GetWindowManager()->createWindow(pGUI->ResolveModernName(CGUIBUTTON_NAME), szUnique); m_pWindow->setDestroyedByParent(false); m_pWindow->setText(CGUI_Impl::GetUTFString(szCaption)); diff --git a/Client/gui/CGUICheckBox_Impl.cpp b/Client/gui/CGUICheckBox_Impl.cpp index e7967fe1263..884c1f9270d 100644 --- a/Client/gui/CGUICheckBox_Impl.cpp +++ b/Client/gui/CGUICheckBox_Impl.cpp @@ -22,7 +22,7 @@ CGUICheckBox_Impl::CGUICheckBox_Impl(CGUI_Impl* pGUI, CGUIElement* pParent, cons pGUI->GetUniqueName(szUnique); // Create the window and set default settings - m_pWindow = pGUI->GetWindowManager()->createWindow(CGUICHECKBOX_NAME, szUnique); + m_pWindow = pGUI->GetWindowManager()->createWindow(pGUI->ResolveModernName(CGUICHECKBOX_NAME), szUnique); m_pWindow->setDestroyedByParent(false); m_pWindow->setText(CGUI_Impl::GetUTFString(szCaption)); diff --git a/Client/gui/CGUIComboBox_Impl.cpp b/Client/gui/CGUIComboBox_Impl.cpp index b5783e8848b..a5605c7a34b 100644 --- a/Client/gui/CGUIComboBox_Impl.cpp +++ b/Client/gui/CGUIComboBox_Impl.cpp @@ -22,7 +22,7 @@ CGUIComboBox_Impl::CGUIComboBox_Impl(CGUI_Impl* pGUI, CGUIElement* pParent, cons pGUI->GetUniqueName(szUnique); // Create the window and set default settings - m_pWindow = pGUI->GetWindowManager()->createWindow(CGUICOMBOBOX_NAME, szUnique); + m_pWindow = pGUI->GetWindowManager()->createWindow(pGUI->ResolveModernName(CGUICOMBOBOX_NAME), szUnique); m_pWindow->setDestroyedByParent(false); // This needs a better alternative, so changing comboBox will change this - Jyrno42 diff --git a/Client/gui/CGUIEdit_Impl.cpp b/Client/gui/CGUIEdit_Impl.cpp index ddb7c91f17c..badbd0675e5 100644 --- a/Client/gui/CGUIEdit_Impl.cpp +++ b/Client/gui/CGUIEdit_Impl.cpp @@ -22,7 +22,7 @@ CGUIEdit_Impl::CGUIEdit_Impl(CGUI_Impl* pGUI, CGUIElement* pParent, const char* pGUI->GetUniqueName(szUnique); // Create the edit and set default settings - m_pWindow = pGUI->GetWindowManager()->createWindow(CGUIEDIT_NAME, szUnique); + m_pWindow = pGUI->GetWindowManager()->createWindow(pGUI->ResolveModernName(CGUIEDIT_NAME), szUnique); m_pWindow->setDestroyedByParent(false); m_pWindow->setRect(CEGUI::Absolute, CEGUI::Rect(0.00f, 0.00f, 0.128f, 0.24f)); diff --git a/Client/gui/CGUIGridList_Impl.cpp b/Client/gui/CGUIGridList_Impl.cpp index bed682670a3..f351a08ea77 100644 --- a/Client/gui/CGUIGridList_Impl.cpp +++ b/Client/gui/CGUIGridList_Impl.cpp @@ -32,9 +32,9 @@ CGUIGridList_Impl::CGUIGridList_Impl(CGUI_Impl* pGUI, CGUIElement* pParent, bool // Create the window and set default settings if (bFrame) - m_pWindow = pGUI->GetWindowManager()->createWindow(CGUIGRIDLIST_NAME, szUnique); + m_pWindow = pGUI->GetWindowManager()->createWindow(pGUI->ResolveModernName(CGUIGRIDLIST_NAME), szUnique); else - m_pWindow = pGUI->GetWindowManager()->createWindow(CGUIGRIDLISTNOFRAME_NAME, szUnique); + m_pWindow = pGUI->GetWindowManager()->createWindow(pGUI->ResolveModernName(CGUIGRIDLISTNOFRAME_NAME), szUnique); m_pWindow->setDestroyedByParent(false); m_pWindow->setRect(CEGUI::Relative, CEGUI::Rect(0.00f, 0.00f, 0.40f, 0.40f)); diff --git a/Client/gui/CGUILabel_Impl.cpp b/Client/gui/CGUILabel_Impl.cpp index c07103da799..e94ceafcee1 100644 --- a/Client/gui/CGUILabel_Impl.cpp +++ b/Client/gui/CGUILabel_Impl.cpp @@ -22,7 +22,7 @@ CGUILabel_Impl::CGUILabel_Impl(CGUI_Impl* pGUI, CGUIElement* pParent, const char pGUI->GetUniqueName(szUnique); // Create the window and set default settings - m_pWindow = pGUI->GetWindowManager()->createWindow(CGUILABEL_NAME, szUnique); + m_pWindow = pGUI->GetWindowManager()->createWindow(pGUI->ResolveModernName(CGUILABEL_NAME), szUnique); m_pWindow->setDestroyedByParent(false); // Store the pointer to this CGUI element in the CEGUI element diff --git a/Client/gui/CGUIListItem_Impl.cpp b/Client/gui/CGUIListItem_Impl.cpp index 0a2e8c65696..a5ffabccb9c 100644 --- a/Client/gui/CGUIListItem_Impl.cpp +++ b/Client/gui/CGUIListItem_Impl.cpp @@ -11,6 +11,8 @@ #include "StdInc.h" +extern CGUI_Impl* g_pGUI; + CGUIListItem_Impl::CGUIListItem_Impl(const char* szText, unsigned int uiType, CGUIStaticImage_Impl* pImage) { ItemType = uiType; @@ -33,7 +35,7 @@ CGUIListItem_Impl::CGUIListItem_Impl(const char* szText, unsigned int uiType, CG { // Set flags and properties m_pListItem->setAutoDeleted(false); - m_pListItem->setSelectionBrushImage("CGUI-Images", "ListboxSelectionBrush"); + m_pListItem->setSelectionBrushImage("CGUI-Images", g_pGUI->ResolveModernName("ListboxSelectionBrush")); } m_pData = NULL; diff --git a/Client/gui/CGUIMemo_Impl.cpp b/Client/gui/CGUIMemo_Impl.cpp index 61746b29f0d..845f38c0a10 100644 --- a/Client/gui/CGUIMemo_Impl.cpp +++ b/Client/gui/CGUIMemo_Impl.cpp @@ -22,7 +22,7 @@ CGUIMemo_Impl::CGUIMemo_Impl(CGUI_Impl* pGUI, CGUIElement* pParent, const char* pGUI->GetUniqueName(szUnique); // Create the window and set default settings - m_pWindow = pGUI->GetWindowManager()->createWindow(CGUIMEMO_NAME, szUnique); + m_pWindow = pGUI->GetWindowManager()->createWindow(pGUI->ResolveModernName(CGUIMEMO_NAME), szUnique); m_pWindow->setDestroyedByParent(false); // Store the pointer to this CGUI element in the CEGUI element diff --git a/Client/gui/CGUIProgressBar_Impl.cpp b/Client/gui/CGUIProgressBar_Impl.cpp index 113afc5fa91..f7e17c39f57 100644 --- a/Client/gui/CGUIProgressBar_Impl.cpp +++ b/Client/gui/CGUIProgressBar_Impl.cpp @@ -22,7 +22,7 @@ CGUIProgressBar_Impl::CGUIProgressBar_Impl(CGUI_Impl* pGUI, CGUIElement* pParent pGUI->GetUniqueName(szUnique); // Create the window and set default settings - m_pWindow = pGUI->GetWindowManager()->createWindow(CGUILABEL_NAME, szUnique); + m_pWindow = pGUI->GetWindowManager()->createWindow(pGUI->ResolveModernName(CGUILABEL_NAME), szUnique); m_pWindow->setDestroyedByParent(false); // Store the pointer to this CGUI element in the CEGUI element diff --git a/Client/gui/CGUIRadioButton_Impl.cpp b/Client/gui/CGUIRadioButton_Impl.cpp index dee554707da..cd07ee2e960 100644 --- a/Client/gui/CGUIRadioButton_Impl.cpp +++ b/Client/gui/CGUIRadioButton_Impl.cpp @@ -22,7 +22,7 @@ CGUIRadioButton_Impl::CGUIRadioButton_Impl(CGUI_Impl* pGUI, CGUIElement* pParent pGUI->GetUniqueName(szUnique); // Create the window and set default settings - m_pWindow = pGUI->GetWindowManager()->createWindow(CGUIRADIOBUTTON_NAME, szUnique); + m_pWindow = pGUI->GetWindowManager()->createWindow(pGUI->ResolveModernName(CGUIRADIOBUTTON_NAME), szUnique); m_pWindow->setDestroyedByParent(false); m_pWindow->setText(CGUI_Impl::GetUTFString(szCaption)); diff --git a/Client/gui/CGUIScrollBar_Impl.cpp b/Client/gui/CGUIScrollBar_Impl.cpp index 07e8cc61adb..2375d9c81cc 100644 --- a/Client/gui/CGUIScrollBar_Impl.cpp +++ b/Client/gui/CGUIScrollBar_Impl.cpp @@ -23,7 +23,7 @@ CGUIScrollBar_Impl::CGUIScrollBar_Impl(CGUI_Impl* pGUI, bool bHorizontal, CGUIEl pGUI->GetUniqueName(szUnique); // Create the window and set default settings - m_pWindow = pGUI->GetWindowManager()->createWindow(bHorizontal ? CGUISCROLLBAR_HORIZONTAL_NAME : CGUISCROLLBAR_VERTICAL_NAME, szUnique); + m_pWindow = pGUI->GetWindowManager()->createWindow(pGUI->ResolveModernName(bHorizontal ? CGUISCROLLBAR_HORIZONTAL_NAME : CGUISCROLLBAR_VERTICAL_NAME), szUnique); m_pWindow->setDestroyedByParent(false); // Store the pointer to this CGUI element in the CEGUI element diff --git a/Client/gui/CGUIScrollPane_Impl.cpp b/Client/gui/CGUIScrollPane_Impl.cpp index 2849308aa16..92d3762636d 100644 --- a/Client/gui/CGUIScrollPane_Impl.cpp +++ b/Client/gui/CGUIScrollPane_Impl.cpp @@ -25,7 +25,7 @@ CGUIScrollPane_Impl::CGUIScrollPane_Impl(CGUI_Impl* pGUI, CGUIElement* pParent) pGUI->GetUniqueName(szUnique); // Create the window and set default settings - m_pWindow = pGUI->GetWindowManager()->createWindow(CGUISCROLLPANE_NAME, szUnique); + m_pWindow = pGUI->GetWindowManager()->createWindow(pGUI->ResolveModernName(CGUISCROLLPANE_NAME), szUnique); m_pWindow->setDestroyedByParent(false); m_pWindow->setRect(CEGUI::Relative, CEGUI::Rect(0.9f, 0.9f, 0.9f, 0.9f)); diff --git a/Client/gui/CGUIStaticImage_Impl.cpp b/Client/gui/CGUIStaticImage_Impl.cpp index b57d75f0ca0..7d8af1cb204 100644 --- a/Client/gui/CGUIStaticImage_Impl.cpp +++ b/Client/gui/CGUIStaticImage_Impl.cpp @@ -29,7 +29,7 @@ CGUIStaticImage_Impl::CGUIStaticImage_Impl(CGUI_Impl* pGUI, CGUIElement* pParent pGUI->GetUniqueName(szUnique); // Create the control and set default properties - m_pWindow = pGUI->GetWindowManager()->createWindow(CGUISTATICIMAGE_NAME, szUnique); + m_pWindow = pGUI->GetWindowManager()->createWindow(pGUI->ResolveModernName(CGUISTATICIMAGE_NAME), szUnique); m_pWindow->setDestroyedByParent(false); m_pWindow->setRect(CEGUI::Relative, CEGUI::Rect(0.0f, 0.0f, 1.0f, 1.0f)); reinterpret_cast(m_pWindow)->setBackgroundEnabled(false); diff --git a/Client/gui/CGUITabPanel_Impl.cpp b/Client/gui/CGUITabPanel_Impl.cpp index 8ad196a4678..b44d6b78df4 100644 --- a/Client/gui/CGUITabPanel_Impl.cpp +++ b/Client/gui/CGUITabPanel_Impl.cpp @@ -25,7 +25,7 @@ CGUITabPanel_Impl::CGUITabPanel_Impl(CGUI_Impl* pGUI, CGUIElement* pParent) pGUI->GetUniqueName(szUnique); // Create the window and set default settings - m_pWindow = pGUI->GetWindowManager()->createWindow(CGUITABPANEL_NAME, szUnique); + m_pWindow = pGUI->GetWindowManager()->createWindow(pGUI->ResolveModernName(CGUITABPANEL_NAME), szUnique); m_pWindow->setDestroyedByParent(false); m_pWindow->setRect(CEGUI::Relative, CEGUI::Rect(0.9f, 0.9f, 0.9f, 0.9f)); reinterpret_cast(m_pWindow)->setAbsoluteTabTextPadding(10.0f); diff --git a/Client/gui/CGUIWebBrowser_Impl.cpp b/Client/gui/CGUIWebBrowser_Impl.cpp index 308f2945d00..b3f7b905316 100644 --- a/Client/gui/CGUIWebBrowser_Impl.cpp +++ b/Client/gui/CGUIWebBrowser_Impl.cpp @@ -26,7 +26,7 @@ CGUIWebBrowser_Impl::CGUIWebBrowser_Impl(CGUI_Impl* pGUI, CGUIElement* pParent) pGUI->GetUniqueName(szUnique); // Create the control and set default properties - m_pWindow = pGUI->GetWindowManager()->createWindow(CGUIWEBBROWSER_NAME, szUnique); + m_pWindow = pGUI->GetWindowManager()->createWindow(pGUI->ResolveModernName(CGUIWEBBROWSER_NAME), szUnique); m_pWindow->setDestroyedByParent(false); m_pWindow->setRect(CEGUI::Relative, CEGUI::Rect(0.0f, 0.0f, 1.0f, 1.0f)); reinterpret_cast(m_pWindow)->setBackgroundEnabled(false); diff --git a/Client/gui/CGUIWindow_Impl.cpp b/Client/gui/CGUIWindow_Impl.cpp index 7d6231674a6..d9e7fd36d04 100644 --- a/Client/gui/CGUIWindow_Impl.cpp +++ b/Client/gui/CGUIWindow_Impl.cpp @@ -32,7 +32,7 @@ CGUIWindow_Impl::CGUIWindow_Impl(CGUI_Impl* pGUI, CGUIElement* pParent, const ch if (!m_pWindow) { // Create new here - m_pWindow = pGUI->GetWindowManager()->createWindow(pGUI->ResolveSkin(CGUIWINDOW_NAME), szUnique); + m_pWindow = pGUI->GetWindowManager()->createWindow(pGUI->ResolveModernName(CGUIWINDOW_NAME), szUnique); m_pWindow->setRect(CEGUI::Relative, CEGUI::Rect(0.10f, 0.10f, 0.60f, 0.90f)); m_pWindow->setAlpha(0.8f); diff --git a/Client/gui/CGUI_Impl.cpp b/Client/gui/CGUI_Impl.cpp index 81705887294..9d33fa89bbc 100644 --- a/Client/gui/CGUI_Impl.cpp +++ b/Client/gui/CGUI_Impl.cpp @@ -137,7 +137,7 @@ void CGUI_Impl::SetSkin(const char* szName) PopGuiWorkingDirectory(); - CEGUI::System::getSingleton().setDefaultMouseCursor("CGUI-Images", "MouseArrow"); + CEGUI::System::getSingleton().setDefaultMouseCursor("CGUI-Images", "MouseArrowModern"); // Always use modern mouse arrow // Destroy any windows we already have CEGUI::WindowManager::getSingleton().destroyAllWindows(); @@ -430,9 +430,9 @@ bool CGUI_Impl::ConvertToModernSkin(const char* skinName) } // Load the requested skin's imageset, read-write - CXMLFile* skinImagesetFile = m_pXML->CreateXML(tempImagesetXmlPath); + CXMLFile* tempImagesetFile = m_pXML->CreateXML(tempImagesetXmlPath); - if (!skinImagesetFile || !skinImagesetFile->Parse()) + if (!tempImagesetFile || !tempImagesetFile->Parse()) { AddReportLog(1337, "ConvertToModernSkin: Failed to open/parse skin imageset"); return false; @@ -472,11 +472,26 @@ bool CGUI_Impl::ConvertToModernSkin(const char* skinName) } } + // Set the Imageset root node NativeHorzRes and NativeVertRes to the modern imageset's resolution + CXMLAttributes& skinAttributes = tempImagesetFile->GetRootNode()->GetAttributes(); + CXMLAttributes& modernAttributes = modernImagesetFile->GetRootNode()->GetAttributes(); + + auto skinHorzRes = skinAttributes.Find("NativeHorzRes"); + auto skinVertRes = skinAttributes.Find("NativeVertRes"); + auto modernHorzRes = modernAttributes.Find("NativeHorzRes"); + auto modernVertRes = modernAttributes.Find("NativeVertRes"); + + if (skinHorzRes && skinVertRes && modernHorzRes && modernVertRes) + { + skinHorzRes->SetValue(modernHorzRes->GetValue().c_str()); + skinVertRes->SetValue(modernVertRes->GetValue().c_str()); + } + // Copy the modern imageset into the skin imageset - modernRootNode->CopyChildrenInto(skinImagesetFile->GetRootNode(), true, false); + modernRootNode->CopyChildrenInto(tempImagesetFile->GetRootNode(), true, false); // Save the skin imageset - if (!skinImagesetFile->Write()) + if (!tempImagesetFile->Write()) { AddReportLog(1337, "ConvertToModernSkin: Failed to write skin imageset"); return false; @@ -2045,8 +2060,10 @@ CEGUI::Window* CGUI_Impl::GetMasterWindow(CEGUI::Window* wnd) return wnd; } -const char* CGUI_Impl::ResolveSkin(const char* szSkin) +std::string CGUI_Impl::ResolveModernName(const char* name) { - const std::string name = m_bUseModernSkin ? std::string(szSkin) + "Modern" : szSkin; - return name.c_str(); + if (name == "DefaultWindow") + return "FrameWindowModern"; + + return m_bUseModernSkin ? std::string(name) + "Modern" : name; } diff --git a/Client/gui/CGUI_Impl.h b/Client/gui/CGUI_Impl.h index f06797feadf..0373caa1dd4 100644 --- a/Client/gui/CGUI_Impl.h +++ b/Client/gui/CGUI_Impl.h @@ -281,8 +281,8 @@ class CGUI_Impl : public CGUI, public CGUITabList CGUIWindow* LoadLayout(CGUIElement* pParent, const SString& strFilename); bool LoadImageset(const SString& strFilename); - void SetModernSkinEnabled(bool bEnabled) { m_bUseModernSkin = bEnabled; } - const char* ResolveSkin(const char* szSkin); + void SetModernSkinEnabled(bool bEnabled) { m_bUseModernSkin = bEnabled; } + std::string ResolveModernName(const char* szSkin); void SetXMLParser(CXML* pXML) { m_pXML = pXML; } void SetGraphics(CGraphicsInterface* pGraphicsInterface) { m_pGraphics = pGraphicsInterface; } @@ -361,8 +361,7 @@ class CGUI_Impl : public CGUI, public CGUITabList SString m_CurrentSchemeName; CElapsedTime m_RenderOkTimer; - bool m_bUseModernSkin = false; + bool m_bUseModernSkin = false; CGraphicsInterface* m_pGraphics; - CXML* m_pXML; - + CXML* m_pXML; }; diff --git a/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/looknfeel.xml b/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/looknfeel.xml index 4e4a4deb1d8..a42ef8f7c16 100644 --- a/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/looknfeel.xml +++ b/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/looknfeel.xml @@ -1,5 +1,400 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+ +
+ + + + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+
+
+ + +
+ +
+
+ +
+
+
+ + +
+ +
+
+ +
+
+
+ + +
+ +
+
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+
+
+ + +
+ +
+
+ +
+
+
+ + +
+ +
+
+ +
+
+
+ + +
+ +
+
+ +
+
+
+
+ @@ -38,7 +433,7 @@ - + @@ -47,7 +442,7 @@ - + @@ -65,15 +460,15 @@ - - - - - - - - - + + + + + + + + + @@ -85,15 +480,15 @@ - - - - - - - - - + + + + + + + + + @@ -105,15 +500,15 @@ - - - - - - - - - + + + + + + + + + @@ -125,15 +520,15 @@ - - - - - - - - - + + + + + + + + + @@ -198,4 +593,1580 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + +
+ + + + +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + +
+ + + + +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ +
+
+
+ + + + + + + + + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+
+
+ + +
+ +
+
+ +
+
+
+ + +
+ +
+
+ +
+
+
+ + +
+ +
+
+
+ + +
+ + + + +
+ + + + +
+ +
+
+
+ + +
+ +
+
+
+ + + +
+ +
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+ + +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ +
+
+
+ + +
+ + + + +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+ +
+ + + + +
+ +
+
+ +
+
+
+ + +
+
+ +
+ + + + +
+
+ +
+ + + + +
+ +
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + + + +
+
+ +
+ + + + +
+ +
+
+ +
+
+
+ + +
+
+ +
+ + + + +
+
+ +
+ + + + +
+ +
+
+ +
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + +
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+ + + diff --git a/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/scheme.xml b/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/scheme.xml index 7302893b2c3..fa92aeb85d1 100644 --- a/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/scheme.xml +++ b/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/scheme.xml @@ -1,4 +1,28 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + From 0310d8d73b7a5be0c1e9382970ba330afb39f291 Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Thu, 16 Jan 2025 03:56:16 +0000 Subject: [PATCH 07/20] Remove unnecessary condition in ResolveModernName --- Client/gui/CGUI_Impl.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/Client/gui/CGUI_Impl.cpp b/Client/gui/CGUI_Impl.cpp index 9d33fa89bbc..0229a7bfbdc 100644 --- a/Client/gui/CGUI_Impl.cpp +++ b/Client/gui/CGUI_Impl.cpp @@ -2062,8 +2062,5 @@ CEGUI::Window* CGUI_Impl::GetMasterWindow(CEGUI::Window* wnd) std::string CGUI_Impl::ResolveModernName(const char* name) { - if (name == "DefaultWindow") - return "FrameWindowModern"; - return m_bUseModernSkin ? std::string(name) + "Modern" : name; } From a0ebf674237b79c7d70d2bbdf2d5fdef661cc898 Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Thu, 16 Jan 2025 03:57:57 +0000 Subject: [PATCH 08/20] Match ResolveModernName declaration to definition --- Client/gui/CGUI_Impl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/gui/CGUI_Impl.h b/Client/gui/CGUI_Impl.h index 0373caa1dd4..c93593144c5 100644 --- a/Client/gui/CGUI_Impl.h +++ b/Client/gui/CGUI_Impl.h @@ -282,7 +282,7 @@ class CGUI_Impl : public CGUI, public CGUITabList bool LoadImageset(const SString& strFilename); void SetModernSkinEnabled(bool bEnabled) { m_bUseModernSkin = bEnabled; } - std::string ResolveModernName(const char* szSkin); + std::string ResolveModernName(const char* name); void SetXMLParser(CXML* pXML) { m_pXML = pXML; } void SetGraphics(CGraphicsInterface* pGraphicsInterface) { m_pGraphics = pGraphicsInterface; } From 4f47d471c84f916d659650be66fcefa545ee2997 Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Fri, 17 Jan 2025 21:25:36 +0000 Subject: [PATCH 09/20] Implement CGUIGridLayout custom element --- Client/core/CSettings.cpp | 108 ++++++- Client/core/CSettings.h | 46 ++- Client/gui/CGUIGridLayout_Impl.cpp | 465 +++++++++++++++++++++++++++++ Client/gui/CGUIGridLayout_Impl.h | 88 ++++++ Client/gui/CGUI_Impl.cpp | 11 + Client/gui/CGUI_Impl.h | 3 + Client/gui/StdInc.h | 1 + Client/sdk/gui/CGUI.h | 3 + Client/sdk/gui/CGUIElement.h | 3 +- Client/sdk/gui/CGUIGridLayout.h | 84 ++++++ 10 files changed, 795 insertions(+), 17 deletions(-) create mode 100644 Client/gui/CGUIGridLayout_Impl.cpp create mode 100644 Client/gui/CGUIGridLayout_Impl.h create mode 100644 Client/sdk/gui/CGUIGridLayout.h diff --git a/Client/core/CSettings.cpp b/Client/core/CSettings.cpp index 566a374247b..3a7f030782e 100644 --- a/Client/core/CSettings.cpp +++ b/Client/core/CSettings.cpp @@ -14,6 +14,8 @@ #include #include +#include + using namespace std; #define CORE_MTA_FILLER "cgui\\images\\mta_filler.png" @@ -56,7 +58,7 @@ void CSettings::CreateGUI() if (m_pWindow) DestroyGUI(); - CGUITab *pTabMultiplayer, *pTabVideo, *pTabAudio, *pTabBinds, *pTabControls, *pTabAdvanced; + CGUITab *pTabMultiplayer, *pTabVideo, *pTabAudio, *pTabBinds, *pTabControls, *pTabAdvanced, *pTabCEGUI; CGUI* pManager = g_pCore->GetGUI(); // Init @@ -121,6 +123,7 @@ void CSettings::CreateGUI() m_pTabInterface = m_pTabs->CreateTab(_("Interface")); m_pTabBrowser = m_pTabs->CreateTab(_("Web Browser")); pTabAdvanced = m_pTabs->CreateTab(_("Advanced")); + pTabCEGUI = m_pTabs->CreateTab(_("CEGUI")); // Create buttons // OK button @@ -1265,6 +1268,82 @@ void CSettings::CreateGUI() m_pAdvancedSettingDescriptionLabel->SetSize(CVector2D(500.0f, 95.0f)); m_pAdvancedSettingDescriptionLabel->SetHorizontalAlign(CGUI_ALIGN_HORIZONTALCENTER_WORDWRAP); + /** + * CEGUI tab. + **/ + m_pGridLayout = reinterpret_cast(pManager->CreateGridLayout(pTabCEGUI)); + m_pGridLayout->SetGrid(1, 1); + + vecTemp = CVector2D(12.f, 12.f); + + // Grid layout section label + m_pGridLayoutLabel = reinterpret_cast(pManager->CreateLabel(pTabCEGUI, _("Grid layout"))); + m_pGridLayoutLabel->SetPosition(CVector2D(vecTemp.fX, vecTemp.fY)); + m_pGridLayoutLabel->SetFont("default-bold-small"); + m_pGridLayoutLabel->AutoSize(); + m_pGridLayoutLabel->SetHorizontalAlign(CGUI_ALIGN_HORIZONTALCENTER_WORDWRAP); + vecTemp.fY += 15.0f; + + m_pCellAlphaLabel = reinterpret_cast(pManager->CreateLabel(pTabCEGUI, _("Cell alpha:"))); + m_pCellAlphaLabel->SetPosition(CVector2D(vecTemp.fX, vecTemp.fY)); + m_pCellAlphaLabel->AutoSize(); + m_pCellAlphaLabel->SetHorizontalAlign(CGUI_ALIGN_LEFT); + + m_pGridColumnsLabel = reinterpret_cast(pManager->CreateLabel(pTabCEGUI, _("Columns:"))); + m_pGridColumnsLabel->SetPosition(CVector2D(vecTemp.fX + 200.0f, vecTemp.fY)); + m_pGridColumnsLabel->AutoSize(); + m_pGridColumnsLabel->SetHorizontalAlign(CGUI_ALIGN_LEFT); + + m_pGridRowsLabel = reinterpret_cast(pManager->CreateLabel(pTabCEGUI, _("Rows:"))); + m_pGridRowsLabel->SetPosition(CVector2D(vecTemp.fX + 400.0f, vecTemp.fY)); + m_pGridRowsLabel->AutoSize(); + m_pGridRowsLabel->SetHorizontalAlign(CGUI_ALIGN_LEFT); + + vecTemp.fY += 5.0f; + + m_pCellAlpha = reinterpret_cast(pManager->CreateScrollBar(true, pTabCEGUI)); + m_pCellAlpha->SetPosition(CVector2D(vecTemp.fX + 50.0f, vecTemp.fY)); + m_pCellAlpha->SetSize(CVector2D(130.0f, 20.0f)); + m_pCellAlpha->SetOnScrollHandler(GUI_CALLBACK(&CSettings::OnCellAlphaChanged, this)); + m_pCellAlpha->SetProperty("StepSize", "0.01"); + + m_pGridColumns = reinterpret_cast(pManager->CreateScrollBar(true, pTabCEGUI)); + m_pGridColumns->SetPosition(CVector2D(vecTemp.fX + 250.0f, vecTemp.fY)); + m_pGridColumns->SetSize(CVector2D(130.0f, 20.0f)); + m_pGridColumns->SetOnScrollHandler(GUI_CALLBACK(&CSettings::OnGridColumnsChanged, this)); + m_pGridColumns->SetProperty("StepSize", "0.1"); + + m_pGridRows = reinterpret_cast(pManager->CreateScrollBar(true, pTabCEGUI)); + m_pGridRows->SetPosition(CVector2D(vecTemp.fX + 450.0f, vecTemp.fY)); + m_pGridRows->SetSize(CVector2D(130.0f, 20.0f)); + m_pGridRows->SetOnScrollHandler(GUI_CALLBACK(&CSettings::OnGridRowsChanged, this)); + m_pGridRows->SetProperty("StepSize", "0.1"); + + vecTemp.fY += 20.0f; + m_pCellAlphaValueLabel = reinterpret_cast(pManager->CreateLabel(pTabCEGUI, "0.5")); + m_pCellAlphaValueLabel->SetPosition(CVector2D(vecTemp.fX + 50.0f, vecTemp.fY)); + m_pCellAlphaValueLabel->AutoSize(); + m_pCellAlphaValueLabel->SetHorizontalAlign(CGUI_ALIGN_LEFT); + + m_pGridColumnsValueLabel = reinterpret_cast(pManager->CreateLabel(pTabCEGUI, SString("%d", m_pGridLayout->GetColumns()))); + m_pGridColumnsValueLabel->SetPosition(CVector2D(vecTemp.fX + 250.0f, vecTemp.fY)); + m_pGridColumnsValueLabel->AutoSize(); + m_pGridColumnsValueLabel->SetHorizontalAlign(CGUI_ALIGN_LEFT); + + m_pGridRowsValueLabel = reinterpret_cast(pManager->CreateLabel(pTabCEGUI, SString("%d", m_pGridLayout->GetRows()))); + m_pGridRowsValueLabel->SetPosition(CVector2D(vecTemp.fX + 450.0f, vecTemp.fY)); + m_pGridRowsValueLabel->AutoSize(); + m_pGridRowsValueLabel->SetHorizontalAlign(CGUI_ALIGN_LEFT); + + m_pCellAlpha->SetScrollPosition(0.5f); + m_pGridColumns->SetScrollPosition(0.2f); + m_pGridRows->SetScrollPosition(0.2f); + + // Grid layout + vecTemp.fY += 20.0f; + m_pGridLayout->SetPosition(CVector2D(vecTemp.fX, vecTemp.fY)); + m_pGridLayout->SetSize(CVector2D(500.0f, 300.0f)); + // Set up the events m_pWindow->SetEnterKeyHandler(GUI_CALLBACK(&CSettings::OnOKButtonClick, this)); m_pButtonOK->SetClickHandler(GUI_CALLBACK(&CSettings::OnOKButtonClick, this)); @@ -4475,6 +4554,33 @@ bool CSettings::OnChatAlphaChanged(CGUIElement* pElement) return false; } +bool CSettings::OnCellAlphaChanged(CGUIElement* pElement) +{ + float alpha = (m_pCellAlpha->GetScrollPosition()); + m_pCellAlphaValueLabel->SetText(SString("%g", alpha).c_str()); + + m_pGridLayout->SetDefaultCellAlpha(alpha); + return true; +} + +bool CSettings::OnGridColumnsChanged(CGUIElement* pElement) +{ + float columns = std::max(0.1f, m_pGridColumns->GetScrollPosition()) * 10; + m_pGridColumnsValueLabel->SetText(SString("%g", columns).c_str()); + + m_pGridLayout->SetColumns(columns); + return true; +} + +bool CSettings::OnGridRowsChanged(CGUIElement* pElement) +{ + float rows = std::max(0.1f, m_pGridRows->GetScrollPosition()) * 10; + m_pGridRowsValueLabel->SetText(SString("%g", rows).c_str()); + + m_pGridLayout->SetRows(rows); + return true; +} + bool CSettings::OnUpdateButtonClick(CGUIElement* pElement) { // Update build type diff --git a/Client/core/CSettings.h b/Client/core/CSettings.h index 91a14ca40eb..d9b48728598 100644 --- a/Client/core/CSettings.h +++ b/Client/core/CSettings.h @@ -17,9 +17,9 @@ class CSettings; #include "CMainMenu.h" #include "CCore.h" -#define SKINS_PATH "skins/*" -#define CHAT_PRESETS_PATH "mta/config/chatboxpresets.xml" -#define CHAT_PRESETS_ROOT "chatboxpresets" +#define SKINS_PATH "skins/*" +#define CHAT_PRESETS_PATH "mta/config/chatboxpresets.xml" +#define CHAT_PRESETS_ROOT "chatboxpresets" // #define SHOWALLSETTINGS @@ -125,18 +125,18 @@ class CSettings const static int SecKeyNum = 3; // Number of secondary keys // Keep these protected so we can access them in the event handlers of CClientGame - CGUIElement* m_pWindow; - CGUITabPanel* m_pTabs; - CGUITab* m_pTabInterface; - CGUITab* m_pTabBrowser; - CGUIButton* m_pButtonOK; - CGUIButton* m_pButtonCancel; - CGUILabel* m_pLabelNick; - CGUIButton* m_pButtonGenerateNick; - CGUIStaticImage* m_pButtonGenerateNickIcon; - CGUIEdit* m_pEditNick; - CGUICheckBox* m_pSavePasswords; - CGUICheckBox* m_pAutoRefreshBrowser; + CGUIElement* m_pWindow; + CGUITabPanel* m_pTabs; + CGUITab* m_pTabInterface; + CGUITab* m_pTabBrowser; + CGUIButton* m_pButtonOK; + CGUIButton* m_pButtonCancel; + CGUILabel* m_pLabelNick; + CGUIButton* m_pButtonGenerateNick; + CGUIStaticImage* m_pButtonGenerateNickIcon; + CGUIEdit* m_pEditNick; + CGUICheckBox* m_pSavePasswords; + CGUICheckBox* m_pAutoRefreshBrowser; CGUILabel* m_pVideoGeneralLabel; CGUILabel* m_pVideoResolutionLabel; @@ -345,6 +345,19 @@ class CSettings bool m_bBrowserListsChanged; bool m_bBrowserListsLoadEnabled; + CGUILabel* m_pGridLayoutLabel; + CGUIGridLayout* m_pGridLayout; + CGUILabel* m_pTestCellLabel; + CGUILabel* m_pCellAlphaLabel; + CGUIScrollBar* m_pCellAlpha; + CGUILabel* m_pCellAlphaValueLabel; + CGUILabel* m_pGridColumnsLabel; + CGUIScrollBar* m_pGridColumns; + CGUILabel* m_pGridColumnsValueLabel; + CGUILabel* m_pGridRowsLabel; + CGUIScrollBar* m_pGridRows; + CGUILabel* m_pGridRowsValueLabel; + bool OnJoypadTextChanged(CGUIElement* pElement); bool OnAxisSelectClick(CGUIElement* pElement); bool OnAudioDefaultClick(CGUIElement* pElement); @@ -386,6 +399,9 @@ class CSettings bool OnBrowserWhitelistRemove(CGUIElement* pElement); bool OnBrowserWhitelistDomainAddFocused(CGUIElement* pElement); bool OnBrowserWhitelistDomainAddDefocused(CGUIElement* pElement); + bool OnCellAlphaChanged(CGUIElement* pElement); + bool OnGridColumnsChanged(CGUIElement* pElement); + bool OnGridRowsChanged(CGUIElement* pElement); bool OnMouseDoubleClick(CGUIMouseEventArgs Args); diff --git a/Client/gui/CGUIGridLayout_Impl.cpp b/Client/gui/CGUIGridLayout_Impl.cpp new file mode 100644 index 00000000000..96dbaa400fd --- /dev/null +++ b/Client/gui/CGUIGridLayout_Impl.cpp @@ -0,0 +1,465 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * LICENSE: See LICENSE in the top level directory + * FILE: gui/CGUIGridList_Impl.cpp + * PURPOSE: Grid list widget class + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#include "StdInc.h" + +#define CGUIGRIDLAYOUT_NAME "CGUI/FrameWindow" + +CGUIGridLayout_Impl::CGUIGridLayout_Impl(CGUI_Impl* pGUI, CGUIElement* pParent) +{ + m_pManager = pGUI; + + // Get an unique identifier for CEGUI (gah, there's gotta be an another way) + char szUnique[CGUI_CHAR_SIZE]; + pGUI->GetUniqueName(szUnique); + + // Create the window and set default settings + m_pWindow = pGUI->GetWindowManager()->createWindow(pGUI->ResolveModernName(CGUIGRIDLAYOUT_NAME), szUnique); + + m_pWindow->setDestroyedByParent(false); + m_pWindow->setRect(CEGUI::Relative, CEGUI::Rect(0.00f, 0.00f, 1.0f, 1.0f)); + + CEGUI::FrameWindow* frameWindow = reinterpret_cast(m_pWindow); + frameWindow->setTitleBarEnabled(false); + frameWindow->setSizingEnabled(false); + frameWindow->setDragMovingEnabled(false); + frameWindow->setCloseButtonEnabled(false); + frameWindow->setFrameEnabled(false); + + // Store the pointer to this CGUI element in the CEGUI element + m_pWindow->setUserData(reinterpret_cast(this)); + + // Register our events + // m_pWindow->subscribeEvent(CEGUI::MultiColumnList::EventSortColumnChanged, CEGUI::Event::Subscriber(&CGUIGridList_Impl::Event_OnSortColumn, this)); + AddEvents(); + + // If a parent is specified, add it to it's children list, if not, add it as a child to the pManager + if (pParent) + { + SetParent(pParent); + } + else + { + pGUI->AddChild(this); + SetParent(NULL); + } + + // Create our cell textures + m_cellTexture = m_pManager->CreateTexture(); + m_cellTextureAlt = m_pManager->CreateTexture(); + + unsigned long ulBackgroundColor255 = COLOR_ARGB(255, 0, 0, 0); + m_cellTexture->LoadFromMemory(&ulBackgroundColor255, 1, 1); + + unsigned long ulBackgroundColor150 = COLOR_ARGB(150, 0, 0, 0); + m_cellTextureAlt->LoadFromMemory(&ulBackgroundColor150, 1, 1); +} + +CGUIGridLayout_Impl::~CGUIGridLayout_Impl() +{ + DestroyElement(); +} + +const bool CGUIGridLayout_Impl::AddItem(CGUIElement* item, int column, int row, const bool moveToNextCell) +{ + if (!InGridRange(column, row)) + return false; + + // If cell is already occupied + if (m_grid[column - 1][row - 1] != 0) + { + return false; + } + + auto* cell = GetCell(column, row); + cell->element = item; + + item->SetParent(nullptr); + item->SetParent(cell->container); + + item->SetPosition(CVector2D(0.0f, 0.0f), true); + item->SetSize(CVector2D(1.0f, 1.0f), true); + + if (moveToNextCell) + { + if (m_activeRow == m_rows) + { + if (m_activeColumn < m_columns) + { + m_activeColumn++; + m_activeRow = 1; + } + } + else + { + m_activeRow++; + } + } + + return true; +} + +const bool CGUIGridLayout_Impl::AddItem(CGUIElement* item, const bool moveToNextCell) +{ + return AddItem(item, m_activeColumn, m_activeRow); +} + +const bool CGUIGridLayout_Impl::RemoveItem(const int column, const int row, const bool moveToPreviousCell) +{ + auto* cell = GetCell(column, row); + + if (cell == nullptr) + return false; + + auto& element = cell->element; + element->SetParent(nullptr); + element = nullptr; + m_grid[column - 1][row - 1] = 0; + + if (moveToPreviousCell) + { + if (m_activeRow == 1) + { + if (m_activeColumn > 1) + { + m_activeColumn--; + m_activeRow = m_rows; + } + } + else + { + m_activeRow--; + } + } + + return true; +} + +const bool CGUIGridLayout_Impl::RemoveItem(const CGUIElement* item, const bool moveToPreviousCell) +{ + auto& ci = m_cells.begin(); + + while (ci != m_cells.end()) + { + if (ci->second->element == item) // todo: store column/row on CGUIElement and use that to find the cell instead of iterating + return RemoveItem(ci->second->column, ci->second->row, moveToPreviousCell); + else + ci++; + } + + return false; +} + +SGridCellItem* CGUIGridLayout_Impl::GetCell(const int column, const int row) const +{ + if (!InGridRange(column, row)) + return nullptr; + + const auto& cell = m_cells.find(m_grid[column - 1][row - 1]); + + if (cell == m_cells.end()) + return nullptr; + + return cell->second; +} + +std::vector CGUIGridLayout_Impl::GetCellsInColumn(const int column) +{ + return GetCellsInGrid(column, 0, column, m_rows - 1); +} + +std::vector CGUIGridLayout_Impl::GetCellsInRow(const int row) +{ + return GetCellsInGrid(0, row, m_columns - 1, row); +} + +std::vector CGUIGridLayout_Impl::GetCellsInGrid(const int startColumn, const int startRow, const int endColumn, const int endRow) +{ + if (startColumn < 1 || startRow < 1 || endColumn > m_columns || endRow > m_rows) + { + return std::vector(); + } + + std::vector cells; + + for (int i = startColumn; i <= endColumn; i++) + { + for (int j = startRow; j <= endRow; j++) + { + if (m_grid[i - 1][j - 1] != 0) + { + auto& cell = m_cells.find(m_grid[i - 1][j - 1]); + + if (cell != m_cells.end()) + { + cells.push_back(cell->second); + } + } + } + } + + return cells; +} + +std::vector CGUIGridLayout_Impl::GetCellsOutsideGrid(const int startColumn, const int startRow, const int endColumn, const int endRow) +{ + if (startColumn < 1 || startRow < 1 || endColumn > m_columns || endRow > m_rows) + { + return std::vector(); + } + + std::vector cells; + + for (auto& cell : m_cells) + { + if (cell.second->column < startColumn || cell.second->column > endColumn || cell.second->row < startRow || cell.second->row > endRow) + { + cells.push_back(cell.second); + } + } + + return cells; +} + +const bool CGUIGridLayout_Impl::SetColumns(int columns) +{ + if (columns < 0 || columns == m_columns) + { + return false; + } + + bool set = SetGrid(columns, m_rows); + + if (set) + m_columns = columns; + + return set; +} + +const bool CGUIGridLayout_Impl::SetRows(int rows) +{ + if (rows < 0 || rows == m_rows) + return false; + + bool set = SetGrid(m_columns, rows); + + if (set) + m_rows = rows; + + return set; +} + +const bool CGUIGridLayout_Impl::SetGrid(int columns, int rows) +{ + if (columns < 0 || rows < 0 || (columns == m_columns && rows == m_rows)) + return false; + + m_columns = columns; + m_rows = rows; + + const size_t& size = m_grid.size(); + const bool larger = (columns * rows) > (size * (size > 0 ? m_grid[0].size() : 0)); + + m_grid.resize(columns); + + for (int i = 0; i < columns; i++) + { + m_grid[i].resize(rows, 0); + } + + if (larger) + { + CreateGridCells(); + + // Since the grid starts at 0,0 make sure an active cell is set + m_activeColumn = (m_activeColumn == 0 ? 1 : m_activeColumn); + m_activeRow = (m_activeRow == 0 ? 1 : m_activeRow); + } + else + { + CleanupGridItems(); + + // Since the grid has been resized, we need to make sure the active cell is still within the grid + m_activeColumn = std::min(m_activeColumn, columns); + m_activeRow = std::min(m_activeRow, rows); + } + + RepositionGridItems(); + return true; +} + +const bool CGUIGridLayout_Impl::SetActiveCell(int column, int row) +{ + if (!InGridRange(column, row)) + return false; + + m_activeColumn = column; + m_activeRow = row; + return true; +} + +const bool CGUIGridLayout_Impl::SetActiveColumn(int column) +{ + if (!InColumnRange(column)) + return false; + + m_activeColumn = column; + return true; +} + +const bool CGUIGridLayout_Impl::SetActiveRow(int row) +{ + if (!InRowRange(row)) + return false; + + m_activeRow = row; + return true; +} + +const std::pair CGUIGridLayout_Impl::GetActiveCell() +{ + return std::make_pair(m_activeColumn, m_activeRow); +} + +void CGUIGridLayout_Impl::SetItemAlignment(const CGUIElement* item, eGridLayoutItemAlignment alignment) +{ + for (auto& cell : m_cells) + { + if (cell.second->element == item) // todo: store column/row on CGUIElement and use that to find the cell instead of iterating + { + cell.second->alignment = alignment; + return; + } + } +} + +const eGridLayoutItemAlignment CGUIGridLayout_Impl::GetItemAlignment(const CGUIElement* item) const +{ + for (auto& cell : m_cells) + { + if (cell.second->element == item) // todo: store column/row on CGUIElement and use that to find the cell instead of iterating + { + return cell.second->alignment; + } + } + + return m_defaultAlignment; +} + +const bool CGUIGridLayout_Impl::SetCellAlpha(const int column, const int row, const float alpha) +{ + if (!InGridRange(column, row)) + return false; + + auto* cell = GetCell(column, row); + + if (cell == nullptr) + return false; + + cell->container->SetAlpha(alpha); + return true; +} + +const bool CGUIGridLayout_Impl::SetDefaultCellAlpha(const float alpha) +{ + m_defaultCellAlpha = alpha; + + for (auto& cell : m_cells) + { + cell.second->container->SetAlpha(alpha); + } + + return true; +} + +void CGUIGridLayout_Impl::CreateGridCells() +{ + auto* parent = reinterpret_cast(this); + + for (int i = 0; i < m_columns; i++) + { + for (int j = 0; j < m_rows; j++) + { + const auto cell = m_cells.count(m_grid[i][j]); + + if (cell == 0) + { + auto* cell = new SGridCellItem(); + cell->container = m_pManager->CreateStaticImage(parent); + cell->container->SetFrameEnabled(false); + + // Assign the cell container texture, alternative between m_cellTexture and m_cellTextureAlt + cell->container->LoadFromTexture((i + j) % 2 == 0 ? m_cellTexture : m_cellTextureAlt); + cell->container->SetAlpha(m_defaultCellAlpha); + + // Position the cell container in the grid + cell->container->SetPosition(CVector2D(i * (1.0f / m_columns), j * (1.0f / m_rows)), true); + + // Size the cell container in the grid + cell->container->SetSize(CVector2D(1.0f / m_columns, 1.0f / m_rows), true); + + cell->element = nullptr; + cell->column = i + 1; + cell->row = j + 1; + cell->alignment = m_defaultAlignment; + + m_cells.emplace(m_nextId, cell); + m_grid[i][j] = m_nextId; + + m_nextId++; + } + } + } +} + +void CGUIGridLayout_Impl::CleanupGridItems() +{ + for (auto& cell : m_cells) + { + if (cell.second->column > m_columns || cell.second->row > m_rows) + { + delete cell.second->container; + + if (cell.second->element) + { + cell.second->element->SetParent(nullptr); + cell.second->element = nullptr; + } + + delete cell.second; + m_cells.erase(cell.first); + } + } +} + +void CGUIGridLayout_Impl::RepositionGridItems() +{ + for (auto& cell : m_cells) + { + cell.second->container->SetPosition(CVector2D((cell.second->column - 1) * (1.0f / m_columns), (cell.second->row - 1) * (1.0f / m_rows)), true); + cell.second->container->SetSize(CVector2D(1.0f / m_columns, 1.0f / m_rows), true); + } +} + +const bool CGUIGridLayout_Impl::InGridRange(const int column, const int row) const +{ + return column >= 1 && row >= 1 && column <= m_columns && row <= m_rows; +} + +const bool CGUIGridLayout_Impl::InColumnRange(const int column) const +{ + return column >= 1 && column <= m_columns; +} + +const bool CGUIGridLayout_Impl::InRowRange(const int row) const +{ + return row >= 1 && row <= m_rows; +} diff --git a/Client/gui/CGUIGridLayout_Impl.h b/Client/gui/CGUIGridLayout_Impl.h new file mode 100644 index 00000000000..dc80400f2d6 --- /dev/null +++ b/Client/gui/CGUIGridLayout_Impl.h @@ -0,0 +1,88 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto: San Andreas + * LICENSE: See LICENSE in the top level directory + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +#include +#include "CGUIElement_Impl.h" + +class CGUIGridLayout_Impl : public CGUIGridLayout, public CGUIElement_Impl +{ +public: + CGUIGridLayout_Impl(class CGUI_Impl* pGUI, CGUIElement* pParent = NULL); + ~CGUIGridLayout_Impl(); + + eCGUIType GetType() { return CGUI_GRIDLAYOUT; }; + + const bool SetColumns(int columns); + const bool SetRows(int rows); + const bool SetGrid(int columns, int rows); + + const int GetColumns() const { return m_columns; } + const int GetRows() const { return m_rows; } + + const bool SetActiveCell(int column, int row); + const bool SetActiveColumn(int column); + const bool SetActiveRow(int row); + + const int GetActiveColumn() const { return m_activeColumn; } + const int GetActiveRow() const { return m_activeRow; } + const std::pair GetActiveCell(); + + const bool AddItem(CGUIElement* item, int column, int row, const bool moveToNextCell = true); + const bool AddItem(CGUIElement* item, const bool moveToNextCell = true); + + const bool RemoveItem(const int column, const int row, const bool moveToPreviousCell = false); + const bool RemoveItem(const CGUIElement* item, const bool moveToPreviousCell = false); + + SGridCellItem* GetCell(const int column, const int row) const; + + std::vector GetCellsInGrid(const int startColumn, const int startRow, const int endColumn, const int endRow); + std::vector GetCellsOutsideGrid(const int startColumn, const int startRow, const int endColumn, const int endRow); + std::vector GetCellsInColumn(const int column); + std::vector GetCellsInRow(const int row); + + void SetItemAlignment(const CGUIElement* item, eGridLayoutItemAlignment alignment); + void SetDefaultItemAlignment(eGridLayoutItemAlignment alignment) { m_defaultAlignment = alignment; } + + const eGridLayoutItemAlignment GetItemAlignment(const CGUIElement* item) const; + const eGridLayoutItemAlignment GetDefaultItemAlignment() const { return m_defaultAlignment; } + + const bool SetCellAlpha(const int column, const int row, const float alpha); + const bool SetDefaultCellAlpha(const float alpha); + +#include "CGUIElement_Inc.h" + +private: + int m_columns = 0; + int m_rows = 0; + + int m_activeColumn = 0; + int m_activeRow = 0; + + int m_nextId = 1; + + float m_defaultCellAlpha = 1.0f; + + std::vector> m_grid; + std::unordered_map m_cells; + + CGUITexture* m_cellTexture = nullptr; + CGUITexture* m_cellTextureAlt = nullptr; + + eGridLayoutItemAlignment m_defaultAlignment = eGridLayoutItemAlignment::MIDDLE_CENTER; + + void CreateGridCells(); + void CleanupGridItems(); + void RepositionGridItems(); + + const bool InGridRange(const int column, const int row) const; + const bool InColumnRange(const int column) const; + const bool InRowRange(const int row) const; +}; diff --git a/Client/gui/CGUI_Impl.cpp b/Client/gui/CGUI_Impl.cpp index 0229a7bfbdc..f16649fb416 100644 --- a/Client/gui/CGUI_Impl.cpp +++ b/Client/gui/CGUI_Impl.cpp @@ -836,6 +836,11 @@ CGUIWindow* CGUI_Impl::CreateWnd(CGUIElement* pParent, const char* szCaption) return new CGUIWindow_Impl(this, pParent, szCaption); } +CGUIGridLayout* CGUI_Impl::_CreateGridLayout(CGUIElement_Impl* pParent) +{ + return new CGUIGridLayout_Impl(this, pParent); +} + void CGUI_Impl::SetCursorEnabled(bool bEnabled) { if (bEnabled) @@ -1996,6 +2001,12 @@ CGUIWebBrowser* CGUI_Impl::CreateWebBrowser(CGUITab* pParent) return _CreateWebBrowser(wnd); } +CGUIGridLayout* CGUI_Impl::CreateGridLayout(CGUIElement* pParent) +{ + CGUIWindow_Impl* wnd = reinterpret_cast(pParent); + return _CreateGridLayout(wnd); +} + void CGUI_Impl::CleanDeadPool() { if (m_pWindowManager) diff --git a/Client/gui/CGUI_Impl.h b/Client/gui/CGUI_Impl.h index c93593144c5..6ef917d1571 100644 --- a/Client/gui/CGUI_Impl.h +++ b/Client/gui/CGUI_Impl.h @@ -142,6 +142,8 @@ class CGUI_Impl : public CGUI, public CGUITabList CGUITexture* CreateTexture(); CGUIFont* CreateFnt(const char* szFontName, const char* szFontFile, unsigned int uSize = 8, unsigned int uFlags = 0, bool bAutoScale = false); + CGUIGridLayout* CreateGridLayout(CGUIElement* pParent = nullptr); + void SetCursorEnabled(bool bEnabled); bool IsCursorEnabled(); void SetCursorAlpha(float fAlpha, bool bOnlyCurrentServer = false); @@ -302,6 +304,7 @@ class CGUI_Impl : public CGUI, public CGUITabList CGUIScrollBar* _CreateScrollBar(bool bHorizontal, CGUIElement_Impl* pParent = NULL); CGUIComboBox* _CreateComboBox(CGUIElement_Impl* pParent = NULL, const char* szCaption = ""); CGUIWebBrowser* _CreateWebBrowser(CGUIElement_Impl* pParent = nullptr); + CGUIGridLayout* _CreateGridLayout(CGUIElement_Impl* pParent = nullptr); void SubscribeToMouseEvents(); CGUIFont* CreateFntFromWinFont(const char* szFontName, const char* szFontWinReg, const char* szFontWinFile, unsigned int uSize = 8, unsigned int uFlags = 0, diff --git a/Client/gui/StdInc.h b/Client/gui/StdInc.h index e2a040a0ee5..fdd2310e9d1 100644 --- a/Client/gui/StdInc.h +++ b/Client/gui/StdInc.h @@ -35,3 +35,4 @@ #include "CGUIWebBrowser_Impl.h" #include "CGUIWindow_Impl.h" #include "CGUIComboBox_Impl.h" +#include "CGUIGridLayout_Impl.h" diff --git a/Client/sdk/gui/CGUI.h b/Client/sdk/gui/CGUI.h index d4d40c2f09a..1e04c283152 100644 --- a/Client/sdk/gui/CGUI.h +++ b/Client/sdk/gui/CGUI.h @@ -35,6 +35,7 @@ class CGUI; #include "CGUIWebBrowser.h" #include "CGUITabPanel.h" #include "CGUIComboBox.h" +#include "CGUIGridLayout.h" #include "CGUITypes.h" class CXML; @@ -126,6 +127,8 @@ class CGUI virtual CGUIFont* CreateFnt(const char* szFontName, const char* szFontFile, unsigned int uSize = 8, unsigned int uFlags = 0, bool bAutoScale = false) = 0; virtual CGUITexture* CreateTexture() = 0; + virtual CGUIGridLayout* CreateGridLayout(CGUIElement* pParent = nullptr) = 0; + virtual void SetCursorEnabled(bool bEnabled) = 0; virtual bool IsCursorEnabled() = 0; virtual void SetCursorAlpha(float fAlpha, bool bOnlyCurrentServer = false) = 0; diff --git a/Client/sdk/gui/CGUIElement.h b/Client/sdk/gui/CGUIElement.h index 252c84de990..c787f149df3 100644 --- a/Client/sdk/gui/CGUIElement.h +++ b/Client/sdk/gui/CGUIElement.h @@ -41,7 +41,8 @@ enum eCGUIType CGUI_SCROLLPANE, CGUI_SCROLLBAR, CGUI_COMBOBOX, - CGUI_WEBBROWSER + CGUI_WEBBROWSER, + CGUI_GRIDLAYOUT }; class CGUIElement diff --git a/Client/sdk/gui/CGUIGridLayout.h b/Client/sdk/gui/CGUIGridLayout.h new file mode 100644 index 00000000000..369abb08e8f --- /dev/null +++ b/Client/sdk/gui/CGUIGridLayout.h @@ -0,0 +1,84 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * LICENSE: See LICENSE in the top level directory + * FILE: sdk/gui/CGUIGridList.h + * PURPOSE: Grid list widget interface + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ + +#pragma once + +#include "CGUIElement.h" +#include "CGUIGridLayout.h" + +#include + +class CGUIStaticImage; + +enum class eGridLayoutItemAlignment +{ + TOP_LEFT, + TOP_CENTER, + TOP_RIGHT, + MIDDLE_LEFT, + MIDDLE_CENTER, + MIDDLE_RIGHT, + BOTTOM_LEFT, + BOTTOM_CENTER, + BOTTOM_RIGHT +}; + +struct SGridCellItem +{ + CGUIStaticImage* container; + CGUIElement* element; + eGridLayoutItemAlignment alignment; + int column; + int row; +}; + +class CGUIGridLayout : public CGUIElement +{ +public: + virtual ~CGUIGridLayout(){}; + + virtual const bool SetColumns(int columns) = 0; + virtual const bool SetRows(int rows) = 0; + virtual const bool SetGrid(int columns, int rows) = 0; + + virtual const int GetColumns() const = 0; + virtual const int GetRows() const = 0; + + virtual const bool SetActiveCell(int column, int row) = 0; + virtual const bool SetActiveColumn(int column) = 0; + virtual const bool SetActiveRow(int row) = 0; + + virtual const int GetActiveColumn() const = 0; + virtual const int GetActiveRow() const = 0; + virtual const std::pair GetActiveCell() = 0; + + virtual const bool AddItem(CGUIElement* item, int column, int row, const bool moveToNextCell = true) = 0; + virtual const bool AddItem(CGUIElement* item, const bool moveToNextCell = true) = 0; + + virtual const bool RemoveItem(const int column, const int row, const bool moveToPreviousCell = false) = 0; + virtual const bool RemoveItem(const CGUIElement* item, const bool moveToPreviousCell = false) = 0; + + virtual SGridCellItem* GetCell(const int column, const int row) const = 0; + + virtual std::vector GetCellsInGrid(const int startColumn, const int startRow, const int endColumn, const int endRow) = 0; + virtual std::vector GetCellsOutsideGrid(const int startColumn, const int startRow, const int endColumn, const int endRow) = 0; + virtual std::vector GetCellsInColumn(const int column) = 0; + virtual std::vector GetCellsInRow(const int row) = 0; + + virtual void SetItemAlignment(const CGUIElement* item, eGridLayoutItemAlignment alignment) = 0; + virtual void SetDefaultItemAlignment(eGridLayoutItemAlignment alignment) = 0; + + virtual const eGridLayoutItemAlignment GetItemAlignment(const CGUIElement* item) const = 0; + virtual const eGridLayoutItemAlignment GetDefaultItemAlignment() const = 0; + + virtual const bool SetCellAlpha(const int column, const int row, const float alpha) = 0; + virtual const bool SetDefaultCellAlpha(const float alpha) = 0; +}; From 5c47f44193f49f840b03f69256cb2668b1bd7975 Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Fri, 17 Jan 2025 22:32:31 +0000 Subject: [PATCH 10/20] Add map for item lookup --- Client/gui/CGUIGridLayout_Impl.cpp | 55 ++++++++++++++++-------------- Client/gui/CGUIGridLayout_Impl.h | 6 ++-- Client/sdk/gui/CGUIGridLayout.h | 2 ++ 3 files changed, 35 insertions(+), 28 deletions(-) diff --git a/Client/gui/CGUIGridLayout_Impl.cpp b/Client/gui/CGUIGridLayout_Impl.cpp index 96dbaa400fd..9fd369bab6a 100644 --- a/Client/gui/CGUIGridLayout_Impl.cpp +++ b/Client/gui/CGUIGridLayout_Impl.cpp @@ -76,7 +76,7 @@ const bool CGUIGridLayout_Impl::AddItem(CGUIElement* item, int column, int row, // If cell is already occupied if (m_grid[column - 1][row - 1] != 0) { - return false; + return false; } auto* cell = GetCell(column, row); @@ -88,6 +88,8 @@ const bool CGUIGridLayout_Impl::AddItem(CGUIElement* item, int column, int row, item->SetPosition(CVector2D(0.0f, 0.0f), true); item->SetSize(CVector2D(1.0f, 1.0f), true); + m_items.emplace(item, cell->id); + if (moveToNextCell) { if (m_activeRow == m_rows) @@ -124,6 +126,8 @@ const bool CGUIGridLayout_Impl::RemoveItem(const int column, const int row, cons element = nullptr; m_grid[column - 1][row - 1] = 0; + m_items.erase(element); + if (moveToPreviousCell) { if (m_activeRow == 1) @@ -145,17 +149,12 @@ const bool CGUIGridLayout_Impl::RemoveItem(const int column, const int row, cons const bool CGUIGridLayout_Impl::RemoveItem(const CGUIElement* item, const bool moveToPreviousCell) { - auto& ci = m_cells.begin(); + auto* cell = GetCell(item); - while (ci != m_cells.end()) - { - if (ci->second->element == item) // todo: store column/row on CGUIElement and use that to find the cell instead of iterating - return RemoveItem(ci->second->column, ci->second->row, moveToPreviousCell); - else - ci++; - } + if (cell == nullptr) + return false; - return false; + return RemoveItem(cell->column, cell->row, moveToPreviousCell); } SGridCellItem* CGUIGridLayout_Impl::GetCell(const int column, const int row) const @@ -171,6 +170,12 @@ SGridCellItem* CGUIGridLayout_Impl::GetCell(const int column, const int row) con return cell->second; } +SGridCellItem* CGUIGridLayout_Impl::GetCell(const CGUIElement* item) const +{ + int id = m_items.count(item) ? m_items.at(item) : 0; + return m_cells.count(id) ? m_cells.at(id) : nullptr; +} + std::vector CGUIGridLayout_Impl::GetCellsInColumn(const int column) { return GetCellsInGrid(column, 0, column, m_rows - 1); @@ -331,27 +336,22 @@ const std::pair CGUIGridLayout_Impl::GetActiveCell() void CGUIGridLayout_Impl::SetItemAlignment(const CGUIElement* item, eGridLayoutItemAlignment alignment) { - for (auto& cell : m_cells) - { - if (cell.second->element == item) // todo: store column/row on CGUIElement and use that to find the cell instead of iterating - { - cell.second->alignment = alignment; - return; - } - } + auto* cell = GetCell(item); + + if (cell == nullptr) + return; + + cell->alignment = alignment; } const eGridLayoutItemAlignment CGUIGridLayout_Impl::GetItemAlignment(const CGUIElement* item) const { - for (auto& cell : m_cells) - { - if (cell.second->element == item) // todo: store column/row on CGUIElement and use that to find the cell instead of iterating - { - return cell.second->alignment; - } - } + const auto* cell = GetCell(item); - return m_defaultAlignment; + if (cell == nullptr) + return m_defaultAlignment; + + return cell->alignment; } const bool CGUIGridLayout_Impl::SetCellAlpha(const int column, const int row, const float alpha) @@ -406,6 +406,7 @@ void CGUIGridLayout_Impl::CreateGridCells() // Size the cell container in the grid cell->container->SetSize(CVector2D(1.0f / m_columns, 1.0f / m_rows), true); + cell->id = m_nextId; cell->element = nullptr; cell->column = i + 1; cell->row = j + 1; @@ -430,6 +431,8 @@ void CGUIGridLayout_Impl::CleanupGridItems() if (cell.second->element) { + m_items.erase(cell.second->element); + cell.second->element->SetParent(nullptr); cell.second->element = nullptr; } diff --git a/Client/gui/CGUIGridLayout_Impl.h b/Client/gui/CGUIGridLayout_Impl.h index dc80400f2d6..5c19a329470 100644 --- a/Client/gui/CGUIGridLayout_Impl.h +++ b/Client/gui/CGUIGridLayout_Impl.h @@ -42,6 +42,7 @@ class CGUIGridLayout_Impl : public CGUIGridLayout, public CGUIElement_Impl const bool RemoveItem(const CGUIElement* item, const bool moveToPreviousCell = false); SGridCellItem* GetCell(const int column, const int row) const; + SGridCellItem* GetCell(const CGUIElement* item) const; std::vector GetCellsInGrid(const int startColumn, const int startRow, const int endColumn, const int endRow); std::vector GetCellsOutsideGrid(const int startColumn, const int startRow, const int endColumn, const int endRow); @@ -70,8 +71,9 @@ class CGUIGridLayout_Impl : public CGUIGridLayout, public CGUIElement_Impl float m_defaultCellAlpha = 1.0f; - std::vector> m_grid; - std::unordered_map m_cells; + std::vector> m_grid; + std::unordered_map m_cells; + std::unordered_map m_items; CGUITexture* m_cellTexture = nullptr; CGUITexture* m_cellTextureAlt = nullptr; diff --git a/Client/sdk/gui/CGUIGridLayout.h b/Client/sdk/gui/CGUIGridLayout.h index 369abb08e8f..60096f463dd 100644 --- a/Client/sdk/gui/CGUIGridLayout.h +++ b/Client/sdk/gui/CGUIGridLayout.h @@ -33,6 +33,7 @@ enum class eGridLayoutItemAlignment struct SGridCellItem { + int id; CGUIStaticImage* container; CGUIElement* element; eGridLayoutItemAlignment alignment; @@ -67,6 +68,7 @@ class CGUIGridLayout : public CGUIElement virtual const bool RemoveItem(const CGUIElement* item, const bool moveToPreviousCell = false) = 0; virtual SGridCellItem* GetCell(const int column, const int row) const = 0; + virtual SGridCellItem* GetCell(const CGUIElement* item) const = 0; virtual std::vector GetCellsInGrid(const int startColumn, const int startRow, const int endColumn, const int endRow) = 0; virtual std::vector GetCellsOutsideGrid(const int startColumn, const int startRow, const int endColumn, const int endRow) = 0; From 42cdfd4fa3aa881d0ec0616a7d514f1556aa02da Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Fri, 17 Jan 2025 22:32:39 +0000 Subject: [PATCH 11/20] Fix issues with CEGUI testing tab --- Client/core/CSettings.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Client/core/CSettings.cpp b/Client/core/CSettings.cpp index 3a7f030782e..78376876de9 100644 --- a/Client/core/CSettings.cpp +++ b/Client/core/CSettings.cpp @@ -1320,17 +1320,17 @@ void CSettings::CreateGUI() m_pGridRows->SetProperty("StepSize", "0.1"); vecTemp.fY += 20.0f; - m_pCellAlphaValueLabel = reinterpret_cast(pManager->CreateLabel(pTabCEGUI, "0.5")); + m_pCellAlphaValueLabel = reinterpret_cast(pManager->CreateLabel(pTabCEGUI, "XXX")); m_pCellAlphaValueLabel->SetPosition(CVector2D(vecTemp.fX + 50.0f, vecTemp.fY)); m_pCellAlphaValueLabel->AutoSize(); m_pCellAlphaValueLabel->SetHorizontalAlign(CGUI_ALIGN_LEFT); - m_pGridColumnsValueLabel = reinterpret_cast(pManager->CreateLabel(pTabCEGUI, SString("%d", m_pGridLayout->GetColumns()))); + m_pGridColumnsValueLabel = reinterpret_cast(pManager->CreateLabel(pTabCEGUI, "XXX")); m_pGridColumnsValueLabel->SetPosition(CVector2D(vecTemp.fX + 250.0f, vecTemp.fY)); m_pGridColumnsValueLabel->AutoSize(); m_pGridColumnsValueLabel->SetHorizontalAlign(CGUI_ALIGN_LEFT); - m_pGridRowsValueLabel = reinterpret_cast(pManager->CreateLabel(pTabCEGUI, SString("%d", m_pGridLayout->GetRows()))); + m_pGridRowsValueLabel = reinterpret_cast(pManager->CreateLabel(pTabCEGUI, "XXX")); m_pGridRowsValueLabel->SetPosition(CVector2D(vecTemp.fX + 450.0f, vecTemp.fY)); m_pGridRowsValueLabel->AutoSize(); m_pGridRowsValueLabel->SetHorizontalAlign(CGUI_ALIGN_LEFT); @@ -4565,8 +4565,8 @@ bool CSettings::OnCellAlphaChanged(CGUIElement* pElement) bool CSettings::OnGridColumnsChanged(CGUIElement* pElement) { - float columns = std::max(0.1f, m_pGridColumns->GetScrollPosition()) * 10; - m_pGridColumnsValueLabel->SetText(SString("%g", columns).c_str()); + int columns = static_cast(std::round(std::max(0.1f, m_pGridColumns->GetScrollPosition()) * 10)); + m_pGridColumnsValueLabel->SetText(SString("%i", columns).c_str()); m_pGridLayout->SetColumns(columns); return true; @@ -4574,8 +4574,8 @@ bool CSettings::OnGridColumnsChanged(CGUIElement* pElement) bool CSettings::OnGridRowsChanged(CGUIElement* pElement) { - float rows = std::max(0.1f, m_pGridRows->GetScrollPosition()) * 10; - m_pGridRowsValueLabel->SetText(SString("%g", rows).c_str()); + int rows = static_cast(std::round(std::max(0.1f, m_pGridRows->GetScrollPosition()) * 10)); + m_pGridRowsValueLabel->SetText(SString("%i", rows).c_str()); m_pGridLayout->SetRows(rows); return true; From c80bb0c21c06deed5f0ad326b49e3fb0843ed1ed Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Fri, 17 Jan 2025 22:35:59 +0000 Subject: [PATCH 12/20] Minor optimization in item lookup map usage --- Client/gui/CGUIGridLayout_Impl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/gui/CGUIGridLayout_Impl.cpp b/Client/gui/CGUIGridLayout_Impl.cpp index 9fd369bab6a..ada5f5deaac 100644 --- a/Client/gui/CGUIGridLayout_Impl.cpp +++ b/Client/gui/CGUIGridLayout_Impl.cpp @@ -173,7 +173,7 @@ SGridCellItem* CGUIGridLayout_Impl::GetCell(const int column, const int row) con SGridCellItem* CGUIGridLayout_Impl::GetCell(const CGUIElement* item) const { int id = m_items.count(item) ? m_items.at(item) : 0; - return m_cells.count(id) ? m_cells.at(id) : nullptr; + return id == 0 ? nullptr : m_cells.at(id); } std::vector CGUIGridLayout_Impl::GetCellsInColumn(const int column) From 068a4975661ec5872318218651a5f3b1d06f7f77 Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Sat, 18 Jan 2025 00:46:37 +0000 Subject: [PATCH 13/20] Fix cell item assignment in CGUIGridLayout --- Client/gui/CGUIGridLayout_Impl.cpp | 57 ++++++++++++++++++------------ Client/gui/CGUIGridLayout_Impl.h | 4 +-- Client/sdk/gui/CGUIGridLayout.h | 4 +-- 3 files changed, 39 insertions(+), 26 deletions(-) diff --git a/Client/gui/CGUIGridLayout_Impl.cpp b/Client/gui/CGUIGridLayout_Impl.cpp index ada5f5deaac..1682756b64f 100644 --- a/Client/gui/CGUIGridLayout_Impl.cpp +++ b/Client/gui/CGUIGridLayout_Impl.cpp @@ -70,24 +70,20 @@ CGUIGridLayout_Impl::~CGUIGridLayout_Impl() const bool CGUIGridLayout_Impl::AddItem(CGUIElement* item, int column, int row, const bool moveToNextCell) { - if (!InGridRange(column, row)) - return false; + auto* cell = GetCell(column, row); - // If cell is already occupied - if (m_grid[column - 1][row - 1] != 0) - { + if (cell == nullptr) return false; - } - auto* cell = GetCell(column, row); - cell->element = item; + if (cell->element != nullptr) + RemoveItem(column, row, false, true); - item->SetParent(nullptr); item->SetParent(cell->container); - item->SetPosition(CVector2D(0.0f, 0.0f), true); item->SetSize(CVector2D(1.0f, 1.0f), true); + item->ForceRedraw(); + cell->element = item; m_items.emplace(item, cell->id); if (moveToNextCell) @@ -114,19 +110,21 @@ const bool CGUIGridLayout_Impl::AddItem(CGUIElement* item, const bool moveToNext return AddItem(item, m_activeColumn, m_activeRow); } -const bool CGUIGridLayout_Impl::RemoveItem(const int column, const int row, const bool moveToPreviousCell) +const bool CGUIGridLayout_Impl::RemoveItem(const int column, const int row, const bool moveToPreviousCell, const bool deleteItem) { auto* cell = GetCell(column, row); - if (cell == nullptr) + if (cell == nullptr || cell->element == nullptr) return false; - auto& element = cell->element; - element->SetParent(nullptr); - element = nullptr; - m_grid[column - 1][row - 1] = 0; + cell->element->SetParent(nullptr); + cell->element->ForceRedraw(); - m_items.erase(element); + m_items.erase(cell->element); + cell->element = nullptr; + + if (deleteItem) + delete cell->element; if (moveToPreviousCell) { @@ -147,7 +145,7 @@ const bool CGUIGridLayout_Impl::RemoveItem(const int column, const int row, cons return true; } -const bool CGUIGridLayout_Impl::RemoveItem(const CGUIElement* item, const bool moveToPreviousCell) +const bool CGUIGridLayout_Impl::RemoveItem(const CGUIElement* item, const bool moveToPreviousCell, const bool deleteItem) { auto* cell = GetCell(item); @@ -356,9 +354,6 @@ const eGridLayoutItemAlignment CGUIGridLayout_Impl::GetItemAlignment(const CGUIE const bool CGUIGridLayout_Impl::SetCellAlpha(const int column, const int row, const float alpha) { - if (!InGridRange(column, row)) - return false; - auto* cell = GetCell(column, row); if (cell == nullptr) @@ -407,7 +402,15 @@ void CGUIGridLayout_Impl::CreateGridCells() cell->container->SetSize(CVector2D(1.0f / m_columns, 1.0f / m_rows), true); cell->id = m_nextId; - cell->element = nullptr; + + auto* label = m_pManager->CreateLabel(cell->container, std::to_string(m_nextId).c_str()); + label->AutoSize(); + label->SetPosition(CVector2D(0.0f, 0.0f), true); + label->SetSize(CVector2D(1.0f, 1.0f), true); + label->SetHorizontalAlign(CGUI_ALIGN_HORIZONTALCENTER); + label->SetVerticalAlign(CGUI_ALIGN_VERTICALCENTER); + + cell->element = label; cell->column = i + 1; cell->row = j + 1; cell->alignment = m_defaultAlignment; @@ -434,6 +437,9 @@ void CGUIGridLayout_Impl::CleanupGridItems() m_items.erase(cell.second->element); cell.second->element->SetParent(nullptr); + cell.second->element->ForceRedraw(); + + delete cell.second->element; cell.second->element = nullptr; } @@ -449,6 +455,13 @@ void CGUIGridLayout_Impl::RepositionGridItems() { cell.second->container->SetPosition(CVector2D((cell.second->column - 1) * (1.0f / m_columns), (cell.second->row - 1) * (1.0f / m_rows)), true); cell.second->container->SetSize(CVector2D(1.0f / m_columns, 1.0f / m_rows), true); + + if (cell.second->element != nullptr) + { + cell.second->element->SetPosition(CVector2D(0.0f, 0.0f), true); + cell.second->element->SetSize(CVector2D(1.0f, 1.0f), true); + cell.second->element->ForceRedraw(); + } } } diff --git a/Client/gui/CGUIGridLayout_Impl.h b/Client/gui/CGUIGridLayout_Impl.h index 5c19a329470..1d62918e9c1 100644 --- a/Client/gui/CGUIGridLayout_Impl.h +++ b/Client/gui/CGUIGridLayout_Impl.h @@ -38,8 +38,8 @@ class CGUIGridLayout_Impl : public CGUIGridLayout, public CGUIElement_Impl const bool AddItem(CGUIElement* item, int column, int row, const bool moveToNextCell = true); const bool AddItem(CGUIElement* item, const bool moveToNextCell = true); - const bool RemoveItem(const int column, const int row, const bool moveToPreviousCell = false); - const bool RemoveItem(const CGUIElement* item, const bool moveToPreviousCell = false); + const bool RemoveItem(const int column, const int row, const bool moveToPreviousCell = false, const bool deleteItem = false); + const bool RemoveItem(const CGUIElement* item, const bool moveToPreviousCell = false, const bool deleteItem = false); SGridCellItem* GetCell(const int column, const int row) const; SGridCellItem* GetCell(const CGUIElement* item) const; diff --git a/Client/sdk/gui/CGUIGridLayout.h b/Client/sdk/gui/CGUIGridLayout.h index 60096f463dd..05833b7ac00 100644 --- a/Client/sdk/gui/CGUIGridLayout.h +++ b/Client/sdk/gui/CGUIGridLayout.h @@ -64,8 +64,8 @@ class CGUIGridLayout : public CGUIElement virtual const bool AddItem(CGUIElement* item, int column, int row, const bool moveToNextCell = true) = 0; virtual const bool AddItem(CGUIElement* item, const bool moveToNextCell = true) = 0; - virtual const bool RemoveItem(const int column, const int row, const bool moveToPreviousCell = false) = 0; - virtual const bool RemoveItem(const CGUIElement* item, const bool moveToPreviousCell = false) = 0; + virtual const bool RemoveItem(const int column, const int row, const bool moveToPreviousCell = false, const bool deleteItem = false) = 0; + virtual const bool RemoveItem(const CGUIElement* item, const bool moveToPreviousCell = false, const bool deleteItem = false) = 0; virtual SGridCellItem* GetCell(const int column, const int row) const = 0; virtual SGridCellItem* GetCell(const CGUIElement* item) const = 0; From 548e53025d20122eb66987201ded0dca190a18f6 Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Sun, 19 Jan 2025 17:47:31 +0000 Subject: [PATCH 14/20] Add flex column & row support to CGUIGridLayout --- Client/core/CSettings.cpp | 19 ++- Client/gui/CGUIGridLayout_Impl.cpp | 263 +++++++++++++++++++++++++---- Client/gui/CGUIGridLayout_Impl.h | 35 +++- Client/sdk/gui/CGUIGridLayout.h | 13 +- 4 files changed, 279 insertions(+), 51 deletions(-) diff --git a/Client/core/CSettings.cpp b/Client/core/CSettings.cpp index 78376876de9..a0568da9982 100644 --- a/Client/core/CSettings.cpp +++ b/Client/core/CSettings.cpp @@ -1272,8 +1272,11 @@ void CSettings::CreateGUI() * CEGUI tab. **/ m_pGridLayout = reinterpret_cast(pManager->CreateGridLayout(pTabCEGUI)); - m_pGridLayout->SetGrid(1, 1); - + m_pGridLayout->SetGrid(9, 9); + m_pGridLayout->SetColumnWidth(1, 0.25f); + m_pGridLayout->SetColumnWidth(3, 0.25f); + m_pGridLayout->SetRowHeight(2, 0.25f); + m_pGridLayout->SetRowHeight(4, 0.25f); vecTemp = CVector2D(12.f, 12.f); // Grid layout section label @@ -1336,14 +1339,22 @@ void CSettings::CreateGUI() m_pGridRowsValueLabel->SetHorizontalAlign(CGUI_ALIGN_LEFT); m_pCellAlpha->SetScrollPosition(0.5f); - m_pGridColumns->SetScrollPosition(0.2f); - m_pGridRows->SetScrollPosition(0.2f); + m_pGridColumns->SetScrollPosition(0.9f); + m_pGridRows->SetScrollPosition(0.9f); // Grid layout vecTemp.fY += 20.0f; m_pGridLayout->SetPosition(CVector2D(vecTemp.fX, vecTemp.fY)); m_pGridLayout->SetSize(CVector2D(500.0f, 300.0f)); + //m_pTestCellLabel = reinterpret_cast(pManager->CreateLabel(pTabCEGUI, _("Test cell"))); + //m_pTestCellLabel->AutoSize(); + //m_pTestCellLabel->SetHorizontalAlign(CGUI_ALIGN_HORIZONTALCENTER); + //m_pTestCellLabel->SetVerticalAlign(CGUI_ALIGN_VERTICALCENTER); + + //bool add = m_pGridLayout->AddItem(m_pTestCellLabel, 1, 1); + //m_pTestCellLabel->SetText(add ? "true" : "false"); + // Set up the events m_pWindow->SetEnterKeyHandler(GUI_CALLBACK(&CSettings::OnOKButtonClick, this)); m_pButtonOK->SetClickHandler(GUI_CALLBACK(&CSettings::OnOKButtonClick, this)); diff --git a/Client/gui/CGUIGridLayout_Impl.cpp b/Client/gui/CGUIGridLayout_Impl.cpp index 1682756b64f..dbc4d3de3d9 100644 --- a/Client/gui/CGUIGridLayout_Impl.cpp +++ b/Client/gui/CGUIGridLayout_Impl.cpp @@ -10,6 +10,7 @@ *****************************************************************************/ #include "StdInc.h" +#include #define CGUIGRIDLAYOUT_NAME "CGUI/FrameWindow" @@ -80,7 +81,6 @@ const bool CGUIGridLayout_Impl::AddItem(CGUIElement* item, int column, int row, item->SetParent(cell->container); item->SetPosition(CVector2D(0.0f, 0.0f), true); - item->SetSize(CVector2D(1.0f, 1.0f), true); item->ForceRedraw(); cell->element = item; @@ -174,17 +174,17 @@ SGridCellItem* CGUIGridLayout_Impl::GetCell(const CGUIElement* item) const return id == 0 ? nullptr : m_cells.at(id); } -std::vector CGUIGridLayout_Impl::GetCellsInColumn(const int column) +std::vector CGUIGridLayout_Impl::GetCellsInColumn(const int column) const { return GetCellsInGrid(column, 0, column, m_rows - 1); } -std::vector CGUIGridLayout_Impl::GetCellsInRow(const int row) +std::vector CGUIGridLayout_Impl::GetCellsInRow(const int row) const { return GetCellsInGrid(0, row, m_columns - 1, row); } -std::vector CGUIGridLayout_Impl::GetCellsInGrid(const int startColumn, const int startRow, const int endColumn, const int endRow) +std::vector CGUIGridLayout_Impl::GetCellsInGrid(const int startColumn, const int startRow, const int endColumn, const int endRow) const { if (startColumn < 1 || startRow < 1 || endColumn > m_columns || endRow > m_rows) { @@ -212,7 +212,7 @@ std::vector CGUIGridLayout_Impl::GetCellsInGrid(const int startC return cells; } -std::vector CGUIGridLayout_Impl::GetCellsOutsideGrid(const int startColumn, const int startRow, const int endColumn, const int endRow) +std::vector CGUIGridLayout_Impl::GetCellsOutsideGrid(const int startColumn, const int startRow, const int endColumn, const int endRow) const { if (startColumn < 1 || startRow < 1 || endColumn > m_columns || endRow > m_rows) { @@ -278,6 +278,9 @@ const bool CGUIGridLayout_Impl::SetGrid(int columns, int rows) m_grid[i].resize(rows, 0); } + m_columnWidths.resize(m_columns, 0.0f); + m_rowHeights.resize(m_rows, 0.0f); + if (larger) { CreateGridCells(); @@ -288,14 +291,14 @@ const bool CGUIGridLayout_Impl::SetGrid(int columns, int rows) } else { - CleanupGridItems(); + CleanupGridCells(); // Since the grid has been resized, we need to make sure the active cell is still within the grid m_activeColumn = std::min(m_activeColumn, columns); m_activeRow = std::min(m_activeRow, rows); } - RepositionGridItems(); + RepositionGridCells(); return true; } @@ -327,7 +330,7 @@ const bool CGUIGridLayout_Impl::SetActiveRow(int row) return true; } -const std::pair CGUIGridLayout_Impl::GetActiveCell() +const std::pair CGUIGridLayout_Impl::GetActiveCell() const { return std::make_pair(m_activeColumn, m_activeRow); } @@ -340,6 +343,7 @@ void CGUIGridLayout_Impl::SetItemAlignment(const CGUIElement* item, eGridLayoutI return; cell->alignment = alignment; + RepositionGridCell(*cell, true); } const eGridLayoutItemAlignment CGUIGridLayout_Impl::GetItemAlignment(const CGUIElement* item) const @@ -375,10 +379,34 @@ const bool CGUIGridLayout_Impl::SetDefaultCellAlpha(const float alpha) return true; } +const bool CGUIGridLayout_Impl::SetColumnWidth(const int column, const float width) +{ + if (!InColumnRange(column)) + return false; + + m_columnWidths[column - 1] = width; + RepositionGridCells(); + return true; +} + +const bool CGUIGridLayout_Impl::SetRowHeight(const int row, const float height) +{ + if (!InRowRange(row)) + return false; + + m_rowHeights[row - 1] = height; + RepositionGridCells(); + return true; +} + void CGUIGridLayout_Impl::CreateGridCells() { auto* parent = reinterpret_cast(this); + // Calculate our own offsets here for performance reasons + std::vector columnOffset(m_columns, 0.0f); + std::vector rowOffset(m_rows, 0.0f); + for (int i = 0; i < m_columns; i++) { for (int j = 0; j < m_rows; j++) @@ -387,44 +415,50 @@ void CGUIGridLayout_Impl::CreateGridCells() if (cell == 0) { - auto* cell = new SGridCellItem(); - cell->container = m_pManager->CreateStaticImage(parent); - cell->container->SetFrameEnabled(false); + auto* cellItem = new SGridCellItem(); + cellItem->container = m_pManager->CreateStaticImage(parent); + cellItem->alignment = m_defaultAlignment; // Assign the cell container texture, alternative between m_cellTexture and m_cellTextureAlt - cell->container->LoadFromTexture((i + j) % 2 == 0 ? m_cellTexture : m_cellTextureAlt); - cell->container->SetAlpha(m_defaultCellAlpha); + cellItem->container->LoadFromTexture((i + j) % 2 == 0 ? m_cellTexture : m_cellTextureAlt); + cellItem->container->SetAlpha(m_defaultCellAlpha); + + auto offsets = AccumulateOffsets({columnOffset, rowOffset}); // Position the cell container in the grid - cell->container->SetPosition(CVector2D(i * (1.0f / m_columns), j * (1.0f / m_rows)), true); + cellItem->container->SetPosition(CalculateCellPosition(*cellItem, offsets), true); // Size the cell container in the grid - cell->container->SetSize(CVector2D(1.0f / m_columns, 1.0f / m_rows), true); + cellItem->container->SetSize(CalculateCellSize(*cellItem, offsets), true); - cell->id = m_nextId; + cellItem->id = m_nextId; - auto* label = m_pManager->CreateLabel(cell->container, std::to_string(m_nextId).c_str()); + auto* label = m_pManager->CreateLabel(cellItem->container, std::to_string(m_nextId).c_str()); label->AutoSize(); - label->SetPosition(CVector2D(0.0f, 0.0f), true); - label->SetSize(CVector2D(1.0f, 1.0f), true); - label->SetHorizontalAlign(CGUI_ALIGN_HORIZONTALCENTER); - label->SetVerticalAlign(CGUI_ALIGN_VERTICALCENTER); - cell->element = label; - cell->column = i + 1; - cell->row = j + 1; - cell->alignment = m_defaultAlignment; + CVector2D offset = GetAlignmentOffset(*cellItem, label->GetSize(true)); + label->SetPosition(offset, true); + label->ForceRedraw(); + + cellItem->element = label; + cellItem->column = i + 1; + cellItem->row = j + 1; - m_cells.emplace(m_nextId, cell); + m_cells.emplace(m_nextId, cellItem); m_grid[i][j] = m_nextId; m_nextId++; } + + rowOffset[j] = m_rowHeights[j]; } + + columnOffset[i] = m_columnWidths[i]; + rowOffset.assign(m_rows, 0.0f); } } -void CGUIGridLayout_Impl::CleanupGridItems() +void CGUIGridLayout_Impl::CleanupGridCells() { for (auto& cell : m_cells) { @@ -449,20 +483,129 @@ void CGUIGridLayout_Impl::CleanupGridItems() } } -void CGUIGridLayout_Impl::RepositionGridItems() +const CVector2D CGUIGridLayout_Impl::CalculateCellPosition(const SGridCellItem& cell, const std::pair& offsets) const { - for (auto& cell : m_cells) + int widths = CountColumnWidths(); + int heights = CountRowHeights(); + + if (cell.element != nullptr) + cell.element->SetText(std::to_string(std::max(0, cell.column - widths)).c_str()); + + return CVector2D(offsets.first + (std::max(0, cell.column - CountColumnWidths(cell.column) - 1) * + ((1.0f - AccumulateOffset(m_columnWidths)) / (m_columns - widths))), + offsets.second + (std::max(0, cell.row - CountRowHeights(cell.row) - 1) * + ((1.0f - AccumulateOffset(m_rowHeights)) / (m_rows - heights)))); +} + +const CVector2D CGUIGridLayout_Impl::CalculateCellSize(const SGridCellItem& cell, const std::pair& offsets) const +{ + int widths = CountColumnWidths(); + int heights = CountRowHeights(); + + return CVector2D( + m_columnWidths[cell.column - 1] == 0.0f ? (1.0f - AccumulateOffset(m_columnWidths)) / (m_columns - widths) : m_columnWidths[cell.column - 1], + m_rowHeights[cell.row - 1] == 0.0f ? (1.0f - AccumulateOffset(m_rowHeights)) / (m_rows - heights) : m_rowHeights[cell.row - 1]); +} + +const std::pair, std::vector> CGUIGridLayout_Impl::CalculateGridOffsets(const int column, const int row) const +{ + std::vector columnOffset(m_columns, 0.0f); + std::vector rowOffset(m_rows, 0.0f); + + for (int i = 0; i < (column == 0 ? m_columns : column) - 1; i++) { - cell.second->container->SetPosition(CVector2D((cell.second->column - 1) * (1.0f / m_columns), (cell.second->row - 1) * (1.0f / m_rows)), true); - cell.second->container->SetSize(CVector2D(1.0f / m_columns, 1.0f / m_rows), true); + for (int j = 0; j < (row == 0 ? m_rows : row) - 1; j++) + { + if (j < (row == 0 ? m_rows : row)) + { + rowOffset[j + 1] = m_rowHeights[j]; + } + } + + columnOffset[i + 1] = m_columnWidths[i]; + rowOffset.assign(m_rows, 0.0f); + } + + return {columnOffset, rowOffset}; +} + +const float CGUIGridLayout_Impl::AccumulateOffset(const std::vector& offset) const +{ + return std::reduce(offset.begin(), offset.end()); +} + +const std::pair CGUIGridLayout_Impl::AccumulateOffsets(const std::pair, std::vector>& offsets) const +{ + return {AccumulateOffset(offsets.first), AccumulateOffset(offsets.second)}; +} + +void CGUIGridLayout_Impl::RepositionGridCells(const bool itemOnly) const +{ + std::vector columnOffset(m_columns, 0.0f); + std::vector rowOffset(m_rows, 0.0f); - if (cell.second->element != nullptr) + for (int i = 0; i < m_columns; i++) + { + for (int j = 0; j < m_rows; j++) { - cell.second->element->SetPosition(CVector2D(0.0f, 0.0f), true); - cell.second->element->SetSize(CVector2D(1.0f, 1.0f), true); - cell.second->element->ForceRedraw(); + const auto cell = m_cells.count(m_grid[i][j]); + + if (cell != 0) + { + const auto& cellItem = m_cells.at(m_grid[i][j]); + + if (!itemOnly) + { + auto offsets = AccumulateOffsets({columnOffset, rowOffset}); + + cellItem->container->SetPosition(CalculateCellPosition(*cellItem, offsets), true); + cellItem->container->SetSize(CalculateCellSize(*cellItem, offsets), true); + cellItem->container->ForceRedraw(); + } + + if (cellItem->element != nullptr) + { + CVector2D position = GetAlignmentOffset(*cellItem, cellItem->element->GetSize(true)); + cellItem->element->SetPosition(position, true); + cellItem->element->ForceRedraw(); + } + } + + rowOffset[j] = m_rowHeights[j]; } + + columnOffset[i] = m_columnWidths[i]; + rowOffset.assign(m_rows, 0.0f); + } +} + +void CGUIGridLayout_Impl::RepositionGridCell(const SGridCellItem& cell, const bool itemOnly) const +{ + if (!itemOnly) + { + auto offsets = AccumulateOffsets(CalculateGridOffsets(cell.column, cell.row)); + + cell.container->SetPosition(CalculateCellPosition(cell, offsets), true); + cell.container->SetSize(CalculateCellSize(cell, offsets), true); + cell.container->ForceRedraw(); } + + if (cell.element != nullptr) + { + CVector2D position = GetAlignmentOffset(cell, cell.element->GetSize(true)); + cell.element->SetPosition(position, true); + cell.element->ForceRedraw(); + } +} + +void CGUIGridLayout_Impl::RepositionGridCell(const int column, const int row, const bool itemOnly) const +{ + const auto* cell = GetCell(column, row); + + if (cell == nullptr) + return; + + RepositionGridCell(*cell); } const bool CGUIGridLayout_Impl::InGridRange(const int column, const int row) const @@ -479,3 +622,53 @@ const bool CGUIGridLayout_Impl::InRowRange(const int row) const { return row >= 1 && row <= m_rows; } + +const CVector2D CGUIGridLayout_Impl::GetAlignmentOffset(const SGridCellItem& cell, const CVector2D& size) const +{ + CVector2D offset; + + switch (cell.alignment) + { + case eGridLayoutItemAlignment::TOP_LEFT: + offset = CVector2D(0.0f, 0.0f); + break; + case eGridLayoutItemAlignment::TOP_CENTER: + offset = CVector2D((1.0f - size.fX) / 2.0f, 0.0f); + break; + case eGridLayoutItemAlignment::TOP_RIGHT: + offset = CVector2D(1.0f - size.fX, 0.0f); + break; + case eGridLayoutItemAlignment::MIDDLE_LEFT: + offset = CVector2D(0.0f, (1.0f - size.fY) / 2.0f); + break; + case eGridLayoutItemAlignment::MIDDLE_CENTER: + offset = CVector2D((1.0f - size.fX) / 2.0f, (1.0f - size.fY) / 2.0f); + break; + case eGridLayoutItemAlignment::MIDDLE_RIGHT: + offset = CVector2D(1.0f - size.fX, (1.0f - size.fY) / 2.0f); + break; + case eGridLayoutItemAlignment::BOTTOM_LEFT: + offset = CVector2D(0.0f, 1.0f - size.fY); + break; + case eGridLayoutItemAlignment::BOTTOM_CENTER: + offset = CVector2D((1.0f - size.fX) / 2.0f, 1.0f - size.fY); + break; + case eGridLayoutItemAlignment::BOTTOM_RIGHT: + offset = CVector2D(1.0f - size.fX, 1.0f - size.fY); + break; + } + + return offset; +} + +const int CGUIGridLayout_Impl::CountColumnWidths(const int maxColumns) const +{ + return std::count_if(m_columnWidths.begin(), m_columnWidths.begin() + (maxColumns == 0 || maxColumns < 1 ? m_columns : maxColumns) - 1, + [&](const float& width) { return width > 0.0f; }); +} + +const int CGUIGridLayout_Impl::CountRowHeights(const int maxRows) const +{ + return std::count_if(m_rowHeights.begin(), m_rowHeights.begin() + (maxRows == 0 || maxRows < 1 ? m_rows : maxRows) - 1, + [&](const float& height) { return height > 0.0f; }); +} diff --git a/Client/gui/CGUIGridLayout_Impl.h b/Client/gui/CGUIGridLayout_Impl.h index 1d62918e9c1..d2a699d480a 100644 --- a/Client/gui/CGUIGridLayout_Impl.h +++ b/Client/gui/CGUIGridLayout_Impl.h @@ -33,7 +33,7 @@ class CGUIGridLayout_Impl : public CGUIGridLayout, public CGUIElement_Impl const int GetActiveColumn() const { return m_activeColumn; } const int GetActiveRow() const { return m_activeRow; } - const std::pair GetActiveCell(); + const std::pair GetActiveCell() const; const bool AddItem(CGUIElement* item, int column, int row, const bool moveToNextCell = true); const bool AddItem(CGUIElement* item, const bool moveToNextCell = true); @@ -44,10 +44,10 @@ class CGUIGridLayout_Impl : public CGUIGridLayout, public CGUIElement_Impl SGridCellItem* GetCell(const int column, const int row) const; SGridCellItem* GetCell(const CGUIElement* item) const; - std::vector GetCellsInGrid(const int startColumn, const int startRow, const int endColumn, const int endRow); - std::vector GetCellsOutsideGrid(const int startColumn, const int startRow, const int endColumn, const int endRow); - std::vector GetCellsInColumn(const int column); - std::vector GetCellsInRow(const int row); + std::vector GetCellsInGrid(const int startColumn, const int startRow, const int endColumn, const int endRow) const; + std::vector GetCellsOutsideGrid(const int startColumn, const int startRow, const int endColumn, const int endRow) const; + std::vector GetCellsInColumn(const int column) const; + std::vector GetCellsInRow(const int row) const; void SetItemAlignment(const CGUIElement* item, eGridLayoutItemAlignment alignment); void SetDefaultItemAlignment(eGridLayoutItemAlignment alignment) { m_defaultAlignment = alignment; } @@ -58,6 +58,9 @@ class CGUIGridLayout_Impl : public CGUIGridLayout, public CGUIElement_Impl const bool SetCellAlpha(const int column, const int row, const float alpha); const bool SetDefaultCellAlpha(const float alpha); + const bool SetColumnWidth(const int column, const float width); + const bool SetRowHeight(const int row, const float height); + #include "CGUIElement_Inc.h" private: @@ -75,16 +78,34 @@ class CGUIGridLayout_Impl : public CGUIGridLayout, public CGUIElement_Impl std::unordered_map m_cells; std::unordered_map m_items; + std::vector m_columnWidths; + std::vector m_rowHeights; + CGUITexture* m_cellTexture = nullptr; CGUITexture* m_cellTextureAlt = nullptr; eGridLayoutItemAlignment m_defaultAlignment = eGridLayoutItemAlignment::MIDDLE_CENTER; void CreateGridCells(); - void CleanupGridItems(); - void RepositionGridItems(); + void CleanupGridCells(); + + void RepositionGridCells(const bool itemOnly = false) const; + void RepositionGridCell(const SGridCellItem& cell, const bool itemOnly = false) const; + void RepositionGridCell(const int column, const int row, const bool itemOnly = false) const; const bool InGridRange(const int column, const int row) const; const bool InColumnRange(const int column) const; const bool InRowRange(const int row) const; + + const CVector2D CalculateCellPosition(const SGridCellItem& cell, const std::pair& offsets) const; + const CVector2D CalculateCellSize(const SGridCellItem& cell, const std::pair& offset) const; + + const std::pair, std::vector> CalculateGridOffsets(const int column = 0, const int row = 0) const; + const std::pair AccumulateOffsets(const std::pair, std::vector>& offsets) const; + const float AccumulateOffset(const std::vector& offset) const; + + const int CountColumnWidths(const int maxColumns = 0) const; + const int CountRowHeights(const int maxRows = 0) const; + + const CVector2D GetAlignmentOffset(const SGridCellItem& cell, const CVector2D& size) const; }; diff --git a/Client/sdk/gui/CGUIGridLayout.h b/Client/sdk/gui/CGUIGridLayout.h index 05833b7ac00..af0f5d1cd66 100644 --- a/Client/sdk/gui/CGUIGridLayout.h +++ b/Client/sdk/gui/CGUIGridLayout.h @@ -59,7 +59,7 @@ class CGUIGridLayout : public CGUIElement virtual const int GetActiveColumn() const = 0; virtual const int GetActiveRow() const = 0; - virtual const std::pair GetActiveCell() = 0; + virtual const std::pair GetActiveCell() const = 0; virtual const bool AddItem(CGUIElement* item, int column, int row, const bool moveToNextCell = true) = 0; virtual const bool AddItem(CGUIElement* item, const bool moveToNextCell = true) = 0; @@ -70,10 +70,10 @@ class CGUIGridLayout : public CGUIElement virtual SGridCellItem* GetCell(const int column, const int row) const = 0; virtual SGridCellItem* GetCell(const CGUIElement* item) const = 0; - virtual std::vector GetCellsInGrid(const int startColumn, const int startRow, const int endColumn, const int endRow) = 0; - virtual std::vector GetCellsOutsideGrid(const int startColumn, const int startRow, const int endColumn, const int endRow) = 0; - virtual std::vector GetCellsInColumn(const int column) = 0; - virtual std::vector GetCellsInRow(const int row) = 0; + virtual std::vector GetCellsInGrid(const int startColumn, const int startRow, const int endColumn, const int endRow) const = 0; + virtual std::vector GetCellsOutsideGrid(const int startColumn, const int startRow, const int endColumn, const int endRow) const = 0; + virtual std::vector GetCellsInColumn(const int column) const = 0; + virtual std::vector GetCellsInRow(const int row) const = 0; virtual void SetItemAlignment(const CGUIElement* item, eGridLayoutItemAlignment alignment) = 0; virtual void SetDefaultItemAlignment(eGridLayoutItemAlignment alignment) = 0; @@ -83,4 +83,7 @@ class CGUIGridLayout : public CGUIElement virtual const bool SetCellAlpha(const int column, const int row, const float alpha) = 0; virtual const bool SetDefaultCellAlpha(const float alpha) = 0; + + virtual const bool SetColumnWidth(const int column, const float width) = 0; + virtual const bool SetRowHeight(const int row, const float height) = 0; }; From dc34672cff71494322e93995de84d089dd79b6ce Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Wed, 22 Jan 2025 19:25:28 +0000 Subject: [PATCH 15/20] Add cell color & padding, move CColor to Shared/sdk --- Client/core/CChat.h | 34 +---- Client/gui/CGUIGridLayout_Impl.cpp | 229 ++++++++++++++++++++++++----- Client/gui/CGUIGridLayout_Impl.h | 21 +++ Client/sdk/gui/CGUIGridLayout.h | 23 +++ Shared/sdk/CColor.h | 42 ++++++ 5 files changed, 278 insertions(+), 71 deletions(-) create mode 100644 Shared/sdk/CColor.h diff --git a/Client/core/CChat.h b/Client/core/CChat.h index e9671270750..9269bcaf9b6 100644 --- a/Client/core/CChat.h +++ b/Client/core/CChat.h @@ -13,6 +13,7 @@ #include "CGUI.h" #include +#include <../Shared/sdk/CColor.h> class CChatLineSection; @@ -22,39 +23,6 @@ class CChatLineSection; #define CHAT_BUFFER 1024 // Chatbox buffer size #define CHAT_INPUT_HISTORY_LENGTH 128 // Chatbox input history length -class CColor -{ -public: - CColor() { R = G = B = A = 255; } - CColor(unsigned char _R, unsigned char _G, unsigned char _B, unsigned char _A = 255) - { - R = _R; - G = _G; - B = _B; - A = _A; - } - CColor(const CColor& other) { *this = other; } - CColor(unsigned long ulColor) { *this = ulColor; } - CColor& operator=(const CColor& color) - { - R = color.R; - G = color.G; - B = color.B; - A = color.A; - return *this; - } - CColor& operator=(unsigned long ulColor) - { - R = (ulColor >> 16) & 0xFF; - G = (ulColor >> 8) & 0xFF; - B = (ulColor)&0xFF; - return *this; - } - bool operator==(const CColor& other) const { return R == other.R && G == other.G && B == other.B && A == other.A; } - - unsigned char R, G, B, A; -}; - class CChatLineSection { public: diff --git a/Client/gui/CGUIGridLayout_Impl.cpp b/Client/gui/CGUIGridLayout_Impl.cpp index dbc4d3de3d9..3e75a3354e0 100644 --- a/Client/gui/CGUIGridLayout_Impl.cpp +++ b/Client/gui/CGUIGridLayout_Impl.cpp @@ -399,6 +399,134 @@ const bool CGUIGridLayout_Impl::SetRowHeight(const int row, const float height) return true; } +const bool CGUIGridLayout_Impl::SetCellFullSize(const int column, const int row, const bool fullSize) +{ + auto* cell = GetCell(column, row); + + if (cell == nullptr) + return false; + + cell->forceFullSize = fullSize; + RepositionGridCell(*cell, true); + return true; +} + +const bool CGUIGridLayout_Impl::SetDefaultCellFullSize(const bool fullSize, const bool updateExisting) +{ + m_defaultFullSize = fullSize; + + if (updateExisting) + { + for (auto& cell : m_cells) + { + cell.second->forceFullSize = fullSize; + } + + RepositionGridCells(true); + } + + return true; +} + +const bool CGUIGridLayout_Impl::GetCellFullSize(const int column, const int row) const +{ + auto* cell = GetCell(column, row); + + if (cell == nullptr) + return false; + + return cell->forceFullSize; +} + +const bool CGUIGridLayout_Impl::SetCellPadding(const int column, const int row, const CVector2D& padding) +{ + auto* cell = GetCell(column, row); + + if (cell == nullptr) + return false; + + cell->padding = padding; + RepositionGridCell(*cell, true); + return true; +} + +const bool CGUIGridLayout_Impl::SetDefaultCellPadding(const CVector2D& padding, const bool updateExisting) +{ + m_defaultPadding = padding; + + for (auto& cell : m_cells) + { + cell.second->padding = padding; + } + + RepositionGridCells(true); + return true; +} + +const CVector2D& CGUIGridLayout_Impl::GetCellPadding(const int column, const int row) const +{ + auto* cell = GetCell(column, row); + + if (cell == nullptr) + return m_defaultPadding; + + return cell->padding; +} + +const bool CGUIGridLayout_Impl::SetCellTexture(const int column, const int row, CGUITexture* texture, const bool alt) +{ + auto* cell = GetCell(column, row); + + if (cell == nullptr) + return false; + + cell->container->LoadFromTexture(texture); + return true; +} + +const bool CGUIGridLayout_Impl::SetCellColor(const int column, const int row, const CColor& color, const bool alt) +{ + auto* cell = GetCell(column, row); + + if (cell == nullptr) + return false; + + auto* texture = m_pManager->CreateTexture(); + unsigned long argb = COLOR_ARGB(color.A, color.R, color.G, color.B); + + texture->LoadFromMemory(&argb, 1, 1); + cell->container->LoadFromTexture(texture); + + return true; +} + +const bool CGUIGridLayout_Impl::SetDefaultCellTexture(CGUITexture* texture, const bool alt, const bool updateExisting) +{ + if (alt) + m_cellTextureAlt = texture; + else + m_cellTexture = texture; + + if (updateExisting) + { + for (auto& cell : m_cells) + { + cell.second->container->LoadFromTexture(texture); + } + } + + return true; +} + +const bool CGUIGridLayout_Impl::SetDefaultCellColor(const CColor& color, const bool alt, const bool updateExisting) +{ + auto* texture = m_pManager->CreateTexture(); + unsigned long argb = COLOR_ARGB(color.A, color.R, color.G, color.B); + + texture->LoadFromMemory(&argb, 1, 1); + return SetDefaultCellTexture(texture, alt, updateExisting); +} + void CGUIGridLayout_Impl::CreateGridCells() { auto* parent = reinterpret_cast(this); @@ -418,6 +546,8 @@ void CGUIGridLayout_Impl::CreateGridCells() auto* cellItem = new SGridCellItem(); cellItem->container = m_pManager->CreateStaticImage(parent); cellItem->alignment = m_defaultAlignment; + cellItem->forceFullSize = m_defaultFullSize; + cellItem->padding = m_defaultPadding; // Assign the cell container texture, alternative between m_cellTexture and m_cellTextureAlt cellItem->container->LoadFromTexture((i + j) % 2 == 0 ? m_cellTexture : m_cellTextureAlt); @@ -431,19 +561,28 @@ void CGUIGridLayout_Impl::CreateGridCells() // Size the cell container in the grid cellItem->container->SetSize(CalculateCellSize(*cellItem, offsets), true); + // Create a test label for the cell + auto* label = m_pManager->CreateLabel(cellItem->container, std::to_string(m_nextId).c_str()); + cellItem->id = m_nextId; + cellItem->element = label; + cellItem->column = i + 1; + cellItem->row = j + 1; - auto* label = m_pManager->CreateLabel(cellItem->container, std::to_string(m_nextId).c_str()); - label->AutoSize(); + label->SetFrameEnabled(true); + label->SetHorizontalAlign(CGUI_ALIGN_HORIZONTALCENTER); + label->SetVerticalAlign(CGUI_ALIGN_VERTICALCENTER); + + // For testing purposes. Use CGUIGridLayout::SetCellFullSize in proper implementation + if (cellItem->forceFullSize) + label->SetSize(CVector2D(1.0f - (cellItem->padding.fX * 2.0f), 1.0f - (cellItem->padding.fY * 2.0f)), true); + else + label->SetSize(CVector2D(0.2f, 0.2f), true); CVector2D offset = GetAlignmentOffset(*cellItem, label->GetSize(true)); label->SetPosition(offset, true); label->ForceRedraw(); - cellItem->element = label; - cellItem->column = i + 1; - cellItem->row = j + 1; - m_cells.emplace(m_nextId, cellItem); m_grid[i][j] = m_nextId; @@ -491,10 +630,9 @@ const CVector2D CGUIGridLayout_Impl::CalculateCellPosition(const SGridCellItem& if (cell.element != nullptr) cell.element->SetText(std::to_string(std::max(0, cell.column - widths)).c_str()); - return CVector2D(offsets.first + (std::max(0, cell.column - CountColumnWidths(cell.column) - 1) * - ((1.0f - AccumulateOffset(m_columnWidths)) / (m_columns - widths))), - offsets.second + (std::max(0, cell.row - CountRowHeights(cell.row) - 1) * - ((1.0f - AccumulateOffset(m_rowHeights)) / (m_rows - heights)))); + return CVector2D( + offsets.first + (std::max(0, cell.column - CountColumnWidths(cell.column) - 1) * ((1.0f - AccumulateOffset(m_columnWidths)) / (m_columns - widths))), + offsets.second + (std::max(0, cell.row - CountRowHeights(cell.row) - 1) * ((1.0f - AccumulateOffset(m_rowHeights)) / (m_rows - heights)))); } const CVector2D CGUIGridLayout_Impl::CalculateCellSize(const SGridCellItem& cell, const std::pair& offsets) const @@ -565,6 +703,11 @@ void CGUIGridLayout_Impl::RepositionGridCells(const bool itemOnly) const if (cellItem->element != nullptr) { + if (cellItem->forceFullSize) + { + cellItem->element->SetSize(CVector2D(1.0f - (cellItem->padding.fX * 2.0f), 1.0f - (cellItem->padding.fY * 2.0f)), true); + } + CVector2D position = GetAlignmentOffset(*cellItem, cellItem->element->GetSize(true)); cellItem->element->SetPosition(position, true); cellItem->element->ForceRedraw(); @@ -592,6 +735,11 @@ void CGUIGridLayout_Impl::RepositionGridCell(const SGridCellItem& cell, const bo if (cell.element != nullptr) { + if (cell.forceFullSize) + { + cell.element->SetSize(CVector2D(1.0f - (cell.padding.fX * 2.0f), 1.0f - (cell.padding.fY * 2.0f)), true); + } + CVector2D position = GetAlignmentOffset(cell, cell.element->GetSize(true)); cell.element->SetPosition(position, true); cell.element->ForceRedraw(); @@ -627,37 +775,42 @@ const CVector2D CGUIGridLayout_Impl::GetAlignmentOffset(const SGridCellItem& cel { CVector2D offset; - switch (cell.alignment) + if (!cell.forceFullSize) { - case eGridLayoutItemAlignment::TOP_LEFT: - offset = CVector2D(0.0f, 0.0f); - break; - case eGridLayoutItemAlignment::TOP_CENTER: - offset = CVector2D((1.0f - size.fX) / 2.0f, 0.0f); - break; - case eGridLayoutItemAlignment::TOP_RIGHT: - offset = CVector2D(1.0f - size.fX, 0.0f); - break; - case eGridLayoutItemAlignment::MIDDLE_LEFT: - offset = CVector2D(0.0f, (1.0f - size.fY) / 2.0f); - break; - case eGridLayoutItemAlignment::MIDDLE_CENTER: - offset = CVector2D((1.0f - size.fX) / 2.0f, (1.0f - size.fY) / 2.0f); - break; - case eGridLayoutItemAlignment::MIDDLE_RIGHT: - offset = CVector2D(1.0f - size.fX, (1.0f - size.fY) / 2.0f); - break; - case eGridLayoutItemAlignment::BOTTOM_LEFT: - offset = CVector2D(0.0f, 1.0f - size.fY); - break; - case eGridLayoutItemAlignment::BOTTOM_CENTER: - offset = CVector2D((1.0f - size.fX) / 2.0f, 1.0f - size.fY); - break; - case eGridLayoutItemAlignment::BOTTOM_RIGHT: - offset = CVector2D(1.0f - size.fX, 1.0f - size.fY); - break; + switch (cell.alignment) + { + case eGridLayoutItemAlignment::TOP_LEFT: + offset += CVector2D(0.0f, 0.0f); + break; + case eGridLayoutItemAlignment::TOP_CENTER: + offset += CVector2D(0.5f - (size.fX / 2.0f), 0.0f); + break; + case eGridLayoutItemAlignment::TOP_RIGHT: + offset += CVector2D(1.0f - size.fX, 0.0f); + break; + case eGridLayoutItemAlignment::MIDDLE_LEFT: + offset += CVector2D(0.0f, 0.5f - (size.fY / 2.0f)); + break; + case eGridLayoutItemAlignment::MIDDLE_CENTER: + offset += CVector2D(0.5f - (size.fX / 2.0f), 0.5f - (size.fY / 2.0f)); + break; + case eGridLayoutItemAlignment::MIDDLE_RIGHT: + offset += CVector2D(1.0f - size.fX, 0.5f - (size.fY / 2.0f)); + break; + case eGridLayoutItemAlignment::BOTTOM_LEFT: + offset += CVector2D(0.0f, 1.0f - size.fY); + break; + case eGridLayoutItemAlignment::BOTTOM_CENTER: + offset += CVector2D(0.5f - (size.fX / 2.0f), 1.0f - size.fY); + break; + case eGridLayoutItemAlignment::BOTTOM_RIGHT: + offset += CVector2D(1.0f - size.fX, 1.0f - size.fY); + break; + } } + offset = CVector2D(std::min(offset.fX, 1.0f - size.fX - cell.padding.fX), std::min(offset.fY, 1.0f - size.fY - cell.padding.fY)); + offset = CVector2D(std::max(offset.fX, cell.padding.fX), std::max(offset.fY, cell.padding.fY)); return offset; } diff --git a/Client/gui/CGUIGridLayout_Impl.h b/Client/gui/CGUIGridLayout_Impl.h index d2a699d480a..2f63fe13ffb 100644 --- a/Client/gui/CGUIGridLayout_Impl.h +++ b/Client/gui/CGUIGridLayout_Impl.h @@ -61,6 +61,24 @@ class CGUIGridLayout_Impl : public CGUIGridLayout, public CGUIElement_Impl const bool SetColumnWidth(const int column, const float width); const bool SetRowHeight(const int row, const float height); + const bool SetCellFullSize(const int column, const int row, const bool fullSize); + const bool SetDefaultCellFullSize(const bool fullSize, const bool updateExisting = false); + + const bool GetCellFullSize(const int column, const int row) const; + const bool GetDefaultCellFullSize() const { return m_defaultFullSize; } + + const bool SetCellPadding(const int column, const int row, const CVector2D& padding); + const bool SetDefaultCellPadding(const CVector2D& padding, const bool updateExisting); + + const CVector2D& GetCellPadding(const int column, const int row) const; + const CVector2D& GetDefaultCellPadding() const { return m_defaultPadding; } + + const bool SetCellTexture(const int column, const int row, CGUITexture* texture, const bool alt = false); + const bool SetCellColor(const int column, const int row, const CColor& color, const bool alt = false); + + const bool SetDefaultCellTexture(CGUITexture* texture, const bool alt = false, const bool updateExisting = false); + const bool SetDefaultCellColor(const CColor& color, const bool alt = false, const bool updateExisting = false); + #include "CGUIElement_Inc.h" private: @@ -86,6 +104,9 @@ class CGUIGridLayout_Impl : public CGUIGridLayout, public CGUIElement_Impl eGridLayoutItemAlignment m_defaultAlignment = eGridLayoutItemAlignment::MIDDLE_CENTER; + bool m_defaultFullSize = false; + CVector2D m_defaultPadding = CVector2D(0.1f, 0.1f); + void CreateGridCells(); void CleanupGridCells(); diff --git a/Client/sdk/gui/CGUIGridLayout.h b/Client/sdk/gui/CGUIGridLayout.h index af0f5d1cd66..796e321e748 100644 --- a/Client/sdk/gui/CGUIGridLayout.h +++ b/Client/sdk/gui/CGUIGridLayout.h @@ -13,10 +13,13 @@ #include "CGUIElement.h" #include "CGUIGridLayout.h" +#include <../Shared/sdk/CVector2D.h> +#include <../Shared/sdk/CColor.h> #include class CGUIStaticImage; +class CGUITexture; enum class eGridLayoutItemAlignment { @@ -39,6 +42,8 @@ struct SGridCellItem eGridLayoutItemAlignment alignment; int column; int row; + bool forceFullSize; + CVector2D padding; }; class CGUIGridLayout : public CGUIElement @@ -86,4 +91,22 @@ class CGUIGridLayout : public CGUIElement virtual const bool SetColumnWidth(const int column, const float width) = 0; virtual const bool SetRowHeight(const int row, const float height) = 0; + + virtual const bool SetCellFullSize(const int column, const int row, const bool fullSize) = 0; + virtual const bool SetDefaultCellFullSize(const bool fullSize, const bool updateExisting = false) = 0; + + virtual const bool GetCellFullSize(const int column, const int row) const = 0; + virtual const bool GetDefaultCellFullSize() const = 0; + + virtual const bool SetCellPadding(const int column, const int row, const CVector2D& padding) = 0; + virtual const bool SetDefaultCellPadding(const CVector2D& padding, const bool updateExisting) = 0; + + virtual const CVector2D& GetCellPadding(const int column, const int row) const = 0; + virtual const CVector2D& GetDefaultCellPadding() const = 0; + + virtual const bool SetCellTexture(const int column, const int row, CGUITexture* texture, const bool alt = false) = 0; + virtual const bool SetCellColor(const int column, const int row, const CColor& color, const bool alt = false) = 0; + + virtual const bool SetDefaultCellTexture(CGUITexture* texture, const bool alt = false, const bool updateExisting = false) = 0; + virtual const bool SetDefaultCellColor(const CColor& color, const bool alt = false, const bool updateExisting = false) = 0; }; diff --git a/Shared/sdk/CColor.h b/Shared/sdk/CColor.h new file mode 100644 index 00000000000..92f506926d3 --- /dev/null +++ b/Shared/sdk/CColor.h @@ -0,0 +1,42 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto: San Andreas + * LICENSE: See LICENSE in the top level directory + * + * Multi Theft Auto is available from http://www.multitheftauto.com/ + * + *****************************************************************************/ +#pragma once + +class CColor +{ +public: + CColor() { R = G = B = A = 255; } + CColor(unsigned char _R, unsigned char _G, unsigned char _B, unsigned char _A = 255) + { + R = _R; + G = _G; + B = _B; + A = _A; + } + CColor(const CColor& other) { *this = other; } + CColor(unsigned long ulColor) { *this = ulColor; } + CColor& operator=(const CColor& color) + { + R = color.R; + G = color.G; + B = color.B; + A = color.A; + return *this; + } + CColor& operator=(unsigned long ulColor) + { + R = (ulColor >> 16) & 0xFF; + G = (ulColor >> 8) & 0xFF; + B = (ulColor)&0xFF; + return *this; + } + bool operator==(const CColor& other) const { return R == other.R && G == other.G && B == other.B && A == other.A; } + + unsigned char R, G, B, A; +}; From 2ae5ecd1ea3ceb7d5fb0b204cf217d1c6f0ea5d6 Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Wed, 22 Jan 2025 19:40:38 +0000 Subject: [PATCH 16/20] Revert CGUIGridLayout (move to cgui-gridlayout branch) This reverts commit dc34672cff71494322e93995de84d089dd79b6ce. Revert "Implement CGUIGridLayout custom element" This reverts commit 4f47d471c84f916d659650be66fcefa545ee2997. Revert "Add map for item lookup" This reverts commit 5c47f44193f49f840b03f69256cb2668b1bd7975. Revert "Fix issues with CEGUI testing tab" This reverts commit 42cdfd4fa3aa881d0ec0616a7d514f1556aa02da. Revert "Minor optimization in item lookup map usage" This reverts commit c80bb0c21c06deed5f0ad326b49e3fb0843ed1ed. Revert "Fix cell item assignment in CGUIGridLayout" This reverts commit 068a4975661ec5872318218651a5f3b1d06f7f77. Revert "Add flex column & row support to CGUIGridLayout" This reverts commit 548e53025d20122eb66987201ded0dca190a18f6. --- Client/core/CChat.h | 34 +- Client/core/CSettings.cpp | 119 +---- Client/core/CSettings.h | 46 +- Client/gui/CGUIGridLayout_Impl.cpp | 827 ----------------------------- Client/gui/CGUIGridLayout_Impl.h | 132 ----- Client/gui/CGUI_Impl.cpp | 11 - Client/gui/CGUI_Impl.h | 3 - Client/gui/StdInc.h | 1 - Client/sdk/gui/CGUI.h | 3 - Client/sdk/gui/CGUIElement.h | 3 +- Client/sdk/gui/CGUIGridLayout.h | 112 ---- Shared/sdk/CColor.h | 42 -- 12 files changed, 50 insertions(+), 1283 deletions(-) delete mode 100644 Client/gui/CGUIGridLayout_Impl.cpp delete mode 100644 Client/gui/CGUIGridLayout_Impl.h delete mode 100644 Client/sdk/gui/CGUIGridLayout.h delete mode 100644 Shared/sdk/CColor.h diff --git a/Client/core/CChat.h b/Client/core/CChat.h index 9269bcaf9b6..e9671270750 100644 --- a/Client/core/CChat.h +++ b/Client/core/CChat.h @@ -13,7 +13,6 @@ #include "CGUI.h" #include -#include <../Shared/sdk/CColor.h> class CChatLineSection; @@ -23,6 +22,39 @@ class CChatLineSection; #define CHAT_BUFFER 1024 // Chatbox buffer size #define CHAT_INPUT_HISTORY_LENGTH 128 // Chatbox input history length +class CColor +{ +public: + CColor() { R = G = B = A = 255; } + CColor(unsigned char _R, unsigned char _G, unsigned char _B, unsigned char _A = 255) + { + R = _R; + G = _G; + B = _B; + A = _A; + } + CColor(const CColor& other) { *this = other; } + CColor(unsigned long ulColor) { *this = ulColor; } + CColor& operator=(const CColor& color) + { + R = color.R; + G = color.G; + B = color.B; + A = color.A; + return *this; + } + CColor& operator=(unsigned long ulColor) + { + R = (ulColor >> 16) & 0xFF; + G = (ulColor >> 8) & 0xFF; + B = (ulColor)&0xFF; + return *this; + } + bool operator==(const CColor& other) const { return R == other.R && G == other.G && B == other.B && A == other.A; } + + unsigned char R, G, B, A; +}; + class CChatLineSection { public: diff --git a/Client/core/CSettings.cpp b/Client/core/CSettings.cpp index a0568da9982..566a374247b 100644 --- a/Client/core/CSettings.cpp +++ b/Client/core/CSettings.cpp @@ -14,8 +14,6 @@ #include #include -#include - using namespace std; #define CORE_MTA_FILLER "cgui\\images\\mta_filler.png" @@ -58,7 +56,7 @@ void CSettings::CreateGUI() if (m_pWindow) DestroyGUI(); - CGUITab *pTabMultiplayer, *pTabVideo, *pTabAudio, *pTabBinds, *pTabControls, *pTabAdvanced, *pTabCEGUI; + CGUITab *pTabMultiplayer, *pTabVideo, *pTabAudio, *pTabBinds, *pTabControls, *pTabAdvanced; CGUI* pManager = g_pCore->GetGUI(); // Init @@ -123,7 +121,6 @@ void CSettings::CreateGUI() m_pTabInterface = m_pTabs->CreateTab(_("Interface")); m_pTabBrowser = m_pTabs->CreateTab(_("Web Browser")); pTabAdvanced = m_pTabs->CreateTab(_("Advanced")); - pTabCEGUI = m_pTabs->CreateTab(_("CEGUI")); // Create buttons // OK button @@ -1268,93 +1265,6 @@ void CSettings::CreateGUI() m_pAdvancedSettingDescriptionLabel->SetSize(CVector2D(500.0f, 95.0f)); m_pAdvancedSettingDescriptionLabel->SetHorizontalAlign(CGUI_ALIGN_HORIZONTALCENTER_WORDWRAP); - /** - * CEGUI tab. - **/ - m_pGridLayout = reinterpret_cast(pManager->CreateGridLayout(pTabCEGUI)); - m_pGridLayout->SetGrid(9, 9); - m_pGridLayout->SetColumnWidth(1, 0.25f); - m_pGridLayout->SetColumnWidth(3, 0.25f); - m_pGridLayout->SetRowHeight(2, 0.25f); - m_pGridLayout->SetRowHeight(4, 0.25f); - vecTemp = CVector2D(12.f, 12.f); - - // Grid layout section label - m_pGridLayoutLabel = reinterpret_cast(pManager->CreateLabel(pTabCEGUI, _("Grid layout"))); - m_pGridLayoutLabel->SetPosition(CVector2D(vecTemp.fX, vecTemp.fY)); - m_pGridLayoutLabel->SetFont("default-bold-small"); - m_pGridLayoutLabel->AutoSize(); - m_pGridLayoutLabel->SetHorizontalAlign(CGUI_ALIGN_HORIZONTALCENTER_WORDWRAP); - vecTemp.fY += 15.0f; - - m_pCellAlphaLabel = reinterpret_cast(pManager->CreateLabel(pTabCEGUI, _("Cell alpha:"))); - m_pCellAlphaLabel->SetPosition(CVector2D(vecTemp.fX, vecTemp.fY)); - m_pCellAlphaLabel->AutoSize(); - m_pCellAlphaLabel->SetHorizontalAlign(CGUI_ALIGN_LEFT); - - m_pGridColumnsLabel = reinterpret_cast(pManager->CreateLabel(pTabCEGUI, _("Columns:"))); - m_pGridColumnsLabel->SetPosition(CVector2D(vecTemp.fX + 200.0f, vecTemp.fY)); - m_pGridColumnsLabel->AutoSize(); - m_pGridColumnsLabel->SetHorizontalAlign(CGUI_ALIGN_LEFT); - - m_pGridRowsLabel = reinterpret_cast(pManager->CreateLabel(pTabCEGUI, _("Rows:"))); - m_pGridRowsLabel->SetPosition(CVector2D(vecTemp.fX + 400.0f, vecTemp.fY)); - m_pGridRowsLabel->AutoSize(); - m_pGridRowsLabel->SetHorizontalAlign(CGUI_ALIGN_LEFT); - - vecTemp.fY += 5.0f; - - m_pCellAlpha = reinterpret_cast(pManager->CreateScrollBar(true, pTabCEGUI)); - m_pCellAlpha->SetPosition(CVector2D(vecTemp.fX + 50.0f, vecTemp.fY)); - m_pCellAlpha->SetSize(CVector2D(130.0f, 20.0f)); - m_pCellAlpha->SetOnScrollHandler(GUI_CALLBACK(&CSettings::OnCellAlphaChanged, this)); - m_pCellAlpha->SetProperty("StepSize", "0.01"); - - m_pGridColumns = reinterpret_cast(pManager->CreateScrollBar(true, pTabCEGUI)); - m_pGridColumns->SetPosition(CVector2D(vecTemp.fX + 250.0f, vecTemp.fY)); - m_pGridColumns->SetSize(CVector2D(130.0f, 20.0f)); - m_pGridColumns->SetOnScrollHandler(GUI_CALLBACK(&CSettings::OnGridColumnsChanged, this)); - m_pGridColumns->SetProperty("StepSize", "0.1"); - - m_pGridRows = reinterpret_cast(pManager->CreateScrollBar(true, pTabCEGUI)); - m_pGridRows->SetPosition(CVector2D(vecTemp.fX + 450.0f, vecTemp.fY)); - m_pGridRows->SetSize(CVector2D(130.0f, 20.0f)); - m_pGridRows->SetOnScrollHandler(GUI_CALLBACK(&CSettings::OnGridRowsChanged, this)); - m_pGridRows->SetProperty("StepSize", "0.1"); - - vecTemp.fY += 20.0f; - m_pCellAlphaValueLabel = reinterpret_cast(pManager->CreateLabel(pTabCEGUI, "XXX")); - m_pCellAlphaValueLabel->SetPosition(CVector2D(vecTemp.fX + 50.0f, vecTemp.fY)); - m_pCellAlphaValueLabel->AutoSize(); - m_pCellAlphaValueLabel->SetHorizontalAlign(CGUI_ALIGN_LEFT); - - m_pGridColumnsValueLabel = reinterpret_cast(pManager->CreateLabel(pTabCEGUI, "XXX")); - m_pGridColumnsValueLabel->SetPosition(CVector2D(vecTemp.fX + 250.0f, vecTemp.fY)); - m_pGridColumnsValueLabel->AutoSize(); - m_pGridColumnsValueLabel->SetHorizontalAlign(CGUI_ALIGN_LEFT); - - m_pGridRowsValueLabel = reinterpret_cast(pManager->CreateLabel(pTabCEGUI, "XXX")); - m_pGridRowsValueLabel->SetPosition(CVector2D(vecTemp.fX + 450.0f, vecTemp.fY)); - m_pGridRowsValueLabel->AutoSize(); - m_pGridRowsValueLabel->SetHorizontalAlign(CGUI_ALIGN_LEFT); - - m_pCellAlpha->SetScrollPosition(0.5f); - m_pGridColumns->SetScrollPosition(0.9f); - m_pGridRows->SetScrollPosition(0.9f); - - // Grid layout - vecTemp.fY += 20.0f; - m_pGridLayout->SetPosition(CVector2D(vecTemp.fX, vecTemp.fY)); - m_pGridLayout->SetSize(CVector2D(500.0f, 300.0f)); - - //m_pTestCellLabel = reinterpret_cast(pManager->CreateLabel(pTabCEGUI, _("Test cell"))); - //m_pTestCellLabel->AutoSize(); - //m_pTestCellLabel->SetHorizontalAlign(CGUI_ALIGN_HORIZONTALCENTER); - //m_pTestCellLabel->SetVerticalAlign(CGUI_ALIGN_VERTICALCENTER); - - //bool add = m_pGridLayout->AddItem(m_pTestCellLabel, 1, 1); - //m_pTestCellLabel->SetText(add ? "true" : "false"); - // Set up the events m_pWindow->SetEnterKeyHandler(GUI_CALLBACK(&CSettings::OnOKButtonClick, this)); m_pButtonOK->SetClickHandler(GUI_CALLBACK(&CSettings::OnOKButtonClick, this)); @@ -4565,33 +4475,6 @@ bool CSettings::OnChatAlphaChanged(CGUIElement* pElement) return false; } -bool CSettings::OnCellAlphaChanged(CGUIElement* pElement) -{ - float alpha = (m_pCellAlpha->GetScrollPosition()); - m_pCellAlphaValueLabel->SetText(SString("%g", alpha).c_str()); - - m_pGridLayout->SetDefaultCellAlpha(alpha); - return true; -} - -bool CSettings::OnGridColumnsChanged(CGUIElement* pElement) -{ - int columns = static_cast(std::round(std::max(0.1f, m_pGridColumns->GetScrollPosition()) * 10)); - m_pGridColumnsValueLabel->SetText(SString("%i", columns).c_str()); - - m_pGridLayout->SetColumns(columns); - return true; -} - -bool CSettings::OnGridRowsChanged(CGUIElement* pElement) -{ - int rows = static_cast(std::round(std::max(0.1f, m_pGridRows->GetScrollPosition()) * 10)); - m_pGridRowsValueLabel->SetText(SString("%i", rows).c_str()); - - m_pGridLayout->SetRows(rows); - return true; -} - bool CSettings::OnUpdateButtonClick(CGUIElement* pElement) { // Update build type diff --git a/Client/core/CSettings.h b/Client/core/CSettings.h index d9b48728598..91a14ca40eb 100644 --- a/Client/core/CSettings.h +++ b/Client/core/CSettings.h @@ -17,9 +17,9 @@ class CSettings; #include "CMainMenu.h" #include "CCore.h" -#define SKINS_PATH "skins/*" -#define CHAT_PRESETS_PATH "mta/config/chatboxpresets.xml" -#define CHAT_PRESETS_ROOT "chatboxpresets" +#define SKINS_PATH "skins/*" +#define CHAT_PRESETS_PATH "mta/config/chatboxpresets.xml" +#define CHAT_PRESETS_ROOT "chatboxpresets" // #define SHOWALLSETTINGS @@ -125,18 +125,18 @@ class CSettings const static int SecKeyNum = 3; // Number of secondary keys // Keep these protected so we can access them in the event handlers of CClientGame - CGUIElement* m_pWindow; - CGUITabPanel* m_pTabs; - CGUITab* m_pTabInterface; - CGUITab* m_pTabBrowser; - CGUIButton* m_pButtonOK; - CGUIButton* m_pButtonCancel; - CGUILabel* m_pLabelNick; - CGUIButton* m_pButtonGenerateNick; - CGUIStaticImage* m_pButtonGenerateNickIcon; - CGUIEdit* m_pEditNick; - CGUICheckBox* m_pSavePasswords; - CGUICheckBox* m_pAutoRefreshBrowser; + CGUIElement* m_pWindow; + CGUITabPanel* m_pTabs; + CGUITab* m_pTabInterface; + CGUITab* m_pTabBrowser; + CGUIButton* m_pButtonOK; + CGUIButton* m_pButtonCancel; + CGUILabel* m_pLabelNick; + CGUIButton* m_pButtonGenerateNick; + CGUIStaticImage* m_pButtonGenerateNickIcon; + CGUIEdit* m_pEditNick; + CGUICheckBox* m_pSavePasswords; + CGUICheckBox* m_pAutoRefreshBrowser; CGUILabel* m_pVideoGeneralLabel; CGUILabel* m_pVideoResolutionLabel; @@ -345,19 +345,6 @@ class CSettings bool m_bBrowserListsChanged; bool m_bBrowserListsLoadEnabled; - CGUILabel* m_pGridLayoutLabel; - CGUIGridLayout* m_pGridLayout; - CGUILabel* m_pTestCellLabel; - CGUILabel* m_pCellAlphaLabel; - CGUIScrollBar* m_pCellAlpha; - CGUILabel* m_pCellAlphaValueLabel; - CGUILabel* m_pGridColumnsLabel; - CGUIScrollBar* m_pGridColumns; - CGUILabel* m_pGridColumnsValueLabel; - CGUILabel* m_pGridRowsLabel; - CGUIScrollBar* m_pGridRows; - CGUILabel* m_pGridRowsValueLabel; - bool OnJoypadTextChanged(CGUIElement* pElement); bool OnAxisSelectClick(CGUIElement* pElement); bool OnAudioDefaultClick(CGUIElement* pElement); @@ -399,9 +386,6 @@ class CSettings bool OnBrowserWhitelistRemove(CGUIElement* pElement); bool OnBrowserWhitelistDomainAddFocused(CGUIElement* pElement); bool OnBrowserWhitelistDomainAddDefocused(CGUIElement* pElement); - bool OnCellAlphaChanged(CGUIElement* pElement); - bool OnGridColumnsChanged(CGUIElement* pElement); - bool OnGridRowsChanged(CGUIElement* pElement); bool OnMouseDoubleClick(CGUIMouseEventArgs Args); diff --git a/Client/gui/CGUIGridLayout_Impl.cpp b/Client/gui/CGUIGridLayout_Impl.cpp deleted file mode 100644 index 3e75a3354e0..00000000000 --- a/Client/gui/CGUIGridLayout_Impl.cpp +++ /dev/null @@ -1,827 +0,0 @@ -/***************************************************************************** - * - * PROJECT: Multi Theft Auto v1.0 - * LICENSE: See LICENSE in the top level directory - * FILE: gui/CGUIGridList_Impl.cpp - * PURPOSE: Grid list widget class - * - * Multi Theft Auto is available from http://www.multitheftauto.com/ - * - *****************************************************************************/ - -#include "StdInc.h" -#include - -#define CGUIGRIDLAYOUT_NAME "CGUI/FrameWindow" - -CGUIGridLayout_Impl::CGUIGridLayout_Impl(CGUI_Impl* pGUI, CGUIElement* pParent) -{ - m_pManager = pGUI; - - // Get an unique identifier for CEGUI (gah, there's gotta be an another way) - char szUnique[CGUI_CHAR_SIZE]; - pGUI->GetUniqueName(szUnique); - - // Create the window and set default settings - m_pWindow = pGUI->GetWindowManager()->createWindow(pGUI->ResolveModernName(CGUIGRIDLAYOUT_NAME), szUnique); - - m_pWindow->setDestroyedByParent(false); - m_pWindow->setRect(CEGUI::Relative, CEGUI::Rect(0.00f, 0.00f, 1.0f, 1.0f)); - - CEGUI::FrameWindow* frameWindow = reinterpret_cast(m_pWindow); - frameWindow->setTitleBarEnabled(false); - frameWindow->setSizingEnabled(false); - frameWindow->setDragMovingEnabled(false); - frameWindow->setCloseButtonEnabled(false); - frameWindow->setFrameEnabled(false); - - // Store the pointer to this CGUI element in the CEGUI element - m_pWindow->setUserData(reinterpret_cast(this)); - - // Register our events - // m_pWindow->subscribeEvent(CEGUI::MultiColumnList::EventSortColumnChanged, CEGUI::Event::Subscriber(&CGUIGridList_Impl::Event_OnSortColumn, this)); - AddEvents(); - - // If a parent is specified, add it to it's children list, if not, add it as a child to the pManager - if (pParent) - { - SetParent(pParent); - } - else - { - pGUI->AddChild(this); - SetParent(NULL); - } - - // Create our cell textures - m_cellTexture = m_pManager->CreateTexture(); - m_cellTextureAlt = m_pManager->CreateTexture(); - - unsigned long ulBackgroundColor255 = COLOR_ARGB(255, 0, 0, 0); - m_cellTexture->LoadFromMemory(&ulBackgroundColor255, 1, 1); - - unsigned long ulBackgroundColor150 = COLOR_ARGB(150, 0, 0, 0); - m_cellTextureAlt->LoadFromMemory(&ulBackgroundColor150, 1, 1); -} - -CGUIGridLayout_Impl::~CGUIGridLayout_Impl() -{ - DestroyElement(); -} - -const bool CGUIGridLayout_Impl::AddItem(CGUIElement* item, int column, int row, const bool moveToNextCell) -{ - auto* cell = GetCell(column, row); - - if (cell == nullptr) - return false; - - if (cell->element != nullptr) - RemoveItem(column, row, false, true); - - item->SetParent(cell->container); - item->SetPosition(CVector2D(0.0f, 0.0f), true); - item->ForceRedraw(); - - cell->element = item; - m_items.emplace(item, cell->id); - - if (moveToNextCell) - { - if (m_activeRow == m_rows) - { - if (m_activeColumn < m_columns) - { - m_activeColumn++; - m_activeRow = 1; - } - } - else - { - m_activeRow++; - } - } - - return true; -} - -const bool CGUIGridLayout_Impl::AddItem(CGUIElement* item, const bool moveToNextCell) -{ - return AddItem(item, m_activeColumn, m_activeRow); -} - -const bool CGUIGridLayout_Impl::RemoveItem(const int column, const int row, const bool moveToPreviousCell, const bool deleteItem) -{ - auto* cell = GetCell(column, row); - - if (cell == nullptr || cell->element == nullptr) - return false; - - cell->element->SetParent(nullptr); - cell->element->ForceRedraw(); - - m_items.erase(cell->element); - cell->element = nullptr; - - if (deleteItem) - delete cell->element; - - if (moveToPreviousCell) - { - if (m_activeRow == 1) - { - if (m_activeColumn > 1) - { - m_activeColumn--; - m_activeRow = m_rows; - } - } - else - { - m_activeRow--; - } - } - - return true; -} - -const bool CGUIGridLayout_Impl::RemoveItem(const CGUIElement* item, const bool moveToPreviousCell, const bool deleteItem) -{ - auto* cell = GetCell(item); - - if (cell == nullptr) - return false; - - return RemoveItem(cell->column, cell->row, moveToPreviousCell); -} - -SGridCellItem* CGUIGridLayout_Impl::GetCell(const int column, const int row) const -{ - if (!InGridRange(column, row)) - return nullptr; - - const auto& cell = m_cells.find(m_grid[column - 1][row - 1]); - - if (cell == m_cells.end()) - return nullptr; - - return cell->second; -} - -SGridCellItem* CGUIGridLayout_Impl::GetCell(const CGUIElement* item) const -{ - int id = m_items.count(item) ? m_items.at(item) : 0; - return id == 0 ? nullptr : m_cells.at(id); -} - -std::vector CGUIGridLayout_Impl::GetCellsInColumn(const int column) const -{ - return GetCellsInGrid(column, 0, column, m_rows - 1); -} - -std::vector CGUIGridLayout_Impl::GetCellsInRow(const int row) const -{ - return GetCellsInGrid(0, row, m_columns - 1, row); -} - -std::vector CGUIGridLayout_Impl::GetCellsInGrid(const int startColumn, const int startRow, const int endColumn, const int endRow) const -{ - if (startColumn < 1 || startRow < 1 || endColumn > m_columns || endRow > m_rows) - { - return std::vector(); - } - - std::vector cells; - - for (int i = startColumn; i <= endColumn; i++) - { - for (int j = startRow; j <= endRow; j++) - { - if (m_grid[i - 1][j - 1] != 0) - { - auto& cell = m_cells.find(m_grid[i - 1][j - 1]); - - if (cell != m_cells.end()) - { - cells.push_back(cell->second); - } - } - } - } - - return cells; -} - -std::vector CGUIGridLayout_Impl::GetCellsOutsideGrid(const int startColumn, const int startRow, const int endColumn, const int endRow) const -{ - if (startColumn < 1 || startRow < 1 || endColumn > m_columns || endRow > m_rows) - { - return std::vector(); - } - - std::vector cells; - - for (auto& cell : m_cells) - { - if (cell.second->column < startColumn || cell.second->column > endColumn || cell.second->row < startRow || cell.second->row > endRow) - { - cells.push_back(cell.second); - } - } - - return cells; -} - -const bool CGUIGridLayout_Impl::SetColumns(int columns) -{ - if (columns < 0 || columns == m_columns) - { - return false; - } - - bool set = SetGrid(columns, m_rows); - - if (set) - m_columns = columns; - - return set; -} - -const bool CGUIGridLayout_Impl::SetRows(int rows) -{ - if (rows < 0 || rows == m_rows) - return false; - - bool set = SetGrid(m_columns, rows); - - if (set) - m_rows = rows; - - return set; -} - -const bool CGUIGridLayout_Impl::SetGrid(int columns, int rows) -{ - if (columns < 0 || rows < 0 || (columns == m_columns && rows == m_rows)) - return false; - - m_columns = columns; - m_rows = rows; - - const size_t& size = m_grid.size(); - const bool larger = (columns * rows) > (size * (size > 0 ? m_grid[0].size() : 0)); - - m_grid.resize(columns); - - for (int i = 0; i < columns; i++) - { - m_grid[i].resize(rows, 0); - } - - m_columnWidths.resize(m_columns, 0.0f); - m_rowHeights.resize(m_rows, 0.0f); - - if (larger) - { - CreateGridCells(); - - // Since the grid starts at 0,0 make sure an active cell is set - m_activeColumn = (m_activeColumn == 0 ? 1 : m_activeColumn); - m_activeRow = (m_activeRow == 0 ? 1 : m_activeRow); - } - else - { - CleanupGridCells(); - - // Since the grid has been resized, we need to make sure the active cell is still within the grid - m_activeColumn = std::min(m_activeColumn, columns); - m_activeRow = std::min(m_activeRow, rows); - } - - RepositionGridCells(); - return true; -} - -const bool CGUIGridLayout_Impl::SetActiveCell(int column, int row) -{ - if (!InGridRange(column, row)) - return false; - - m_activeColumn = column; - m_activeRow = row; - return true; -} - -const bool CGUIGridLayout_Impl::SetActiveColumn(int column) -{ - if (!InColumnRange(column)) - return false; - - m_activeColumn = column; - return true; -} - -const bool CGUIGridLayout_Impl::SetActiveRow(int row) -{ - if (!InRowRange(row)) - return false; - - m_activeRow = row; - return true; -} - -const std::pair CGUIGridLayout_Impl::GetActiveCell() const -{ - return std::make_pair(m_activeColumn, m_activeRow); -} - -void CGUIGridLayout_Impl::SetItemAlignment(const CGUIElement* item, eGridLayoutItemAlignment alignment) -{ - auto* cell = GetCell(item); - - if (cell == nullptr) - return; - - cell->alignment = alignment; - RepositionGridCell(*cell, true); -} - -const eGridLayoutItemAlignment CGUIGridLayout_Impl::GetItemAlignment(const CGUIElement* item) const -{ - const auto* cell = GetCell(item); - - if (cell == nullptr) - return m_defaultAlignment; - - return cell->alignment; -} - -const bool CGUIGridLayout_Impl::SetCellAlpha(const int column, const int row, const float alpha) -{ - auto* cell = GetCell(column, row); - - if (cell == nullptr) - return false; - - cell->container->SetAlpha(alpha); - return true; -} - -const bool CGUIGridLayout_Impl::SetDefaultCellAlpha(const float alpha) -{ - m_defaultCellAlpha = alpha; - - for (auto& cell : m_cells) - { - cell.second->container->SetAlpha(alpha); - } - - return true; -} - -const bool CGUIGridLayout_Impl::SetColumnWidth(const int column, const float width) -{ - if (!InColumnRange(column)) - return false; - - m_columnWidths[column - 1] = width; - RepositionGridCells(); - return true; -} - -const bool CGUIGridLayout_Impl::SetRowHeight(const int row, const float height) -{ - if (!InRowRange(row)) - return false; - - m_rowHeights[row - 1] = height; - RepositionGridCells(); - return true; -} - -const bool CGUIGridLayout_Impl::SetCellFullSize(const int column, const int row, const bool fullSize) -{ - auto* cell = GetCell(column, row); - - if (cell == nullptr) - return false; - - cell->forceFullSize = fullSize; - RepositionGridCell(*cell, true); - return true; -} - -const bool CGUIGridLayout_Impl::SetDefaultCellFullSize(const bool fullSize, const bool updateExisting) -{ - m_defaultFullSize = fullSize; - - if (updateExisting) - { - for (auto& cell : m_cells) - { - cell.second->forceFullSize = fullSize; - } - - RepositionGridCells(true); - } - - return true; -} - -const bool CGUIGridLayout_Impl::GetCellFullSize(const int column, const int row) const -{ - auto* cell = GetCell(column, row); - - if (cell == nullptr) - return false; - - return cell->forceFullSize; -} - -const bool CGUIGridLayout_Impl::SetCellPadding(const int column, const int row, const CVector2D& padding) -{ - auto* cell = GetCell(column, row); - - if (cell == nullptr) - return false; - - cell->padding = padding; - RepositionGridCell(*cell, true); - return true; -} - -const bool CGUIGridLayout_Impl::SetDefaultCellPadding(const CVector2D& padding, const bool updateExisting) -{ - m_defaultPadding = padding; - - for (auto& cell : m_cells) - { - cell.second->padding = padding; - } - - RepositionGridCells(true); - return true; -} - -const CVector2D& CGUIGridLayout_Impl::GetCellPadding(const int column, const int row) const -{ - auto* cell = GetCell(column, row); - - if (cell == nullptr) - return m_defaultPadding; - - return cell->padding; -} - -const bool CGUIGridLayout_Impl::SetCellTexture(const int column, const int row, CGUITexture* texture, const bool alt) -{ - auto* cell = GetCell(column, row); - - if (cell == nullptr) - return false; - - cell->container->LoadFromTexture(texture); - return true; -} - -const bool CGUIGridLayout_Impl::SetCellColor(const int column, const int row, const CColor& color, const bool alt) -{ - auto* cell = GetCell(column, row); - - if (cell == nullptr) - return false; - - auto* texture = m_pManager->CreateTexture(); - unsigned long argb = COLOR_ARGB(color.A, color.R, color.G, color.B); - - texture->LoadFromMemory(&argb, 1, 1); - cell->container->LoadFromTexture(texture); - - return true; -} - -const bool CGUIGridLayout_Impl::SetDefaultCellTexture(CGUITexture* texture, const bool alt, const bool updateExisting) -{ - if (alt) - m_cellTextureAlt = texture; - else - m_cellTexture = texture; - - if (updateExisting) - { - for (auto& cell : m_cells) - { - cell.second->container->LoadFromTexture(texture); - } - } - - return true; -} - -const bool CGUIGridLayout_Impl::SetDefaultCellColor(const CColor& color, const bool alt, const bool updateExisting) -{ - auto* texture = m_pManager->CreateTexture(); - unsigned long argb = COLOR_ARGB(color.A, color.R, color.G, color.B); - - texture->LoadFromMemory(&argb, 1, 1); - return SetDefaultCellTexture(texture, alt, updateExisting); -} - -void CGUIGridLayout_Impl::CreateGridCells() -{ - auto* parent = reinterpret_cast(this); - - // Calculate our own offsets here for performance reasons - std::vector columnOffset(m_columns, 0.0f); - std::vector rowOffset(m_rows, 0.0f); - - for (int i = 0; i < m_columns; i++) - { - for (int j = 0; j < m_rows; j++) - { - const auto cell = m_cells.count(m_grid[i][j]); - - if (cell == 0) - { - auto* cellItem = new SGridCellItem(); - cellItem->container = m_pManager->CreateStaticImage(parent); - cellItem->alignment = m_defaultAlignment; - cellItem->forceFullSize = m_defaultFullSize; - cellItem->padding = m_defaultPadding; - - // Assign the cell container texture, alternative between m_cellTexture and m_cellTextureAlt - cellItem->container->LoadFromTexture((i + j) % 2 == 0 ? m_cellTexture : m_cellTextureAlt); - cellItem->container->SetAlpha(m_defaultCellAlpha); - - auto offsets = AccumulateOffsets({columnOffset, rowOffset}); - - // Position the cell container in the grid - cellItem->container->SetPosition(CalculateCellPosition(*cellItem, offsets), true); - - // Size the cell container in the grid - cellItem->container->SetSize(CalculateCellSize(*cellItem, offsets), true); - - // Create a test label for the cell - auto* label = m_pManager->CreateLabel(cellItem->container, std::to_string(m_nextId).c_str()); - - cellItem->id = m_nextId; - cellItem->element = label; - cellItem->column = i + 1; - cellItem->row = j + 1; - - label->SetFrameEnabled(true); - label->SetHorizontalAlign(CGUI_ALIGN_HORIZONTALCENTER); - label->SetVerticalAlign(CGUI_ALIGN_VERTICALCENTER); - - // For testing purposes. Use CGUIGridLayout::SetCellFullSize in proper implementation - if (cellItem->forceFullSize) - label->SetSize(CVector2D(1.0f - (cellItem->padding.fX * 2.0f), 1.0f - (cellItem->padding.fY * 2.0f)), true); - else - label->SetSize(CVector2D(0.2f, 0.2f), true); - - CVector2D offset = GetAlignmentOffset(*cellItem, label->GetSize(true)); - label->SetPosition(offset, true); - label->ForceRedraw(); - - m_cells.emplace(m_nextId, cellItem); - m_grid[i][j] = m_nextId; - - m_nextId++; - } - - rowOffset[j] = m_rowHeights[j]; - } - - columnOffset[i] = m_columnWidths[i]; - rowOffset.assign(m_rows, 0.0f); - } -} - -void CGUIGridLayout_Impl::CleanupGridCells() -{ - for (auto& cell : m_cells) - { - if (cell.second->column > m_columns || cell.second->row > m_rows) - { - delete cell.second->container; - - if (cell.second->element) - { - m_items.erase(cell.second->element); - - cell.second->element->SetParent(nullptr); - cell.second->element->ForceRedraw(); - - delete cell.second->element; - cell.second->element = nullptr; - } - - delete cell.second; - m_cells.erase(cell.first); - } - } -} - -const CVector2D CGUIGridLayout_Impl::CalculateCellPosition(const SGridCellItem& cell, const std::pair& offsets) const -{ - int widths = CountColumnWidths(); - int heights = CountRowHeights(); - - if (cell.element != nullptr) - cell.element->SetText(std::to_string(std::max(0, cell.column - widths)).c_str()); - - return CVector2D( - offsets.first + (std::max(0, cell.column - CountColumnWidths(cell.column) - 1) * ((1.0f - AccumulateOffset(m_columnWidths)) / (m_columns - widths))), - offsets.second + (std::max(0, cell.row - CountRowHeights(cell.row) - 1) * ((1.0f - AccumulateOffset(m_rowHeights)) / (m_rows - heights)))); -} - -const CVector2D CGUIGridLayout_Impl::CalculateCellSize(const SGridCellItem& cell, const std::pair& offsets) const -{ - int widths = CountColumnWidths(); - int heights = CountRowHeights(); - - return CVector2D( - m_columnWidths[cell.column - 1] == 0.0f ? (1.0f - AccumulateOffset(m_columnWidths)) / (m_columns - widths) : m_columnWidths[cell.column - 1], - m_rowHeights[cell.row - 1] == 0.0f ? (1.0f - AccumulateOffset(m_rowHeights)) / (m_rows - heights) : m_rowHeights[cell.row - 1]); -} - -const std::pair, std::vector> CGUIGridLayout_Impl::CalculateGridOffsets(const int column, const int row) const -{ - std::vector columnOffset(m_columns, 0.0f); - std::vector rowOffset(m_rows, 0.0f); - - for (int i = 0; i < (column == 0 ? m_columns : column) - 1; i++) - { - for (int j = 0; j < (row == 0 ? m_rows : row) - 1; j++) - { - if (j < (row == 0 ? m_rows : row)) - { - rowOffset[j + 1] = m_rowHeights[j]; - } - } - - columnOffset[i + 1] = m_columnWidths[i]; - rowOffset.assign(m_rows, 0.0f); - } - - return {columnOffset, rowOffset}; -} - -const float CGUIGridLayout_Impl::AccumulateOffset(const std::vector& offset) const -{ - return std::reduce(offset.begin(), offset.end()); -} - -const std::pair CGUIGridLayout_Impl::AccumulateOffsets(const std::pair, std::vector>& offsets) const -{ - return {AccumulateOffset(offsets.first), AccumulateOffset(offsets.second)}; -} - -void CGUIGridLayout_Impl::RepositionGridCells(const bool itemOnly) const -{ - std::vector columnOffset(m_columns, 0.0f); - std::vector rowOffset(m_rows, 0.0f); - - for (int i = 0; i < m_columns; i++) - { - for (int j = 0; j < m_rows; j++) - { - const auto cell = m_cells.count(m_grid[i][j]); - - if (cell != 0) - { - const auto& cellItem = m_cells.at(m_grid[i][j]); - - if (!itemOnly) - { - auto offsets = AccumulateOffsets({columnOffset, rowOffset}); - - cellItem->container->SetPosition(CalculateCellPosition(*cellItem, offsets), true); - cellItem->container->SetSize(CalculateCellSize(*cellItem, offsets), true); - cellItem->container->ForceRedraw(); - } - - if (cellItem->element != nullptr) - { - if (cellItem->forceFullSize) - { - cellItem->element->SetSize(CVector2D(1.0f - (cellItem->padding.fX * 2.0f), 1.0f - (cellItem->padding.fY * 2.0f)), true); - } - - CVector2D position = GetAlignmentOffset(*cellItem, cellItem->element->GetSize(true)); - cellItem->element->SetPosition(position, true); - cellItem->element->ForceRedraw(); - } - } - - rowOffset[j] = m_rowHeights[j]; - } - - columnOffset[i] = m_columnWidths[i]; - rowOffset.assign(m_rows, 0.0f); - } -} - -void CGUIGridLayout_Impl::RepositionGridCell(const SGridCellItem& cell, const bool itemOnly) const -{ - if (!itemOnly) - { - auto offsets = AccumulateOffsets(CalculateGridOffsets(cell.column, cell.row)); - - cell.container->SetPosition(CalculateCellPosition(cell, offsets), true); - cell.container->SetSize(CalculateCellSize(cell, offsets), true); - cell.container->ForceRedraw(); - } - - if (cell.element != nullptr) - { - if (cell.forceFullSize) - { - cell.element->SetSize(CVector2D(1.0f - (cell.padding.fX * 2.0f), 1.0f - (cell.padding.fY * 2.0f)), true); - } - - CVector2D position = GetAlignmentOffset(cell, cell.element->GetSize(true)); - cell.element->SetPosition(position, true); - cell.element->ForceRedraw(); - } -} - -void CGUIGridLayout_Impl::RepositionGridCell(const int column, const int row, const bool itemOnly) const -{ - const auto* cell = GetCell(column, row); - - if (cell == nullptr) - return; - - RepositionGridCell(*cell); -} - -const bool CGUIGridLayout_Impl::InGridRange(const int column, const int row) const -{ - return column >= 1 && row >= 1 && column <= m_columns && row <= m_rows; -} - -const bool CGUIGridLayout_Impl::InColumnRange(const int column) const -{ - return column >= 1 && column <= m_columns; -} - -const bool CGUIGridLayout_Impl::InRowRange(const int row) const -{ - return row >= 1 && row <= m_rows; -} - -const CVector2D CGUIGridLayout_Impl::GetAlignmentOffset(const SGridCellItem& cell, const CVector2D& size) const -{ - CVector2D offset; - - if (!cell.forceFullSize) - { - switch (cell.alignment) - { - case eGridLayoutItemAlignment::TOP_LEFT: - offset += CVector2D(0.0f, 0.0f); - break; - case eGridLayoutItemAlignment::TOP_CENTER: - offset += CVector2D(0.5f - (size.fX / 2.0f), 0.0f); - break; - case eGridLayoutItemAlignment::TOP_RIGHT: - offset += CVector2D(1.0f - size.fX, 0.0f); - break; - case eGridLayoutItemAlignment::MIDDLE_LEFT: - offset += CVector2D(0.0f, 0.5f - (size.fY / 2.0f)); - break; - case eGridLayoutItemAlignment::MIDDLE_CENTER: - offset += CVector2D(0.5f - (size.fX / 2.0f), 0.5f - (size.fY / 2.0f)); - break; - case eGridLayoutItemAlignment::MIDDLE_RIGHT: - offset += CVector2D(1.0f - size.fX, 0.5f - (size.fY / 2.0f)); - break; - case eGridLayoutItemAlignment::BOTTOM_LEFT: - offset += CVector2D(0.0f, 1.0f - size.fY); - break; - case eGridLayoutItemAlignment::BOTTOM_CENTER: - offset += CVector2D(0.5f - (size.fX / 2.0f), 1.0f - size.fY); - break; - case eGridLayoutItemAlignment::BOTTOM_RIGHT: - offset += CVector2D(1.0f - size.fX, 1.0f - size.fY); - break; - } - } - - offset = CVector2D(std::min(offset.fX, 1.0f - size.fX - cell.padding.fX), std::min(offset.fY, 1.0f - size.fY - cell.padding.fY)); - offset = CVector2D(std::max(offset.fX, cell.padding.fX), std::max(offset.fY, cell.padding.fY)); - return offset; -} - -const int CGUIGridLayout_Impl::CountColumnWidths(const int maxColumns) const -{ - return std::count_if(m_columnWidths.begin(), m_columnWidths.begin() + (maxColumns == 0 || maxColumns < 1 ? m_columns : maxColumns) - 1, - [&](const float& width) { return width > 0.0f; }); -} - -const int CGUIGridLayout_Impl::CountRowHeights(const int maxRows) const -{ - return std::count_if(m_rowHeights.begin(), m_rowHeights.begin() + (maxRows == 0 || maxRows < 1 ? m_rows : maxRows) - 1, - [&](const float& height) { return height > 0.0f; }); -} diff --git a/Client/gui/CGUIGridLayout_Impl.h b/Client/gui/CGUIGridLayout_Impl.h deleted file mode 100644 index 2f63fe13ffb..00000000000 --- a/Client/gui/CGUIGridLayout_Impl.h +++ /dev/null @@ -1,132 +0,0 @@ -/***************************************************************************** - * - * PROJECT: Multi Theft Auto: San Andreas - * LICENSE: See LICENSE in the top level directory - * - * Multi Theft Auto is available from http://www.multitheftauto.com/ - * - *****************************************************************************/ - -#pragma once - -#include -#include "CGUIElement_Impl.h" - -class CGUIGridLayout_Impl : public CGUIGridLayout, public CGUIElement_Impl -{ -public: - CGUIGridLayout_Impl(class CGUI_Impl* pGUI, CGUIElement* pParent = NULL); - ~CGUIGridLayout_Impl(); - - eCGUIType GetType() { return CGUI_GRIDLAYOUT; }; - - const bool SetColumns(int columns); - const bool SetRows(int rows); - const bool SetGrid(int columns, int rows); - - const int GetColumns() const { return m_columns; } - const int GetRows() const { return m_rows; } - - const bool SetActiveCell(int column, int row); - const bool SetActiveColumn(int column); - const bool SetActiveRow(int row); - - const int GetActiveColumn() const { return m_activeColumn; } - const int GetActiveRow() const { return m_activeRow; } - const std::pair GetActiveCell() const; - - const bool AddItem(CGUIElement* item, int column, int row, const bool moveToNextCell = true); - const bool AddItem(CGUIElement* item, const bool moveToNextCell = true); - - const bool RemoveItem(const int column, const int row, const bool moveToPreviousCell = false, const bool deleteItem = false); - const bool RemoveItem(const CGUIElement* item, const bool moveToPreviousCell = false, const bool deleteItem = false); - - SGridCellItem* GetCell(const int column, const int row) const; - SGridCellItem* GetCell(const CGUIElement* item) const; - - std::vector GetCellsInGrid(const int startColumn, const int startRow, const int endColumn, const int endRow) const; - std::vector GetCellsOutsideGrid(const int startColumn, const int startRow, const int endColumn, const int endRow) const; - std::vector GetCellsInColumn(const int column) const; - std::vector GetCellsInRow(const int row) const; - - void SetItemAlignment(const CGUIElement* item, eGridLayoutItemAlignment alignment); - void SetDefaultItemAlignment(eGridLayoutItemAlignment alignment) { m_defaultAlignment = alignment; } - - const eGridLayoutItemAlignment GetItemAlignment(const CGUIElement* item) const; - const eGridLayoutItemAlignment GetDefaultItemAlignment() const { return m_defaultAlignment; } - - const bool SetCellAlpha(const int column, const int row, const float alpha); - const bool SetDefaultCellAlpha(const float alpha); - - const bool SetColumnWidth(const int column, const float width); - const bool SetRowHeight(const int row, const float height); - - const bool SetCellFullSize(const int column, const int row, const bool fullSize); - const bool SetDefaultCellFullSize(const bool fullSize, const bool updateExisting = false); - - const bool GetCellFullSize(const int column, const int row) const; - const bool GetDefaultCellFullSize() const { return m_defaultFullSize; } - - const bool SetCellPadding(const int column, const int row, const CVector2D& padding); - const bool SetDefaultCellPadding(const CVector2D& padding, const bool updateExisting); - - const CVector2D& GetCellPadding(const int column, const int row) const; - const CVector2D& GetDefaultCellPadding() const { return m_defaultPadding; } - - const bool SetCellTexture(const int column, const int row, CGUITexture* texture, const bool alt = false); - const bool SetCellColor(const int column, const int row, const CColor& color, const bool alt = false); - - const bool SetDefaultCellTexture(CGUITexture* texture, const bool alt = false, const bool updateExisting = false); - const bool SetDefaultCellColor(const CColor& color, const bool alt = false, const bool updateExisting = false); - -#include "CGUIElement_Inc.h" - -private: - int m_columns = 0; - int m_rows = 0; - - int m_activeColumn = 0; - int m_activeRow = 0; - - int m_nextId = 1; - - float m_defaultCellAlpha = 1.0f; - - std::vector> m_grid; - std::unordered_map m_cells; - std::unordered_map m_items; - - std::vector m_columnWidths; - std::vector m_rowHeights; - - CGUITexture* m_cellTexture = nullptr; - CGUITexture* m_cellTextureAlt = nullptr; - - eGridLayoutItemAlignment m_defaultAlignment = eGridLayoutItemAlignment::MIDDLE_CENTER; - - bool m_defaultFullSize = false; - CVector2D m_defaultPadding = CVector2D(0.1f, 0.1f); - - void CreateGridCells(); - void CleanupGridCells(); - - void RepositionGridCells(const bool itemOnly = false) const; - void RepositionGridCell(const SGridCellItem& cell, const bool itemOnly = false) const; - void RepositionGridCell(const int column, const int row, const bool itemOnly = false) const; - - const bool InGridRange(const int column, const int row) const; - const bool InColumnRange(const int column) const; - const bool InRowRange(const int row) const; - - const CVector2D CalculateCellPosition(const SGridCellItem& cell, const std::pair& offsets) const; - const CVector2D CalculateCellSize(const SGridCellItem& cell, const std::pair& offset) const; - - const std::pair, std::vector> CalculateGridOffsets(const int column = 0, const int row = 0) const; - const std::pair AccumulateOffsets(const std::pair, std::vector>& offsets) const; - const float AccumulateOffset(const std::vector& offset) const; - - const int CountColumnWidths(const int maxColumns = 0) const; - const int CountRowHeights(const int maxRows = 0) const; - - const CVector2D GetAlignmentOffset(const SGridCellItem& cell, const CVector2D& size) const; -}; diff --git a/Client/gui/CGUI_Impl.cpp b/Client/gui/CGUI_Impl.cpp index f16649fb416..0229a7bfbdc 100644 --- a/Client/gui/CGUI_Impl.cpp +++ b/Client/gui/CGUI_Impl.cpp @@ -836,11 +836,6 @@ CGUIWindow* CGUI_Impl::CreateWnd(CGUIElement* pParent, const char* szCaption) return new CGUIWindow_Impl(this, pParent, szCaption); } -CGUIGridLayout* CGUI_Impl::_CreateGridLayout(CGUIElement_Impl* pParent) -{ - return new CGUIGridLayout_Impl(this, pParent); -} - void CGUI_Impl::SetCursorEnabled(bool bEnabled) { if (bEnabled) @@ -2001,12 +1996,6 @@ CGUIWebBrowser* CGUI_Impl::CreateWebBrowser(CGUITab* pParent) return _CreateWebBrowser(wnd); } -CGUIGridLayout* CGUI_Impl::CreateGridLayout(CGUIElement* pParent) -{ - CGUIWindow_Impl* wnd = reinterpret_cast(pParent); - return _CreateGridLayout(wnd); -} - void CGUI_Impl::CleanDeadPool() { if (m_pWindowManager) diff --git a/Client/gui/CGUI_Impl.h b/Client/gui/CGUI_Impl.h index 6ef917d1571..c93593144c5 100644 --- a/Client/gui/CGUI_Impl.h +++ b/Client/gui/CGUI_Impl.h @@ -142,8 +142,6 @@ class CGUI_Impl : public CGUI, public CGUITabList CGUITexture* CreateTexture(); CGUIFont* CreateFnt(const char* szFontName, const char* szFontFile, unsigned int uSize = 8, unsigned int uFlags = 0, bool bAutoScale = false); - CGUIGridLayout* CreateGridLayout(CGUIElement* pParent = nullptr); - void SetCursorEnabled(bool bEnabled); bool IsCursorEnabled(); void SetCursorAlpha(float fAlpha, bool bOnlyCurrentServer = false); @@ -304,7 +302,6 @@ class CGUI_Impl : public CGUI, public CGUITabList CGUIScrollBar* _CreateScrollBar(bool bHorizontal, CGUIElement_Impl* pParent = NULL); CGUIComboBox* _CreateComboBox(CGUIElement_Impl* pParent = NULL, const char* szCaption = ""); CGUIWebBrowser* _CreateWebBrowser(CGUIElement_Impl* pParent = nullptr); - CGUIGridLayout* _CreateGridLayout(CGUIElement_Impl* pParent = nullptr); void SubscribeToMouseEvents(); CGUIFont* CreateFntFromWinFont(const char* szFontName, const char* szFontWinReg, const char* szFontWinFile, unsigned int uSize = 8, unsigned int uFlags = 0, diff --git a/Client/gui/StdInc.h b/Client/gui/StdInc.h index fdd2310e9d1..e2a040a0ee5 100644 --- a/Client/gui/StdInc.h +++ b/Client/gui/StdInc.h @@ -35,4 +35,3 @@ #include "CGUIWebBrowser_Impl.h" #include "CGUIWindow_Impl.h" #include "CGUIComboBox_Impl.h" -#include "CGUIGridLayout_Impl.h" diff --git a/Client/sdk/gui/CGUI.h b/Client/sdk/gui/CGUI.h index 1e04c283152..d4d40c2f09a 100644 --- a/Client/sdk/gui/CGUI.h +++ b/Client/sdk/gui/CGUI.h @@ -35,7 +35,6 @@ class CGUI; #include "CGUIWebBrowser.h" #include "CGUITabPanel.h" #include "CGUIComboBox.h" -#include "CGUIGridLayout.h" #include "CGUITypes.h" class CXML; @@ -127,8 +126,6 @@ class CGUI virtual CGUIFont* CreateFnt(const char* szFontName, const char* szFontFile, unsigned int uSize = 8, unsigned int uFlags = 0, bool bAutoScale = false) = 0; virtual CGUITexture* CreateTexture() = 0; - virtual CGUIGridLayout* CreateGridLayout(CGUIElement* pParent = nullptr) = 0; - virtual void SetCursorEnabled(bool bEnabled) = 0; virtual bool IsCursorEnabled() = 0; virtual void SetCursorAlpha(float fAlpha, bool bOnlyCurrentServer = false) = 0; diff --git a/Client/sdk/gui/CGUIElement.h b/Client/sdk/gui/CGUIElement.h index c787f149df3..252c84de990 100644 --- a/Client/sdk/gui/CGUIElement.h +++ b/Client/sdk/gui/CGUIElement.h @@ -41,8 +41,7 @@ enum eCGUIType CGUI_SCROLLPANE, CGUI_SCROLLBAR, CGUI_COMBOBOX, - CGUI_WEBBROWSER, - CGUI_GRIDLAYOUT + CGUI_WEBBROWSER }; class CGUIElement diff --git a/Client/sdk/gui/CGUIGridLayout.h b/Client/sdk/gui/CGUIGridLayout.h deleted file mode 100644 index 796e321e748..00000000000 --- a/Client/sdk/gui/CGUIGridLayout.h +++ /dev/null @@ -1,112 +0,0 @@ -/***************************************************************************** - * - * PROJECT: Multi Theft Auto v1.0 - * LICENSE: See LICENSE in the top level directory - * FILE: sdk/gui/CGUIGridList.h - * PURPOSE: Grid list widget interface - * - * Multi Theft Auto is available from http://www.multitheftauto.com/ - * - *****************************************************************************/ - -#pragma once - -#include "CGUIElement.h" -#include "CGUIGridLayout.h" -#include <../Shared/sdk/CVector2D.h> -#include <../Shared/sdk/CColor.h> - -#include - -class CGUIStaticImage; -class CGUITexture; - -enum class eGridLayoutItemAlignment -{ - TOP_LEFT, - TOP_CENTER, - TOP_RIGHT, - MIDDLE_LEFT, - MIDDLE_CENTER, - MIDDLE_RIGHT, - BOTTOM_LEFT, - BOTTOM_CENTER, - BOTTOM_RIGHT -}; - -struct SGridCellItem -{ - int id; - CGUIStaticImage* container; - CGUIElement* element; - eGridLayoutItemAlignment alignment; - int column; - int row; - bool forceFullSize; - CVector2D padding; -}; - -class CGUIGridLayout : public CGUIElement -{ -public: - virtual ~CGUIGridLayout(){}; - - virtual const bool SetColumns(int columns) = 0; - virtual const bool SetRows(int rows) = 0; - virtual const bool SetGrid(int columns, int rows) = 0; - - virtual const int GetColumns() const = 0; - virtual const int GetRows() const = 0; - - virtual const bool SetActiveCell(int column, int row) = 0; - virtual const bool SetActiveColumn(int column) = 0; - virtual const bool SetActiveRow(int row) = 0; - - virtual const int GetActiveColumn() const = 0; - virtual const int GetActiveRow() const = 0; - virtual const std::pair GetActiveCell() const = 0; - - virtual const bool AddItem(CGUIElement* item, int column, int row, const bool moveToNextCell = true) = 0; - virtual const bool AddItem(CGUIElement* item, const bool moveToNextCell = true) = 0; - - virtual const bool RemoveItem(const int column, const int row, const bool moveToPreviousCell = false, const bool deleteItem = false) = 0; - virtual const bool RemoveItem(const CGUIElement* item, const bool moveToPreviousCell = false, const bool deleteItem = false) = 0; - - virtual SGridCellItem* GetCell(const int column, const int row) const = 0; - virtual SGridCellItem* GetCell(const CGUIElement* item) const = 0; - - virtual std::vector GetCellsInGrid(const int startColumn, const int startRow, const int endColumn, const int endRow) const = 0; - virtual std::vector GetCellsOutsideGrid(const int startColumn, const int startRow, const int endColumn, const int endRow) const = 0; - virtual std::vector GetCellsInColumn(const int column) const = 0; - virtual std::vector GetCellsInRow(const int row) const = 0; - - virtual void SetItemAlignment(const CGUIElement* item, eGridLayoutItemAlignment alignment) = 0; - virtual void SetDefaultItemAlignment(eGridLayoutItemAlignment alignment) = 0; - - virtual const eGridLayoutItemAlignment GetItemAlignment(const CGUIElement* item) const = 0; - virtual const eGridLayoutItemAlignment GetDefaultItemAlignment() const = 0; - - virtual const bool SetCellAlpha(const int column, const int row, const float alpha) = 0; - virtual const bool SetDefaultCellAlpha(const float alpha) = 0; - - virtual const bool SetColumnWidth(const int column, const float width) = 0; - virtual const bool SetRowHeight(const int row, const float height) = 0; - - virtual const bool SetCellFullSize(const int column, const int row, const bool fullSize) = 0; - virtual const bool SetDefaultCellFullSize(const bool fullSize, const bool updateExisting = false) = 0; - - virtual const bool GetCellFullSize(const int column, const int row) const = 0; - virtual const bool GetDefaultCellFullSize() const = 0; - - virtual const bool SetCellPadding(const int column, const int row, const CVector2D& padding) = 0; - virtual const bool SetDefaultCellPadding(const CVector2D& padding, const bool updateExisting) = 0; - - virtual const CVector2D& GetCellPadding(const int column, const int row) const = 0; - virtual const CVector2D& GetDefaultCellPadding() const = 0; - - virtual const bool SetCellTexture(const int column, const int row, CGUITexture* texture, const bool alt = false) = 0; - virtual const bool SetCellColor(const int column, const int row, const CColor& color, const bool alt = false) = 0; - - virtual const bool SetDefaultCellTexture(CGUITexture* texture, const bool alt = false, const bool updateExisting = false) = 0; - virtual const bool SetDefaultCellColor(const CColor& color, const bool alt = false, const bool updateExisting = false) = 0; -}; diff --git a/Shared/sdk/CColor.h b/Shared/sdk/CColor.h deleted file mode 100644 index 92f506926d3..00000000000 --- a/Shared/sdk/CColor.h +++ /dev/null @@ -1,42 +0,0 @@ -/***************************************************************************** - * - * PROJECT: Multi Theft Auto: San Andreas - * LICENSE: See LICENSE in the top level directory - * - * Multi Theft Auto is available from http://www.multitheftauto.com/ - * - *****************************************************************************/ -#pragma once - -class CColor -{ -public: - CColor() { R = G = B = A = 255; } - CColor(unsigned char _R, unsigned char _G, unsigned char _B, unsigned char _A = 255) - { - R = _R; - G = _G; - B = _B; - A = _A; - } - CColor(const CColor& other) { *this = other; } - CColor(unsigned long ulColor) { *this = ulColor; } - CColor& operator=(const CColor& color) - { - R = color.R; - G = color.G; - B = color.B; - A = color.A; - return *this; - } - CColor& operator=(unsigned long ulColor) - { - R = (ulColor >> 16) & 0xFF; - G = (ulColor >> 8) & 0xFF; - B = (ulColor)&0xFF; - return *this; - } - bool operator==(const CColor& other) const { return R == other.R && G == other.G && B == other.B && A == other.A; } - - unsigned char R, G, B, A; -}; From 794ff7233bec96b769af4510209b87d2b15b0266 Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Thu, 23 Jan 2025 00:50:25 +0000 Subject: [PATCH 17/20] Add cvar "cgui_modern_interface" & main menu setting --- Client/core/CClientVariables.cpp | 1 + Client/core/CGUI.cpp | 38 ++++++++++++++++++++++++++++++-- Client/core/CGUI.h | 1 + Client/core/CSettings.cpp | 30 +++++++++++++++++++++++++ Client/core/CSettings.h | 5 +++++ 5 files changed, 73 insertions(+), 2 deletions(-) diff --git a/Client/core/CClientVariables.cpp b/Client/core/CClientVariables.cpp index 85dee800fa4..01b3006234e 100644 --- a/Client/core/CClientVariables.cpp +++ b/Client/core/CClientVariables.cpp @@ -357,6 +357,7 @@ void CClientVariables::LoadDefaults() DEFAULT("discord_rpc_share_data", false); // Consistent Rich Presence data sharing DEFAULT("discord_rpc_share_data_firsttime", false); // Display the user data sharing consent dialog box - for the first time DEFAULT("browser_enable_gpu", true); // Enable GPU in CEF? (allows stuff like WebGL to function) + DEFAULT("cgui_modern_interface", true); // Enable modern CGUI interface (testing for upcoming main menu revamp) if (!Exists("locale")) { diff --git a/Client/core/CGUI.cpp b/Client/core/CGUI.cpp index e73d711ef6c..149ee89eeb1 100644 --- a/Client/core/CGUI.cpp +++ b/Client/core/CGUI.cpp @@ -44,6 +44,7 @@ CLocalGUI::CLocalGUI() m_LastSettingsRevision = -1; m_LocaleChangeCounter = 0; + CVARS_GET("cgui_modern_interface", m_ModernSkinEnabled); } CLocalGUI::~CLocalGUI() @@ -137,7 +138,12 @@ void CLocalGUI::ChangeLocale(const char* szName) void CLocalGUI::CreateWindows(bool bGameIsAlreadyLoaded) { CGUI* pGUI = CCore::GetSingleton().GetGUI(); - pGUI->SetModernSkinEnabled(true); + + bool modern; + CVARS_GET("cgui_modern_interface", modern); + + if (modern) + pGUI->SetModernSkinEnabled(true); // Create chatbox m_pChat = new CChat(pGUI, CVector2D(0.0125f, 0.015f)); @@ -173,7 +179,8 @@ void CLocalGUI::CreateWindows(bool bGameIsAlreadyLoaded) if (bGameIsAlreadyLoaded) m_pMainMenu->GetNewsBrowser()->CreateHeadlines(); - pGUI->SetModernSkinEnabled(false); + if (modern) + pGUI->SetModernSkinEnabled(false); } void CLocalGUI::CreateObjects(IUnknown* pDevice) @@ -215,6 +222,8 @@ void CLocalGUI::DestroyObjects() void CLocalGUI::DoPulse() { + bool didSetSkin = false; + m_pVersionUpdater->DoPulse(); CClientVariables* cvars = CCore::GetSingleton().GetCVars(); @@ -231,7 +240,10 @@ void CLocalGUI::DoPulse() if (currentSkinName != m_LastSkinName) { if (!CCore::GetSingleton().GetModManager()->IsLoaded()) + { SetSkin(currentSkinName); + didSetSkin = true; + } else { CCore::GetSingleton().GetConsole()->Printf("Please disconnect before changing skin"); @@ -276,6 +288,28 @@ void CLocalGUI::DoPulse() } } } + + // Check for modern skin change + bool modern; + CVARS_GET("cgui_modern_interface", modern); + + if (modern != m_ModernSkinEnabled) + { + if (!CCore::GetSingleton().GetModManager()->IsLoaded()) + { + m_ModernSkinEnabled = modern; + + if (!didSetSkin) + { + SetSkin(m_LastSkinName); + } + } + else + { + CCore::GetSingleton().GetConsole()->Printf("Please disconnect before setting modern interface"); + cvars->Set("cgui_modern_interface", m_ModernSkinEnabled); + } + } } } diff --git a/Client/core/CGUI.h b/Client/core/CGUI.h index f6670828441..70b33373392 100644 --- a/Client/core/CGUI.h +++ b/Client/core/CGUI.h @@ -118,4 +118,5 @@ class CLocalGUI : public CSingleton SString m_LastSkinName; SString m_LastLocaleName; uint m_LocaleChangeCounter; + bool m_ModernSkinEnabled; }; diff --git a/Client/core/CSettings.cpp b/Client/core/CSettings.cpp index 566a374247b..97fdd04acaf 100644 --- a/Client/core/CSettings.cpp +++ b/Client/core/CSettings.cpp @@ -1272,6 +1272,7 @@ void CSettings::CreateGUI() m_pChatLoadPreset->SetClickHandler(GUI_CALLBACK(&CSettings::OnChatLoadPresetClick, this)); m_pInterfaceLanguageSelector->SetSelectionHandler(GUI_CALLBACK(&CSettings::OnLanguageChanged, this)); m_pInterfaceSkinSelector->SetSelectionHandler(GUI_CALLBACK(&CSettings::OnSkinChanged, this)); + m_pInterfaceModern->SetClickHandler(GUI_CALLBACK(&CSettings::OnModernClick, this)); m_pMapAlpha->SetOnScrollHandler(GUI_CALLBACK(&CSettings::OnMapAlphaChanged, this)); m_pAudioMasterVolume->SetOnScrollHandler(GUI_CALLBACK(&CSettings::OnMasterVolumeChanged, this)); m_pAudioRadioVolume->SetOnScrollHandler(GUI_CALLBACK(&CSettings::OnRadioVolumeChanged, this)); @@ -2055,6 +2056,11 @@ void CSettings::CreateInterfaceTabGUI() // Language pLabel = reinterpret_cast(pManager->CreateLabel(m_pTabInterface, strLanguage)); pLabel->SetPosition(CVector2D(vecTemp.fX, vecTemp.fY + 20.0f)); + + m_pInterfaceModernLabel = reinterpret_cast(pManager->CreateLabel(m_pTabInterface, _("Use modern interface"))); + m_pInterfaceModernLabel->SetPosition(CVector2D(vecTemp.fX + fIndentX + fComboWidth + 35.0f, vecTemp.fY + 20.0f)); + m_pInterfaceModernLabel->AutoSize(); + pLabel->GetPosition(vecTemp); pLabel->AutoSize(); @@ -2063,6 +2069,14 @@ void CSettings::CreateInterfaceTabGUI() m_pInterfaceLanguageSelector->SetSize(CVector2D(fComboWidth, 200.0f)); m_pInterfaceLanguageSelector->SetReadOnly(true); + m_pInterfaceModern = reinterpret_cast(pManager->CreateCheckBox(m_pTabInterface, "")); + m_pInterfaceModern->SetPosition(CVector2D(vecTemp.fX + fIndentX + fComboWidth + 10.0f, vecTemp.fY - 1.0f)); + m_pInterfaceModern->SetSize(CVector2D(20.0f, 20.0f)); + + bool modern; + CVARS_GET("cgui_modern_interface", modern); + m_pInterfaceModern->SetSelected(modern); + // Grab languages and populate for (const auto& strLocale : g_pCore->GetLocalization()->GetAvailableLocales()) { @@ -3711,6 +3725,10 @@ void CSettings::SaveData() CVARS_SET("server_can_flash_window", m_pFlashWindow->GetSelected()); CVARS_SET("allow_tray_notifications", m_pTrayBalloon->GetSelected()); + // Save modern skin setting + CVARS_SET("cgui_modern_interface", m_pInterfaceModern->GetSelected()); + + // Set our new skin last, as it'll destroy all our GUI pItem = m_pInterfaceSkinSelector->GetSelectedItem(); if (pItem) @@ -4926,3 +4944,15 @@ bool CSettings::IsActive() { return m_pWindow->IsActive(); } + +bool CSettings::OnModernClick(CGUIElement* pElement) +{ + if (m_bIsModLoaded) + { + m_pInterfaceModern->SetSelected(!m_pInterfaceModern->GetSelected()); + g_pCore->ShowMessageBox(_("Error") + _E("CC82"), _("Please disconnect before changing the modern interface"), MB_BUTTON_OK | MB_ICON_INFO); + m_pWindow->MoveToBack(); + return true; + } + return true; +} diff --git a/Client/core/CSettings.h b/Client/core/CSettings.h index 91a14ca40eb..4678d948267 100644 --- a/Client/core/CSettings.h +++ b/Client/core/CSettings.h @@ -288,6 +288,9 @@ class CSettings CGUIComboBox* m_pInterfaceSkinSelector; CGUIButton* m_pInterfaceLoadSkin; + CGUILabel* m_pInterfaceModernLabel; + CGUICheckBox* m_pInterfaceModern; + CGUIComboBox* m_pChatPresets; CGUIButton* m_pChatLoadPreset; @@ -408,6 +411,8 @@ class CSettings bool OnTabChanged(CGUIElement* pElement); void ReloadBrowserLists(); + bool OnModernClick(CGUIElement* pElement); + private: void CreateInterfaceTabGUI(); void UpdateChatColorPreview(eChatColorType eType); From c24ccf4497d38cf93f1258ca7d8b99f182c796b7 Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Fri, 24 Jan 2025 01:10:25 +0000 Subject: [PATCH 18/20] Update modern template --- .../MTA/cgui/modern/templates/imageset.png | Bin 38269 -> 12056 bytes .../MTA/cgui/modern/templates/looknfeel.xml | 105 +++++++++--------- 2 files changed, 55 insertions(+), 50 deletions(-) diff --git a/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/imageset.png b/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/imageset.png index 55bea772430d42f5f0c40b41dfb46836cd236a07..9103dd11fcf31747da4ed93d94096d634947a6d5 100644 GIT binary patch literal 12056 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 z z3+rxTBBa73c1dV>vj(l|CsU=MBP{CIQl$h@Lkzp%+WfC$L}T7 zobOL5HR-2!HZ<7R=GNxaO60RD8I>Az#@+`j(v$*5e0zU<_msN77Yg|)T7%Ef&wyq_dhO;K*{rY}mb z#_5$C7s<%Whf#C=(93{!=Uj{H(T1~?k0f3XBK&V92YSr(jIICfeIH)f_i*-f)_*nU zS5}^L=`38lo!RgujflX+iAxqlpZ9q?@& z{rKlXT`ZdhMFC}m997L85fNdUlAN4l@B{X@%VTBP*K1;A(B3%DAIrkzgKD)F1YT$hdvsMF< z=iOGCbz2}j8Weyn=5L~T zw=v$8q^_%}qoMI2siZVD4E*T3iDG48sr}V_l&H_*Hd8wx*vd5L-KNWISiPtyru52U zG!Uh^@p{*oB$Y^Gu$pSWs9W3a_44mF@$D+s_qlUI@adFsw8m%i%I&VWEKv@K$QM?^ z_IkJqO=Yu2dVO3MBGq`9?8#&9{C6!EV(=FqLsp<|)4%h1kGX_dw+%l}5usb3-mvdtDV7}Iu!*K zAMcH-iRXpR*Lv-RTTS4b%GxLma8Fe4^Ws6ob`bO1{y=mj9<%tDFVLtYR-dV+;bu6_lzv$de*5-qa(X&odK!}f?2qHYc)QZVE$T3g zoSaEIKc#(uUcMe#PcNSv>{~fvjos!N~R9rB2U!8tmkrm6P2UNLu zIX|jxbRHA0&Ayy@Z+!{QrxVg>vLnAcT?z~dfeZRv5!P?WB8MdkLlIDU0=`EG{x&r^ z=_lZMr@z$`JjrpE9>cG){%NiRt1H-sYQyrzf9&BVL};gMpbB`Ws5U3-nwX!TPtVl( z?0}7fV=$Mkrt!RFSYED$7|8(sA5JUi2~~FTwV=EWSzX%)mj2_%7T&dXWlTRW%^4Q^ z=f;@9?UEdR@4Mz&A*+iOl8<1xn0%hj8mDGwgBrL5uV=bBC_kK5b^XC^;=As#=!~nL6y{u>Y2;{&d{v~67(=6@WtsdIYUlEme^pf@^}y%PFVQCdhmTbDztk5z5avVM9vsxeH6!T88oX+sWpwXI|;|5r5Ps zW8fVw!{}!NC2~}uQ@e8Ac0$@Qe#mG_=l8SnU+#`zMq3~M?srGFGNce>ksc;Wq4=Zj zA`>?_IM`wWHn3YBD+ouddm*ptYVg(<#^3Ju8JTn&b*g|N7mb=6)Q>+d&@&v)Gb{p0 zk4bc+^k2%>BltPda2ez znqKB=obMvnX2FL4{@L+f$Mwbetwd$LgBWavb5W7WSx04IA&jW%0<79GcGO3bU_(7t zFz)$++Oe2=h^Dcb(FO3tD7GD+=&#@cyHT_nth$Ht{KOppfMj#*ugohBw3q$RW zf2y#S+u?IrF9+$fJh-=bE-CODZes+4Yr7x*?Nz_KW^eS|^Qb=s9X0qWlJ7#b@^x#@ z*KI$|Y!-}-wvwu9-R`c6yv@BcZI%-omFS9;Z zyT7>wTWAl~Oo_wC_{ea+F9!#2$T|!5BR^H_`lr-$DXO4E1i~WPki0GqFSfKOJAxY; zxSl6!-;C-S8eaL0D#8$K+h68mtDdf$7gW{NcTOG0;V_7~FWPPp0PpI%cOK^o6FON^ zpU;t0N-WYo*!?Fc6&NI|9z@h zYzUyJz>w|>fPMGZ>5SiK0C^&cfZfOU?d4vDp~qHpMOh}t`-NbxVHOiZi2<{~^P)Ya zSRtpw?s)c>G#E6ZOm)_mUL(2ZhgDC2J4m64R(n?eOCpsq0F0+^o>S? zs6^a$`pzv+ECWt69KJ2a!wGuN&*M2KWnU|L?B48T-}9sNzVudqpZoZ~m#f;YN0=E# zTeQ^X>XeHZVZN%W=C(Bvy>GM;{mBz#S=D}rHYcH~*I++0uoD>(ae1AP(#dPEj~F@+ zMckEMQPH^OG$SYR^zu66d*8ZYFp(|rVHD*(7xIFxC!^2v4rynrspX0Fn)X=cACt9$02X;)PfkQ7C0e|vo*E!SziJAQtCPE=E? z;E<~&CqnJqn4Z4oFgi6-Q!~<}PmZoX_;(~dJ2$t6iyC^=a#C6HOQmGF+#L+E)0*>w z3O7-4OH0d_GcZ4;{?Y&feR3>p~V3@?GTXt?E9f zZhqs!w)VvL;!cCW5yG-9BSF_EB<8IxK(!i^7Z%+0$A;ZfC=_(o=>rC(8iVhbyZ z{7$T??G#;)544Te&d#?%!en-5-GT55cOKp<5Hj4z$`6c;-a^<&FE%s9av81H0;sT^ z|K_Sb;v3!5MX;r?eogqrfI(cxaB?B~x3=}X<;3#7neV)*_r;OU=+sb5N!}=mn6^^K zLUUdy+#C^DA^dlu_r|+d+mZ<|wH)SK=1(nRIXipWU2clN1d0H{1_lPs78Vw_lX<;1 zZHM`hKF`;q%*^9XIjlwltD>T@Ubl1V2*ICSgu@1R(g=~^N|KY3*0@doq`sB^`lUHL zcQK2>GT(xW5r^I9$bMl>-op<`l# z12{f?%Gp?4bbHMf@T^1$?ft|yunrYBk2-0>*40XCt9~$^_2lC1{elI~K~ZaKMsUcj zj%tgFVC(DaHR``pAD^7$r1H6*uUz=F2@J13pAW9gfe%EZh##VCbvgU2qLR4w2;`pd zrl+U>wp?HmrHGd(Eh`g}bk4~R$)~#+85u!GhEvkhi}ilGD|((?SSV<2CcweLQB=DP zP`#(fcNY^Avz5>0ckhXpAPJ9%Kr&Bee*eNLG5KZb-Zph zzz4zH)BS~nqvK6@qpq*iw6_OgR5jE9{_^s2E?orjq3l1phyZhrk+Hgn1koW;4jOcX zXVd9IDSQrd#e}lVu+~0!6v7}7r;&pn;9;Q1s58TEI+iV*WGzl9fYS@+UhaP<;Yx#b zYjyRN@Hoosq8qT%Xxn*}Aj4FB`v^u2F;DSf;N|IlWsf6z;#-Kp;>OzG-Ni<%BAu6! zVU`Dkf$Wpok{XSagR0uCka#qq+E*ndj6p=Kyc6D!^z^JAe-9B$OJC`9cu)YB)5W|$ z;Fy|{p}RT!NoD0Cv&5+n87E)f!(dpYRpF4tW-5Q z{rcC9KXyEwGcwEP-;p9VY_-OpV$oqk?Mmr~7oT#yP7;|ErWc4GH`IgIO=UB&l4lq~ zj_o=pUj9ycM#e<6D8-%o{ZEBK2!|X9LO+9}(EI?02^f|0q9g2rCHR{6e^@oN(9si0AE0}`@21-;!lm!`Dg4h{b@bzJ*Fe4=6czT>?X|v<&4FAq< zTMqXzyFH+@kVhxIoI>IU-AHJTAL4Jv9VJNxrKXlghXg)WM~4^*2`Sg&TjnGPWR=(J zDk@O6?3%vdNOMZG@bT@*6mq}Gamrv#6)Vzl@CXSBm0Vj}Ta)FV9gPpxXGYbhD1R3b zfi&1|#1zS-Y%dzc_7j+88k}rKv1%kICE(00EVwq^YH4NNzRTdUKE3qjvOTZo%KH*v zqG`*)!67Sn`n$Jg1VR!E|8#d2XrftFOrKZ?hj~?R3<44J@85fBOh$iu+mUxD>6{JAHk>p(0<7+nSqqjc4(F$|!y`ZgH@O zA#+mrSsJm{>bi3^NID2l>gAW5jBB-AhZrmL$_)n22pvOJ?d&VXp+%zwQx3!J0)f%x zqZikK>}@r}JKZXEheay2DAoo_Y$TmV5V4=GG)^#`@S;^MM1qUg0IW;E^z?~+Kv`mn zEcky5JR{)=^bpYdtSWcSj-e>m6VnW z=nRJ_aD+6)Yly3<&Cbu;bb_5VbCPI>bk&p>fy=WKM8gy%{R6wJurUtG*0Qj7T4rV4LEv;4_BGiqabR$}6*;o=l6f1rRq?g5R)qZem zH&G$xol^2wni&p^Xz{IEmkVY8cuI+E-@7FhF~vJ4~yMTxfh^(|H#Rmf#uNDLaXX0a9r_ij~9aPLG>I#xXC zxUSXX{UYW&`y8Ndi{|IqSMo)&OwXhS=B^MpY{)1Hz&3PjmO#ox?rO(0iF;gwYg) z(LFT`q6JFO8^9t=7ycJrT@r9K0kWNIDvE%G6EQOM!%4!nabXIKp>c)hEVn;3%$MHXIn@7ygH71%(jW z%cvl5i><20>5#V5kD&73oz6_B=I}JS8|$9nRw7~}nvGEYy&3U+Noo}3z&agM$F{7)P;75&>wiS@SqHiWNxal0Y{?j+ZocvbVqL z8D;K#?%9dqL){s$-&nXiTal>LZoW=9$nkw`+uz@R`}^pnXX23z2PD$SDT90i*5JN_ zyGWXdCAe`&yZ-dQPP1ur${$JLzNbyLkKO*_ZUcXVX8Z?(%Kl+1)a(xicC@(6=;&z9 z(~*KNuS#)!;su|{2)1k*8|;@5~ zTn3V6p4@tLW*qU@TWlNeNIKdv9ez+_2_0IlJ2r%CgV%?0dq{@raw+Hl!5%)J3xi=b zKDL;Mx3>UO+hxz)SAl~O#gGW~uU~atj(?SywhtbahQ+*p_f$f8N`3zNo9U`>Uy4WK7$O77bR|_ zK+X}5)OUT`JzxWdY9B#5)QTs-OHj|q$Y^H|jsLQXeuDo7`Mag1HPp|FihlM9{=iUD zpNAxHcAd2A+J1WvkQw_8C(;`AhM-7UF}%l+l+ETRU~d1bJdbyBA(=!;hY@8mfkDNQ zmes9)=?Ri3K8{N@CL8p0bg3|QkNO)O-oDe$FB5_}%$ag1F)GklJi@j!P{sm$@7GLb zF&kdeLOn=Rtc8#(cS`1NYRTnMWJQ^Xa!X45Vc+3C-+1$8z#Jw7w1f=TA5%g z7!ZY%fsc3J3s;$iDezDyv66*9$@A>}Q28%UWsV-~?PV=H*B^OG5@76}t3C?MZDgN& z0p`}$C{T1(=H^4+8ft2klS%#j#yT14d47CUIiO8G`cS7%b2#iau#T{Rt$!A{YYFT_Rp{~jvkUvy(psmwDbSByE;p6y!igRMEU znqT14?a8vFQ#+->n&0<^zt5{_!wjT{FX-ra4RE@j0n8{r6JVaxK=rarzrsHJ^Z%ey z>@;;^8fuX~=`3&%MR2y{UC7SjdaezG>3G?w;0;H*r+07RH*h7qvtK3yApqj8n=&&&G-slTmfk?plEkQ#m6M(BDOhHW=NI^4VI z6f_$rQs5>MXGj)sY+X13SxCpyMt6dGC2= z{{{-8o;ySAXM~ydmF|1~WP@}mN$jBR?|4*EnApJ?qw1*eqg7S4;K3cK^kguV&bxj2 zuuE*k6Smx5kI(6b9k50A{lpT3-B2+#pmh4<1HYigk)tS-WIV_j76)48tOEpn+o&t@ zxArMV79e8RS-1kHipauk`3E~?5hPFeK=^9sg|DZ}jqg~q4(te3c@YZWVz=+drsXsl z{nIwr`H8eC1Z#`JQ&;Bg$0K}9OQ!Q+pZ2WdrBXJo&%gLeOGJXqMv1vsqE!}6x z8G4ZGoIqQf#6Wi3p)o=Zlz}%go|F6;fyB59Oi7vwm-fA)%W;?5%mG!}?KX7jDm2gXA})AQIjTr_JtC@VUm~`(n<8j`6H3 zG=NMHnC0nu9gh|Q_I&-ZG~M||3NZutZ;DHTTOsSfAMKQowBldj)|?zPLalR(YD+;e zf_*-#yXt+vcIHskVszB{9t3(IB<^3Xx7>-T(rLw&NoME{#?wuGCsDsr+4hqNrf0m$ zHNP$w8;napDR@Oo zC|NRT5GO%i`h_@AY1kjEU^QnI82Fwz{y7|>2Ui|gF8g%+p3CZC!bNw6oXCAQJ{FM7 z@n+@Z2Nn~kWol5v*17ekF#$iS(X^rHnJF`G0qbQBZM$w*o=+?oTa~TnhAoxM$sYVw zIfhk6lG>?TuNj9Qnhk!AMg!xg|*5-$T?kel1(c)8i# zMC2QhpPQQtR8&-az~Y4n>t+jCR7X6Yb6pfie3i%PraCzn+<&#i7ss^6!^Ne*M;?$` zFSf|9tHTDSk8Q!(hBD-OhWvb;(pd*mAE3a%^c8!LLrf_K>sIeSmV$BgTMvGjjC0m| zskF~Ul!p&Hu@YqHU7)lP-qONLECgglCp9DST^vjeD#bwboi=izF;oyYIYh*g&t)xNj~0W~)*!;;n=(rJ+yj&hA^sFExfLQKS_P=>s_N`nIN=yXMJ zs?{iGum8T%-+4qqK{;5MpWpAWTnxjYJcW+BLeA^fN0%URrve#Le%TBz6mUk1NJ&Xe z(@C}o9(+<%`|;z)=Iw&sAbnL^0_$0M>kWH_uJqoa6o2-^iuZ-nrANElUXuFdzmuwh z!oor=M~XtQ*t!7+d!7Go(TG+yhl^NEO>I&tRGzoAw3Nkr*@?*Wv>FS>GLls{lqS-(3#+Y`*q$%mUYS8^gO`fDNl8iONL+cWt6^DBu26uudX%`P z55a#;0B5q-^)T%l4pQ*Ee@T+uXKfjGei9J0xwY$hitZmA7#&XLDPnhchAo@~Z(K+9nvR-)8P%$aVG-?#FYj=S;W^8PXi}uhw3k?Tk1_r?@e*{h) zMO>x{*3z}2zolxP95a#(Gg5y|{p7dH!nYtw17wf@+!=@-ZL&;VV{3K27{Jr>@(&1r z!p6fZ`28ClWMjaRP*DxXUl;%jQ!sf7ySwv%#k|)wB1n~Z{2`{}@e}18pPeBY9ST}M#mNveP+m#SKQiC>JcDSf=kz_&P8_j`@L=X>HTx)u-%c$vc{ayEz|2I}ra1tFdHk9zbPH&j`o8Ien3Vi(U z7p+RN{Ffz}{v-)1%x-Y&1f{e~P+BYa@?F_=7!U<}@G;YUKaKl-)p6>-_=>3L1;7Ei zRgDngyME^^gc*+iw3MYkKNhz9L4htTYsOwJ}yW@3mNsFHA z5J*;T_hBiBf{-v1^2a*-lqt3NabW#l`_RpSf!mVZkKGh9LvGD;N^zL}$9{Zeei)xl zQoRLllo^hhbHe$(mBH4WIZ&o-Yilc<0>PTH^xP6*BR#j@tP3VM00}Xk?YhllI*s)O z7U`WEATO;jGCCSb4-!hwyQ;ICb9tshXsl%<+R=H`U*az&20>QoH(2jSwT3v;LS~uu zI=n8ygcx`86A>;D=we$USsk67&Qf?jp$Q2R*1$!EbF9oUz}M*T;sIt4oJ45-Kn4LH zS@#~S3ts=@eDMXXxZBe40wW&wX&%?Tb{}tkdrsK=r_+Fm?h}1P!c(PzP8d0ut&8W% z)EifbSC^Mdn;HGXH?TCT`IF5{<(!~~V_cIWLGD=REJ<8oXdCg=iZ0^XP(@|NUqSG3 zcM7CI5x<0@iYbwA92QV727tVkAxN`ie92Rs*XeB$i>fFqYh@hF_wR%LrQ8Aq7%}{^ zk`NY^(yUXqqDx|(N$&ekWFETAbeU)1#LVRF#TI+a)A*QOXNj*p-{~}-+fg<67vpbo zFh}^|qX`@Ip%OB>u+Sg?`Rj;01hnI?k$*zA^Ye*;Kph_7ll}kSu)y4Yb}HF1_k%9< zvq*n2)d&9!)IXI2v(Fb!x}>~xD}x+z^TDEVw~xaA1mb4=S(DX4^79p3)~}FUGD~Xh zd->b^zsF@&+s`)>@Ez`KPy#Q1XEfS9P@a8Aq=~~t^DI7^j%S9u-|U0RA1nZ}JrN?p z%pDIDU&}Q>Vr1tSOhKyJ-nVmpVg62(p||k9?k~3s2RL|mJ0M@;L(;WJK)x`f#NXJYk9Fn^O5a#J zw8Y$k0CyaWjaiOvojHb0%Mp!hvsLux#&r*RL2%qMkD0H)MY27wW!wJ+P2h)(V^HD2 zu5ya32?a*>?ssr2U_r)ux|`d{_B`*-a$T1Ku+`L#)?#NW3y+Z}1@2b^oloWs{vNVU za^Qf~TtLpeJQ`r1N@3%f=3?z3eTaJ&l3$>pd=hW0^$~9}v!O?H#kJa_M>bKgfNy>X~ z)8xNDkz>2C1vV+K+3{dnOsW(KH*jt50d12TcUD-ZVqD>>j(;Ds zef|;D)dwr7apDgWu|tDi2?$`d+JvF!T|dgi@34z7R9C~v$@u_ITqV6uJa_lU|KJOo zKw=PoKq!d^ol1L6QUSQTu_|pAlvTD6*4OhX>FWA}iV((ySDhT+%Y%JZdn58#hU*7Z zqQzLH0`Z+Rv+S6%z*b$UZ+@Zn(=1!CmwIUY&t13hcf?(RXo4E3x%MD8YkRuh{=^qA z2?LrxUL8R$8wU_y^L%UPv$V9l+P%nrhV}E@g3X0CHX*Te0;R`$8q~?kC$na=kpq?i z^Yu2Fvske1QcKBfQcY*$uw5_W0L#tvPZ!g5Cc9TZhyZ-(&0dMu6ATK97La>F=vo3o zAPp7RKA!!5u7llI>e9Da{jX&Tcd1Tjc$kmU3#%*iZ&Y>JnHG7@AGA- zo5Pjiz4JLnI!3}JQ5XabFOR&y zM@B|9NcPxix@F~~5J;NyL0HnJFG_@7K71UZXKL|&J(DpVtfwEHbb6qUtzF|Hwcg=;#`@TKKeRaduz zkskh#Oap4q_3PuEU8;7I^tZN~DXz0WiKWpXVWGd);@kv208E1*8y8q@G-Ik69%zuN zQEQ4~)3gPL9}@oGlDXa;Zn_!gThSp6AApXIBze9KY!tfPRdI=E8O!)&bhAGO0G%7L zw!Tj*zZu@yc03kmS#E(Dq@mg5OugRCeCrDWT;N;QnfqB1q~%>!9&BOIE}#s3>Bj$` zzB$8lkog6)sub0Z^G4cqJnm<5cYzC0)-1~zcPxu9WgPq*)qaMf!|y^qN6>o%f_^IWT=Z}aaaRMEQZf`$utNKvLJP!oz*X}xJ~K<55#_E zp(yVgpf?2rx3{;Wfo#~zK~P+8yL^7febkruy7s$ji^%-J(s0zt%mFarWx07yCwl+6w+VCE}?CEs!{{|z~iFqh-NtQm0iO{t8 zV|8l8`7~o6Cn~V+HOpPO{%X~6!eDPC38W&m6B?QDh8)fFfGr}pIO(m}mU?D?qVEU^CK*4pAB!xCuQa1vh#OSehNxYc{LuVveA za`F!}jWIynM%r~MOG_06KB`tBsi{0Uiz;GL!Q1iqNsqu;fYvn^H`~lFV$;~0IBRj- zL<9gaY(I5>*+QkJXH#h@K99q$2QYJebCdW9z`+dT8?*k%=trHZ(0PT|IZ+~>egt|*}(8ltI>wh9jqUH;Lc|O0Gn{& zx%o>{{_$~d-bW-!Nk(K)Dy0OX5+NdxKvOn#CFsQfx!m4^ z6Wv=^FbO@ZY`o&8Ak9mta(MrvdhoJ=NA_i;;f=$d`epaYkFFPg2lKVt@8Bj@;5rUPia&uVxH7BMcLoVH!vH)7-oWz}30EMEVrZ6^H<&P_>5asT*{ zeiAo4%&e#saa)rb!TptAFb<#E9!={VSUH1cwKWkidVw?Op6?`A=%6I($?4efIHu@R2Y2lURWD4Va| zU9CksP1NE0V-LJfYyO9Y9Q9DQ&ygRACoSnx#S%wdj39+>n7%otz{Q#>83P$Jv)aXw zkQe2^t-r^2IX4L(cNRz$QI&V$hsZ(_-^;PV(fo`P^_E@h`tL6N7YRysuCJLQY0Zo2 zk?=|mXtmswPS=~~S?BuO1C~(!AI94lpXNZn0U1J*Tso31{Ai5?ac2s?zeDQCARQ8I z(5!N2u{jdM8hRG(iP`lOny@P1LZ65z^36pv6ic#(l4TT%wTlXld83NeQI7?RHBv~y zeq968z6EtZwy-WcOjiPc0g#p)+nzUG`-vLH^oJqvk3!wM8`!Wg?|$=*uPOf70$4<( z|0Q8j#juOJ2(d5y)JK&`F$ll=Mqk2?zcT|jRMR>{QZ5RN8bE(s3g2$cG(5FH2|Lk; z!}E_ot2Tx@a)v=io5~G{T*2fIykSe0p>rA*l|SHP9*2pfq1m?;t1*`5;ROJs3wzfC ztetXVTwgfsJnzL3I7g3=K68`g6YflRIT!i)WIJangox6vgWH~+tKxDGhGI~+=waK# zmb#k$Eqp-*4CrK2m6+hfupl&PFmd}!#u8j@z}V%JmlzN@j`xyux3Lo#ecKk7s z1Z_C$sQF02u&}AB{D`u96Mk-?suN%w0m)<=3LjavAtkekBRk&Y@TopT$?xcfO)JW& zCttZ3GmL^f$0aK>)fE**3^3h--ipyMNee@x z)n$~AHFRXW{wuivVt=I<^?iHe43#333^P0+oQK!M8)jHv05;CcN&fMlooHrc^EoO035R#S?o)7^;O3?W%blVj53NB3F zQ~XBa3VbT%uuOhkJR>@?NJtr~5ys$0jiraV%{x_txJZSSUYhT*4fD#26ljos_qVs6 zQi^+6n<*Sjj$sq@hcQp~ciIoCnHG}rc*;Y8uv!~Pf0_7j)Al0uAuFC!0tCY94-26>E+(btPM?EnGHEK=5yn zi1Mc1{zi(f3Y0MT`f*dKK`*(Z;_W&0==_(y1QSD|Jy{0HXZrT}fKjPxNGL21RzF)Z3w*?oUA*Z=ISK-mi}b zv;}J-1 zFXo?9BA^S+H*wWJG=iHTmybudWr$L6oI%65t4?z9TBkN^&+tAu&Sg!lL9^o|B*3ol zZZJGk3kMOokWP|16pQSbe320`qjPqJaWMP~F5W5r4)I7(l)(xK1CvabH8&>9N@7o0 zV!SA@_5r#OIuLf0&0uMoUA>G5I*KT|CZ4+W0F_V~rn?UV%B)rv#xPFnnufE2$tIl_ zSq0sXgRZ$mZ>6cPDN@~j7Ez1%hsux4kFpLyO2u6qx0&3+>SXe8hc<Td4jI^5E5!xgmM{Lt|5qG>^Ks=5ge z5P=OPx1c?t9YdKSNQV&mV>U~eCHUJ2tn|tzC9Es&2 zI=e`-9OMyC#|dK~`u#z;D5h)U1Tt7>ovk$PfSi&%F*edAMj@Sa;|OpRB_g|p%a~%K zlWx^#TNCT>E~d*i=8IL^MHcjlVr$5L>>@Mh^nmpP zrQ~O&t`f3h1h#zcaZ!sXMa0R$7~&HGAs>E_%6HIh6c(lX@F9Mc%UhAID?GOqSJc=8 z6&iq5+zb-@U?^yEE=lx(kWJ?KtJR7SYd;NK%D2-mt46Vx2m3-hPjLrxB_Mn?-g2zh__0XK-_IqRDhulx)Vg8># zo4ZCa_~Ac(zEYQTfR#u0I<=P~is`!@M@sE!y5Psb_y6_zflB>3 zcV(3B`@yV=9bUqQb^tx3PnAx&6$3-MxuIouw)sw_p~hZC*ww|S_<%K@aBV7?h;uRq z@`EzM%KqCku49|ih_F9$Uz`a}7j=PV43GjqanmYmvj#PYUZ4l;B6D?OLJC|&nfetZ zrtSK7n{B>DB_c_w2rKXf76L;-Xj(1;jtWoXcqwtT7bmY3O<_H2JZgnH4bmu20dT;ZNB_{W(arS{2REz74U*)oSoSN*+dI zmG^M^Gd7(Ig8|YfQE_w(&~66h4_x0Q!gvDwh@v3#QmicjXv2^lM4VIkIR!-JI}B@% zXh^Bmz?v`d{E5HbLJ#-+8j^XJHeXl!8Or=6jeCZw&ns69Tt(U-rh?r<&N6 z+jy>bD%bedk4>3)r z;d|UoGy>(*%NP=Y7AfNxG6k9(Jx%irqm*nTx|xmn?Z@T_lRN z&yY2>IQ&0droJBMzs3QZpq$wln^v6WEWH3T+9~GcL3BbH4-4S_brJwJBQ>mc+L?Fk zQ201MZ8bd!sNEFvorh%D$aXD;EY9D}Mu+L+!-1|s(~0b8@Vo-hb-GlGffPI~Ulo3s z)Zw=t@NF|}IlEF6X;GwTvnB)dVSp?SwBNfxRy^8anwi^=P=`2Qht}VLmMgHjYM2mT zwg&AYpcV;q?ZW^UUSdj{w;m?fu~gqDflcntH+AY__b+)CxMh{f3|p;jUuf=9=otVJ zHEY42rLh%JqUQ;NgHZ&IDh#+X6o1S)(o@`Im40wjFYa6|nmFm$I_A_ejE=_~-aKxP zySM9R=z&%ShGRx&$nPQV?_jd~ZAC_Nh5)i1;E3|MoC1EcB6F_oiA218*Lk}9VKB77OUAW_)JStC|a3Jg$S z1&z3SqbYhjObZ#uP@4t!8@|4Tzy_#l#Ffctv;=H`=QHTyXy`a5I9}KlG9jG`g(n!L zDeR|+S0b@Y+vqO#jqYMK@F^8=TxpUjOq72z-5Fl%jMcPb$N+G8d=`Ohz^t6ybQf5} zHQNf1paCUVt0;^UkgKb!8fd4b#TL{V4g*bGr(R(OLNQCrOt{7OCk~%MkC!KC?{jzx z{Ory$Bu!;Q`z{4}^Tr)xuHe`GkJ`s}L*^(a6YfnCE)2xPL$cavqiw&lXcOcPe&1;h zWnJBvt=>>dA#^{98IW}Y2bX-N$&;VNJl#|alT$9Rw3LhQ$DSJvV}M0E3vw^2D?2iD zOXZ5P0|Rbg<%zkC4agL@S5=IHmWQ}fXfzyoRhHquls(f-XP4-LZctsaf<3|BS~1oX z3lyRY99@*o+EeUNJTaK)L(|}POVWkG|m^%=RypfK2lL=6%8j4#nS4-o7OaOe(%Yr&Z2-3hT*fLAs=O{ z&i?YP>*4ljz8_QzJ)N50FRy{}@}P~|lD-4>2nIEui1ZlP{sK(9LSwm^XB(eJFaL48dUmHg(M#Ukavjycnz z#+6r_3~)~b0ktC?iauI$q9^W9LyF&Y=R1`Nc>$8j4J3=J$r8^ zyj|-F1`z2ETiCc8QzoN{@BkAIG8BxGi3R8*k#;AOLvcP}e$ZN=HD!nX@BjhNKL$`K z@9q%@-}hiZiA7*#heS{87}r|b73mN))>c@t!XlB0>C!v}+Q>$F6n*`{X(qqG} z>fL^583sX|h^+{^lz|x*ea+&_zwueZ{*DkOMK{cX_CH&TR$mggB$a7czG!4>zhP zUy)EjTgehiRTg3R#JQaF(MRreVGS0!EKi8!@r|K-%&QWs@bDSuy>+V~g(2*)aj=Q98pTI2zC%MUW=S-pM-COZfY~gxLv@fGUBX$$ zE=+`mato2A7)WFqIUwTMLnj2K-Y^cq#u>L}Ag;#! z7=pl|k*tbUr4#?Z{*04}$0%$d?qbD>lBq({DEhMk#ISb+efyO#(vYe4CiES?V^qUH zA8MNW;XfcNs?kheN+nh$o~*M%wwlIBGh?m{snkRW0-}v z9_@@9`-{xTX_eSg;7zniyJm7)F+Z#n3;B#|5~9#Hz_i_HGFeQ+PZrrxhmm#gByvDj zD3e7=d6nD#3}PwJ^=KmlcY*}q;bX+{O?3Q;ws;1puWD+}OQWV={_l*wx&rjs3QF}4 z*yPIop70f{AYO!w4kgm@qHnZHF&rPTFXmM+Bb3gQNsuiuxoS=<5JXWCJZ%W z6p5Q^EXY0ti6VB~w#V7%`djxRngz%GbBA{i2{SOLL#c zYfX8t{r|o?=wZ4t4NLijBNRFv9*vc)9)gp@YEf20$)tvAd@OCTKVV{xiZ{yt7uEPX zoLGh1Z6bwVG&j$zaKfj^7&7eR?(%42Ic@DU8sXVS_9r0gzmY(c-uSK#e zS0IBM?9s<22biPFgD0K%jw`Fx*K9kwSE|6;;>ifC=hcNk2$B)YwDIt~GjU7hAa#Pn z;ErXwDeV`ihmrScYG-p?HTG*|c9`l1P3lMJ7$DpLF)Zd3c$Nic5+Ez3t4_-CI?K|? zn$WCQT`0^Q+*r0pZ=U0mOkvHO^yi+)vCMb9Z4e6@d#{DA_M@u8 zha7mO zC>Xo83Da0tY|IUR{-g`szY&If!=ztTKVDMbL_!Mw^_~}*exN;o4Xe@1qEW8P#(2~@ zCKka=fZSiTSt-y9srEhpcnMj1Y?QwyOfOq*2CIhTx^nG*&nIJ|hW&t>isYB}0a?f^ zsHtB3on3G=R;p))a~jEbKkFV|ahRplKLl;9s*hd|rpr=g;J28`1Zl%%ck<(E=|5h% z`I5lXsl!HMe8$}%(BcRXGn}Dnrco5f!V)!aqSu5WU@7>Y)NfKdn>JVOh2xuRO6Wzho zqcfx>u6}_8LJFI!z{zpGzeQGKFp=$|c#zV^?2q>^ysYn~YW6=fS$2ro-IIRIG!;7)>p}l4 zbZv_pm0KE#3qePVK9YqIxe}>u$&KijBhV0?j*EpcgY^%G9fZHdi28W(YYaBHco6Zo zOTm()rMGW?l%#Xf1#a#io zooCrc9cj>|Va1NQ&ip<)jzOEUNjLW>Dd4aYfd?o5x;6jLi32lX@i^c|*hU5pgA(dk zT;>J0h-uF*u~pt&c9GpCSed_D3j!klq>f?ypwU*t-{4>G*G?*!`p>%v^BE`w_IZ23 znR-6($&J5y1`SK`!4TjO&+?yX4~)2BL@u`~w>QySo0|_V-Z~LJv?DiRZ#4~~+$VeA z;iE~x-@Bs0h2FXTU*TH+XWZ8Rj~3oB9ZSt@hwLsNp_~}N7indw8VTc|t^ZybfMXxz zPy|Sl{!bR(DJI0#-1p}{zWsMfiWh=LOXdE5w1NYy0>ACoo{&_XgjFQ37?9VRLE)J) z=(4D_+QA(HEn0$riYc9Lo^P!H*C0kpv5PXZD{VBKVlY&~W;!<*+4?Z&_)n+jLFBXJ z+*rv$xb>b`T4RYut?vca-JTIGF)OfQ7iQKSA_ST%x-xKcL+3j%{Xk7nIgm1Gk1q{M z5`YCKiqI)}@1R{ASL(5Y`uQBMOO(xAX|tU7^Dhkgr%>+)T*uEo^-O2|@iAGNuwJno z6~aS-@9cCDM#tC#IrZKL6`!Iw=_7VS{wc|zE^DflX%-V|B4fZb+gVF0K=@uZ;f?w} z1-zoScli7=SGzuBUt(o~Y;Bm$byIqu3c#juxYB32o=K0GU9Gf&H0;F-7l>x4d9#%} zx4ntO0@vQ%MTc6N>7c=#G$R=17YmJ*PQSHuwo65=hg_#c0;Qwx`cqkZU|jc=dzJ=b>6~|ova^f;F$Q6665|>l! zlvOsOw%K8Xle&-!tO@eSTreooZEz#xCCHQHO^PvYp<*`8Q^Mb_hrMF*;n3$^VOJTo zUQ;yB zWZ!)KthNyju=?qjufe@2NR7sR+o``z54CH!lUSXt7=rT^OR9_xwFLSE5ewzzfCDI` z;vfr1*}oSaJ?CBQlKWYFEsL!SftD6G<9FE2@V#;EydTq$Qg~MI?C}H*{sRdZ3Um1t zd`pt4UF*xw_$qGs*pQqne6W6<^R!#a*89BSfwd|iczMvX9$YfyebO;aZTELBwmG)T zDw`XzWz(4}$NM;L+$7;+M#fNrwM;MUmhu356GfgT69EDur!7wTAWYX|{R-%p(-0xG z8g6iKx{h6krlGw$1Kw%@wr$i-E0%hJcj&e|8F&7w!Bq9~#}I~rVFkwVX<$C*lVaQ=Q|(@5ER13J6(MG0C zJa|ex&j^|7ha%lr1Zca{U>+Gsi~)J?u>&EqY+|8FJLIfK3B`up#+c%OINJL+_vr_EpXaVaAv zh3lPr;xTJ?#tf5pp_1^{w4?OupfgwC>u(RFb~o7nkfB)yatLgJH4dv9mDPJ*Pd^E& zWUV~SYYH@lV4JFIH90TVX`v81eUVF_KFB#dIqd#{#HNabjZrKXQ*`lRI#cw(qh8mp z1oE_tszz`GO4mBovvtMvF*GoMiIfs>OfP9r#7!s*wc;+|04P;Yq@`@E2Rc{u&WVYC zfuH_)p^w>pFmSEE5{J0v&aGN}K5*|h_JMQMu0)l_YfOF%VS^&q050pC157%pHs(qvTPZ3{%7DkhZ zCW0`$k01Lc>0>dAr4N_M76RgFKrbQ^3Lj*Eo_YG(`I^G97S+g^JCqTr9ZT$Lf=gv$ z!}pr3?^(yFC{vb^NB%2!RkUn2zKxv{Z9dxk2+!w*!t#2K><;aedN^xCw*#3LpB`Cln`qr|P>uZ@H+HD`FkPzuPp*`H7)ij%r0p(&xJW z0wb@nM%EY(8yh|iQ=Soi8@Y@!1?QtQ4IFImxkI}a35QhrRmYs17PDqNAT-nQZH+-k zi?xi^mJqg+l^Vkd9UCU!Z0P>i^%&+0W(`}fJ{urW><3KH z+>Nv^aC@RyHeDS{f??>!y*|qoM_rr&`>Nf0GC5CPLA=77GXL%zn>WV2#(POCnX~zR zHWbTR`7_#IB>5CI3Gmfw?1fkvL83ZkxtIh~c5UhRjQkBwWidHmIcWFBm#+b-)16GK z*g(@$Q35*wKaz?(8l)%7d)9qDwM^EVM=6J&7bH&Kg18c7E=>1JcI=eQU(-{L5qa}o z4hhJaX0omd+#tpilX+ZSAal+yu8r=>W1S!*z=rY-l3;X?rW>a9=-riwV%Dn}!$nxP^-P{8WsV~D)uSeffA!~3 z+2Ea3(j%y2Xe+|1_kpoOoJM+P6i)=PFK*Y{dpQk3vCv11?FTzr zu+TJYB(csshGyL=8X$r%-2z9r3o_ew>(!+6w2Pl-~K!P9oFP(SoEvYKPFr}_Ge<2_CpFk?BWE)yk)*qqx%CSv#}(5ZN1T5jit zZ)<{nh(&i64v&+PAdntOg7ftgVbMRVb^7gR$|2}&?jH?J8@iphMef2*UeeqBSZBTC z(&W5z#qO&rjAbJYxswK>cQhYcPWo}5IP#plILXieFMLVBFb5W31d9RoPeIf_KwyZUe$q}710GzIO^&d<3a07Ab};n|H9ANl z2Ac+hx@@8R*I_@$~CqKZl9jw}G*#BWG3V_fCRe?*jm){tMf7uf346jdRtVRKCIzbyLo{iU{kDT73;WfDQo;@d%%n}~PW7=c z0n`@UBe|({)EsFRaI_s@U2bHfhX~qZ)ynl)E7CLwClU@iL0JOCB$+c%(3;WvKY`B9D=#S%=UrDx zD+e2K#{kR>;>_92%~G6EZu`IOz!FgZwJ_m2T9BwJ8XUEZ1TwlWqhDGW{CrG5JIjhY zc7hdJv&LWyQ3vRF0p95^4w^;pEoo=_-PfC2hgo(m%?9H) z0>0r9*tL5x0ih9ubjmptCr-sM)s*Y&9P5#K)af=M5CNy`0-5p(2FQSZmAB28k0e{| zv_*-xa>W^Hn=>)UKTE*a(5pw9-%8`Kr_-;E$bCN{hHC5Qcvs0yXCsC-{Nf8|SNO0M zahjWJ`zvj8SGRpOkK4O8EW61OJS&YX6ggW%%_@MC)f(F!wA|dE2bxjSKD!Zow*MBa zWAqhU|0_A4qqzb80YX#rRN#3V;n?>+x9CE{dxAw!Yd`N2m%Cgv*?0_LWp4l~>nUhPE?j zX*;ZWr!0Ck6Bt@yZzO(gWL@u@pX`S&0?8q%yt1Z_~z;U#5vP9@FIswou;teSKW`bMYK}@B}9QcE4Xmw z&;B==qAmLFv!xV=b`%03u7yfSxo88b{rBZ}{*;R8YKzWEE{n390)w9z6YYPQq?v!i?RLJMo?YY(>F#D7q#C_m zC0rlzdtMWz;5B0wdS7p}s#558wkt|R{>9C`RZ+>Nn4CqdIXgR!K2+QHY47>0$dl=@ z*Q4itKGLZDq@Vt?y>3UmIq~0|1Kdg!dea!|D59`@_0bR)10T*E-$#z&?6n3LB}5F- z{e^Tu4sL&Ku8`ZeTXiq3Hv|B+6g2@Z+?9cRdEGsKP|;d3wquB)c6b zGO6!09oH4Q3UXK^4aMg2J?ky|gZ%2*L)xF{Ztg8rSv*G$gwgQrTXQ}L`3x#7s4^vM zPfmUsyZ_4w(C$2cz*N!iXeN>EK8Mu+vbYJ0E-tJTwSv&%g=5ONOk?_@G8!!>vYJlD zZ2g71MLy$uygoWw+2=$$8og&OEg|out`^TxXYVWag(!QY{X}VwG?i!Ku{=&9s%8E5 zYfXRHS*B{Ry2S2twD8?GA&vJi+T;RG5OPuA$%_%JL$NqD5${voFWl2kr#^zhWwV-xlDr_=no z13YiWo9{3Cr%_Kxm;R=ITd+4jodz!*m)nJ!l0&5Isl+R zAKoyV@dIrLtC2-9gPSalazz=4AWoQt4}_=kEyxhR-pAU}f6PIh-%Ode`s9iH zi742Ce|$0({xxBX0>0AcC61&&>%J50v5O+Y?tB`%UbzSVw%WX5B1GDJcw@7a`ZwAk zG|eT;*jS>a%T3^h6ZwE_)O|9)#`^Y>{z#wmP`PSd8u>8%w`cf0J3#$xZCStDerX+^ zo=(FWQMD+KGD)*sGuy_>PCe&fibo0ZyVl5U=SIJ8IHfwX1dxVOhR5x$MEtG3999*x z8d+tn-D@YtcNW@BdMnf}UbvPROL=NXNS4z~QB(VBL`jS9o*`g9v>MuR!qH+TxXqM* zHJ*$@%d&1?`{!qgqTWH408ft9Z@)Cp_OP2w`QtEB6aYihx*heiR z%jvIm7s5vEH@K&%`Y(MibERj!HG(RA1DLPa9|G>G`kQq0S9x5g)_)RbWC#J&v^Q+a z^&O-U<>+w=3hg1NWT%-HFln1VD%j3Ch7ILP=lKDspB;S7hvwr&f02Sws2q#27zj|( zINerX7`aM52_D6$C?B4U?QC?s$2ou6*$YAbX_Uk5Ebk*J>FOh3u(`om(`0`YGHU;m zPH~mfxqmQj6t}cu=IV&KY8ep<7|alhzl;*>3akV59CQ9}SX>%(wgyyBdXM=4_Pe+T9q_HwQ$@ zMB|57if-%o*EkXdGr1zjhpjZ+sqCp_-xTnE1iltAyWj3juyGA>b$qzTH@OmPrcKv< zdD;{*=M(6&iP?ll`0{e>p_Ah8Gr#!9GZA!eaEFrKr2S4wrm8TA%S5hnvOcp?=btLw zG39j^DXVS&rvdaC{CJu6)#(%mDG8B1V7`5^F~{p=8~@tluV1Nibv1Kw5*HzFKbD(u zMgmM;%tkj5CJ%{$F)y2>L^Nqs>@b;%3<+(@msKH)D})5rJBLK?ek|iT;I^ZO*67=x zxXt%79lnP=Kj(#Qokk)Jiz2e+-^V&jd5wkDod#YCw#@$tfGN?0HPpX^G2Cb+x_^1q z&bkZaa@ynV5#2LA$a(?WuenByL=KrmNSP8IpXXWH(~d?bd`QnIBtEY?pcTp8_bnIE_Xqi0i4dbIv6gLp2*j%)g1ey)3_{!m_g&jf@+; z?jECucO9HOWbym#_|aF4dThRp7psp2D~_Lh7fxkZ!BS^PKrn{k)?J*{yj7aPnc9oN z4_SDg{gzUmbYX!#+ZaynrtEzdL+knPGGHs`*?}b^;ZQlp_QP5G-hnM!*VeJ~r7kr- zj0jG|uj(SK$f3pFt=`@JeP>MlfWMCP`fhu_FMv>We^NtxzkW z0HLAhn%51LQX}u#VT#l9qqQ~87>DKiNNkNhljeqSHoW5U(Vrzv*Ztv&qwY&1a z`|&hONp-ZPunw|{)KL#6R*$+(<3v3=HJ$BrThLU5{7V}YnK6r#l7f6=N%Dj?!_B|S z(c!4dQ>RYT--~WZH6L`#7F3$jlKWn$)R;>Xcr>+i9k%EE3kz%$WXnn&#}IDW1@9yZ zPOgwBq;ACHvx!hD4NNeXk2sV5=GV~vD1pGW)oy6hB8-vGk9v<#F8*>l&Fkg}`Ey6U z>1s%zK1gJ$XM#Qdr3ivCg=W6=*o{cFtIuWfH((^;K-r*m{B7lB%(V5|QPdGv{WKwh z{wYLLj_%)1=y~nvZiAvc50Ppc=oA6)Wk}bDI>8BTe%0J6uSYN;g2sqp$|OeEXOJV; z2CAx0@VwoVb-wo#y(FFfOOF%TQ&tqO%PlZ_|7s~;tlp(*1Cw_JS70SnVsZ(G@Z?>J zbuj&Y5!MsKY%bq=gGvDdCIUPGi*QEUbwsWr*^2xhf7TvkCw5U1lEwl)8@=y{22wir zd09HZ*FEnd|3vV9_TGLtD?|H{Bjo(;SzIIV)R z>IR-oIrv^IXmE5d_J-!ZYu+2b_i9?TJ|O@;gA+l7AAKp-*^|znHLB!pLW3~`E3LPu zIx2GPYiIs?Z2V&UIeBKDMnI>M^>Xz)Ri6h6IC&IBL&M;PLl?>dLLqp^h&_+l6x;2N zMzbvruTR;0^il<*=;1y0Sz%x2@B5aT#xLR#{nft<--}sDrJ!z71!p?x%ZkI=4F3w5 z_0lTA?<*?T@NhOb$cM!gF1EgU@Y1~=bM>@-^lM0C+Y_FKvRVmIqLWzKVWge9>7>yN`2)2{Kyr$>f`eL`hJNos((`O*oSweuO= zxq!~2_H`?=FO*oz)FH003A&p2E^YglpLQr7_dNo-uRXfmyq=F!brgyIh&26j+9qda zYkLtC47k&v7_o2(MkJCk3%wAIJwQFQGcpmJUbB9CJr_JXxs({JbKc<;x?I^z9*q@( zWi;|G3Q?b6myXmpweF5XsOct7LMU6rbAI)9`hx-whmSC3qV^Qp zfM)lF#h^(&eVoYMNo2=q8wIDI{lhI=57&w)dvCYzZ%l~EsDD#aKIRVh50Dl5I` ztiaI95{X0Sr@!@X(AM>0XmF%d))fD}!YwZSX{cq+BjQ;%1Yy{<>vsu}*X;G*DCH6c zcF_3m(AW<&L!aM{<2_=}S270J99pr%yY8`0ggodtyMlGQ|9w@=X#-q)tf8moyaV;$ z7IQ(@`#jKEwy!gL-(+<+((_;Xhsz6Gm7Sdfe<&VV)@g%&`h*ETD;@Bs%q2aA$QatX zv_~X$SAUA{c{L>PHVs!iHSe=VrRI&2565!Ik~1sL zw}9r2i+RMEE_<%-x7&D3ag%`g25ngy>|GcBxi{3my?E{H!6kIT%tz1j>g5X!D!K2A z`+a>?>WYcyD-lB^NvHfbnIyGiFlh)gGwcQ`;(Gk{6i*}O<(}LnLY5H`u25>GW;fvV z#M0=D86V{wG@)B_&%nB=)3wezL;qa@6`P76b~2OC>v%KXclxMNu@b*hhxK-$o8R{7>?qjpmRu zX&f*~xkKbwB&ON)+Usi_o#<)z8oP1x;^gsJm2@N+uMFZj#@A88y1M-);VVm&Gb7-m z-E9$Pl=Daf8-O&sfM1iDJ$m8ElrJwIboc-maF&&!=NkDxXbLTGQJB_#fX;s41hshSIeweM%+#YnQ^(w=zc6Q_vJ7OecqC1r%Sd6^UpdZXInmeoJI!s z$|nE$s-wRCO5G*?`b#+EI>+GKR$K8GXmIFMFzoC1^;vq8xj{Vt_waK8GJCS50ub-%^Z>cH*2rD(#m@Kz(6_1irNZ9SX7K5jwQ`Lu81*0AIax8V@m?*m>(oGukkaow9t!+ z)EgSaT~!r-JL`Y7rc735F=sGq{ML_$A}IPc{j3uLfoGOXz>1bR{Jl4BczjBh2D>GW zZO4d8)JnrVN#>1ZyQ(jb{M-8Fg#UWk=q;IocBfoMR|ZD#{1=ka5rQJAXgLawgQh6J zd1z);fLtl|Cx_{iudWQ*wfhFFj{bG@!n&7WZLCc7rTiBjlTXssjOAp|QGR?d$R|f3 z!K$j(NADT*!FPY5Iu`d^ytSP*52z!pjRtOpS)NtP=%k)th0n^`~#zFw$nsA!Ae7SVqgXzr(MZMSWfz+pCc<)D$K~a3 z$91{}tz36VF3PoGmniED#sRESWkf2qrG^6#U6s7%_V2;OV<|x9@p~>TM#@4kJa`Pl z2Ln(;E8JLUa1G20CjN>TCAEosc^6$w?nC^v z|F#dhUd!&q)@#7P7y8^F=P$U+n@(->i*2XIw6B)MNr4ql8P?0DjPkXq731YLGuy8M z$NB=Dxb8))$O|Q-=pDIm;1@yx01$~*$|@br2QzU7M;@KobmFe4q5qqgEOmkI$A;_H z6l9Mak6E|*7KhWJV9dX+m*%fgCCBT)d zZ52OpgZG{1g<+999{5wCKl613-bC23NJ&c*dZxbdcYPR!EnUySJ;;R=@2O+!7>oK( zZ0klEle4QJ!E;%j<@Ay-eHPf&fF(nx(fb8QNB^&WPo0%`H{5Nu9|;h5WZW#44sR`h zGcfaOLn;~kK8EjX0r!|Zpt&S8u<+>CRu=XS$VT@O-aYTil$-)`&I{qv9`6Z!wh?fw zYVf#eD>UF;y&cFcWzK7npyEUaYYePEb~rrE+pCmi@1Joa#f4gMM_sgoQr^nk7~kuv z7^o9=9kdr#GwkHfCkqJ(ccfw}Zo;u?jAadtWd*$_c~)JAy*I4x z&yj*-w3bhk%CG4%zR?>mO?;h?tTX=?V;xNc#NsuLsUYpD9le3WqW#H;(%cpc0|uo_ z#i$j>ENeaTUUMy78&;AJOgq-r2uiG(){zwlghp0QY5BVh6n$?ehsGl70*qw}D2KlH zEV{py?akkOl{LK%^FacXObfKjd?7sb^t9ts%60oi3|ZHcGvBkmT9DLyUXr!R5%pT7 zs_=^z`E0(Lk3#v&{;~8;_|*v>V4q1*Q0sqNsVVAD4h#N!x1vMV?5WM;#F?-Cg#>bb zETA=icSdn*&>RpeLOKN_v2&>{EQ1K49FJ@XIb}HI_FI~z!Eo4c!bG30p}RqKK`S`~e+^Hew7Q#EyXe#(XM1X6S#4`DaI*EW_SQg-Gto4!#DE0*r;4 zJACcJzeqSzy~BY?$h}_9WCw6Fmo|m>l`L~#cV2?yYzm0<>_ynZ2xfUv3-WRC(Ikj)#A)XT*l?=|Bwr3qssp zO#6<9@=x}VSD;nax$=0RbQEI&=eTKMBSU$~j)>0jv+VwbLY# z?^oYwR*qv3gfRdfwtK~WH9EyJCCmGiQ?vaN3f7EEFXUX@FEzJtHN_d;Myu9d5SQs~his75Tfs zZ789Tx%_DJ9;G_xwsBdqNMfP=;;EJQ7ll|p8ITAkO^i?*)BVpH%WnEIzo_#?Z}aJq z%jlthc)(ufyYgS?gY(I={~&sMRbjd1MT@j8_J9%MC>gP}^NyOE;X{VB-Fq#s+h)Tk zU))a*z3nT(;XTKHEZ;qQ)XSaTqEd514*13DPAg?F%3rE`q!&3jPH2K%3--x1-_emb zJ?c&NeljGZ96$|Q;xP%UABlE2reaa~cyqbX8m$OZP())c1NJmN{+s3)Vl|E#HmrP> z2LE6!8WO@&mq7k1qnuiCr^kp)MO%dTqtBtT`yzo2FJW?@wTg8ONovOOv!fS$jb2C+ zdD0DYY{W9T`C4Ja!H=||A`n6n-b&}5)A@KQ^*V#_zVo_+4)td6`@N^YF|N0t}CmnGYiTP|D30O9*b z(;@01G$#je1Y+Cs4I6U+6^$+2XCRfY+z}hhiwf|e&+6Vvjg2z+OsP~IYFdOtrnvIC5@AH)!;=u&dQ1e_J(nYf8ww>sS(^H9s)Q{;fdj$Eg~@#yOTp$ z8O?}#uu)dp%^|jD=P&DA<9S64dg%GMY8XEORaJ+{JjX2s*OC=DkO4mDK{u`JTA8Dl zV=p|dfOvnqB%geyWzdbs{(2n~PA|Zej*zQD6}b6)x54Hl5dM%5Wu`LIrH$gKOhm5@TYna(SYcWszi;n2M|6ln4pA zk=_&39w;Gxc5yM-Rm2_CD~B*8axd}S<38VBfutzO905z7D-P+by2MFxgI9VjK={|| z0s48G+FK0>!%t=JPIZ@=63|yt!TjqEoAozRr*+{%X>+6G@AFBd;E?F8@-W6ByVyKf zY`cu&2r(Sp=dsEauhh`J*X(?j&UOo}#c`%G<7gkE=0M^;z_}PP9gr!poElyA4knIP zNz?cmkxdz;h$#)Y_v)Wtp^c12?~N+Z({9Ja)ps{Wpe`0AHiixOV&40fYUE2aZ=pCJ zQ0qz5Btxkx+#AWjP%1IBCUoHuQE~c`@z}Bc*whWKFZ$w@jjmZIng_>-{z*a$$5_h7 z+Vh>{=DLS`b))fyj;LYcz}tw4$>VegJ+mUHFEBUHh6Xb8K}?EL=JB{;GaUz@RoL;( z3w5G<`3?~-!_D|i>y7RcvT6bV)V78g>cvd7-88N(Auf-dHiy(1xUF2{-KTemHW!2S z3th{sCnN;qBt^62x@g|RMU-cO0+3-B-?K{Q)X@M0b))ZlFc}$P@z^{XF}C<_3#Rrf)iPc4+vq50x^b&k;$R~|gSyir3Y&_LjvWIa(E+mf zU6Mq`X%Cn@-;4#bMH68qoQ)>om~_blUC_GZ`+URI1+u|Xt!qSJrdf&?7}eqzjZks4 zDb#w;n?GS;?dJjaf~oI*o`25e5C}K$P2CzQULDOCqhyG-Dx&zyo(=_5{O=lVH#v!J zjj@A2+l-pcY62A`oG4)w~i)Xr__z0A! z$k+-ZZB>QT^1uw(R${(c#yv%gyelP7?l&EwSZ&IbeJGTts+zMOSTfGqGp!$Sb-O)+ zbYI?1$I79+Vw!+wJ6fMs4!e^oKK{rseTVY%li2yvs_QQ$nK~K+8ycdS_P3-)vqmvL z?J9)(Z%1!-J?SkQ>i@)hipzot^T14&<&5ogm`G6Kfid2_R2%aS$1xRXOm)T}_fLBx z_g^<=dB_ZOMKFQeOdZ)nJv^uAe2HRy+jNFLVM$ss^9A+85_J7cs!k292t7e*yjX^U z&d+f{_Kst=nUzE~+{l+x)KlpjN4Sdw*g@DvuYwEV6=u?l-;GmAcdve<0+S~DJQ!f% zG;?+aHZX(@2}6-(HUv7a)!vp4n4+^c|Cl*p1Yf&^FY^UzF8;z|fUB?}FS8(ohhM%8 zqYbr599YKgQR2?{#u~{{f;~{t;APX_{`5I}FPalKW(K=|ijAULE*T{nPO%}|>7*x9 zm-4vs!@xp}gY@EG184uxY?bXz6sa;L6!h+n!r9#r34eg&2kAm&F}4pR%716AOeD(F zf|)!BgATR%V3@XNz8#7B8rH@|>&*F63pCI5h4%AE{)*xrEwc_(a^4u6s|_9f(C z663u%4MNd;e9ZT2457zC*h0e|v-bh0Q3SoowM@qZHx)gaC0OY6m#kdS9&uo{OU4koI9>V`pkRODF3cCxUU3wmu4(K2=7d!YX zkQCm14IqV(9*_!|0j9RSMA`+Kw@=~?BA%XxuH63jkn)1YLex0Ov6;pI5@DVRlQu4F zf6_$cnlur=4}&JAbmeafc+s>R_N%OXCw1|8(B0<%Uj$P~cCM{M&dogR zoglVG$%G_#3V6dk2RP8pNcz|O3)Xi5rWlTY4TDcY-2LXHiog4cPG0}%_>uMR zQ5Ady$HVdgd#Mk*oHiKH0iHy?WB~t~q_Li%TO^_4QgOC_`e?@MnX2*2AB*vbvVuuu zk}*`{LQ?~@p9GAfdR;5{B?@ck|EU$tkDlwK8d-oPF@$7xuK0?tTkH(N0|UwV;$nGW zp*Ax}F<%o72TO)hpa_X<*HviG<{H;((nnFBoLb-~$6XgqoagidO}sCK`yX=;Ztl*@ zavLY7y368JL%xSV#53WCKE(5mqzI87VXl}63z!F}h000u0b^-aWY^s)qLR|2V=H;B z^z%t078nD;Ie0PR^~$Lh8CVI5$s@Chag{10IEEzZK;24)<5Tb|09%N#!CeQn`nN1E zyd*So%O87@$4vD+NC-ZyJkQ6*elq=cg*CejHQaUoy{Hp_c)kD0)Ablj^*=VPA`k4O zz89c}neZv{fXBVmm%xa9hT~>Kfc{w)BDELe?O;%?y-b6XUC(0rBitR zz`vs`J{-Z)qwlbzqduSb@tMpH2KKz&dhyh;NPXWQjQsg_0WsQ)87e7hppWMIEHgI@ zfyU0B#hpK2_yq>O`Da1BI1f)w_!Jm#pP_-IfN9?&1)Y=IxBCD}s8T>c==pOMafWCB zT@o?*fv+p$Ad-ON_>#^hP7Ea__W%~30~4RPUUSv&&bE%_tt%|GL=N}AP)uWZ$`Zj~ z0rns+hZGHg;ye<8-5bPBO};kaUWlT32dcvAW9O^sx)bodDYVpAk@8?H4htGr*)&i6 zdnfP+TS`UT^dmwRJ6am_Z9j7;=G_5>BM9U4$Ub14JqTP;XN#|_>S#o=Kl%4{29)Z5 z6G5}F*1mS;Lz%5inJo_maP~IzPX+8$lM8_Km&3Cos4|(2TmIfd`FEWyM{m#Pk62&| z_|zP?AtFnvOelb3?+O z@Q8`$B4lW`UM6D~agVC)OgS*1$Rc=}r`K~)A}G3H`m&6@*AS7y1t}x*I6-j;MEGKF z!2aYuvlfO82gmWsltsB6znUg^-f=EsWhDni$#w71#JYy*D(*ne+m^`zb#=$zuTQ>m z{eY&;c+Y0Vr3ta|f^Yrd@9A0aKET$n=+(ATKC9q;jDbmzL!JwGf zjmunRmJoCwALu<|$7&$UqI}bicwMHRBEs>0?#$R|R@~`PKGD(mB!jhaJcVfu&9LP0 zoXELQnDunft?(#p`m^}hmTT|Gt_N&8sUpjafJcMo# z?&|7#&q2Ju0f}|i(p%96^2_}*cdG3!vePO!(Y5@WNu}~<3w>gBI`O6jzbz0+b zig|E9?f(OwAi+K;1kYeGCAUo@ZoQ;VG}zx$iG63wx*31VtyJ>lGY68`?J3 z=?F1{dykX?=%KLaHKPT*UFSXnU%HPk&Y=w4u$CrrN8V7Jt>H$?^T%co9FUma z>aI#S5Y#FpU*4H?bq)1#_{K-yv$rrmA3U!th^{_{T1gNJ=*`W6dKZ|#Pby-WzzGw@ zmpZOkZtiTlK);beWC195{x6a~IY3qZ6$G^x(D{#Dvt!doPbkZ)-t*D~%5AWqIqK*>A`}M?A!msA>5#gw$j+_` z_@U{OE6JBL^V+Z8Ly>9=4{kYjUiQY>q`7o#ONWnw6exwG=l?Aba;rynS~$T*n)y zPLn=Fc6#B}pf6<7>lweC!fd}##7c)j_Jc%`*CT>xo*b)Zthi0@IG}HX)$| zIV_6m5oJ3=@lUTsHGE+bTo*~h& zT$sY@rVJx~oG*b183%4mX0+i>Gj4vi;WHqjXH<&`#*={f_l;xiy#f0Hn2k1|O7DMt0Z`0>`UGDB>LqQ6cCB%5)-7=a~-w(aCwFmw-*>$Nhh}bzy zymqmkejVj7w8Lr0Q=dMn|CE!*>~GOQ*$l(NNV>_}fHC@wvB;iPpo`>9CT;nwfhyZ} z^MFXM?>!9v!7y5)`(jD4yzdv$`}=9Ou*#Y$UYJp-90!Pn%j5)23Z)7^ozU3K7~l_N zzo4&>c?GhBcH!V?c!>QowJV=(XO{Z#lryj0-5VUh{u5cM#e~@u z{b@NvAh129h!x+GaPN7a{`PlMXSXq3&^|>t*LcshTxBt!*M=>)71lV!JT~ggyJ4KY zVf&PQlHJ!upz-6M=mEFWcdnNjvDTcg|4yU<11GOv2iN+2G=7DOQl43+bl3YhUOB@( zwB3svwENo69c#?J1(~jl$l*|T!vD{y zwOFfM_ixcNtM4WvST@s?0jxJ=+w^BbS-Wx)AfN{_hHfAxfm9Mwcx=Lt?Qwoy(gF4| zRf2mE+7H%dXV0^{x^nqML?kZ9XmPl}P7MD`(;H0x;<*@erH9skOM~+P{r?NoBdVw=Zhg0>588j{ z1#s2{{Hp^Df`AbNFj(R6lo+UDqy%8Tci=xg%6)#XbK(C!njNLf6vxTyK$#>7Xb0;F zv(>cL#{zI}Mf^XKL0A6Y^ZWnY@eUAFDrL-Ta(?~q6aC-wdx6K^&C1xO*Sz}gV}NjR zNR&=efIu1;BvNd#tq1^&EF2aeHMkf1KfCe_bGX^}9g>5k0k?A@d)CDq|6{>X>^}=U z4P9;?WOe+ zR)WI+<1GJ^0{p-1dWX%Q^DyS zX#jM9oD${k7t^A$(Z{}G$YS0Lv|=tvfRD1s|Le0iSfs(Z3RCmm>Hof@lgICZFns`h zR?O6MQ(sVYjWrS+0!MKbOp}6kFJs5HlPVImI3*SVCYhDca!!jiaH-nwg4J~`HECea zWgFUCf&*LrBQ*>-7jEf4bsn~_ULy7^q(+w0tZdPPHK{ba&hpg$z30BY;j;|reM)Qa z&1x@YhkM2$yYR&0BgMdy*mB}i`^rOhJ@pN#rw5T9BCBz22^BQL zqkhOQ_yv>rQ34B)x)$miPa>iCQV76GN@nx-c)BU?S+4_;osfJkf`J+~v2{Oi+gNCd z>S%ZKHU9?o#vLDzxlN1S^IcsDCyenjXZG(rS`IIGBd9W$$MgFoJ z@P-F%8~YJRewrIf5{a?;$lU|~^9t&23G^cCFO|&*y#NSC`9kd^hhr8F*P<6WcY$v##16o$(sdrKZE>s}u5Q_giHdTW zS0mv->C#S$ljR6#!=gs>R=MF_#t%9!{28cugjGyP;NEoZXB3*Z?H8 z+`*`*`bWT4Px@eluq-7t6Vup5Z0%-5wPR1v|5n+#M?>AMaeT~785)cXQJ5JsLZN1m z+Z4lKL=19G$dDvO6y>goiNTEAa(~^%rHhjCGH#Iw?@N+QrEyQ1T;5QK^Lx)(XRXd! zXPvdqzk5AQPm!fE2E&+9;|xM>XVfugH^La7yv5;u!;utOh* z9{o_D7riM91hWUf&jd>Fztm-y)CTR<Q2&&5WvTYN7{lH{_@A?~norz%^}6+8 z-S&;Ou-OPh1GZ<|mHiu$KXVAbBZoLnC7gNO)rF&s@;!$jaj9=;F9sI=!n78HGVH0< z9fBn^vH~DpzDPc_nY<#eti(XachXjKQYR)xFX1a%GdGndDnw*33<8t{mbB3qTe`oL zjVSj`W;Nsw7RG#|BepLDjOt$ya&ucf;_TS(Df;Xy6x+iTKL&c{0bc9{X4hdZY*;`* zav(Mw$fLfWt377V7k{h|gsU^F{WBXTUP_;v|3nU(wWL;*RyH6&68+h@9Xoa?>kWp+ zf8F~2U{H#bJ{6Dq9Wi=1#Cv?js3m_nKvw!2a)N#_YLR5}F_5gcy-61$$sjp!jzfY& z7RK`Zh$IW~`b8}r^KP>1h7}ZX4R=HXc z`wt6)-uq?Zz)H(WD(1#DLvzq_cy7gSoTn0xR88!VE!kMeGEz4w>7^_?f9hx2lf*E= z`jCvH*j88J8Un1Xc|6mLWFwKzIt3+t|B)o^soA=@eDGn=xHnQdrMc$w+*~`SD4Get zejq@7%Cm;$-_CToJUb(cnSz7zWHjAF$4=;ei0AQ?Rn_hgXD;W9T4nVK)0px`R|$_} zKH9ywH7O`YvwQ|c{gT~aG@DyeLgQ41&Qtjp5i#%HY-G2-UMOu`*8=5G66?MuHu$&-3~&R}82aYWWB(!4&mP+? zg^=08%zQ#;d;5)zrEcs;Gl!ui3pv`hJV)M-QSyDvR3fVNTScA|=dS#c*KV(lUEBSs zI!D^)1v}9u>Ey+G!GV|XDQQ72reU^eS4-yVT1*p` z%ubvT7y@r;3wnlOkEGQJkQZ0tmx+qpA6*Zqr%&U_*@=kDR=&Q&y+BsS9GRVAoKB{s zcVf?^A4d4V2h?s>_z!3+d;ns+pR2>bxpM80ty`ybd|B9Rmr%UdJf84?q}_`Vt?0UZ z9Div{&Wp*m{Bctwt~f@tTwC3efCVKNz9v{0pfQe)+8SJx*QI!)5#9E+J-D+9hbvCi ztI0iFStD-rIu9<)L&^{2luLQhQ`3U=&Nbgu0mH^SY>I4OX#Ke*v%UHtU?DCAL}w96 zh{EtM4^^mcPNLZp^mmi--tag&K0OxSV#tuQnMT2Z6#x`MsRqH5;Z`(#lZ}59fNQ-r z{>-f2^C$0sY7#{8DLC6I?hEU-c^A9*&yiD^^a~SCB~_9q*EmMyHdq*g+m1a_P#pMf zOVlgywDqfZPInthE(}hD*irc;Qj2iX1VE*7+Im}LK!a!4(>wOYEACm3y5aKpyE`E0 zW<6OND<46NkJ+L9pN9ijMGNNRWs%{_?m^wVp(HxGtZLN(ZiTI^`rW_MoSM(bdx&;Y zsrB5F5-BZhY?qIC?juH4nnsvOYG%U{O%poK4eGvMbi6QXAETiJ zt!(!BaoJi^jqFjFx7Iv&e%0TEryUQ14~p1#1fym~0o)(S&Q8c(Q+ZnB1ae1%ySwK$ zmlq=B)goA&a@7i8WzQz47Zp0}PgfgGzn+3d@8;s~+E88lnR8U6AFuB&zywK>($UcZ z6~B(|yFDA9(Oz@2OEhjU^gL9E9o|YeAbB6?+p0n*fr*hD+)Ccn?pOYPs{({l2Qlx< z@t1h64NE=EHaR)$G)XV&l#v(K_>n>j8$Af4aTTv~=Lw|stFGITl3>9EN2AA1! za5K*$Co6-Sw!+a;B7DJ@FY-mZBAP*bh3w=sDQhs5L^0Y^g0scGZ3-B>5ZlD?XC551?M3I#2fD5jBh|X)cjacN2_{rZaEgex;$8FfokT*?_!;|0zky5 zf3i^jZu4a}BOSy=`7?iWl*U%(X)C2R60Db1drU_*&t2M}f)`OQ(sZNddsK?r^DbR9 zi-@pz7U*gbg)yOecqAN(suB+v8B3AVsxA`#c>gG|Pv3J7blmTt^L=&C=Ic5DGTY9{ zDXaUMykzL^uY{jtuii#HGD18}T0ApG~GX}+Me zJ;ysdJe4Wf8>}7ws-)M!oiM9m7g{o8$lZDS1d4xsu{gzgKv+-0-AHY^#@WFpwrXg; zIDqtNr;xpz9I{3Vmt+(AnB$OiDA9!imTZU0S-;i@u~ow#3AfP{>U(QUVxeK;Fz-gP zUI+zhiMVFgeM|R&V%Y1x#_R46K88=N1UE+?IAhuQc2OE60d@hSjaI$(@*%am+8k0W zJU+rZmD`j-Z(;*>mPfxOAaC%s5?cL<%j@()wImE^10QRlR1rgr-)W5(0fW+X6|1(R zwT0e7KwtZc3CBK$mu>X~tCH=#{GoBHf!`9~yMzmK5VJwOnz!MbbBf1Ynw zUdjSdK1W9M->vxx8WUc-cvih|~D_x9hM*#Tf~`audm0ZL=b(k{B2PZ4U~% zW6*Wd`LvvyxnId^;QGHV-?nByTZhn%ou;#db{(_?<69snsSU9Rn)} zS;rqv(~|?Oxw{uCOyow8_JkJZ5>@N3>HxVxyV-%_`E6x-}J z)dkzCvpG(~tR%~@-igCk=ul7w3{goJvFmD>J15`z63LgRVbqIoZ%}6>gu(;MMk#PO z)6Ropx-W>Us4hGr>777_`x@*wk(-Po2sTfytBY*1d4p4!;M-)4sN$ul+WjtN>P&Bw z47|Agg0tBMwr}x{`RyCNO|Li-+Iz{n9Bt?X9$UOfxultrvYmD@aCS~DIvYbt{L|b2 z&@!bkWF}pC12_Zk-8=W z-JTRbj+7j#FRp)mEVDL?XC=xN6fxiC&gRX3fvkGY*K^l8oZAUZNoEpr&7eA9 z2F7w@CSGi-ynhF4EIJ;z?W1KJUD$FkU8Px2v>AQUZ^}cMqxk%J-F?o<7?@YRtnbtx zFa)SOF&=$v@I#c($xI1AXji(g2>loJ{ZH@zhrt#~qIgQm{kHknUqeouaa<`S7Hq)= z?MPJ;5|uXtBlVJ?szFE*DqnSXaof^%EUe4hGbJPW*Iv7!OAJ?LIGvn>{dPTW@t3_dfg$IsXrv5r|8H_arIPGC88K+~-VsREppnn^5%0$sv zoJ)Gb92nvh^XAlC?XtBb`ibnW4*-GVj*>Ba{AgXwKI3TVR{0*tnAV1;;v2cDasFGZ zVN<9EjdwQI**oW8*dLH%v5}OjZzu(Y`i{%TSW3pPu*1-QE$e?c|33`A#~k!H8yG5F J?epu-{{w1nv~>Ug diff --git a/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/looknfeel.xml b/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/looknfeel.xml index a42ef8f7c16..2608d5171f2 100644 --- a/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/looknfeel.xml +++ b/Shared/data/MTA San Andreas/MTA/cgui/modern/templates/looknfeel.xml @@ -107,7 +107,7 @@ - + @@ -186,10 +186,10 @@ - - - - + + + + @@ -286,7 +286,7 @@ - + @@ -336,10 +336,10 @@ - - - - + + + + @@ -396,10 +396,10 @@ - - - - + + + + @@ -596,10 +596,10 @@ - - - - + + + + @@ -630,7 +630,7 @@ - + @@ -638,6 +638,9 @@
+
+ +
@@ -775,10 +778,10 @@ - - - - + + + + @@ -789,10 +792,10 @@ - - - - + + + + @@ -861,10 +864,10 @@ - - - - + + + + @@ -875,10 +878,10 @@ - - - - + + + + @@ -959,7 +962,8 @@ - + + @@ -1021,8 +1025,8 @@ - - + + @@ -1350,8 +1354,8 @@ - - + + @@ -1402,7 +1406,7 @@ - + @@ -1498,6 +1502,7 @@ + @@ -1924,10 +1929,10 @@ - - - - + + + + @@ -1977,7 +1982,7 @@ - + @@ -2078,10 +2083,10 @@ - - - - + + + + From f12ba0bfdb6d3d195910bdeb2643cd91da4fcd82 Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Fri, 24 Jan 2025 01:11:34 +0000 Subject: [PATCH 19/20] Add label placeholder looknfeel system Instead of hardcoding color values in the implementation code --- Client/core/CGUI.cpp | 2 +- Client/core/CSettings.cpp | 4 ++-- Client/core/ServerBrowser/CServerBrowser.cpp | 4 ++-- Client/gui/CGUILabel_Impl.cpp | 24 +++++++++++++++++++ Client/gui/CGUILabel_Impl.h | 4 ++++ Client/mods/deathmatch/logic/CTransferBox.cpp | 2 +- Client/sdk/gui/CGUILabel.h | 3 +++ 7 files changed, 37 insertions(+), 6 deletions(-) diff --git a/Client/core/CGUI.cpp b/Client/core/CGUI.cpp index 149ee89eeb1..a1373a34742 100644 --- a/Client/core/CGUI.cpp +++ b/Client/core/CGUI.cpp @@ -162,7 +162,7 @@ void CLocalGUI::CreateWindows(bool bGameIsAlreadyLoaded) m_pLabelVersionTag->SetSize(CVector2D(m_pLabelVersionTag->GetTextExtent() + 5, 18)); m_pLabelVersionTag->SetPosition(CVector2D(ScreenSize.fX - m_pLabelVersionTag->GetTextExtent() - 5, ScreenSize.fY - 15)); m_pLabelVersionTag->SetAlpha(0.5f); - m_pLabelVersionTag->SetTextColor(255, 255, 255); + m_pLabelVersionTag->SetPlaceholderColors(); m_pLabelVersionTag->SetZOrderingEnabled(false); m_pLabelVersionTag->MoveToBack(); m_pLabelVersionTag->SetVisible(false); diff --git a/Client/core/CSettings.cpp b/Client/core/CSettings.cpp index 97fdd04acaf..20a6534a9c0 100644 --- a/Client/core/CSettings.cpp +++ b/Client/core/CSettings.cpp @@ -947,7 +947,7 @@ void CSettings::CreateGUI() m_pLabelBrowserBlacklistAdd = reinterpret_cast(pManager->CreateLabel(m_pEditBrowserBlacklistAdd, _("Enter a domain e.g. google.com"))); m_pLabelBrowserBlacklistAdd->SetPosition(CVector2D(10.0f, 3.0f), false); - m_pLabelBrowserBlacklistAdd->SetTextColor(0, 0, 0); + m_pLabelBrowserBlacklistAdd->SetPlaceholderColors(); m_pLabelBrowserBlacklistAdd->SetSize(CVector2D(1, 1), true); m_pLabelBrowserBlacklistAdd->SetAlpha(0.7f); m_pLabelBrowserBlacklistAdd->SetProperty("MousePassThroughEnabled", "True"); @@ -982,7 +982,7 @@ void CSettings::CreateGUI() m_pLabelBrowserWhitelistAdd = reinterpret_cast(pManager->CreateLabel(m_pEditBrowserWhitelistAdd, _("Enter a domain e.g. google.com"))); m_pLabelBrowserWhitelistAdd->SetPosition(CVector2D(10.0f, 3.0f), false); - m_pLabelBrowserWhitelistAdd->SetTextColor(0, 0, 0); + m_pLabelBrowserWhitelistAdd->SetPlaceholderColors(); m_pLabelBrowserWhitelistAdd->SetSize(CVector2D(1, 1), true); m_pLabelBrowserWhitelistAdd->SetAlpha(0.7f); m_pLabelBrowserWhitelistAdd->SetProperty("MousePassThroughEnabled", "True"); diff --git a/Client/core/ServerBrowser/CServerBrowser.cpp b/Client/core/ServerBrowser/CServerBrowser.cpp index 2ea47cafdf8..eb9f3f1bab9 100644 --- a/Client/core/ServerBrowser/CServerBrowser.cpp +++ b/Client/core/ServerBrowser/CServerBrowser.cpp @@ -333,7 +333,7 @@ void CServerBrowser::CreateTab(ServerBrowserType type, const char* szName) m_pLabelAddressDescription[type] = reinterpret_cast(pManager->CreateLabel(m_pEditAddress[type], "Enter an address [IP:Port]")); m_pLabelAddressDescription[type]->SetPosition(CVector2D(10, 5), false); - m_pLabelAddressDescription[type]->SetTextColor(0, 0, 0); + m_pLabelAddressDescription[type]->SetPlaceholderColors(); m_pLabelAddressDescription[type]->AutoSize(m_pLabelAddressDescription[type]->GetText().c_str()); m_pLabelAddressDescription[type]->SetAlpha(0.6f); m_pLabelAddressDescription[type]->SetProperty("MousePassThroughEnabled", "True"); @@ -421,7 +421,7 @@ void CServerBrowser::CreateTab(ServerBrowserType type, const char* szName) m_pLabelSearchDescription[type] = reinterpret_cast(pManager->CreateLabel(m_pEditSearch[type], _("Search servers..."))); m_pLabelSearchDescription[type]->SetPosition(CVector2D(10, 3), false); - m_pLabelSearchDescription[type]->SetTextColor(0, 0, 0); + m_pLabelSearchDescription[type]->SetPlaceholderColors(); m_pLabelSearchDescription[type]->SetSize(CVector2D(1, 1), true); m_pLabelSearchDescription[type]->SetAlpha(0.6f); m_pLabelSearchDescription[type]->SetProperty("MousePassThroughEnabled", "True"); diff --git a/Client/gui/CGUILabel_Impl.cpp b/Client/gui/CGUILabel_Impl.cpp index e94ceafcee1..1b470abc7a4 100644 --- a/Client/gui/CGUILabel_Impl.cpp +++ b/Client/gui/CGUILabel_Impl.cpp @@ -164,3 +164,27 @@ float CGUILabel_Impl::GetTextExtent() return 0.0f; } + +void CGUILabel_Impl::InvertTextColor() +{ + auto& color = GetTextColor(); + SetTextColor(255 - color.R, 255 - color.G, 255 - color.B); +} + +void CGUILabel_Impl::SetPlaceholderColors() +{ + auto* text = reinterpret_cast(m_pWindow); + + if (!text->isPropertyPresent("PlaceholderTextColours")) + { + InvertTextColor(); + return; + } + + auto& prop = text->getProperty("PlaceholderTextColours"); + + unsigned int color = 0; + const char* buffer = prop.c_str(); + sscanf(buffer, "tl:%x tr:%x bl:%x br:%x", &color, &color, &color, &color); + SetTextColor(color >> 16, (color >> 8) & 0xFF, color & 0xFF); +} diff --git a/Client/gui/CGUILabel_Impl.h b/Client/gui/CGUILabel_Impl.h index 121c7cb3e6f..563ebab70e7 100644 --- a/Client/gui/CGUILabel_Impl.h +++ b/Client/gui/CGUILabel_Impl.h @@ -43,7 +43,11 @@ class CGUILabel_Impl : public CGUILabel, public CGUIElement_Impl eCGUIType GetType() { return CGUI_LABEL; }; + void InvertTextColor(); + void SetPlaceholderColors(); + #define EXCLUDE_SET_TEXT #include "CGUIElement_Inc.h" #undef EXCLUDE_SET_TEXT + }; diff --git a/Client/mods/deathmatch/logic/CTransferBox.cpp b/Client/mods/deathmatch/logic/CTransferBox.cpp index 1fb33f54bc2..d5b16df17d4 100644 --- a/Client/mods/deathmatch/logic/CTransferBox.cpp +++ b/Client/mods/deathmatch/logic/CTransferBox.cpp @@ -66,7 +66,7 @@ void CTransferBox::CreateTransferWindow() float fTempX = (m_progressBar->GetSize().fX - m_GUI->GetTextExtent(m_infoLabel->GetText().c_str()) - TRANSFERBOX_ICONSIZE - 4) * 0.5f; m_infoLabel->SetPosition(CVector2D(fTempX + TRANSFERBOX_ICONSIZE + 4, 0)); m_infoLabel->SetSize(CVector2D(fTransferBoxWidth, TRANSFERBOX_PROGRESSHEIGHT)); - m_infoLabel->SetTextColor(0, 0, 0); + m_infoLabel->SetPlaceholderColors(); m_infoLabel->SetVerticalAlign(CGUI_ALIGN_VERTICALCENTER); for (size_t i = 0; i < m_iconImages.size(); ++i) diff --git a/Client/sdk/gui/CGUILabel.h b/Client/sdk/gui/CGUILabel.h index b7eebff98df..993eb30bb47 100644 --- a/Client/sdk/gui/CGUILabel.h +++ b/Client/sdk/gui/CGUILabel.h @@ -36,4 +36,7 @@ class CGUILabel : public CGUIElement virtual float GetCharacterWidth(int iCharIndex) = 0; virtual float GetFontHeight() = 0; virtual float GetTextExtent() = 0; + + virtual void InvertTextColor() = 0; + virtual void SetPlaceholderColors() = 0; }; From 6e7d0f30956f17b2e3859441f84d25d9a24a24b4 Mon Sep 17 00:00:00 2001 From: Lpsd <40902730+Lpsd@users.noreply.github.com> Date: Fri, 24 Jan 2025 02:27:22 +0000 Subject: [PATCH 20/20] Revert usage of SetPlaceholderColors on version label --- Client/core/CGUI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/core/CGUI.cpp b/Client/core/CGUI.cpp index a1373a34742..149ee89eeb1 100644 --- a/Client/core/CGUI.cpp +++ b/Client/core/CGUI.cpp @@ -162,7 +162,7 @@ void CLocalGUI::CreateWindows(bool bGameIsAlreadyLoaded) m_pLabelVersionTag->SetSize(CVector2D(m_pLabelVersionTag->GetTextExtent() + 5, 18)); m_pLabelVersionTag->SetPosition(CVector2D(ScreenSize.fX - m_pLabelVersionTag->GetTextExtent() - 5, ScreenSize.fY - 15)); m_pLabelVersionTag->SetAlpha(0.5f); - m_pLabelVersionTag->SetPlaceholderColors(); + m_pLabelVersionTag->SetTextColor(255, 255, 255); m_pLabelVersionTag->SetZOrderingEnabled(false); m_pLabelVersionTag->MoveToBack(); m_pLabelVersionTag->SetVisible(false);