Index: ps/trunk/source/ps/Overlay.h =================================================================== --- ps/trunk/source/ps/Overlay.h (revision 1524) +++ ps/trunk/source/ps/Overlay.h (revision 1525) @@ -1,306 +1,309 @@ /* COverlay by Rich Cross, rich@0ad.wildfiregames.com --Overview-- Class representing 2D screen overlays; includes functionality for overlay position, color, texture and borders. */ #ifndef COVERLAY_H #define COVERLAY_H #include "lib.h" struct CColor { CColor() : r(-1.f), g(-1.f), b(-1.f), a(1.f) {} CColor(float cr,float cg,float cb,float ca) : r(cr), g(cg), b(cb), a(ca) {} bool operator == (const CColor &color) const { return r==color.r && g==color.g && b==color.b && a==color.a; } bool operator != (const CColor &color) const { return !(*this==color); } + // For passing to glColor[34]fv: + const float* FloatArray() const { return &r; } + float r, g, b, a; }; // yuck - MFC already defines CRect/CSize classes ... #define CRect PS_CRect #define CSize PS_CSize class CPos; class CSize; /** * @author Gustav Larsson * * Rectangle class used for screen rectangles. It's very similiar to the MS * CRect, but with FLOATS because it's meant to be used with OpenGL which * takes float values. * * Changed to floats 2004-08-31 /GL */ class CRect { public: CRect(); CRect(const CPos &pos); CRect(const CSize &size); CRect(const CPos &upperleft, const CPos &bottomright); CRect(const CPos &pos, const CSize &size); CRect(const float &_l, const float &_t, const float &_r, const float &_b); // Operators void operator = (const CRect& a); bool operator == (const CRect& a) const; bool operator != (const CRect& a) const; CRect operator - (void) const; CRect operator + (void) const; CRect operator + (const CRect& a) const; CRect operator + (const CPos& a) const; CRect operator + (const CSize& a) const; CRect operator - (const CRect& a) const; CRect operator - (const CPos& a) const; CRect operator - (const CSize& a) const; void operator += (const CRect& a); void operator += (const CPos& a); void operator += (const CSize& a); void operator -= (const CRect& a); void operator -= (const CPos& a); void operator -= (const CSize& a); /** * @return Width of Rectangle */ float GetWidth() const; /** * @return Height of Rectangle */ float GetHeight() const; /** * Get Size */ CSize GetSize() const; /** * Get Position equivalent to top/left corner */ CPos TopLeft() const; /** * Get Position equivalent to bottom/right corner */ CPos BottomRight() const; /** * Get Position equivalent to the center of the rectangle */ CPos CenterPoint() const; /** * Evalutates if point is within the rectangle * @param point CPos representing point * @return true if inside. */ bool PointInside(const CPos &point) const; CRect Scale(float x, float y) const; /** * Returning CPos representing each corner. */ public: /** * Dimensions */ float left, top, right, bottom; }; /** * @author Gustav Larsson * * Made to represent screen positions and delta values. * @see CRect * @see CSize */ class CPos { public: CPos(); CPos(const float &_x, const float &_y); // Operators void operator = (const CPos& a); bool operator == (const CPos& a) const; bool operator != (const CPos& a) const; CPos operator - (void) const; CPos operator + (void) const; CPos operator + (const CPos& a) const; CPos operator + (const CSize& a) const; CPos operator - (const CPos& a) const; CPos operator - (const CSize& a) const; void operator += (const CPos& a); void operator += (const CSize& a); void operator -= (const CPos& a); void operator -= (const CSize& a); public: /** * Position */ float x, y; }; /** * @author Gustav Larsson * * Made to represent a screen size, should in philosophy * be made of unsigned ints, but for the sake of compatibility * with CRect and CPos it's not. * @see CRect * @see CPos */ class CSize { public: CSize(); CSize(const CRect &rect); CSize(const CPos &pos); CSize(const float &_cx, const float &_cy); // Operators void operator = (const CSize& a); bool operator == (const CSize& a) const; bool operator != (const CSize& a) const; CSize operator - (void) const; CSize operator + (void) const; CSize operator + (const CSize& a) const; CSize operator - (const CSize& a) const; CSize operator / (const float &a) const; CSize operator * (const float &a) const; void operator += (const CSize& a); void operator -= (const CSize& a); void operator /= (const float& a); void operator *= (const float& a); public: /** * Size */ float cx, cy; }; //-------------------------------------------------------- // Includes / Compiler directives //-------------------------------------------------------- #include "Texture.h" //-------------------------------------------------------- // Macros //-------------------------------------------------------- //-------------------------------------------------------- // Types //-------------------------------------------------------- //-------------------------------------------------------- // Error declarations //-------------------------------------------------------- //-------------------------------------------------------- // Declarations //-------------------------------------------------------- /** * @author Rich Cross * * Overlay class definition. */ class COverlay { public: /** * Default constructor; creates an overlay that won't actually be renderable */ COverlay(); /** * Constructor with setup for more common parameters */ COverlay(const CRect& rect,int z,const CColor& color,const char* texturename="",bool hasBorder=false, const CColor& bordercolor=CColor(0,0,0,0)); /** * Destructor */ ~COverlay(); /** * Get coordinates */ const CRect& GetRect() const { return m_Rect; } /** * Get depth */ int GetZ() const { return m_Z; } /** * Get texture (not const as Renderer need to modify texture to store handle) */ CTexture& GetTexture() { return m_Texture; } /** * Get color */ const CColor& GetColor() const { return m_Color; } /** * Get border flag */ bool HasBorder() const { return m_HasBorder; } /** * Get border color */ const CColor& GetBorderColor() const { return m_BorderColor; } private: /// screen space coordinates of overlay CRect m_Rect; /// depth of overlay, for correctly overlapping overlays; higher z implies in-front-of behaviour int m_Z; /// texture to use in rendering overlay; can be a null texture CTexture m_Texture; /// overlay color CColor m_Color; // flag indicating whether to render overlay using a border bool m_HasBorder; /// border color CColor m_BorderColor; }; #endif Index: ps/trunk/source/ps/XeroXMB.h =================================================================== --- ps/trunk/source/ps/XeroXMB.h (revision 1524) +++ ps/trunk/source/ps/XeroXMB.h (revision 1525) @@ -1,222 +1,220 @@ -/* $Id: XeroXMB.h,v 1.7 2004/11/11 07:09:32 markt Exp $ +/* $Id$ Xeromyces - XMB reading library Philip Taylor (philip@zaynar.demon.co.uk / @wildfiregames.com) */ /* Brief outline: XMB is a binary representation of XML, with some limitations but much more efficiency (particularly for loading simple data classes that don't need much initialisation). Main limitations: * Only handles UTF16 internally. (It's meant to be a feature, but can be detrimental if it's always being converted back to ASCII.) * Can't correctly handle mixed text/elements inside elements - "
Text
" and "
Text
" are considered identical. * Tries to avoid using strings - you usually have to load the numeric IDs and use them instead. * Case-sensitive (but converts all element/attribute names in the XML file to lowercase, so you only have to be careful in the code) Theoretical file structure: XMB_File { char Header[4]; // because everyone has one; currently "XMB0" - int Checksum; // CRC32 of original XML file, to detect changes - int ElementNameCount; ZStrA ElementNames[]; int AttributeNameCount; ZStrA AttributeNames[]; XMB_Node Root; } XMB_Node { 0) int Length; // of entire struct, so it can be skipped over 4) int ElementName; 8) int AttributeCount; 12) int ChildCount; 16) int ChildrenOffset; // == sizeof(Text)+sizeof(Attributes) 20) XMB_Text Text; XMB_Attribute Attributes[]; XMB_Node Children[]; } XMB_Attribute { int Name; ZStrW Value; } ZStrA { int Length; // in bytes char* Text; // null-terminated ASCII } ZStrW { int Length; // in bytes char16* Text; // null-terminated UTF16 } XMB_Text { 20) int Length; // 0 if there's no text, else 4+sizeof(Text) in bytes including terminator // If Length != 0: 24) int LineNumber; // for e.g. debugging scripts 28) char16* Text; // null-terminated UTF16 } */ #ifndef _XEROXMB_H_ #define _XEROXMB_H_ // Define to use a std::map for name lookups rather than a linear search. // (The map is usually slower.) //#define XERO_USEMAP #include #include "ps/utf16string.h" #ifdef XERO_USEMAP # include #endif // File headers, to make sure it doesn't try loading anything other than an XMB extern const int HeaderMagic; extern const char* HeaderMagicStr; class XMBElement; class XMBElementList; class XMBAttributeList; class XMBFile { public: XMBFile() : m_Pointer(NULL) {}; // Initialise from the contents of an XMB file. // FileData must remain allocated and unchanged while // the XMBFile is being used. void Initialise(char* FileData); // Returns the root element XMBElement getRoot() const; // Returns internal ID for a given ASCII element/attribute string. int getElementID(const char* Name) const; int getAttributeID(const char* Name) const; // For lazy people (e.g. me) when speed isn't vital: // Returns element/attribute string for a given internal ID std::string getElementString(const int ID) const; std::string getAttributeString(const int ID) const; private: char* m_Pointer; #ifdef XERO_USEMAP std::map m_ElementNames; std::map m_AttributeNames; #else int m_ElementNameCount; int m_AttributeNameCount; char* m_ElementPointer; char* m_AttributePointer; #endif std::string ReadZStrA(); }; class XMBElement { public: XMBElement(char* offset) : m_Pointer(offset) {} int getNodeName() const; XMBElementList getChildNodes() const; XMBAttributeList getAttributes() const; utf16string getText() const; int getLineNumber() const; private: // Pointer to the start of the node char* m_Pointer; }; class XMBElementList { public: XMBElementList(char* offset, int count) : Count(count), m_Pointer(offset), m_LastItemID(-2) {} // use -2 because it isn't x-1 where x is a non-negative integer XMBElement item(const int id); // returns Children[id] int Count; private: char* m_Pointer; // For optimised sequential access: int m_LastItemID; char* m_LastPointer; }; struct XMBAttribute { XMBAttribute(int name, utf16string value) : Name(name), Value(value) {}; int Name; utf16string Value; }; class XMBAttributeList { public: XMBAttributeList(char* offset, int count) : Count(count), m_Pointer(offset), m_LastItemID( -2 ) {}; // Get the attribute value directly (unlike Xerces) utf16string getNamedItem(const int AttributeName) const; // Returns an attribute by position in the list XMBAttribute item(const int id); int Count; private: // Pointer to start of attribute list char* m_Pointer; // For optimised sequential access: int m_LastItemID; char* m_LastPointer; }; #endif // _XEROXMB_H_ Index: ps/trunk/source/gui/CInput.cpp =================================================================== --- ps/trunk/source/gui/CInput.cpp (revision 1524) +++ ps/trunk/source/gui/CInput.cpp (revision 1525) @@ -1,832 +1,832 @@ /* CInput by Gustav Larsson gee@pyro.nu */ #include "precompiled.h" #include "GUI.h" #include "CInput.h" #include "ps/Font.h" #include "ogl.h" // TODO Gee: new #include "OverlayText.h" #include "lib/res/unifont.h" #include "ps/CLogger.h" #define LOG_CATEGORY "gui" using namespace std; //------------------------------------------------------------------- // Constructor / Destructor //------------------------------------------------------------------- CInput::CInput() : m_iBufferPos(0) { AddSetting(GUIST_float, "buffer-zone"); AddSetting(GUIST_CStrW, "caption"); AddSetting(GUIST_CStr, "font"); AddSetting(GUIST_bool, "scrollbar"); AddSetting(GUIST_CStr, "scrollbar-style"); AddSetting(GUIST_CGUISpriteInstance, "sprite"); - AddSetting(GUIST_int, "icon-id"); + AddSetting(GUIST_int, "cell-id"); AddSetting(GUIST_CColor, "textcolor"); // TODO Gee: (2004-08-14) // Add a setting for buffer zone //AddSetting(GUIST_int, " //GUI::SetSetting(this, "ghost", true); GUI::SetSetting(this, "scrollbar", false); // Add scroll-bar CGUIScrollBarVertical * bar = new CGUIScrollBarVertical(); bar->SetRightAligned(true); bar->SetUseEdgeButtons(true); AddScrollBar(bar); UpdateText(); // will create an empty row, just so we have somewhere to position the insertion marker } CInput::~CInput() { } int CInput::ManuallyHandleEvent(const SDL_Event* ev) { assert(m_iBufferPos != -1); int szChar = ev->key.keysym.sym; wchar_t cooked = (wchar_t)ev->key.keysym.unicode; // Since the GUI framework doesn't handle to set settings // in Unicode (CStrW), we'll simply retrieve the actual // pointer and edit that. CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting; switch (szChar){ //TODOcase '\r': /* case '\n': // TODO Gee: (2004-09-07) New line? I should just add '\n' *pCaption += wchar_t('\n'); ++m_iBufferPos; break; */ /* case '\r': // TODO Gee: (2004-09-07) New line? I should just add '\n' *pCaption += wchar_t('\n'); ++m_iBufferPos; break; */ case '\t': /* Auto Complete */ // TODO Gee: (2004-09-07) What to do with tab? break; case '\b': // TODO Gee: (2004-09-07) What is this? if (pCaption->Length() == 0 || m_iBufferPos == 0) break; if (m_iBufferPos == pCaption->Length()) *pCaption = pCaption->Left( (long) pCaption->Length()-1); else *pCaption = pCaption->Left( m_iBufferPos-1 ) + pCaption->Right( (long) pCaption->Length()-m_iBufferPos ); UpdateText(m_iBufferPos-1, m_iBufferPos, m_iBufferPos-1); --m_iBufferPos; break; case SDLK_DELETE: if (pCaption->Length() == 0 || m_iBufferPos == pCaption->Length()) break; *pCaption = pCaption->Left( m_iBufferPos ) + pCaption->Right( (long) pCaption->Length()-(m_iBufferPos+1) ); UpdateText(m_iBufferPos, m_iBufferPos+1, m_iBufferPos); break; case SDLK_HOME: m_iBufferPos = 0; break; case SDLK_END: m_iBufferPos = (long) pCaption->Length(); break; case SDLK_LEFT: if (m_iBufferPos) --m_iBufferPos; break; case SDLK_RIGHT: if (m_iBufferPos != pCaption->Length()) ++m_iBufferPos; break; case SDLK_UP: /*if (m_deqBufHistory.size() && iHistoryPos != (int)m_deqBufHistory.size() - 1) { iHistoryPos++; SetBuffer(m_deqBufHistory.at(iHistoryPos).data()); }*/ break; case SDLK_DOWN: /*if (iHistoryPos != -1) iHistoryPos--; if (iHistoryPos != -1) SetBuffer(m_deqBufHistory.at(iHistoryPos).data()); else FlushBuffer();*/ break; case SDLK_PAGEUP: //if (m_iMsgHistPos != (int)m_deqMsgHistory.size()) m_iMsgHistPos++; break; case SDLK_PAGEDOWN: //if (m_iMsgHistPos != 1) m_iMsgHistPos--; break; /* END: Message History Lookup */ case '\r': cooked = '\n'; // Change to '\n' and do default: // NOTE: Fall-through default: //Insert a character if (cooked == 0) return EV_PASS; // Important, because we didn't use any key if (m_iBufferPos == pCaption->Length()) *pCaption += cooked; else *pCaption = pCaption->Left(m_iBufferPos) + CStrW(cooked) + pCaption->Right((long) pCaption->Length()-m_iBufferPos); UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1); ++m_iBufferPos; // TODO //UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+4); //m_iBufferPos+=4; break; } //UpdateText(); return EV_HANDLED; } void CInput::HandleMessage(const SGUIMessage &Message) { // TODO Gee: IGUIScrollBarOwner::HandleMessage(Message); switch (Message.type) { case GUIM_SETTINGS_UPDATED: bool scrollbar; GUI::GetSetting(this, "scrollbar", scrollbar); // Update scroll-bar // TODO Gee: (2004-09-01) Is this really updated each time it should? if (scrollbar && (Message.value == CStr("size") || Message.value == CStr("z") || Message.value == CStr("absolute"))) { GetScrollBar(0).SetX( m_CachedActualSize.right ); GetScrollBar(0).SetY( m_CachedActualSize.top ); GetScrollBar(0).SetZ( GetBufferedZ() ); GetScrollBar(0).SetLength( m_CachedActualSize.bottom - m_CachedActualSize.top ); } // Update scrollbar if (Message.value == CStr("scrollbar-style")) { CStr scrollbar_style; GUI::GetSetting(this, Message.value, scrollbar_style); GetScrollBar(0).SetScrollBarStyle( scrollbar_style ); } if (Message.value == CStr("caption")) { UpdateText(); } break; case GUIM_MOUSE_PRESS_LEFT: // Okay, this section is about pressing the mouse and // choosing where the point should be placed. For // instance, if we press between a and b, the point // should of course be placed accordingly. Other // special cases are handled like the input box norms. { CPos mouse = GetMousePos(); // Pointer to caption, will come in handy CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting; // Now get the height of the font. CFont font ("Console"); float spacing = (float)font.GetLineSpacing(); float height = (float)font.GetHeight(); // Change mouse position relative to text. // Include scrolling. mouse -= m_CachedActualSize.TopLeft(); //if ((m_CharacterPositions.size()-1) * spacing + height < mouse.y) // m_iBufferPos = pCaption->Length(); int row = (int)(mouse.y / spacing);//m_CharachterPositions.size() if (row > (int)m_CharacterPositions.size()-1) row = (int)m_CharacterPositions.size()-1; // TODO Gee (2004-11-21): Okay, I need a 'list' for some reasons, but I would really // be able to get the specific element here. This is hopefully a temporary hack. list::iterator current = m_CharacterPositions.begin(); advance(current, row); //m_iBufferPos = m_CharacterPositions.get.m_ListStart; m_iBufferPos = current->m_ListStart; // Okay, no loop through the glyphs to find the appropriate X position float previous=0.f; int i=0; for (vector::iterator it=current->m_ListOfX.begin(); it!=current->m_ListOfX.end(); ++it, ++i) { if (*it >= mouse.x) { if (i != 0) { if (mouse.x - previous > *it - mouse.x) m_iBufferPos += i+1; else m_iBufferPos += i; } // else let the value be current->m_ListStart which it already is. // check if the previous or the current is closest. break; } previous = *it; } // If a position wasn't found, we will assume the last // character of that line. if (i == current->m_ListOfX.size()) m_iBufferPos += i; break; } case GUIM_MOUSE_WHEEL_DOWN: GetScrollBar(0).ScrollMinus(); // Since the scroll was changed, let's simulate a mouse movement // to check if scrollbar now is hovered HandleMessage(SGUIMessage(GUIM_MOUSE_MOTION)); break; case GUIM_MOUSE_WHEEL_UP: GetScrollBar(0).ScrollPlus(); // Since the scroll was changed, let's simulate a mouse movement // to check if scrollbar now is hovered HandleMessage(SGUIMessage(GUIM_MOUSE_MOTION)); break; case GUIM_LOAD: //SetFocus(); case GUIM_GOT_FOCUS: m_iBufferPos = 0; break; case GUIM_LOST_FOCUS: m_iBufferPos = -1; break; default: break; } } void CInput::Draw() { float bz = GetBufferedZ(); // First call draw on ScrollBarOwner bool scrollbar; GUI::GetSetting(this, "scrollbar", scrollbar); if (scrollbar) { // Draw scrollbar IGUIScrollBarOwner::Draw(); } if (GetGUI()) { CGUISpriteInstance *sprite; - int icon_id; + int cell_id; GUI::GetSettingPointer(this, "sprite", sprite); - GUI::GetSetting(this, "icon-id", icon_id); + GUI::GetSetting(this, "cell-id", cell_id); - GetGUI()->DrawSprite(*sprite, icon_id, bz, m_CachedActualSize); + GetGUI()->DrawSprite(*sprite, cell_id, bz, m_CachedActualSize); float scroll=0.f; if (scrollbar) { scroll = GetScrollBar(0).GetPos(); } CColor color; GUI::GetSetting(this, "textcolor", color); 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 ("Console"); font.Bind(); glPushMatrix(); CStrW caption; GUI::GetSetting(this, "caption", caption); // Get the height of this font. float h = (float)font.GetHeight(); float ls = (float)font.GetLineSpacing(); glTranslatef((GLfloat)int(m_CachedActualSize.left), (GLfloat)int(m_CachedActualSize.top+h), bz); glColor4f(1.f, 1.f, 1.f, 1.f); float buffered_y=0.f; for (list::const_iterator it = m_CharacterPositions.begin(); it != m_CharacterPositions.end(); ++it) { if (buffered_y > m_CachedActualSize.GetHeight()) break; glPushMatrix(); // We might as well use 'i' here, because we need it for (int i=0; i < (int)it->m_ListOfX.size(); ++i) { if (it->m_ListStart + i == m_iBufferPos) { // U+FE33: PRESENTATION FORM FOR VERTICAL LOW LINE // (sort of like a | which is aligned to the left of most characters) glPushMatrix(); glwprintf(L"%lc", 0xFE33); glPopMatrix(); } glwprintf(L"%lc", caption[it->m_ListStart + i]); } if (it->m_ListStart + it->m_ListOfX.size() == m_iBufferPos) { glwprintf(L"%lc", 0xFE33); } glPopMatrix(); glTranslatef(0.f, ls, 0.f); buffered_y += ls; } glPopMatrix(); glDisable(GL_TEXTURE_2D); } } void CInput::UpdateText(int from, int to_before, int to_after) { CStrW caption; GUI::GetSetting(this, "caption", caption); SRow row; row.m_ListStart = 0; int to; if (to_before == -1) to = (int)caption.Length(); CFont font ("Console"); //LOG(ERROR, LOG_CATEGORY, "Point 1 %d %d", to_before, to_after); list::iterator current_line; // Used to ... TODO int check_point_row_start = -1; int check_point_row_end = -1; // Reset if (from == 0 && to_before == -1) { m_CharacterPositions.clear(); current_line = m_CharacterPositions.begin(); } else { assert(to_before != -1); //LOG(ERROR, LOG_CATEGORY, "Point 2 - %d %d", from, to_before); list::iterator destroy_row_from, destroy_row_to; // Iterate, and remove everything between 'from' and 'to_before' // actually remove the entire lines they are on, it'll all have // to be redone. And when going along, we'll delete a row at a time // when continuing to see how much more after 'to' we need to remake. int i=0; for (list::iterator it=m_CharacterPositions.begin(); it!=m_CharacterPositions.end(); ++it, ++i) { if (destroy_row_from == list::iterator() && it->m_ListStart > from) { // Destroy the previous line, and all to 'to_before' //if (i >= 2) // destroy_row_from = it-2; //else // destroy_row_from = it-1; destroy_row_from = it; --destroy_row_from; // For the rare case that we might remove characters to a word // so that it should go on the previous line, we need to // by standards re-do the whole previous line too (if one // exists) if (destroy_row_from != m_CharacterPositions.begin()) --destroy_row_from; } if (destroy_row_to == list::iterator() && it->m_ListStart > to_before) { destroy_row_to = it; // If it isn't the last row, we'll add another row to delete, // just so we can see if the last restorted line is // identical to what it was before. If it isn't, then we'll // have to continue. // 'check_point_row_start' is where we store how the that // line looked. if (destroy_row_to != m_CharacterPositions.end()) { check_point_row_start = destroy_row_to->m_ListStart; check_point_row_end = check_point_row_start + (int)destroy_row_to->m_ListOfX.size(); if (destroy_row_to->m_ListOfX.empty()) ++check_point_row_end; } ++destroy_row_to; break; } } if (destroy_row_from == list::iterator()) { destroy_row_from = m_CharacterPositions.end(); --destroy_row_from; current_line = destroy_row_from; } if (destroy_row_to == list::iterator()) { destroy_row_to = m_CharacterPositions.end(); check_point_row_start = -1; } // set 'from' to the row we'll destroy from // and 'to' to the row we'll destroy to from = destroy_row_from->m_ListStart; if (destroy_row_to != m_CharacterPositions.end()) to = destroy_row_to->m_ListStart; // notice it will iterate [from, to), so it will never reach to. else to = (int)caption.Length(); // Setup the first row row.m_ListStart = destroy_row_from->m_ListStart; //LOG(ERROR, LOG_CATEGORY, "Point 3 %d", to); // Set current line, new rows will be added before current_line, so // we'll choose the destroy_row_to, because it won't be deleted // in the coming erase. current_line = destroy_row_to; list::iterator temp_it = destroy_row_to; --temp_it; CStr c_caption1(caption.GetSubstring(destroy_row_from->m_ListStart, (temp_it->m_ListStart + temp_it->m_ListOfX.size()) -destroy_row_from->m_ListStart)); //LOG(ERROR, LOG_CATEGORY, "Now destroying: \"%s\"", c_caption1.c_str()); m_CharacterPositions.erase(destroy_row_from, destroy_row_to); //LOG(ERROR, LOG_CATEGORY, "--> %d %d", from, to); ////CStr c_caption(caption.GetSubstring(from, to-from)); //LOG(ERROR, LOG_CATEGORY, "Re-doing string: \"%s\"", c_caption.c_str()); // If there has been a change in number of characters // we need to change all m_ListStart that comes after // the interval we just destroyed. We'll change all // values with the delta change of the string length. int delta = to_after - to_before; if (delta != 0) { for (list::iterator it=current_line; it!=m_CharacterPositions.end(); ++it) { it->m_ListStart += delta; } // Update our check point too! check_point_row_start += delta; check_point_row_end += delta; if (to != caption.Length()) to += delta; } } int last_word_started=from; int last_list_start=-1; float x_pos = 0.f; //if (to_before != -1) // return; //LOG(ERROR, LOG_CATEGORY, "%d %d", from, to); for (int i=from; i::iterator pos( &m_CharacterPositions[current_line] ); m_CharacterPositions.insert( pos, row ); ++current_line; }*/ if (i==to-1) break; // it will be added outside CStr c_caption1(caption.GetSubstring(row.m_ListStart, row.m_ListOfX.size())); //LOG(ERROR, LOG_CATEGORY, "Adding1: \"%s\" (%d,%d,%d)", c_caption1.c_str(), from, to, i); current_line = m_CharacterPositions.insert( current_line, row ); ++current_line; // Setup the next row: row.m_ListOfX.clear(); row.m_ListStart = i+1; x_pos = 0.f; // If it's done now, no more word-wrapping // needs to be done. //if (i==to-1) // break; } else { if (caption[i] == wchar_t(' ')/* || TODO Gee (2004-10-13): the '-' disappears, fix. caption[i] == wchar_t('-')*/) last_word_started = i+1; x_pos += (float)font.GetCharacterWidth(caption[i]); //LOG(ERROR, LOG_CATEGORY, "%c %f", (char)caption[i], (float)font->GetCharacterWidth(caption[i])); if (x_pos >= m_CachedActualSize.GetWidth()) { // The following decides whether it will word-wrap a word, // or if it's only one word on the line, where it has to // break the word apart. if (last_word_started == row.m_ListStart) { last_word_started = i; row.m_ListOfX.resize(row.m_ListOfX.size() - (i-last_word_started)); //row.m_ListOfX.push_back( x_pos ); //continue; } else { // regular word-wrap row.m_ListOfX.resize(row.m_ListOfX.size() - (i-last_word_started+1)); } // Now, create a new line: // notice: when we enter a newline, you can stand with the cursor // both before and after that character, being on different // rows. With automatic word-wrapping, that is not possible. Which // is intuitively correct. CStr c_caption1(caption.GetSubstring(row.m_ListStart, row.m_ListOfX.size())); //LOG(ERROR, LOG_CATEGORY, "Adding2: \"%s\" - %d [%d, %d[", c_caption1.c_str(), last_word_started, from, to); current_line = m_CharacterPositions.insert( current_line, row ); ++current_line; // Setup the next row: row.m_ListOfX.clear(); row.m_ListStart = last_word_started; i=last_word_started-1; x_pos = 0.f; } else // Get width of this character: row.m_ListOfX.push_back( x_pos ); } // Check if it's the last iteration, and we're not revising the whole string // because in that case, more word-wrapping might be needed. // also check if the current line isn't the end if (to_before != -1 && i == to-1 && current_line != m_CharacterPositions.end()) { //LOG(ERROR, LOG_CATEGORY, "row.m_ListStart = %d", row.m_ListStart); //LOG(ERROR, LOG_CATEGORY, "check_point_row_start = %d", check_point_row_start); //LOG(ERROR, LOG_CATEGORY, "last_list_start = %d", last_list_start); /* if (last_list_start != -1 && last_list_start == row.m_ListStart) { to = current_line->m_ListStart; LOG(ERROR, LOG_CATEGORY, "*** %d %d", i, to); } else { */ // check all rows and see if any existing //LOG(ERROR, LOG_CATEGORY, "(%d %d) (%d %d)", i, to, row.m_ListStart, check_point_row_start); if (row.m_ListStart != check_point_row_start) { list::iterator destroy_row_from, destroy_row_to; // Iterate, and remove everything between 'from' and 'to_before' // actually remove the entire lines they are on, it'll all have // to be redone. And when going along, we'll delete a row at a time // when continuing to see how much more after 'to' we need to remake. int i=0; for (list::iterator it=m_CharacterPositions.begin(); it!=m_CharacterPositions.end(); ++it, ++i) { if (destroy_row_from == list::iterator() && it->m_ListStart > check_point_row_start) { // Destroy the previous line, and all to 'to_before' //if (i >= 2) // destroy_row_from = it-2; //else // destroy_row_from = it-1; destroy_row_from = it; //--destroy_row_from; //LOG(ERROR, LOG_CATEGORY, "[ %d %d %d", i, it->m_ListStart, check_point_row_start); } if (destroy_row_to == list::iterator() && it->m_ListStart > check_point_row_end) { destroy_row_to = it; // If it isn't the last row, we'll add another row to delete, // just so we can see if the last restorted line is // identical to what it was before. If it isn't, then we'll // have to continue. // 'check_point_row_start' is where we store how the that // line looked. // if (destroy_row_to != if (destroy_row_to != m_CharacterPositions.end()) { check_point_row_start = destroy_row_to->m_ListStart; check_point_row_end = check_point_row_start + (int)destroy_row_to->m_ListOfX.size(); if (destroy_row_to->m_ListOfX.empty()) ++check_point_row_end; //LOG(ERROR, LOG_CATEGORY, "] %d %d %d", i, destroy_row_to->m_ListStart, check_point_row_end); } else check_point_row_start = check_point_row_end = -1; ++destroy_row_to; break; } } if (destroy_row_from == list::iterator()) { destroy_row_from = m_CharacterPositions.end(); --destroy_row_from; current_line = destroy_row_from; } if (destroy_row_to == list::iterator()) { destroy_row_to = m_CharacterPositions.end(); check_point_row_start = check_point_row_end = -1; } // set 'from' to the from row we'll destroy // and 'to' to 'to_after' from = destroy_row_from->m_ListStart; if (destroy_row_to != m_CharacterPositions.end()) to = destroy_row_to->m_ListStart; // notice it will iterate [from, to[, so it will never reach to. else to = (int)caption.Length(); //LOG(ERROR, LOG_CATEGORY, "Point 3 %d", to); // Set current line, new rows will be added before current_line, so // we'll choose the destroy_row_to, because it won't be deleted // in the coming erase. current_line = destroy_row_to; m_CharacterPositions.erase(destroy_row_from, destroy_row_to); //LOG(ERROR, LOG_CATEGORY, "--> %d %d", from, to); CStr c_caption(caption.GetSubstring(from, to-from)); //LOG(ERROR, LOG_CATEGORY, "Re-doing string: \"%s\"", c_caption.c_str()); /*if (current_line != m_CharacterPositions.end()) { current_line = m_CharacterPositions.erase(current_line); if (current_line != m_CharacterPositions.end()) { check_point_row_start = current_line->m_ListStart; to = check_point_row_start; } else { to = caption.Length(); } } else { check_point_row_start = -1; // just one more row, which is the last, we don't need this anymore to = caption.Length(); }*/ /*if (destroy_row_to != m_CharacterPositions.end()) to = destroy_row_to->m_ListStart-1; else to = caption.Length();*/ } // else, the for loop will end naturally. //} //last_list_start = row.m_ListStart; } } // add the final row (even if empty) CStr c_caption1(caption.GetSubstring(row.m_ListStart, row.m_ListOfX.size())); //LOG(ERROR, LOG_CATEGORY, "Adding3: \"%s\"", c_caption1.c_str()); m_CharacterPositions.insert( current_line, row ); //++current_line; } Index: ps/trunk/source/gui/IGUIButtonBehavior.cpp =================================================================== --- ps/trunk/source/gui/IGUIButtonBehavior.cpp (revision 1524) +++ ps/trunk/source/gui/IGUIButtonBehavior.cpp (revision 1525) @@ -1,116 +1,116 @@ /* IGUIButtonBehavior by Gustav Larsson gee@pyro.nu */ #include "precompiled.h" #include "GUI.h" using namespace std; //------------------------------------------------------------------- // Constructor / Destructor //------------------------------------------------------------------- IGUIButtonBehavior::IGUIButtonBehavior() : m_Pressed(false) { } IGUIButtonBehavior::~IGUIButtonBehavior() { } void IGUIButtonBehavior::HandleMessage(const SGUIMessage &Message) { // TODO Gee: easier access functions switch (Message.type) { case GUIM_MOUSE_PRESS_LEFT: { bool enabled; GUI::GetSetting(this, "enabled", enabled); if (!enabled) break; m_Pressed = true; } break; case GUIM_MOUSE_RELEASE_LEFT: { bool enabled; GUI::GetSetting(this, "enabled", enabled); if (!enabled) break; if (m_Pressed) { m_Pressed = false; // BUTTON WAS CLICKED HandleMessage(GUIM_PRESSED); ScriptEvent("press"); } } break; default: break; } } CColor IGUIButtonBehavior::ChooseColor() { CColor color, color_over, color_pressed, color_disabled; // Yes, the object must possess these settings. They are standard GUI::GetSetting(this, "textcolor", color); GUI::GetSetting(this, "textcolor-over", color_over); GUI::GetSetting(this, "textcolor-pressed", color_pressed); GUI::GetSetting(this, "textcolor-disabled", color_disabled); bool enabled; GUI::GetSetting(this, "enabled", enabled); if (!enabled) { return GUI<>::FallBackColor(color_disabled, color); } else if (m_MouseHovering) { if (m_Pressed) return GUI<>::FallBackColor(color_pressed, color); else return GUI<>::FallBackColor(color_over, color); } else return color; } void IGUIButtonBehavior::DrawButton(const CRect &rect, const float &z, CGUISpriteInstance& sprite, CGUISpriteInstance& sprite_over, CGUISpriteInstance& sprite_pressed, CGUISpriteInstance& sprite_disabled, - int icon_id) + int cell_id) { if (GetGUI()) { bool enabled; GUI::GetSetting(this, "enabled", enabled); if (!enabled) { - GetGUI()->DrawSprite(GUI<>::FallBackSprite(sprite_disabled, sprite), icon_id, z, rect); + GetGUI()->DrawSprite(GUI<>::FallBackSprite(sprite_disabled, sprite), cell_id, z, rect); } else if (m_MouseHovering) { if (m_Pressed) - GetGUI()->DrawSprite(GUI<>::FallBackSprite(sprite_pressed, sprite), icon_id, z, rect); + GetGUI()->DrawSprite(GUI<>::FallBackSprite(sprite_pressed, sprite), cell_id, z, rect); else - GetGUI()->DrawSprite(GUI<>::FallBackSprite(sprite_over, sprite), icon_id, z, rect); + GetGUI()->DrawSprite(GUI<>::FallBackSprite(sprite_over, sprite), cell_id, z, rect); } - else GetGUI()->DrawSprite(sprite, icon_id, z, rect); + else GetGUI()->DrawSprite(sprite, cell_id, z, rect); } } Index: ps/trunk/source/gui/CButton.cpp =================================================================== --- ps/trunk/source/gui/CButton.cpp (revision 1524) +++ ps/trunk/source/gui/CButton.cpp (revision 1525) @@ -1,147 +1,147 @@ /* 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_float, "buffer-zone"); AddSetting(GUIST_CGUIString, "caption"); AddSetting(GUIST_CStr, "font"); AddSetting(GUIST_CGUISpriteInstance, "sprite"); AddSetting(GUIST_CGUISpriteInstance, "sprite-over"); AddSetting(GUIST_CGUISpriteInstance, "sprite-pressed"); AddSetting(GUIST_CGUISpriteInstance, "sprite-disabled"); - AddSetting(GUIST_int, "icon-id"); + AddSetting(GUIST_int, "cell-id"); AddSetting(GUIST_EAlign, "text-align"); AddSetting(GUIST_EVAlign, "text-valign"); 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, this); // Set position of text // Check which alignment to use! EAlign align; EVAlign valign; float bz; GUI::GetSetting(this, "text-align", align); GUI::GetSetting(this, "text-valign", valign); GUI::GetSetting(this, "buffer-zone", bz); switch (align) { case EAlign_Left: m_TextPos.x = m_CachedActualSize.left + bz; break; case EAlign_Center: // Round to integer pixel values, else the fonts look awful m_TextPos.x = floorf(m_CachedActualSize.CenterPoint().x - m_GeneratedTexts[0]->m_Size.cx/2.f); break; case EAlign_Right: m_TextPos.x = m_CachedActualSize.right - m_GeneratedTexts[0]->m_Size.cx - bz; break; default: debug_warn("Broken EAlign in CButton::SetupText()"); break; } switch (valign) { case EVAlign_Top: m_TextPos.y = m_CachedActualSize.top + bz; break; case EVAlign_Center: // Round to integer pixel values, else the fonts look awful m_TextPos.y = floorf(m_CachedActualSize.CenterPoint().y - m_GeneratedTexts[0]->m_Size.cy/2.f); break; case EVAlign_Bottom: m_TextPos.y = m_CachedActualSize.bottom - m_GeneratedTexts[0]->m_Size.cy - bz; break; default: debug_warn("Broken EVAlign in CButton::SetupText()"); break; } } 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(); CGUISpriteInstance *sprite, *sprite_over, *sprite_pressed, *sprite_disabled; - int icon_id; + int cell_id; GUI::GetSettingPointer(this, "sprite", sprite); GUI::GetSettingPointer(this, "sprite-over", sprite_over); GUI::GetSettingPointer(this, "sprite-pressed", sprite_pressed); GUI::GetSettingPointer(this, "sprite-disabled", sprite_disabled); - GUI::GetSetting(this, "icon-id", icon_id); + GUI::GetSetting(this, "cell-id", cell_id); DrawButton(m_CachedActualSize, bz, *sprite, *sprite_over, *sprite_pressed, *sprite_disabled, - icon_id); + cell_id); CColor color = ChooseColor(); IGUITextOwner::Draw(0, color, m_TextPos, bz+0.1f); } Index: ps/trunk/source/gui/GUIRenderer.h =================================================================== --- ps/trunk/source/gui/GUIRenderer.h (revision 1524) +++ ps/trunk/source/gui/GUIRenderer.h (revision 1525) @@ -1,47 +1,51 @@ #ifndef GUIRenderer_h #define GUIRenderer_h #include "lib/res/handle.h" #include "ps/Overlay.h" #include "ps/CStr.h" #include +struct SGUIImageEffects; + namespace GUIRenderer { struct Handle_rfcnt_tex { Handle h; Handle_rfcnt_tex(); Handle_rfcnt_tex(Handle h_); Handle_rfcnt_tex(const Handle_rfcnt_tex& that); ~Handle_rfcnt_tex(); Handle_rfcnt_tex& operator=(Handle h_); }; struct SDrawCall { Handle_rfcnt_tex m_TexHandle; bool m_EnableBlending; + SGUIImageEffects* m_Effects; + CRect m_Vertices; CRect m_TexCoords; float m_DeltaZ; CColor m_BorderColor; // == CColor() for no border CColor m_BackColor; }; typedef std::vector DrawCalls; } #include "gui/CGUISprite.h" namespace GUIRenderer { - void UpdateDrawCallCache(DrawCalls &Calls, CStr &SpriteName, CRect& Size, int IconID, std::map &Sprites); + void UpdateDrawCallCache(DrawCalls &Calls, CStr &SpriteName, CRect& Size, int CellID, std::map &Sprites); void Draw(DrawCalls &Calls); } #endif // GUIRenderer_h Index: ps/trunk/source/gui/CGUISprite.h =================================================================== --- ps/trunk/source/gui/CGUISprite.h (revision 1524) +++ ps/trunk/source/gui/CGUISprite.h (revision 1525) @@ -1,149 +1,161 @@ /* A GUI Sprite by Gustav Larsson gee@pyro.nu --Overview-- A GUI Sprite, which is actually a collage of several sprites. --Usage-- Used internally and declared in XML files, read documentations on how. --More info-- Check GUI.h */ #ifndef CGUISprite_H #define CGUISprite_H //-------------------------------------------------------- // Includes / Compiler directives //-------------------------------------------------------- #include "GUIutil.h" #include "Overlay.h" #include "lib/res/ogl_tex.h" //-------------------------------------------------------- // Macros //-------------------------------------------------------- //-------------------------------------------------------- // Types //-------------------------------------------------------- //-------------------------------------------------------- // Error declarations //-------------------------------------------------------- //-------------------------------------------------------- // Declarations //-------------------------------------------------------- + +struct SGUIImageEffects +{ + CColor m_AddColor; + CColor m_MultiplyColor; +}; + + /** * @author Gustav Larsson * * A CGUISprite is actually a collage of several real * sprites, this struct represents is such real sprite. */ struct SGUIImage { - SGUIImage() : m_Border(false), m_DeltaZ(0.f) {} + SGUIImage() : m_Effects(NULL), m_Border(false), m_DeltaZ(0.f) {} // Filename of the texture CStr m_TextureName; // Image placement (relative to object) CClientArea m_Size; // Texture placement (relative to image placement) CClientArea m_TextureSize; // Because OpenGL wants textures in squares with a power of 2 (64x64, 256x256) // it's sometimes tedious to adjust this. So this value simulates which area // is the real texture CRect m_TexturePlacementInFile; // For textures that contain a collection of icons (e.g. unit portraits), this - // will be set to the size of one icon. An object's icon-id will determine + // will be set to the size of one icon. An object's cell-id will determine // which part of the texture is used. - // Equal to CSize(0,0) for non-icon textures. - CSize m_IconSize; + // Equal to CSize(0,0) for non-celled textures. + CSize m_CellSize; + + // Visual effects (e.g. colour modulation) + SGUIImageEffects* m_Effects; // Color CColor m_BackColor; CColor m_BorderColor; // 0 or 1 pixel border is the only option bool m_Border; /** * Z value modification of the image. * Inputted in XML as x-level, although it just an easier and safer * way of declaring delta-z. */ float m_DeltaZ; }; + /** * @author Gustav Larsson * * The GUI sprite, is actually several real sprites (images) * like a collage. View the section in the GUI * TDD for more information. * * Drawing routine is located in CGUI * * @see CGUI#DrawSprite */ class CGUISprite { public: CGUISprite() {} virtual ~CGUISprite() {} /** * Adds an image to the sprite collage. * * @param image Adds this image to the sprite collage. */ void AddImage(const SGUIImage &image) { m_Images.push_back(image); } /// List of images std::vector m_Images; }; #include "GUIRenderer.h" // An instance of a sprite, usually stored in IGUIObjects - basically a string // giving the sprite's name, but with some extra data to cache rendering // calculations between draw calls. class CGUISpriteInstance { public: CGUISpriteInstance(); CGUISpriteInstance(CStr SpriteName); CGUISpriteInstance &operator=(CStr SpriteName); - void Draw(CRect Size, int IconID, std::map &Sprites); + void Draw(CRect Size, int CellID, std::map &Sprites); void Invalidate(); bool IsEmpty() const; CStr GetName() { return m_SpriteName; } private: CStr m_SpriteName; // Stored drawing calls, for more efficient rendering GUIRenderer::DrawCalls m_DrawCallCache; // Relevant details of previously rendered sprite; the cache is invalidated // whenever any of these values changes. CRect m_CachedSize; - int m_CachedIconID; + int m_CachedCellID; }; #endif Index: ps/trunk/source/gui/GUIutil.h =================================================================== --- ps/trunk/source/gui/GUIutil.h (revision 1524) +++ ps/trunk/source/gui/GUIutil.h (revision 1525) @@ -1,432 +1,437 @@ /* 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 "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); // 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 { + SGUIIcon() : m_CellID(0) {} + // Texture name of icon CStr m_TextureName; // Size CSize m_Size; + + // Cell of texture to use; ignored unless the texture has specified cell-size + int m_CellID; }; /** * @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 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: * * "0 0 100% 100%" * "50%-10 50%-10 50%+10 50%+10" * * 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; struct SGUIMessage; class CGUISpriteInstance; /** * @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 * 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); }; #ifndef NDEBUG // Used to ensure type-safety, sort of template void CheckType(const IGUIObject* obj, const CStr& setting); #endif /** * @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: // Like GetSetting (below), but doesn't make a copy of the value // (so it can be modified later) static PS_RESULT GetSettingPointer(const IGUIObject *pObject, const CStr& Setting, T* &Value); /** * 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); /** * 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 cautiously 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); #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 CGUISpriteInstance& FallBackSprite( CGUISpriteInstance& prim, CGUISpriteInstance& sec) { // CStr() == empty string, null return (prim.IsEmpty() ? sec : prim); } /** * 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/CText.cpp =================================================================== --- ps/trunk/source/gui/CText.cpp (revision 1524) +++ ps/trunk/source/gui/CText.cpp (revision 1525) @@ -1,184 +1,184 @@ /* CText by Gustav Larsson gee@pyro.nu */ #include "precompiled.h" #include "GUI.h" #include "CText.h" #include "ogl.h" // TODO Gee: new #include "OverlayText.h" using namespace std; //------------------------------------------------------------------- // Constructor / Destructor //------------------------------------------------------------------- CText::CText() { AddSetting(GUIST_float, "buffer-zone"); AddSetting(GUIST_CGUIString, "caption"); AddSetting(GUIST_CStr, "font"); AddSetting(GUIST_bool, "scrollbar"); AddSetting(GUIST_CStr, "scrollbar-style"); AddSetting(GUIST_CGUISpriteInstance, "sprite"); - AddSetting(GUIST_int, "icon-id"); + AddSetting(GUIST_int, "cell-id"); AddSetting(GUIST_CColor, "textcolor"); // TODO Gee: (2004-08-14) // Add a setting for buffer zone //AddSetting(GUIST_int, " //GUI::SetSetting(this, "ghost", true); GUI::SetSetting(this, "scrollbar", false); // Add scroll-bar CGUIScrollBarVertical * bar = new CGUIScrollBarVertical(); bar->SetRightAligned(true); bar->SetUseEdgeButtons(true); AddScrollBar(bar); // Add text AddText(new SGUIText()); } CText::~CText() { } void CText::SetupText() { if (!GetGUI()) return; assert(m_GeneratedTexts.size()>=1); CColor color; 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) Don't define standard like this. Do it with the default style. font = "default"; CGUIString caption; bool scrollbar; GUI::GetSetting(this, "textcolor", color); GUI::GetSetting(this, "caption", caption); GUI::GetSetting(this, "scrollbar", scrollbar); float width = m_CachedActualSize.GetWidth(); // remove scrollbar if applicable if (scrollbar && GetScrollBar(0).GetStyle()) width -= GetScrollBar(0).GetStyle()->m_Width; float buffer_zone=0.f; GUI::GetSetting(this, "buffer-zone", buffer_zone); *m_GeneratedTexts[0] = GetGUI()->GenerateText(caption, font, width, buffer_zone, this); // Setup scrollbar if (scrollbar) { GetScrollBar(0).SetScrollRange( m_GeneratedTexts[0]->m_Size.cy ); GetScrollBar(0).SetScrollSpace( m_CachedActualSize.GetHeight() ); } } void CText::HandleMessage(const SGUIMessage &Message) { // TODO Gee: IGUIScrollBarOwner::HandleMessage(Message); IGUITextOwner::HandleMessage(Message); switch (Message.type) { case GUIM_SETTINGS_UPDATED: bool scrollbar; GUI::GetSetting(this, "scrollbar", scrollbar); // Update scroll-bar // TODO Gee: (2004-09-01) Is this really updated each time it should? if (scrollbar && (Message.value == CStr("size") || Message.value == CStr("z") || Message.value == CStr("absolute"))) { GetScrollBar(0).SetX( m_CachedActualSize.right ); GetScrollBar(0).SetY( m_CachedActualSize.top ); GetScrollBar(0).SetZ( GetBufferedZ() ); GetScrollBar(0).SetLength( m_CachedActualSize.bottom - m_CachedActualSize.top ); } // Update scrollbar if (Message.value == CStr("scrollbar-style")) { CStr scrollbar_style; GUI::GetSetting(this, Message.value, scrollbar_style); GetScrollBar(0).SetScrollBarStyle( scrollbar_style ); // Update text SetupText(); } break; case GUIM_MOUSE_WHEEL_DOWN: GetScrollBar(0).ScrollMinus(); // Since the scroll was changed, let's simulate a mouse movement // to check if scrollbar now is hovered HandleMessage(SGUIMessage(GUIM_MOUSE_MOTION)); break; case GUIM_MOUSE_WHEEL_UP: GetScrollBar(0).ScrollPlus(); // Since the scroll was changed, let's simulate a mouse movement // to check if scrollbar now is hovered HandleMessage(SGUIMessage(GUIM_MOUSE_MOTION)); break; default: break; } } void CText::Draw() { float bz = GetBufferedZ(); // First call draw on ScrollBarOwner bool scrollbar; GUI::GetSetting(this, "scrollbar", scrollbar); if (scrollbar) { // Draw scrollbar IGUIScrollBarOwner::Draw(); } if (GetGUI()) { CGUISpriteInstance *sprite; - int icon_id; + int cell_id; GUI::GetSettingPointer(this, "sprite", sprite); - GUI::GetSetting(this, "icon-id", icon_id); + GUI::GetSetting(this, "cell-id", cell_id); - GetGUI()->DrawSprite(*sprite, icon_id, bz, m_CachedActualSize); + GetGUI()->DrawSprite(*sprite, cell_id, bz, m_CachedActualSize); float scroll=0.f; if (scrollbar) { scroll = GetScrollBar(0).GetPos(); } CColor color; GUI::GetSetting(this, "textcolor", color); // Draw text IGUITextOwner::Draw(0, color, m_CachedActualSize.TopLeft() - CPos(0.f, scroll), bz+0.1f); } } Index: ps/trunk/source/gui/CGUI.cpp =================================================================== --- ps/trunk/source/gui/CGUI.cpp (revision 1524) +++ ps/trunk/source/gui/CGUI.cpp (revision 1525) @@ -1,1659 +1,1746 @@ /* CGUI by Gustav Larsson gee@pyro.nu */ #include "precompiled.h" #include #include #include #include "lib/res/unifont.h" #include "GUI.h" // Types - when including them into the engine. #include "CButton.h" #include "CImage.h" #include "CText.h" #include "CCheckBox.h" #include "CRadioButton.h" #include "CInput.h" #include "CProgressBar.h" #include "MiniMap.h" #include "ps/Xeromyces.h" #include "ps/Font.h" #include "Pyrogenesis.h" #include "input.h" #include "OverlayText.h" // TODO Gee: Whatever include CRect/CPos/CSize #include "Overlay.h" #include "scripting/ScriptingHost.h" #include "Hotkey.h" // namespaces used using namespace std; #include "ps/CLogger.h" #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, }; // Globals used. extern int g_xres, g_yres; extern bool keys[SDLK_LAST]; //------------------------------------------------------------------- // 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) { int ret = EV_PASS; if (ev->type == SDL_GUIHOTKEYPRESS) { const CStr& objectName = *(CStr*) ev->user.code; IGUIObject* object = FindObjectByName(objectName); if (! object) { LOG(ERROR, LOG_CATEGORY, "Cannot find hotkeyed object '%s'", objectName.c_str()); } else { object->HandleMessage( SGUIMessage( GUIM_PRESSED ) ); object->ScriptEvent("press"); } } else if (ev->type == SDL_MOUSEMOTION) { // Yes the mouse position is stored as float to avoid // constant conversations when operating in a // float-based environment. m_MousePos = CPos((float)ev->motion.x, (float)ev->motion.y); GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::HandleMessage, SGUIMessage(GUIM_MOUSE_MOTION)); } // Update m_MouseButtons. (BUTTONUP is handled later.) else 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; // TODO Gee: (2004-09-08) Big TODO, don't do the below if the SDL_Event is something like a keypress! 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) { if (pNearest != m_FocusedObject) { // Update focused object if (m_FocusedObject) m_FocusedObject->HandleMessage(SGUIMessage(GUIM_LOST_FOCUS)); m_FocusedObject = pNearest; m_FocusedObject->HandleMessage(SGUIMessage(GUIM_GOT_FOCUS)); } pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_PRESS_LEFT)); pNearest->ScriptEvent("mouseleftpress"); // Block event, so things on the map (behind the GUI) won't be pressed LOG(ERROR, LOG_CATEGORY, "Left click blocked"); ret = EV_HANDLED; } break; case SDL_BUTTON_WHEELDOWN: // wheel down if (pNearest) { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_WHEEL_DOWN)); pNearest->ScriptEvent("mousewheeldown"); ret = EV_HANDLED; } break; case SDL_BUTTON_WHEELUP: // wheel up if (pNearest) { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_WHEEL_UP)); pNearest->ScriptEvent("mousewheelup"); ret = EV_HANDLED; } 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"); ret = EV_HANDLED; } } // 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); } // Handle keys for input boxes if (GetFocusedObject() && ev->type == SDL_KEYDOWN) { if( (ev->key.keysym.sym != SDLK_ESCAPE ) && !keys[SDLK_LCTRL] && !keys[SDLK_RCTRL] && !keys[SDLK_LALT] && !keys[SDLK_RALT]) { ret = GetFocusedObject()->ManuallyHandleEvent(ev); } // else will return EV_PASS because we never used the button. } return ret; } 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_FocusedObject(NULL) { m_BaseObject = new CGUIDummyObject; m_BaseObject->SetGUI(this); // Construct the parent object for all GUI JavaScript things m_ScriptObject = 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 { // Error reporting will be handled with the NULL return. return NULL; } } void CGUI::Initialize() { // Add base types! // You can also add types outside the GUI to extend the flexibility of the GUI. // Pyrogenesis though will have all the object types inserted from here. AddObjectType("empty", &CGUIDummyObject::ConstructObject); AddObjectType("button", &CButton::ConstructObject); AddObjectType("image", &CImage::ConstructObject); AddObjectType("text", &CText::ConstructObject); AddObjectType("checkbox", &CCheckBox::ConstructObject); AddObjectType("radiobutton", &CRadioButton::ConstructObject); AddObjectType("progressbar", &CProgressBar::ConstructObject); AddObjectType("minimap", &CMiniMap::ConstructObject); AddObjectType("input", &CInput::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(CGUISpriteInstance& Sprite, - int IconID, + int CellID, const float &Z, const CRect &Rect, const CRect &Clipping) { // If the sprite doesn't exist (name == ""), don't bother drawing anything if (Sprite.IsEmpty()) return; // TODO: Clipping? glPushMatrix(); glTranslatef(0.0f, 0.0f, Z); - Sprite.Draw(Rect, IconID, m_Sprites); + Sprite.Draw(Rect, CellID, m_Sprites); 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; } + for (std::map::iterator it2 = m_Sprites.begin(); it2 != m_Sprites.end(); ++it2) + for (std::vector::iterator it3 = it2->second.m_Images.begin(); it3 != it2->second.m_Images.end(); ++it3) + delete it3->m_Effects; + // 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); // can throw // 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 { - float m_YFrom, // The images starting location in Y - m_YTo, // The images end location in Y + float m_YFrom, // The image's starting location in Y + m_YTo, // The image's 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 float &width, const float &y, - const CSize &Size, const CStr& TextureName, - const float &BufferZone) + void SetupSpriteCall(const bool Left, SGUIText::SSpriteCall &SpriteCall, + const float width, const float y, + const CSize &Size, const CStr &TextureName, + const float BufferZone, const int CellID) { // 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_CellID = CellID; SpriteCall.m_Sprite = 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 CStr& Font, const float &Width, const float &BufferZone, const IGUIObject *pObject) { SGUIText Text; // object we're generating if (string.m_Words.size() == 0) return Text; float x=BufferZone, y=BufferZone; // drawing pointer int from=0; bool done=false; bool FirstLine = true; // Necessary because text in the first line is shorter // (it doesn't count the line spacing) // 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); // 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. float prelim_line_height=0.f; // Width and height of all text calls generated. string.GenerateTextCall(Feedback, Font, string.m_Words[i], string.m_Words[i+1], FirstLine); // 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) { for (vector::const_iterator it = Feedback.m_Images[j].begin(); 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. float _y; if (Images[j].size() > 0) _y = MAX(y, Images[j].back().m_YTo); else _y = y; // 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); + Image.SetupSpriteCall((j==CGUIString::SFeedback::Left), SpriteCall, Width, _y, size, icon.m_TextureName, BufferZone, icon.m_CellID); // 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. int temp_from = from; from = i; static const int From=0, To=1; //int width_from=0, width_to=width; float width_range[2]; width_range[From] = BufferZone; width_range[To] = Width - BufferZone; // 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. float 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. float line_height=0.f; 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; // 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], FirstLine); // 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 one. CGUIString::SFeedback Feedback2; // Defaults string.GenerateTextCall(Feedback2, Font, string.m_Words[j], string.m_Words[j+1], FirstLine, 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. float x_pointer=0.f; 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_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, 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.f; // Update height of all Text.m_Size.cy = MAX(Text.m_Size.cy, y+BufferZone); FirstLine = false; // 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(SGUIText &Text, const CColor &DefaultColor, const CPos &pos, const float &z) { // TODO Gee: All these really necessary? Some // are deafults and if you changed them // the opposite value at the end of the functions, // some things won't be drawn correctly. 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(); // TODO Gee: (2004-09-04) Why are font corrupted if inputted float value? glTranslatef((GLfloat)int(pos.x+it->m_Pos.x), (GLfloat)int(pos.y+it->m_Pos.y), z); glColor4f(color.r, color.g, color.b, color.a); glwprintf(L"%ls", it->m_String.c_str()); // "%ls" is necessary in case m_String contains % symbols glPopMatrix(); } delete font; for (list::iterator it=Text.m_SpriteCalls.begin(); it!=Text.m_SpriteCalls.end(); ++it) { - DrawSprite(it->m_Sprite, 0, z, it->m_Area + pos); + DrawSprite(it->m_Sprite, it->m_CellID, z, it->m_Area + pos); } // TODO To whom it may concern: Thing were not reset, so // I added this line, modify if incorrect -- glDisable(GL_TEXTURE_2D); // -- GL } 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, 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 CStr 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