Index: ps/trunk/source/gui/IGUIScrollBarOwner.cpp =================================================================== --- ps/trunk/source/gui/IGUIScrollBarOwner.cpp (revision 1084) +++ ps/trunk/source/gui/IGUIScrollBarOwner.cpp (revision 1085) @@ -1,86 +1,80 @@ /* IGUIScrollBarOwner by Gustav Larsson gee@pyro.nu */ #include "precompiled.h" #include "GUI.h" using namespace std; //------------------------------------------------------------------- // Constructor / Destructor //------------------------------------------------------------------- IGUIScrollBarOwner::IGUIScrollBarOwner() { } IGUIScrollBarOwner::~IGUIScrollBarOwner() { // Delete scroll-bars vector::iterator it; for (it=m_ScrollBars.begin(); it!=m_ScrollBars.end(); ++it) { delete *it; } } void IGUIScrollBarOwner::ResetStates() { IGUIObject::ResetStates(); vector::iterator it; for (it=m_ScrollBars.begin(); it!=m_ScrollBars.end(); ++it) { (*it)->SetBarPressed(false); } } void IGUIScrollBarOwner::AddScrollBar(IGUIScrollBar * scrollbar) { scrollbar->SetHostObject(this); scrollbar->SetGUI(GetGUI()); m_ScrollBars.push_back(scrollbar); } -/* -void SetGUI(CGUI * const &pGUI) -{ - m_pGUI = pGUI; - scrollbar->SetGUI(m_pGUI); -} -*/ + const SGUIScrollBarStyle * IGUIScrollBarOwner::GetScrollBarStyle(const CStr& style) const { if (!GetGUI()) { // TODO Gee: Output in log return NULL; } if (GetGUI()->m_ScrollBarStyles.count(style) == 0) { // TODO Gee: Output in log return NULL; } return &GetGUI()->m_ScrollBarStyles.find(style)->second; } void IGUIScrollBarOwner::HandleMessage(const SGUIMessage &Message) { vector::iterator it; for (it=m_ScrollBars.begin(); it!=m_ScrollBars.end(); ++it) { (*it)->HandleMessage(Message); } } void IGUIScrollBarOwner::Draw() { vector::iterator it; for (it=m_ScrollBars.begin(); it!=m_ScrollBars.end(); ++it) { (*it)->Draw(); } } Index: ps/trunk/source/gui/CButton.cpp =================================================================== --- ps/trunk/source/gui/CButton.cpp (revision 1084) +++ ps/trunk/source/gui/CButton.cpp (revision 1085) @@ -1,99 +1,100 @@ /* CButton by Gustav Larsson gee@pyro.nu */ #include "precompiled.h" #include "GUI.h" #include "CButton.h" #include "ogl.h" using namespace std; //------------------------------------------------------------------- // Constructor / Destructor //------------------------------------------------------------------- CButton::CButton() { AddSetting(GUIST_CGUIString, "caption"); AddSetting(GUIST_CStr, "font"); AddSetting(GUIST_CStr, "sprite"); AddSetting(GUIST_CStr, "sprite-over"); AddSetting(GUIST_CStr, "sprite-pressed"); AddSetting(GUIST_CStr, "sprite-disabled"); AddSetting(GUIST_CColor, "textcolor"); AddSetting(GUIST_CColor, "textcolor-over"); AddSetting(GUIST_CColor, "textcolor-pressed"); AddSetting(GUIST_CColor, "textcolor-disabled"); // Add text AddText(new SGUIText()); } CButton::~CButton() { } void CButton::SetupText() { if (!GetGUI()) return; assert(m_GeneratedTexts.size()>=1); CStr font; if (GUI::GetSetting(this, "font", font) != PS_OK || font.Length()==0) // Use the default if none is specified + // TODO Gee: (2004-08-14) Default should not be hard-coded, but be in styles! font = "default"; CGUIString caption; GUI::GetSetting(this, "caption", caption); - *m_GeneratedTexts[0] = GetGUI()->GenerateText(caption, font, m_CachedActualSize.GetWidth(), 0); + *m_GeneratedTexts[0] = GetGUI()->GenerateText(caption, font, m_CachedActualSize.GetWidth(), 0, this); // Set position of text //m_TextPos = m_CachedActualSize.CenterPoint() - m_GeneratedTexts[0]->m_Size/2; m_TextPos = m_CachedActualSize.TopLeft(); } void CButton::HandleMessage(const SGUIMessage &Message) { // Important IGUIButtonBehavior::HandleMessage(Message); IGUITextOwner::HandleMessage(Message); switch (Message.type) { case GUIM_PRESSED: // GetGUI()->TEMPmessage = "Button " + string((const TCHAR*)m_Name) + " was pressed!"; break; default: break; } } void CButton::Draw() { float bz = GetBufferedZ(); CStr sprite, sprite_over, sprite_pressed, sprite_disabled; GUI::GetSetting(this, "sprite", sprite); GUI::GetSetting(this, "sprite-over", sprite_over); GUI::GetSetting(this, "sprite-pressed", sprite_pressed); GUI::GetSetting(this, "sprite-disabled", sprite_disabled); DrawButton(m_CachedActualSize, bz, sprite, sprite_over, sprite_pressed, sprite_disabled); CColor color = ChooseColor(); IGUITextOwner::Draw(0, color, m_TextPos, bz+0.1f); } Index: ps/trunk/source/gui/GUIbase.h =================================================================== --- ps/trunk/source/gui/GUIbase.h (revision 1084) +++ ps/trunk/source/gui/GUIbase.h (revision 1085) @@ -1,146 +1,162 @@ /* GUI Core, stuff that the whole GUI uses by Gustav Larsson gee@pyro.nu --Overview-- Contains defines, includes, types etc that the whole GUI should have included. --More info-- Check GUI.h */ #ifndef GUIbase_H #define GUIbase_H //-------------------------------------------------------- // Includes / Compiler directives //-------------------------------------------------------- +// I would like to just forward declare CSize, but it doesn't +// seem to be defined anywhere in the predefined header. +#include "Overlay.h" + //-------------------------------------------------------- // Forward declarations //-------------------------------------------------------- class IGUIObject; - //-------------------------------------------------------- // Macros //-------------------------------------------------------- // Global CGUI #define g_GUI CGUI::GetSingleton() // Object settings setups // Setup an object's ConstructObject function #define GUI_OBJECT(obj) \ public: \ static IGUIObject *ConstructObject() { return new obj(); } //-------------------------------------------------------- // Types //-------------------------------------------------------- /** * @enum EGUIMessage * Message types * * @see SGUIMessage */ enum EGUIMessageType { GUIM_PREPROCESS, // questionable GUIM_POSTPROCESS, // questionable GUIM_MOUSE_OVER, GUIM_MOUSE_ENTER, GUIM_MOUSE_LEAVE, GUIM_MOUSE_PRESS_LEFT, GUIM_MOUSE_PRESS_RIGHT, GUIM_MOUSE_DOWN_LEFT, GUIM_MOUSE_DOWN_RIGHT, GUIM_MOUSE_RELEASE_LEFT, GUIM_MOUSE_RELEASE_RIGHT, GUIM_MOUSE_WHEEL_UP, GUIM_MOUSE_WHEEL_DOWN, GUIM_SETTINGS_UPDATED, // SGUIMessage.m_Value = name of setting GUIM_PRESSED, GUIM_MOUSE_MOTION, GUIM_LOAD // Called when an object is added to the GUI. }; /** * @author Gustav Larsson * * Message send to IGUIObject::HandleMessage() in order * to give life to Objects manually with * a derived HandleMessage(). */ struct SGUIMessage { SGUIMessage() {} SGUIMessage(const EGUIMessageType &_type) : type(_type) {} SGUIMessage(const EGUIMessageType &_type, const CStr& _value) : type(_type), value(_value) {} ~SGUIMessage() {} /** * Describes what the message regards */ EGUIMessageType type; /** * Optional data */ CStr value; }; /** * Recurse restrictions, when we recurse, if an object * is hidden for instance, you might want it to skip * the children also * Notice these are flags! and we don't really need one * for no restrictions, because then you'll just enter 0 */ enum { GUIRR_HIDDEN = 0x00000001, GUIRR_DISABLED = 0x00000010, GUIRR_GHOST = 0x00000100 }; /** * @enum EGUISettingsStruct * * Stored in SGUISetting, tells us in which struct * the setting is located, that way we can query * for the structs address. */ enum EGUISettingsStruct { GUISS_BASE, GUISS_EXTENDED }; // Typedefs typedef std::map map_pObjects; typedef std::vector vector_pObjects; +// Smaller structs that don't deserve their own files :) + +// Icon, you create them in the XML file with root element +// you use them in text owned by different objects... Such as CText. +struct SGUIIcon +{ + // Texture name of icon + CStr m_TextureName; + + // Size + CSize m_Size; +}; + //-------------------------------------------------------- // Error declarations //-------------------------------------------------------- DECLARE_ERROR(PS_NAME_TAKEN); DECLARE_ERROR(PS_OBJECT_FAIL); DECLARE_ERROR(PS_SETTING_FAIL); DECLARE_ERROR(PS_VALUE_INVALID); DECLARE_ERROR(PS_NEEDS_PGUI); DECLARE_ERROR(PS_NAME_AMBIGUITY); DECLARE_ERROR(PS_NEEDS_NAME); DECLARE_ERROR(PS_LEXICAL_FAIL); DECLARE_ERROR(PS_SYNTACTICAL_FAIL); #endif Index: ps/trunk/source/gui/GUIutil.h =================================================================== --- ps/trunk/source/gui/GUIutil.h (revision 1084) +++ ps/trunk/source/gui/GUIutil.h (revision 1085) @@ -1,473 +1,476 @@ /* GUI util by Gustav Larsson gee@pyro.nu --Overview-- Contains help class GUI<>, which gives us templated parameter to all functions within GUI. --More info-- Check GUI.h */ #ifndef GUIutil_H #define GUIutil_H //-------------------------------------------------------- // Includes / Compiler directives //-------------------------------------------------------- #include "GUI.h" #include "Parser.h" // TODO Gee: New #include "Overlay.h" //-------------------------------------------------------- // Help Classes/Structs for the GUI //-------------------------------------------------------- class CClientArea; class CGUIString; template bool __ParseString(const CStr& Value, T &tOutput); template <> bool __ParseString(const CStr& Value, bool &Output); template <> bool __ParseString(const CStr& Value, int &Output); template <> bool __ParseString(const CStr& Value, float &Output); template <> bool __ParseString(const CStr& Value, CRect &Output); template <> bool __ParseString(const CStr& Value, CClientArea &Output); template <> bool __ParseString(const CStr& Value, CColor &Output); template <> +bool __ParseString(const CStr& Value, CSize &Output); + +template <> bool __ParseString(const CStr& Value, CGUIString &Output); /** * @author Gustav Larsson * * Client Area is a rectangle relative to a parent rectangle * * You can input the whole value of the Client Area by * string. Like used in the GUI. */ class CClientArea { public: CClientArea(); CClientArea(const CStr& Value); /// Pixel modifiers CRect pixel; /// Percent modifiers (I'll let this be integers, I don't think a greater precision is needed) CRect percent; /** * Get client area rectangle when the parent is given */ CRect GetClientArea(const CRect &parent) const; /** * The ClientArea can be set from a string looking like: * * @code * "0 0 100% 100%" * "50%-10 50%-10 50%+10 50%+10" @endcode * * i.e. First percent modifier, then + or - and the pixel modifier. * Although you can use just the percent or the pixel modifier. Notice * though that the percent modifier must always be the first when * both modifiers are inputted. * * @return true if success, false if failure. If false then the client area * will be unchanged. */ bool SetClientArea(const CStr& Value); }; //-------------------------------------------------------- // Forward declarations //-------------------------------------------------------- class CGUI; class IGUIObject; /** * @author Gustav Larsson * * Base class to only the class GUI. This superclass is * kind of a templateless extention of the class GUI. * Used for other functions to friend with, because it * it can't friend with GUI since it's templated (at least * not on all compilers we're using). */ class CInternalCGUIAccessorBase { protected: /// Get object pointer static IGUIObject * GetObjectPointer(CGUI &GUIinstance, const CStr& Object); /// const version static const IGUIObject * GetObjectPointer(const CGUI &GUIinstance, const CStr& Object); /// Wrapper for ResetStates static void QueryResetting(IGUIObject *pObject); static void HandleMessage(IGUIObject *pObject, const SGUIMessage &message); }; /** * @author Gustav Larsson * * Includes static functions that needs one template * argument. * * int is only to please functions that doesn't even use T * and are only within this class because it's convenient */ template class GUI : public CInternalCGUIAccessorBase { // Private functions further ahead friend class CGUI; friend class IGUIObject; friend class CInternalCGUIAccessorBase; public: /** * Retrieves a setting by name from object pointer * * @param pObject Object pointer * @param Setting Setting by name * @param Value Stores value here, note type T! */ static PS_RESULT GetSetting(const IGUIObject *pObject, const CStr& Setting, T &Value) { if (pObject == NULL) return PS_OBJECT_FAIL; if (!pObject->SettingExists(Setting)) return PS_SETTING_FAIL; if (!pObject->m_Settings.find(Setting)->second.m_pSetting) return PS_FAIL; // Set value Value = *(T*)pObject->m_Settings.find(Setting)->second.m_pSetting; return PS_OK; } /** * Sets a value by name using a real datatype as input. * * This is the official way of setting a setting, no other * way should only causiously be used! * * @param pObject Object pointer * @param Setting Setting by name * @param Value Sets value to this, note type T! */ static PS_RESULT SetSetting(IGUIObject *pObject, const CStr& Setting, const T &Value) { if (pObject == NULL) return PS_OBJECT_FAIL; if (!pObject->SettingExists(Setting)) return PS_SETTING_FAIL; // Set value *(T*)pObject->m_Settings[Setting].m_pSetting = Value; // // Some settings needs special attention at change // // If setting was "size", we need to re-cache itself and all children if (Setting == CStr("size")) { RecurseObject(0, pObject, &IGUIObject::UpdateCachedSize); } else if (Setting == CStr("hidden")) { // Hiding an object requires us to reset it and all children QueryResetting(pObject); //RecurseObject(0, pObject, IGUIObject::ResetStates); } HandleMessage(pObject, SGUIMessage(GUIM_SETTINGS_UPDATED, Setting)); return PS_OK; } #ifdef g_GUI /** * Adapter that uses the singleton g_GUI * Can safely be removed. */ static PS_RESULT GetSetting( const CStr& Object, const CStr& Setting, T &Value) { return GetSetting(g_GUI, Object, Setting, Value); } #endif // g_GUI /** * Retrieves a setting by settings name and object name * * @param GUI GUI Object const ref * @param Object Object name * @param Setting Setting by name * @param Value Stores value here, note type T! */ static PS_RESULT GetSetting( const CGUI &GUIinstance, const CStr& Object, const CStr& Setting, T &Value) { if (!GUIinstance.ObjectExists(Object)) return PS_OBJECT_FAIL; // Retrieve pointer and call sibling function const IGUIObject *pObject = GetObjectPointer(GUIinstance, Object); return GetSetting(pObject, Setting, Value); } #ifdef g_GUI /** * Adapter that uses the singleton g_GUI * Can safely be removed. */ static PS_RESULT SetSetting( const CStr& Object, const CStr& Setting, const T &Value) { return SetSetting(g_GUI, Object, Setting, Value); } #endif // g_GUI /** * Sets a value by setting and object name using a real * datatype as input * * This is just a wrapper so that we can type the object name * and not input the actual pointer. * * @param GUI GUI Object, reference since we'll be changing values * @param Object Object name * @param Setting Setting by name * @param Value Sets value to this, note type T! */ static PS_RESULT SetSetting( CGUI &GUIinstance, const CStr& Object, const CStr& Setting, const T &Value) { if (!GUIinstance.ObjectExists(Object)) return PS_OBJECT_FAIL; // Retrieve pointer and call sibling function // Important, we don't want to use this T, we want // to use the standard T, since that will be the // one with the friend relationship IGUIObject *pObject = GetObjectPointer(GUIinstance, Object); return SetSetting(pObject, Setting, Value); } /** * This will return the value of the first sprite if it's not null, * if it is null, it will return the value of the second sprite, if * that one is null, then null it is. * * @param prim Primary sprite that should be used * @param sec Secondary sprite if Primary should fail * @return Resulting string */ static CStr FallBackSprite(const CStr& prim, const CStr& sec) { // CStr() == empty string, null return ((prim!=CStr())?(prim):(sec)); } /** * Same principle as FallBackSprite * * @param prim Primary color that should be used * @param sec Secondary color if Primary should fail * @return Resulting color * @see FallBackSprite */ static CColor FallBackColor(const CColor &prim, const CColor &sec) { // CColor() == null. return ((prim!=CColor())?(prim):(sec)); } /** * Sets a value by setting and object name using a real * datatype as input. * * This is just a wrapper for _mem_ParseString() which really * works the magic. * * @param Value The value in string form, like "0 0 100% 100%" * @param tOutput Parsed value of type T * @return True at success. * * @see _mem_ParseString() */ static bool ParseString(const CStr& Value, T &tOutput) { return __ParseString(Value, tOutput); } private: // templated typedef of function pointer typedef void (IGUIObject::*void_Object_pFunction_argT)(const T &arg); typedef void (IGUIObject::*void_Object_pFunction_argRefT)(T &arg); typedef void (IGUIObject::*void_Object_pFunction)(); /** * If you want to call a IGUIObject-function * on not just an object, but also on ALL of their children * you want to use this recursion system. * It recurses an object calling a function on itself * and all children (and so forth). * * Restrictions:\n * You can also set restrictions, so that if the recursion * reaches an objects with certain setup, it just doesn't * call the function on the object, nor it's children for * that matter. i.e. it cuts that object off from the * recursion tree. What setups that can cause restrictions * are hardcoded and specific. Check out the defines * GUIRR_* for all different setups. * * Error reports are either logged or thrown out of RecurseObject. * Always use it with try/catch! * * @param RR Recurse Restrictions, set to 0 if no restrictions * @param pObject Top object, this is where the iteration starts * @param pFunc Function to recurse * @param Argument Argument for pFunc of type T * @throws PS_RESULT Depends on what pFunc might throw. PS_RESULT is standard. * Itself doesn't throw anything. */ static void RecurseObject(const int &RR, IGUIObject *pObject, void_Object_pFunction_argT pFunc, const T &Argument) { // TODO Gee: Don't run this for the base object. if (CheckIfRestricted(RR, pObject)) return; (pObject->*pFunc)(Argument); // Iterate children vector_pObjects::iterator it; for (it = pObject->ChildrenItBegin(); it != pObject->ChildrenItEnd(); ++it) { RecurseObject(RR, *it, pFunc, Argument); } } /** * Argument is reference. * * @see RecurseObject() */ static void RecurseObject(const int &RR, IGUIObject *pObject, void_Object_pFunction_argRefT pFunc, T &Argument) { if (CheckIfRestricted(RR, pObject)) return; (pObject->*pFunc)(Argument); // Iterate children vector_pObjects::iterator it; for (it = pObject->ChildrenItBegin(); it != pObject->ChildrenItEnd(); ++it) { RecurseObject(RR, *it, pFunc, Argument); } } /** * With no argument. * * @see RecurseObject() */ static void RecurseObject(const int &RR, IGUIObject *pObject, void_Object_pFunction pFunc) { if (CheckIfRestricted(RR, pObject)) return; (pObject->*pFunc)(); // Iterate children vector_pObjects::iterator it; for (it = pObject->ChildrenItBegin(); it != pObject->ChildrenItEnd(); ++it) { RecurseObject(RR, *it, pFunc); } } private: /** * Checks restrictions for the iteration, for instance if * you tell the recursor to avoid all hidden objects, it * will, and this function checks a certain object's * restriction values. * * @param RR What kind of restriction, for instance hidden or disabled * @param pObject Object * @return true if restricted */ static bool CheckIfRestricted(const int &RR, IGUIObject *pObject) { if (RR & GUIRR_HIDDEN) { bool hidden; GUI::GetSetting(pObject, "hidden", hidden); if (hidden) return true; } if (RR & GUIRR_DISABLED) { bool enabled; GUI::GetSetting(pObject, "enabled", enabled); if (!enabled) return true; } if (RR & GUIRR_GHOST) { bool ghost; GUI::GetSetting(pObject, "ghost", ghost); if (ghost) return true; } // false means not restricted return false; } }; #endif Index: ps/trunk/source/gui/CGUI.cpp =================================================================== --- ps/trunk/source/gui/CGUI.cpp (revision 1084) +++ ps/trunk/source/gui/CGUI.cpp (revision 1085) @@ -1,1656 +1,1685 @@ /* CGUI by Gustav Larsson gee@pyro.nu */ #include "precompiled.h" #include "GUI.h" // Types - when including them into the engine. #include "CButton.h" #include "CText.h" #include "CCheckBox.h" #include "CRadioButton.h" #include "ps/Xeromyces.h" #include "ps/Font.h" #include "Prometheus.h" #include "input.h" #include "OverlayText.h" // TODO Gee: Whatever include CRect/CPos/CSize #include "Overlay.h" #include "scripting/ScriptingHost.h" #include "Hotkey.h" #include #include #include // namespaces used using namespace std; #include "ps/CLogger.h" #define XERO_TIME #define LOG_CATEGORY "gui" // Class for global JavaScript object JSClass GUIClass = { "GUIClass", 0, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, }; extern int g_xres, g_yres; //------------------------------------------------------------------- // called from main loop when (input) events are received. // event is passed to other handlers if false is returned. // trampoline: we don't want to make the implementation (in CGUI) static //------------------------------------------------------------------- int gui_handler(const SDL_Event* ev) { return g_GUI.HandleEvent(ev); } int CGUI::HandleEvent(const SDL_Event* ev) { // MT: If something's gone wrong, check this block... (added for hotkey support) if( ev->type == SDL_GUIHOTKEYPRESS ) { const CStr& objectName = *( (CStr*)ev->user.code ); IGUIObject* object = FindObjectByName( objectName ); object->HandleMessage( SGUIMessage( GUIM_PRESSED ) ); object->ScriptEvent( "press" ); } // -- MT if(ev->type == SDL_MOUSEMOTION) { m_MousePos = CPos(ev->motion.x, ev->motion.y); GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::HandleMessage, SGUIMessage(GUIM_MOUSE_MOTION)); } // TODO Gee: temp-stuff // char buf[30]; // sprintf(buf, "type = %d", ev->type); //TEMPmessage = buf; // Update m_MouseButtons. (BUTTONUP is handled later.) if (ev->type == SDL_MOUSEBUTTONDOWN) { // (0,1,2) = (LMB,RMB,MMB) if (ev->button.button < 3) m_MouseButtons |= (1 << ev->button.button); } // JW: (pre|post)process omitted; what're they for? why would we need any special button_released handling? // Only one object can be hovered IGUIObject *pNearest = NULL; try { // TODO Gee: Optimizations needed! // these two recursive function are quite overhead heavy. // pNearest will after this point at the hovered object, possibly NULL GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::ChooseMouseOverAndClosest, pNearest); if (ev->type == SDL_MOUSEMOTION && pNearest) pNearest->ScriptEvent("mousemove"); // Now we'll call UpdateMouseOver on *all* objects, // we'll input the one hovered, and they will each // update their own data and send messages accordingly GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::UpdateMouseOver, pNearest); if (ev->type == SDL_MOUSEBUTTONDOWN) { switch (ev->button.button) { case SDL_BUTTON_LEFT: if (pNearest) { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_PRESS_LEFT)); pNearest->ScriptEvent("mouseleftpress"); } break; case SDL_BUTTON_WHEELDOWN: // wheel down if (pNearest) { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_WHEEL_DOWN)); pNearest->ScriptEvent("mousewheeldown"); } break; case SDL_BUTTON_WHEELUP: // wheel up if (pNearest) { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_WHEEL_UP)); pNearest->ScriptEvent("mousewheelup"); } break; default: break; } } else if (ev->type == SDL_MOUSEBUTTONUP) { if (ev->button.button == SDL_BUTTON_LEFT) { if (pNearest) { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_RELEASE_LEFT)); pNearest->ScriptEvent("mouseleftrelease"); } } // Reset all states on all visible objects GUI<>::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::ResetStates); // It will have reset the mouse over of the current hovered, so we'll // have to restore that if (pNearest) pNearest->m_MouseHovering = true; } } catch (PS_RESULT e) { UNUSED(e); debug_warn("CGUI::HandleEvent error"); // TODO Gee: Handle } // JW: what's the difference between mPress and mDown? what's the code below responsible for? /* // Generally if just mouse is clicked if (m_pInput->mDown(NEMM_BUTTON1) && pNearest) { pNearest->HandleMessage(GUIM_MOUSE_DOWN_LEFT); } */ // BUTTONUP's effect on m_MouseButtons is handled after // everything else, so that e.g. 'press' handlers (activated // on button up) see which mouse button had been pressed. if (ev->type == SDL_MOUSEBUTTONUP) { // (0,1,2) = (LMB,RMB,MMB) if (ev->button.button < 3) m_MouseButtons &= ~(1 << ev->button.button); } return EV_PASS; } void CGUI::TickObjects() { CStr action = "tick"; GUI::RecurseObject(0, m_BaseObject, &IGUIObject::ScriptEvent, action); } //------------------------------------------------------------------- // Constructor / Destructor //------------------------------------------------------------------- CGUI::CGUI() : m_InternalNameNumber(0), m_MouseButtons(0) { m_BaseObject = new CGUIDummyObject; m_BaseObject->SetGUI(this); // Construct the parent object for all GUI JavaScript things m_ScriptObject = (void*)JS_NewObject(g_ScriptingHost.getContext(), &GUIClass, NULL, NULL); assert(m_ScriptObject != NULL); // How should it handle errors? JS_AddRoot(g_ScriptingHost.getContext(), &m_ScriptObject); // This will make this invisible, not add //m_BaseObject->SetName(BASE_OBJECT_NAME); } CGUI::~CGUI() { if (m_BaseObject) delete m_BaseObject; if (m_ScriptObject) // Let it be garbage-collected JS_RemoveRoot(g_ScriptingHost.getContext(), &m_ScriptObject); } //------------------------------------------------------------------- // Functions //------------------------------------------------------------------- IGUIObject *CGUI::ConstructObject(const CStr& str) { if (m_ObjectTypes.count(str) > 0) return (*m_ObjectTypes[str])(); else { debug_warn("CGUI::ConstructObject error"); // TODO Gee: Report in log return NULL; } } void CGUI::Initialize() { // Add base types! // You can also add types outside the GUI to extend the flexibility of the GUI. // Prometheus though will have all the object types inserted from here. AddObjectType("empty", &CGUIDummyObject::ConstructObject); AddObjectType("button", &CButton::ConstructObject); AddObjectType("text", &CText::ConstructObject); AddObjectType("checkbox", &CCheckBox::ConstructObject); AddObjectType("radiobutton", &CRadioButton::ConstructObject); } void CGUI::Process() { /* // TODO Gee: check if m_pInput is valid, otherwise return /// assert(m_pInput); // Pre-process all objects try { GUI::RecurseObject(0, m_BaseObject, &IGUIObject::HandleMessage, GUIM_PREPROCESS); } catch (PS_RESULT e) { return; } // Check mouse over try { // Only one object can be hovered // check which one it is, if any ! IGUIObject *pNearest = NULL; GUI::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::ChooseMouseOverAndClosest, pNearest); // Now we'll call UpdateMouseOver on *all* objects, // we'll input the one hovered, and they will each // update their own data and send messages accordingly GUI::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::UpdateMouseOver, pNearest); // If pressed if (m_pInput->mPress(NEMM_BUTTON1) && pNearest) { pNearest->HandleMessage(GUIM_MOUSE_PRESS_LEFT); } else // If released if (m_pInput->mRelease(NEMM_BUTTON1) && pNearest) { pNearest->HandleMessage(GUIM_MOUSE_RELEASE_LEFT); } // Generally if just mouse is clicked if (m_pInput->mDown(NEMM_BUTTON1) && pNearest) { pNearest->HandleMessage(GUIM_MOUSE_DOWN_LEFT); } } catch (PS_RESULT e) { return; } // Post-process all objects try { GUI::RecurseObject(0, m_BaseObject, &IGUIObject::HandleMessage, GUIM_POSTPROCESS); } catch (PS_RESULT e) { return; } */ } void CGUI::Draw() { // Clear the depth buffer, so the GUI is // drawn on top of everything else glClear(GL_DEPTH_BUFFER_BIT); glPushMatrix(); glLoadIdentity(); // Adapt (origio) to being in top left corner and down // just like the mouse position glTranslatef(0.0f, (GLfloat)g_yres, -1000.0f); glScalef(1.0f, -1.f, 1.0f); try { // Recurse IGUIObject::Draw() with restriction: hidden // meaning all hidden objects won't call Draw (nor will it recurse its children) GUI<>::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::Draw); } catch (PS_RESULT e) { UNUSED(e); glPopMatrix(); // TODO Gee: Report error. debug_warn("CGUI::Draw error"); return; } glPopMatrix(); } void CGUI::DrawSprite(const CStr& SpriteName, const float &Z, const CRect &Rect, const CRect &Clipping) { // This is not an error, it's just a choice not to draw any sprite. if (SpriteName == CStr()) return; // TODO: Clipping? bool DoClipping = (Clipping != CRect()); CGUISprite Sprite; // Fetch real sprite from name if (m_Sprites.count(SpriteName) == 0) { - debug_warn("CGUI::DrawSprite error"); - // TODO Gee: Report error + LOG(ERROR, LOG_CATEGORY, "Trying to use a sprite that doesn't exist (\"%s\").", SpriteName.c_str()); + // TODO Gee: (2004-08-31) This will be called continuously when it happens. return; } else Sprite = m_Sprites[SpriteName]; glPushMatrix(); glTranslatef(0.0f, 0.0f, Z); // Iterate all images and request them being drawn be the // CRenderer std::vector::const_iterator cit; for (cit=Sprite.m_Images.begin(); cit!=Sprite.m_Images.end(); ++cit) { if (cit->m_Texture) { // TODO: Handle the GL state in a nicer way glEnable(GL_TEXTURE_2D); glColor3f(1.0f, 1.0f, 1.0f); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); int fmt; tex_info(cit->m_Texture, NULL, NULL, &fmt, NULL, NULL); if (fmt == GL_RGBA || fmt == GL_BGRA) { glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); } else { glDisable(GL_BLEND); } tex_bind(cit->m_Texture); CRect real = cit->m_Size.GetClientArea(Rect); // Get the screen position/size of a single tiling of the texture CRect TexSize = cit->m_TextureSize.GetClientArea(real); float TexLeft = (float)(TexSize.left - real.left) / (float)TexSize.GetWidth(); float TexRight = TexLeft + (float)real.GetWidth() / (float)TexSize.GetWidth(); // 'Bottom' is actually the top in screen-space (I think), // because the GUI puts (0,0) at the top-left float TexBottom = (float)(TexSize.bottom - real.bottom) / (float)TexSize.GetHeight(); float TexTop = TexBottom + (float)real.GetHeight() / (float)TexSize.GetHeight(); glBegin(GL_QUADS); glTexCoord2f(TexRight, TexBottom); glVertex3f((float)real.right, (float)real.bottom, cit->m_DeltaZ); glTexCoord2f(TexLeft, TexBottom); glVertex3f((float)real.left, (float)real.bottom, cit->m_DeltaZ); glTexCoord2f(TexLeft, TexTop); glVertex3f((float)real.left, (float)real.top, cit->m_DeltaZ); glTexCoord2f(TexRight, TexTop); glVertex3f((float)real.right, (float)real.top, cit->m_DeltaZ); glEnd(); glDisable(GL_TEXTURE_2D); } else { glColor3f(cit->m_BackColor.r, cit->m_BackColor.g, cit->m_BackColor.b); CRect real = cit->m_Size.GetClientArea(Rect); glBegin(GL_QUADS); glVertex3f((float)real.right, (float)real.bottom, cit->m_DeltaZ); glVertex3f((float)real.left, (float)real.bottom, cit->m_DeltaZ); glVertex3f((float)real.left, (float)real.top, cit->m_DeltaZ); glVertex3f((float)real.right, (float)real.top, cit->m_DeltaZ); glEnd(); } } glPopMatrix(); } void CGUI::Destroy() { // We can use the map to delete all // now we don't want to cancel all if one Destroy fails map_pObjects::iterator it; for (it = m_pAllObjects.begin(); it != m_pAllObjects.end(); ++it) { try { it->second->Destroy(); } catch (PS_RESULT e) { UNUSED(e); debug_warn("CGUI::Destroy error"); // TODO Gee: Handle } delete it->second; } // Clear all m_pAllObjects.clear(); m_Sprites.clear(); + m_Icons.clear(); } void CGUI::UpdateResolution() { // Update ALL cached GUI<>::RecurseObject(0, m_BaseObject, &IGUIObject::UpdateCachedSize ); } void CGUI::AddObject(IGUIObject* pObject) { try { // Add CGUI pointer GUI::RecurseObject(0, pObject, &IGUIObject::SetGUI, this); // Add child to base object m_BaseObject->AddChild(pObject); // Cache tree GUI<>::RecurseObject(0, pObject, &IGUIObject::UpdateCachedSize); // Loaded GUI::RecurseObject(0, pObject, &IGUIObject::HandleMessage, SGUIMessage(GUIM_LOAD)); GUI::RecurseObject(0, pObject, &IGUIObject::ScriptEvent, "load"); } catch (PS_RESULT e) { throw e; } } void CGUI::UpdateObjects() { // We'll fill a temporary map until we know everything // succeeded map_pObjects AllObjects; try { // Fill freshly GUI< map_pObjects >::RecurseObject(0, m_BaseObject, &IGUIObject::AddToPointersMap, AllObjects ); } catch (PS_RESULT e) { // Throw the same error throw e; } // Else actually update the real one m_pAllObjects = AllObjects; } bool CGUI::ObjectExists(const CStr& Name) const { return m_pAllObjects.count(Name) != 0; } IGUIObject* CGUI::FindObjectByName(const CStr& Name) const { map_pObjects::const_iterator it = m_pAllObjects.find(Name); if (it == m_pAllObjects.end()) return NULL; else return it->second; } // private struct used only in GenerateText(...) struct SGenerateTextImage { int m_YFrom, // The images starting location in Y m_YTo, // The images end location in Y m_Indentation; // The image width in other words // Some help functions // TODO Gee: CRect => CPoint ? void SetupSpriteCall(const bool &Left, SGUIText::SSpriteCall &SpriteCall, const int &width, const int &y, const CSize &Size, const CStr& TextureName, const int &BufferZone) { // TODO Gee: Temp hardcoded values SpriteCall.m_Area.top = y+BufferZone; SpriteCall.m_Area.bottom = y+BufferZone + Size.cy; if (Left) { SpriteCall.m_Area.left = BufferZone; SpriteCall.m_Area.right = Size.cx+BufferZone; } else { SpriteCall.m_Area.left = width-BufferZone - Size.cx; SpriteCall.m_Area.right = width-BufferZone; } SpriteCall.m_TextureName = TextureName; m_YFrom = SpriteCall.m_Area.top-BufferZone; m_YTo = SpriteCall.m_Area.bottom+BufferZone; m_Indentation = Size.cx+BufferZone*2; } }; -SGUIText CGUI::GenerateText(const CGUIString &string, /*const CColor &Color, */ - const CStr& Font, const int &Width, const int &BufferZone) +SGUIText CGUI::GenerateText(const CGUIString &string, + const CStr& Font, const int &Width, const int &BufferZone, + const IGUIObject *pObject) { SGUIText Text; // object we're generating if (string.m_Words.size() == 0) return Text; int x=BufferZone, y=BufferZone; // drawing pointer int from=0; bool done=false; // Images on the left or the right side. vector Images[2]; int pos_last_img=-1; // Position in the string where last img (either left or right) were encountered. // in order to avoid duplicate processing. // Easier to read. bool WordWrapping = (Width != 0); + size_t TEMP = string.m_Words.size(); + // Go through string word by word for (int i=0; i<(int)string.m_Words.size()-1 && !done; ++i) { // Pre-process each line one time, so we know which floating images // will be added for that line. // Generated stuff is stored in Feedback. CGUIString::SFeedback Feedback; // Preliminary line_height, used for word-wrapping with floating images. int prelim_line_height=0; // Width and height of all text calls generated. - string.GenerateTextCall(Feedback, Font, /*CColor(),*/ + string.GenerateTextCall(Feedback, Font, string.m_Words[i], string.m_Words[i+1]); // Loop through our images queues, to see if images has been added. // Check if this has already been processed. // Also, floating images are only applicable if Word-Wrapping is on if (WordWrapping && i > pos_last_img) { // Loop left/right for (int j=0; j<2; ++j) { + // TEMP + int TEMPsize = Feedback.m_Images[j].size(); + for (vector::const_iterator it = Feedback.m_Images[j].begin(); - it != Feedback.m_Images[j].end(); + it != Feedback.m_Images[j].end(); ++it) { SGUIText::SSpriteCall SpriteCall; SGenerateTextImage Image; // Y is if no other floating images is above, y. Else it is placed // after the last image, like a stack downwards. int _y; if (Images[j].size() > 0) _y = max(y, Images[j].back().m_YTo); else _y = y; - // TODO Gee: CSize temp - CSize size; size.cx = 100; size.cy = 100; - Image.SetupSpriteCall((j==CGUIString::SFeedback::Left), SpriteCall, Width, _y, size, CStr("white-border"), BufferZone); + // Get Size from Icon database + SGUIIcon icon = GetIcon(*it); + + CSize size = icon.m_Size; + Image.SetupSpriteCall((j==CGUIString::SFeedback::Left), SpriteCall, Width, _y, size, icon.m_TextureName, BufferZone); // Check if image is the lowest thing. Text.m_Size.cy = max(Text.m_Size.cy, Image.m_YTo); Images[j].push_back(Image); Text.m_SpriteCalls.push_back(SpriteCall); } } } pos_last_img = max(pos_last_img, i); x += Feedback.m_Size.cx; prelim_line_height = max(prelim_line_height, Feedback.m_Size.cy); // If Width is 0, then there's no word-wrapping, disable NewLine. if ((WordWrapping && (x > Width-BufferZone || Feedback.m_NewLine)) || i == (int)string.m_Words.size()-2) { - // Change from to i, but first keep a copy of its value. + // Change 'from' to 'i', but first keep a copy of its value. int temp_from = from; from = i; static const int From=0, To=1; //int width_from=0, width_to=width; int width_range[2]; width_range[From] = BufferZone; width_range[To] = Width - BufferZone; - // Floating images are only appicable if word-wrapping is enabled. + // Floating images are only applicable if word-wrapping is enabled. if (WordWrapping) { // Decide width of the line. We need to iterate our floating images. // this won't be exact because we're assuming the line_height // will be as our preliminary calculation said. But that may change, // although we'd have to add a couple of more loops to try straightening // this problem out, and it is very unlikely to happen noticably if one // stuctures his text in a stylistically pure fashion. Even if not, it // is still quite unlikely it will happen. // Loop through left and right side, from and to. for (int j=0; j<2; ++j) { for (vector::const_iterator it = Images[j].begin(); it != Images[j].end(); ++it) { // We're working with two intervals here, the image's and the line height's. // let's find the union of these two. int union_from, union_to; union_from = max(y, it->m_YFrom); union_to = min(y+prelim_line_height, it->m_YTo); // The union is not empty if (union_to > union_from) { if (j == From) width_range[From] = max(width_range[From], it->m_Indentation); else width_range[To] = min(width_range[To], Width - it->m_Indentation); } } } } // Reset X for the next loop x = width_range[From]; // Now we'll do another loop to figure out the height of // the line (the height of the largest character). This // couldn't be determined in the first loop (main loop) // because it didn't regard images, so we don't know // if all characters processed, will actually be involved // in that line. int line_height=0; for (int j=temp_from; j<=i; ++j) { // We don't want to use Feedback now, so we'll have to use // another. CGUIString::SFeedback Feedback2; - string.GenerateTextCall(Feedback2, Font, /*CColor(),*/ + // Don't attach object, it'll suppress the errors + // we want them to be reported in the final GenerateTextCall() + // so that we don't get duplicates. + string.GenerateTextCall(Feedback2, Font, string.m_Words[j], string.m_Words[j+1]); // Append X value. x += Feedback2.m_Size.cx; if (WordWrapping && x > width_range[To] && j!=temp_from && !Feedback2.m_NewLine) break; // Let line_height be the maximum m_Height we encounter. line_height = max(line_height, Feedback2.m_Size.cy); if (WordWrapping && Feedback2.m_NewLine) break; } // Reset x once more x = width_range[From]; // Move down, because font drawing starts from the baseline y += line_height; // Do the real processing now for (int j=temp_from; j<=i; ++j) { // We don't want to use Feedback now, so we'll have to use - // another. + // another one. CGUIString::SFeedback Feedback2; // Defaults - string.GenerateTextCall(Feedback2, Font, /*Color, */ - string.m_Words[j], string.m_Words[j+1]); + string.GenerateTextCall(Feedback2, Font, + string.m_Words[j], string.m_Words[j+1], + pObject); // Iterate all and set X/Y values // Since X values are not set, we need to make an internal // iteration with an increment that will append the internal // x, that is what x_pointer is for. int x_pointer=0; vector::iterator it; for (it = Feedback2.m_TextCalls.begin(); it != Feedback2.m_TextCalls.end(); ++it) { it->m_Pos = CPos(x + x_pointer, y); x_pointer += it->m_Size.cx; if (it->m_pSpriteCall) { - it->m_pSpriteCall->m_Area = - it->m_pSpriteCall->m_Area + it->m_Pos; + it->m_pSpriteCall->m_Area += it->m_Pos - CSize(0,it->m_pSpriteCall->m_Area.GetHeight()); } } // Append X value. x += Feedback2.m_Size.cx; Text.m_Size.cx = max(Text.m_Size.cx, x+BufferZone); - // The first word overrides the width limit, that we - // do in those cases, are just draw that word even + // The first word overrides the width limit, what we + // do, in those cases, are just drawing that word even // though it'll extend the object. if (WordWrapping) // only if word-wrapping is applicable { if (Feedback2.m_NewLine) { from = j+1; + + // Sprite call can exist within only a newline segment, + // therefore we need this. + Text.m_SpriteCalls.insert(Text.m_SpriteCalls.end(), Feedback2.m_SpriteCalls.begin(), Feedback2.m_SpriteCalls.end()); break; } else if (x > width_range[To] && j==temp_from) { from = j+1; // do not break, since we want it to be added to m_TextCalls } else if (x > width_range[To]) { from = j; break; } } // Add the whole Feedback2.m_TextCalls to our m_TextCalls. Text.m_TextCalls.insert(Text.m_TextCalls.end(), Feedback2.m_TextCalls.begin(), Feedback2.m_TextCalls.end()); Text.m_SpriteCalls.insert(Text.m_SpriteCalls.end(), Feedback2.m_SpriteCalls.begin(), Feedback2.m_SpriteCalls.end()); if (j == (int)string.m_Words.size()-2) done = true; } - + // Reset X x = 0; // Update height of all Text.m_Size.cy = max(Text.m_Size.cy, y+BufferZone); // Now if we entered as from = i, then we want // i being one minus that, so that it will become // the same i in the next loop. The difference is that // we're on a new line now. i = from-1; } } return Text; } void CGUI::DrawText(const SGUIText &Text, const CColor &DefaultColor, const CPos &pos, const float &z) { glEnable(GL_TEXTURE_2D); glDisable(GL_CULL_FACE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glDisable(GL_ALPHA_TEST); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); CFont* font = NULL; CStr LastFontName; for (vector::const_iterator it = Text.m_TextCalls.begin(); it != Text.m_TextCalls.end(); ++it) { + // If this is just a placeholder for a sprite call, continue if (it->m_pSpriteCall) continue; // Switch fonts when necessary, but remember the last one used if (it->m_Font != LastFontName) { delete font; font = new CFont(it->m_Font); font->Bind(); LastFontName = it->m_Font; } CColor color = it->m_UseCustomColor ? it->m_Color : DefaultColor; glPushMatrix(); glTranslatef((float)pos.x+it->m_Pos.x, (float)pos.y+it->m_Pos.y, (float)z); glColor4f(color.r, color.g, color.b, color.a); glwprintf(L"%hs", it->m_String.c_str()); glPopMatrix(); } delete font; - for (vector::const_iterator it=Text.m_SpriteCalls.begin(); + for (list::const_iterator it=Text.m_SpriteCalls.begin(); it!=Text.m_SpriteCalls.end(); ++it) { DrawSprite(it->m_TextureName, z, it->m_Area + pos); } } -void CGUI::ReportParseError(const CStr& str) +void CGUI::ReportParseError(const char *str, ...) { + va_list argp; + char buffer[512]; + memset(buffer,0,sizeof(buffer)); + + va_start(argp, str); + vsnprintf2(buffer, sizeof(buffer), str, argp); + va_end(argp); + // Print header if (m_Errors==0) { LOG(ERROR, LOG_CATEGORY, "*** GUI Tree Creation Errors:"); } // Important, set ParseError to true ++m_Errors; - LOG(ERROR, LOG_CATEGORY, str); + LOG(ERROR, LOG_CATEGORY, buffer); } /** * @callgraph */ void CGUI::LoadXMLFile(const string &Filename) { // Reset parse error // we can later check if this has increased m_Errors = 0; CXeromyces XeroFile; if (XeroFile.Load(Filename.c_str()) != PSRETURN_OK) // Fail silently return; XMBElement node = XeroFile.getRoot(); // Check root element's (node) name so we know what kind of // data we'll be expecting std::string root_name = XeroFile.getElementString(node.getNodeName()); try { if (root_name == "objects") { Xeromyces_ReadRootObjects(node, &XeroFile); // Re-cache all values so these gets cached too. //UpdateResolution(); } else if (root_name == "sprites") { Xeromyces_ReadRootSprites(node, &XeroFile); } else if (root_name == "styles") { Xeromyces_ReadRootStyles(node, &XeroFile); } else if (root_name == "setup") { Xeromyces_ReadRootSetup(node, &XeroFile); } else { debug_warn("CGUI::LoadXMLFile error"); // TODO Gee: Output in log } } catch (PSERROR_GUI) { LOG(ERROR, LOG_CATEGORY, "Errors loading GUI file %s", Filename.c_str()); return; } // Now report if any other errors occured if (m_Errors > 0) { /// g_console.submit("echo GUI Tree Creation Reports %d errors", m_Errors); } } //=================================================================== // XML Reading Xeromyces Specific Sub-Routines //=================================================================== void CGUI::Xeromyces_ReadRootObjects(XMBElement Element, CXeromyces* pFile) { int el_script = pFile->getElementID("script"); // Iterate main children // they should all be or