Index: ps/trunk/source/ps/Font.cpp =================================================================== --- ps/trunk/source/ps/Font.cpp (revision 10984) +++ ps/trunk/source/ps/Font.cpp (revision 10985) @@ -1,91 +1,74 @@ /* Copyright (C) 2009 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Font.h" #include "lib/res/graphics/unifont.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/CLogger.h" #include #include const wchar_t* DefaultFont = L"sans-10"; CFont::CFont(const CStrW& name) { - // TODO perhaps: cache the resultant filename (or Handle) for each - // font name; but it's nice to allow run-time alteration of the fonts + h = unifont_load(g_VFS, name); - std::string fontFilename; - - CStr fontName = "font." + name.ToUTF8(); - - // See if the config value can be loaded - CConfigValue* fontFilenameVar = g_ConfigDB.GetValue(CFG_USER, fontName); - if (fontFilenameVar && fontFilenameVar->GetString(fontFilename)) - { - h = unifont_load(g_VFS, CStr(fontFilename).FromUTF8()); - } - else - { - // Not found in the config file -- try it as a simple filename - h = unifont_load(g_VFS, name); - - // Found it - if (h > 0) - return; - - // Not found as a font -- give up and use the default. - LOGERROR(L"Failed to find font '%ls'", name.c_str()); - h = unifont_load(g_VFS, DefaultFont); - // Assume this worked - } + // Found it + if (h > 0) + return; + + // Not found as a font -- give up and use the default. + LOGERROR(L"Failed to find font '%ls'", name.c_str()); + h = unifont_load(g_VFS, DefaultFont); + // Assume this worked } CFont::~CFont() { unifont_unload(h); } void CFont::Bind() { unifont_bind(h); } int CFont::GetLineSpacing() { return unifont_linespacing(h); } int CFont::GetHeight() { return unifont_height(h); } int CFont::GetCharacterWidth(wchar_t c) { return unifont_character_width(h, c); } void CFont::CalculateStringSize(const CStrW& string, int& width, int& height) { unifont_stringsize(h, string.c_str(), width, height); } Index: ps/trunk/source/gui/CInput.cpp =================================================================== --- ps/trunk/source/gui/CInput.cpp (revision 10984) +++ ps/trunk/source/gui/CInput.cpp (revision 10985) @@ -1,2010 +1,1989 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* CInput */ #include "precompiled.h" #include "GUI.h" #include "CInput.h" #include "CGUIScrollBarVertical.h" -#include "ps/Font.h" +#include "graphics/TextRenderer.h" #include "lib/ogl.h" - #include "lib/res/graphics/unifont.h" #include "lib/sysdep/clipboard.h" - -#include "ps/Hotkey.h" #include "ps/CLogger.h" +#include "ps/Font.h" #include "ps/Globals.h" +#include "ps/Hotkey.h" #include +extern int g_yres; + //------------------------------------------------------------------- // Constructor / Destructor //------------------------------------------------------------------- CInput::CInput() : m_iBufferPos(-1), m_iBufferPos_Tail(-1), m_SelectingText(false), m_HorizontalScroll(0.f) { AddSetting(GUIST_float, "buffer_zone"); AddSetting(GUIST_CStrW, "caption"); AddSetting(GUIST_int, "cell_id"); AddSetting(GUIST_CStrW, "font"); AddSetting(GUIST_int, "max_length"); AddSetting(GUIST_bool, "multiline"); AddSetting(GUIST_bool, "scrollbar"); AddSetting(GUIST_CStr, "scrollbar_style"); AddSetting(GUIST_CGUISpriteInstance, "sprite"); AddSetting(GUIST_CGUISpriteInstance, "sprite_selectarea"); AddSetting(GUIST_CColor, "textcolor"); AddSetting(GUIST_CColor, "textcolor_selected"); AddSetting(GUIST_CStrW, "tooltip"); AddSetting(GUIST_CStr, "tooltip_style"); // Add scroll-bar CGUIScrollBarVertical * bar = new CGUIScrollBarVertical(); bar->SetRightAligned(true); bar->SetUseEdgeButtons(true); AddScrollBar(bar); } CInput::~CInput() { } InReaction CInput::ManuallyHandleEvent(const SDL_Event_* ev) { ENSURE(m_iBufferPos != -1); if (ev->ev.type == SDL_HOTKEYDOWN) { return(ManuallyHandleHotkeyEvent(ev)); } else if (ev->ev.type == SDL_KEYDOWN) { // 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; bool shiftKeyPressed = g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT]; int szChar = ev->ev.key.keysym.sym; wchar_t cooked = (wchar_t)ev->ev.key.keysym.unicode; switch (szChar) { case SDLK_TAB: // '\t' /* Auto Complete */ // TODO Gee: (2004-09-07) What to do with tab? break; case SDLK_BACKSPACE: // '\b' m_WantedX=0.0f; if (SelectingText()) DeleteCurSelection(); else { m_iBufferPos_Tail = -1; if (pCaption->empty() || m_iBufferPos == 0) { break; } else { if (m_iBufferPos == (int)pCaption->length()) *pCaption = pCaption->Left( (long) pCaption->length()-1); else *pCaption = pCaption->Left( m_iBufferPos-1 ) + pCaption->Right( (long) pCaption->length()-m_iBufferPos ); --m_iBufferPos; UpdateText(m_iBufferPos, m_iBufferPos+1, m_iBufferPos); } } UpdateAutoScroll(); break; case SDLK_DELETE: m_WantedX=0.0f; // If selection: if (SelectingText()) { DeleteCurSelection(); } else { if (pCaption->empty() || m_iBufferPos == (int)pCaption->length()) { break; } else { *pCaption = pCaption->Left( m_iBufferPos ) + pCaption->Right( (long) pCaption->length()-(m_iBufferPos+1) ); UpdateText(m_iBufferPos, m_iBufferPos+1, m_iBufferPos); } } UpdateAutoScroll(); break; case SDLK_HOME: // If there's not a selection, we should create one now if (!shiftKeyPressed) { // Make sure a selection isn't created. m_iBufferPos_Tail = -1; } else if (!SelectingText()) { // Place tail at the current point: m_iBufferPos_Tail = m_iBufferPos; } m_iBufferPos = 0; m_WantedX=0.0f; UpdateAutoScroll(); break; case SDLK_END: // If there's not a selection, we should create one now if (!shiftKeyPressed) { // Make sure a selection isn't created. m_iBufferPos_Tail = -1; } else if (!SelectingText()) { // Place tail at the current point: m_iBufferPos_Tail = m_iBufferPos; } m_iBufferPos = (long) pCaption->length(); m_WantedX=0.0f; UpdateAutoScroll(); break; /** Conventions for Left/Right when text is selected: References: Visual Studio Visual Studio has the 'newer' approach, used by newer versions of things, and in newer applications. A left press will always place the pointer on the left edge of the selection, and then of course remove the selection. Right will do the exakt same thing. If you have the pointer on the right edge and press right, it will in other words just remove the selection. Windows (eg. Notepad) A left press always takes the pointer a step to the left and removes the selection as if it were never there in the first place. Right of course does the same thing but to the right. I chose the Visual Studio convention. Used also in Word, gtk 2.0, MSN Messenger. **/ case SDLK_LEFT: m_WantedX=0.f; if (shiftKeyPressed || !SelectingText()) { if (!shiftKeyPressed) { m_iBufferPos_Tail = -1; } else if (!SelectingText()) { m_iBufferPos_Tail = m_iBufferPos; } if (m_iBufferPos > 0) --m_iBufferPos; } else { if (m_iBufferPos_Tail < m_iBufferPos) m_iBufferPos = m_iBufferPos_Tail; m_iBufferPos_Tail = -1; } UpdateAutoScroll(); break; case SDLK_RIGHT: m_WantedX=0.0f; if (shiftKeyPressed || !SelectingText()) { if (!shiftKeyPressed) { m_iBufferPos_Tail = -1; } else if (!SelectingText()) { m_iBufferPos_Tail = m_iBufferPos; } if (m_iBufferPos < (int)pCaption->length()) ++m_iBufferPos; } else { if (m_iBufferPos_Tail > m_iBufferPos) m_iBufferPos = m_iBufferPos_Tail; m_iBufferPos_Tail = -1; } UpdateAutoScroll(); break; /** Conventions for Up/Down when text is selected: References: Visual Studio Visual Studio has a very strange approach, down takes you below the selection to the next row, and up to the one prior to the whole selection. The weird part is that it is always aligned as the 'pointer'. I decided this is to much work for something that is a bit arbitrary Windows (eg. Notepad) Just like with left/right, the selection is destroyed and it moves just as if there never were a selection. I chose the Notepad convention even though I use the VS convention with left/right. **/ case SDLK_UP: { if (!shiftKeyPressed) { m_iBufferPos_Tail = -1; } else if (!SelectingText()) { m_iBufferPos_Tail = m_iBufferPos; } std::list::iterator current = m_CharacterPositions.begin(); while (current != m_CharacterPositions.end()) { if (m_iBufferPos >= current->m_ListStart && m_iBufferPos <= current->m_ListStart+(int)current->m_ListOfX.size()) break; ++current; } float pos_x; if (m_iBufferPos-current->m_ListStart == 0) pos_x = 0.f; else pos_x = current->m_ListOfX[m_iBufferPos-current->m_ListStart-1]; if (m_WantedX > pos_x) pos_x = m_WantedX; // Now change row: if (current != m_CharacterPositions.begin()) { --current; // Find X-position: m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX); } // else we can't move up UpdateAutoScroll(); } break; case SDLK_DOWN: { if (!shiftKeyPressed) { m_iBufferPos_Tail = -1; } else if (!SelectingText()) { m_iBufferPos_Tail = m_iBufferPos; } std::list::iterator current = m_CharacterPositions.begin(); while (current != m_CharacterPositions.end()) { if (m_iBufferPos >= current->m_ListStart && m_iBufferPos <= current->m_ListStart+(int)current->m_ListOfX.size()) break; ++current; } float pos_x; if (m_iBufferPos-current->m_ListStart == 0) pos_x = 0.f; else pos_x = current->m_ListOfX[m_iBufferPos-current->m_ListStart-1]; if (m_WantedX > pos_x) pos_x = m_WantedX; // Now change row: // Add first, so we can check if it's .end() ++current; if (current != m_CharacterPositions.end()) { // Find X-position: m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX); } // else we can't move up UpdateAutoScroll(); } break; case SDLK_PAGEUP: GetScrollBar(0).ScrollMinusPlenty(); break; case SDLK_PAGEDOWN: GetScrollBar(0).ScrollPlusPlenty(); break; /* END: Message History Lookup */ case '\r': // 'Return' should do a Press event for single liners (e.g. submitting forms) // otherwise a '\n' character will be added. { bool multiline; GUI::GetSetting(this, "multiline", multiline); if (!multiline) { SendEvent(GUIM_PRESSED, "press"); break; } cooked = '\n'; // Change to '\n' and do default: // NOTE: Fall-through } default: //Insert a character { if (cooked == 0) return IN_PASS; // Important, because we didn't use any key // check max length int max_length; GUI::GetSetting(this, "max_length", max_length); if (max_length != 0 && (int)pCaption->length() >= max_length) break; m_WantedX=0.0f; if (SelectingText()) DeleteCurSelection(); m_iBufferPos_Tail = -1; if (m_iBufferPos == (int)pCaption->length()) *pCaption += cooked; else *pCaption = pCaption->Left(m_iBufferPos) + cooked + pCaption->Right((long) pCaption->length()-m_iBufferPos); UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1); ++m_iBufferPos; UpdateAutoScroll(); } break; } return IN_HANDLED; } return IN_PASS; } InReaction CInput::ManuallyHandleHotkeyEvent(const SDL_Event_* ev) { CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting; bool shiftKeyPressed = g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT]; std::string hotkey = static_cast(ev->ev.user.data1); if (hotkey == "paste") { m_WantedX=0.0f; wchar_t* text = sys_clipboard_get(); if (text) { if (SelectingText()) { DeleteCurSelection(); } if (m_iBufferPos == (int)pCaption->length()) *pCaption += text; else *pCaption = pCaption->Left(m_iBufferPos) + text + pCaption->Right((long) pCaption->length()-m_iBufferPos); UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1); m_iBufferPos += (int)wcslen(text); sys_clipboard_free(text); } return IN_HANDLED; } else if (hotkey == "copy" || hotkey == "cut") { m_WantedX=0.0f; if (SelectingText()) { int virtualFrom; int virtualTo; if (m_iBufferPos_Tail >= m_iBufferPos) { virtualFrom = m_iBufferPos; virtualTo = m_iBufferPos_Tail; } else { virtualFrom = m_iBufferPos_Tail; virtualTo = m_iBufferPos; } CStrW text = (pCaption->Left(virtualTo)).Right(virtualTo - virtualFrom); sys_clipboard_set(&text[0]); if (hotkey == "cut") { DeleteCurSelection(); } } return IN_HANDLED; } else if (hotkey == "text.delete.left") { m_WantedX=0.0f; if (SelectingText()) { DeleteCurSelection(); } if (!pCaption->empty() && !m_iBufferPos == 0) { m_iBufferPos_Tail = m_iBufferPos; CStrW searchString = pCaption->Left( m_iBufferPos ); // If we are starting in whitespace, adjust position until we get a non whitespace while (m_iBufferPos > 0) { if (!iswspace(searchString[m_iBufferPos - 1])) break; m_iBufferPos--; } // If we end up on a puctuation char we just delete it (treat punct like a word) if (iswpunct(searchString[m_iBufferPos - 1])) m_iBufferPos--; else { // Now we are on a non white space character, adjust position to char after next whitespace char is found while (m_iBufferPos > 0) { if (iswspace(searchString[m_iBufferPos - 1]) || iswpunct(searchString[m_iBufferPos - 1])) break; m_iBufferPos--; } } DeleteCurSelection(); } return IN_HANDLED; } else if (hotkey == "text.delete.right") { m_WantedX=0.0f; if (SelectingText()) { DeleteCurSelection(); } if (!pCaption->empty() && m_iBufferPos < (int)pCaption->length()) { // Delete the word to the right of the cursor m_iBufferPos_Tail = m_iBufferPos; // Delete chars to the right unit we hit whitespace while (++m_iBufferPos < (int)pCaption->length()) { if (iswspace((*pCaption)[m_iBufferPos]) || iswpunct((*pCaption)[m_iBufferPos])) break; } // Eliminate any whitespace behind the word we just deleted while (m_iBufferPos < (int)pCaption->length()) { if (!iswspace((*pCaption)[m_iBufferPos])) break; m_iBufferPos++; } DeleteCurSelection(); } return IN_HANDLED; } else if (hotkey == "text.move.left") { m_WantedX=0.0f; if (shiftKeyPressed || !SelectingText()) { if (!shiftKeyPressed) { m_iBufferPos_Tail = -1; } else if (!SelectingText()) { m_iBufferPos_Tail = m_iBufferPos; } if (!pCaption->empty() && !m_iBufferPos == 0) { CStrW searchString = pCaption->Left( m_iBufferPos ); // If we are starting in whitespace, adjust position until we get a non whitespace while (m_iBufferPos > 0) { if (!iswspace(searchString[m_iBufferPos - 1])) break; m_iBufferPos--; } // If we end up on a puctuation char we just select it (treat punct like a word) if (iswpunct(searchString[m_iBufferPos - 1])) m_iBufferPos--; else { // Now we are on a non white space character, adjust position to char after next whitespace char is found while (m_iBufferPos > 0) { if (iswspace(searchString[m_iBufferPos - 1]) || iswpunct(searchString[m_iBufferPos - 1])) break; m_iBufferPos--; } } } } else { if (m_iBufferPos_Tail < m_iBufferPos) m_iBufferPos = m_iBufferPos_Tail; m_iBufferPos_Tail = -1; } UpdateAutoScroll(); return IN_HANDLED; } else if (hotkey == "text.move.right") { m_WantedX=0.0f; if (shiftKeyPressed || !SelectingText()) { if (!shiftKeyPressed) { m_iBufferPos_Tail = -1; } else if (!SelectingText()) { m_iBufferPos_Tail = m_iBufferPos; } if (!pCaption->empty() && m_iBufferPos < (int)pCaption->length()) { CStrW searchString = *pCaption; // Select chars to the right until we hit whitespace while (++m_iBufferPos < (int)pCaption->length()) { if (iswspace((*pCaption)[m_iBufferPos]) || iswpunct((*pCaption)[m_iBufferPos])) break; } // Also select any whitespace following the word we just selected while (m_iBufferPos < (int)pCaption->length()) { if (!iswspace((*pCaption)[m_iBufferPos])) break; m_iBufferPos++; } } } else { if (m_iBufferPos_Tail > m_iBufferPos) m_iBufferPos = m_iBufferPos_Tail; m_iBufferPos_Tail = -1; } UpdateAutoScroll(); return IN_HANDLED; } else { return IN_PASS; } } void CInput::HandleMessage(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("size") || Message.value == CStr("z") || Message.value == CStr("font") || Message.value == CStr("absolute") || Message.value == CStr("caption") || Message.value == CStr("scrollbar") || Message.value == CStr("scrollbar_style")) { UpdateText(); } if (Message.value == CStr("multiline")) { bool multiline; GUI::GetSetting(this, "multiline", multiline); if (multiline == false) { GetScrollBar(0).SetLength(0.f); } else { GetScrollBar(0).SetLength( m_CachedActualSize.bottom - m_CachedActualSize.top ); } UpdateText(); } }break; case GUIM_MOUSE_PRESS_LEFT: // Check if we're selecting the scrollbar: { bool scrollbar, multiline; GUI::GetSetting(this, "scrollbar", scrollbar); GUI::GetSetting(this, "multiline", multiline); if (GetScrollBar(0).GetStyle() && multiline) { if (GetMousePos().x > m_CachedActualSize.right - GetScrollBar(0).GetStyle()->m_Width) break; } // 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. if (g_keys[SDLK_RSHIFT] || g_keys[SDLK_LSHIFT]) { m_iBufferPos = GetMouseHoveringTextPosition(); } else { m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition(); } m_SelectingText = true; UpdateAutoScroll(); // If we immediately release the button it will just be seen as a click // for the user though. }break; case GUIM_MOUSE_DBLCLICK_LEFT: { CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting; if (pCaption->length() == 0) break; m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition(); if (m_iBufferPos >= (int)pCaption->length()) m_iBufferPos = m_iBufferPos_Tail = pCaption->length() - 1; // See if we are clicking over whitespace if (iswspace((*pCaption)[m_iBufferPos])) { // see if we are in a section of whitespace greater than one character if ((m_iBufferPos + 1 < (int) pCaption->length() && iswspace((*pCaption)[m_iBufferPos + 1])) || (m_iBufferPos - 1 > 0 && iswspace((*pCaption)[m_iBufferPos - 1]))) { // // We are clicking in an area with more than one whitespace character // so we select both the word to the left and then the word to the right // // [1] First the left // skip the whitespace while (m_iBufferPos > 0) { if (!iswspace((*pCaption)[m_iBufferPos - 1])) break; m_iBufferPos--; } // now go until we hit white space or punctuation while (m_iBufferPos > 0) { if (iswspace((*pCaption)[m_iBufferPos - 1])) break; m_iBufferPos--; if (iswpunct((*pCaption)[m_iBufferPos])) break; } // [2] Then the right // go right until we are not in whitespace while (++m_iBufferPos_Tail < (int)pCaption->length()) { if (!iswspace((*pCaption)[m_iBufferPos_Tail])) break; } if (m_iBufferPos_Tail == (int)pCaption->length()) break; // now go to the right until we hit whitespace or punctuation while (++m_iBufferPos_Tail < (int)pCaption->length()) { if (iswspace((*pCaption)[m_iBufferPos_Tail]) || iswpunct((*pCaption)[m_iBufferPos_Tail])) break; } } else { // single whitespace so select word to the right while (++m_iBufferPos_Tail < (int)pCaption->length()) { if (!iswspace((*pCaption)[m_iBufferPos_Tail])) break; } if (m_iBufferPos_Tail == (int)pCaption->length()) break; // Don't include the leading whitespace m_iBufferPos = m_iBufferPos_Tail; // now go to the right until we hit whitespace or punctuation while (++m_iBufferPos_Tail < (int)pCaption->length()) { if (iswspace((*pCaption)[m_iBufferPos_Tail]) || iswpunct((*pCaption)[m_iBufferPos_Tail])) break; } } } else { // clicked on non-whitespace so select current word // go until we hit white space or punctuation while (m_iBufferPos > 0) { if (iswspace((*pCaption)[m_iBufferPos - 1])) break; m_iBufferPos--; if (iswpunct((*pCaption)[m_iBufferPos])) break; } // go to the right until we hit whitespace or punctuation while (++m_iBufferPos_Tail < (int)pCaption->length()) { if (iswspace((*pCaption)[m_iBufferPos_Tail]) || iswpunct((*pCaption)[m_iBufferPos_Tail])) break; } } } break; case GUIM_MOUSE_RELEASE_LEFT: if (m_SelectingText) { m_SelectingText = false; } break; case GUIM_MOUSE_MOTION: // If we just pressed down and started to move before releasing // this is one way of selecting larger portions of text. if (m_SelectingText) { // Actually, first we need to re-check that the mouse button is // really pressed (it can be released while outside the control. if (!g_mouse_buttons[SDL_BUTTON_LEFT]) m_SelectingText = false; else m_iBufferPos = GetMouseHoveringTextPosition(); UpdateAutoScroll(); } break; case GUIM_MOUSE_WHEEL_DOWN: { GetScrollBar(0).ScrollPlus(); // Since the scroll was changed, let's simulate a mouse movement // to check if scrollbar now is hovered SGUIMessage msg(GUIM_MOUSE_MOTION); HandleMessage(msg); break; } case GUIM_MOUSE_WHEEL_UP: { GetScrollBar(0).ScrollMinus(); // Since the scroll was changed, let's simulate a mouse movement // to check if scrollbar now is hovered SGUIMessage msg(GUIM_MOUSE_MOTION); HandleMessage(msg); break; } case GUIM_LOAD: { 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 ); CStr scrollbar_style; GUI::GetSetting(this, "scrollbar_style", scrollbar_style); GetScrollBar(0).SetScrollBarStyle( scrollbar_style ); UpdateText(); } break; case GUIM_GOT_FOCUS: m_iBufferPos = 0; break; case GUIM_LOST_FOCUS: m_iBufferPos = -1; m_iBufferPos_Tail = -1; break; default: break; } } void CInput::UpdateCachedSize() { // If an ancestor's size changed, this will let us intercept the change and // update our scrollbar positions IGUIObject::UpdateCachedSize(); bool scrollbar; GUI::GetSetting(this, "scrollbar", scrollbar); if (scrollbar) { 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); } } void CInput::Draw() { float bz = GetBufferedZ(); // First call draw on ScrollBarOwner bool scrollbar; float buffer_zone; bool multiline; GUI::GetSetting(this, "scrollbar", scrollbar); GUI::GetSetting(this, "buffer_zone", buffer_zone); GUI::GetSetting(this, "multiline", multiline); if (scrollbar && multiline) { // Draw scrollbar IGUIScrollBarOwner::Draw(); } if (GetGUI()) { CStrW font_name; CColor color, color_selected; //CStrW caption; GUI::GetSetting(this, "font", font_name); GUI::GetSetting(this, "textcolor", color); GUI::GetSetting(this, "textcolor_selected", color_selected); // Get pointer of caption, it might be very large, and we don't // want to copy it continuously. CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting; CGUISpriteInstance *sprite=NULL, *sprite_selectarea=NULL; int cell_id; GUI::GetSettingPointer(this, "sprite", sprite); GUI::GetSettingPointer(this, "sprite_selectarea", sprite_selectarea); GUI::GetSetting(this, "cell_id", cell_id); GetGUI()->DrawSprite(*sprite, cell_id, bz, m_CachedActualSize); float scroll=0.f; if (scrollbar && multiline) { scroll = GetScrollBar(0).GetPos(); } - 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(font_name); font.Bind(); - glPushMatrix(); - // We'll have to setup clipping manually, since we're doing the rendering manually. CRect cliparea(m_CachedActualSize); // First we'll figure out the clipping area, which is the cached actual size // substracted by an optional scrollbar if (scrollbar) { scroll = GetScrollBar(0).GetPos(); // substract scrollbar from cliparea if (cliparea.right > GetScrollBar(0).GetOuterRect().left && cliparea.right <= GetScrollBar(0).GetOuterRect().right) cliparea.right = GetScrollBar(0).GetOuterRect().left; if (cliparea.left >= GetScrollBar(0).GetOuterRect().left && cliparea.left < GetScrollBar(0).GetOuterRect().right) cliparea.left = GetScrollBar(0).GetOuterRect().right; } if (cliparea != CRect()) { - double eq[4][4] = - { - { 0.0, 1.0, 0.0, -cliparea.top }, - { 1.0, 0.0, 0.0, -cliparea.left }, - { 0.0, -1.0, 0.0, cliparea.bottom }, - { -1.0, 0.0, 0.0, cliparea.right } - }; - - for (int i=0; i<4; ++i) - { - glClipPlane(GL_CLIP_PLANE0+i, eq[i]); - glEnable(GL_CLIP_PLANE0+i); - } + glEnable(GL_SCISSOR_TEST); + glScissor(cliparea.left, g_yres - cliparea.bottom, cliparea.GetWidth(), cliparea.GetHeight()); } // These are useful later. int VirtualFrom, VirtualTo; if (m_iBufferPos_Tail >= m_iBufferPos) { VirtualFrom = m_iBufferPos; VirtualTo = m_iBufferPos_Tail; } else { VirtualFrom = m_iBufferPos_Tail; VirtualTo = m_iBufferPos; } // Get the height of this font. float h = (float)font.GetHeight(); float ls = (float)font.GetLineSpacing(); + CTextRenderer textRenderer; + textRenderer.Font(font_name); + // Set the Z to somewhat more, so we can draw a selected area between the // the control and the text. - glTranslatef((GLfloat)int(m_CachedActualSize.left) + buffer_zone, - (GLfloat)int(m_CachedActualSize.top+h) + buffer_zone, bz+0.1f); - - //glColor4f(1.f, 1.f, 1.f, 1.f); + textRenderer.Translate( + (float)(int)(m_CachedActualSize.left) + buffer_zone, + (float)(int)(m_CachedActualSize.top+h) + buffer_zone, + bz+0.1f); // U+FE33: PRESENTATION FORM FOR VERTICAL LOW LINE // (sort of like a | which is aligned to the left of most characters) float buffered_y = -scroll+buffer_zone; // When selecting larger areas, we need to draw a rectangle box // around it, and this is to keep track of where the box // started, because we need to follow the iteration until we // reach the end, before we can actually draw it. bool drawing_box = false; float box_x=0.f; float x_pointer=0.f; // If we have a selecting box (i.e. when you have selected letters, not just when // the pointer is between two letters) we need to process all letters once // before we do it the second time and render all the text. We can't do it // in the same loop because text will have been drawn, so it will disappear when // drawn behind the text that has already been drawn. Confusing, well it's necessary // (I think). if (SelectingText()) { // Now m_iBufferPos_Tail can be of both sides of m_iBufferPos, // just like you can select from right to left, as you can // left to right. Is there a difference? Yes, the pointer // be placed accordingly, so that if you select shift and // expand this selection, it will expand on appropriate side. // Anyway, since the drawing procedure needs "To" to be // greater than from, we need virtual values that might switch // place. int VirtualFrom, VirtualTo; if (m_iBufferPos_Tail >= m_iBufferPos) { VirtualFrom = m_iBufferPos; VirtualTo = m_iBufferPos_Tail; } else { VirtualFrom = m_iBufferPos_Tail; VirtualTo = m_iBufferPos; } bool done = false; for (std::list::const_iterator it = m_CharacterPositions.begin(); it != m_CharacterPositions.end(); ++it, buffered_y += ls, x_pointer = 0.f) { if (multiline) { if (buffered_y > m_CachedActualSize.GetHeight()) break; } // We might as well use 'i' here to iterate, because we need it // (often compared against ints, so don't make it size_t) for (int i=0; i < (int)it->m_ListOfX.size()+2; ++i) { if (it->m_ListStart + i == VirtualFrom) { // we won't actually draw it now, because we don't // know the width of each glyph to that position. // we need to go along with the iteration, and // make a mark where the box started: drawing_box = true; // will turn false when finally rendered. // Get current x position box_x = x_pointer; } // no else! const bool at_end = (i == (int)it->m_ListOfX.size()+1); if (drawing_box == true && (it->m_ListStart + i == VirtualTo || at_end)) { // Depending on if it's just a row change, or if it's // the end of the select box, do slightly different things. if (at_end) { if (it->m_ListStart + i != VirtualFrom) { // and actually add a white space! yes, this is done in any common input x_pointer += (float)font.GetCharacterWidth(L' '); } } else { drawing_box = false; done = true; } CRect rect; // Set 'rect' depending on if it's a multiline control, or a one-line control if (multiline) { rect = CRect(m_CachedActualSize.left+box_x+buffer_zone, m_CachedActualSize.top+buffered_y+(h-ls)/2, m_CachedActualSize.left+x_pointer+buffer_zone, m_CachedActualSize.top+buffered_y+(h+ls)/2); if (rect.bottom < m_CachedActualSize.top) continue; if (rect.top < m_CachedActualSize.top) rect.top = m_CachedActualSize.top; if (rect.bottom > m_CachedActualSize.bottom) rect.bottom = m_CachedActualSize.bottom; } else // if one-line { rect = CRect(m_CachedActualSize.left+box_x+buffer_zone-m_HorizontalScroll, m_CachedActualSize.top+buffered_y+(h-ls)/2, m_CachedActualSize.left+x_pointer+buffer_zone-m_HorizontalScroll, m_CachedActualSize.top+buffered_y+(h+ls)/2); if (rect.left < m_CachedActualSize.left) rect.left = m_CachedActualSize.left; if (rect.right > m_CachedActualSize.right) rect.right = m_CachedActualSize.right; } - glPushMatrix(); - guiLoadIdentity(); - glEnable(GL_BLEND); - glDisable(GL_TEXTURE_2D); - if (sprite_selectarea) GetGUI()->DrawSprite(*sprite_selectarea, cell_id, bz+0.05f, rect); - - // Blend can have been reset - glEnable(GL_BLEND); - glEnable(GL_TEXTURE_2D); - glDisable(GL_ALPHA_TEST); - - glPopMatrix(); } if (i < (int)it->m_ListOfX.size()) x_pointer += (float)font.GetCharacterWidth((*pCaption)[it->m_ListStart + i]); } if (done) break; // If we're about to draw a box, and all of a sudden changes // line, we need to draw that line's box, and then reset // the box drawing to the beginning of the new line. if (drawing_box) { box_x = 0.f; } } } // Reset some from previous run buffered_y = -scroll; // Setup initial color (then it might change and change back, when drawing selected area) - glColor4f(color.r, color.g, color.b, color.a); + textRenderer.Color(color); + + // Setup state for text rendering + + glEnable(GL_TEXTURE_2D); + glDisable(GL_CULL_FACE); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_BLEND); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); bool using_selected_color = false; for (std::list::const_iterator it = m_CharacterPositions.begin(); it != m_CharacterPositions.end(); ++it, buffered_y += ls) { if (buffered_y + buffer_zone >= -ls || !multiline) { if (multiline) { if (buffered_y + buffer_zone > m_CachedActualSize.GetHeight()) break; } - glPushMatrix(); + CMatrix3D savedTransform = textRenderer.GetTransform(); // Text must always be drawn in integer values. So we have to convert scroll if (multiline) - glTranslatef(0.f, -(float)(int)scroll, 0.f); + textRenderer.Translate(0.f, -(float)(int)scroll, 0.f); else - glTranslatef(-(float)(int)m_HorizontalScroll, 0.f, 0.f); + textRenderer.Translate(-(float)(int)m_HorizontalScroll, 0.f, 0.f); // We might as well use 'i' here, because we need it // (often compared against ints, so don't make it size_t) for (int i=0; i < (int)it->m_ListOfX.size()+1; ++i) { if (!multiline && i < (int)it->m_ListOfX.size()) { if (it->m_ListOfX[i] - m_HorizontalScroll < -buffer_zone) { // We still need to translate the OpenGL matrix if (i == 0) - glTranslatef(it->m_ListOfX[i], 0.f, 0.f); + textRenderer.Translate(it->m_ListOfX[i], 0.f, 0.f); else - glTranslatef(it->m_ListOfX[i] - it->m_ListOfX[i-1], 0.f, 0.f); + textRenderer.Translate(it->m_ListOfX[i] - it->m_ListOfX[i-1], 0.f, 0.f); continue; } } // End of selected area, change back color if (SelectingText() && it->m_ListStart + i == VirtualTo) { using_selected_color = false; - glColor4f(color.r, color.g, color.b, color.a); + textRenderer.Color(color); } if (i != (int)it->m_ListOfX.size() && it->m_ListStart + i == m_iBufferPos) { // selecting only one, then we need only to draw a cursor. - glPushMatrix(); - glwprintf(L"_"); - glPopMatrix(); + CMatrix3D t = textRenderer.GetTransform(); + textRenderer.Printf(L"_"); + textRenderer.SetTransform(t); } // Drawing selected area if (SelectingText() && it->m_ListStart + i >= VirtualFrom && it->m_ListStart + i < VirtualTo && using_selected_color == false) { using_selected_color = true; - glColor4f(color_selected.r, color_selected.g, color_selected.b, color_selected.a); + textRenderer.Color(color_selected); } if (i != (int)it->m_ListOfX.size()) - glwprintf(L"%lc", (*pCaption)[it->m_ListStart + i]); + textRenderer.Printf(L"%lc", (*pCaption)[it->m_ListStart + i]); // check it's now outside a one-liner, then we'll break if (!multiline && i < (int)it->m_ListOfX.size()) { if (it->m_ListOfX[i] - m_HorizontalScroll > m_CachedActualSize.GetWidth()-buffer_zone) break; } } if (it->m_ListStart + (int)it->m_ListOfX.size() == m_iBufferPos) { - glColor4f(color.r, color.g, color.b, color.a); - glwprintf(L"_"); + textRenderer.Color(color); + textRenderer.Printf(L"_"); if (using_selected_color) { - glColor4f(color_selected.r, color_selected.g, color_selected.b, color_selected.a); + textRenderer.Color(color_selected); } } - glPopMatrix(); + textRenderer.SetTransform(savedTransform); } - glTranslatef(0.f, ls, 0.f); + + textRenderer.Translate(0.f, ls, 0.f); } - glPopMatrix(); + textRenderer.Render(); + + if (cliparea != CRect()) + glDisable(GL_SCISSOR_TEST); - // Disable clipping - for (int i=0; i<4; ++i) - glDisable(GL_CLIP_PLANE0+i); glDisable(GL_TEXTURE_2D); } } void CInput::UpdateText(int from, int to_before, int to_after) { CStrW caption; CStrW font_name; float buffer_zone; bool multiline; GUI::GetSetting(this, "font", font_name); GUI::GetSetting(this, "caption", caption); GUI::GetSetting(this, "buffer_zone", buffer_zone); GUI::GetSetting(this, "multiline", multiline); // Ensure positions are valid after caption changes m_iBufferPos = std::min(m_iBufferPos, (int)caption.size()); m_iBufferPos_Tail = std::min(m_iBufferPos_Tail, (int)caption.size()); if (font_name == CStrW()) { // Destroy everything stored, there's no font, so there can be // no data. m_CharacterPositions.clear(); return; } SRow row; row.m_ListStart = 0; int to = 0; // make sure it's initialized if (to_before == -1) to = (int)caption.length(); CFont font(font_name); std::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 { ENSURE(to_before != -1); std::list::iterator destroy_row_from, destroy_row_to; // Used to check if the above has been set to anything, // previously a comparison like: // destroy_row_from == std::list::iterator() // ... was used, but it didn't work with GCC. bool destroy_row_from_used=false, destroy_row_to_used=false; // 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 (std::list::iterator it=m_CharacterPositions.begin(); it!=m_CharacterPositions.end(); ++it, ++i) { if (destroy_row_from_used == false && it->m_ListStart > from) { // Destroy the previous line, and all to 'to_before' destroy_row_from = it; --destroy_row_from; destroy_row_from_used = true; // For the rare case that we might remove characters to a word // so that it suddenly fits on the previous row, // 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_used == false && it->m_ListStart > to_before) { destroy_row_to = it; destroy_row_to_used = true; // 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_used == false) { destroy_row_from = m_CharacterPositions.end(); --destroy_row_from; // As usual, let's destroy another row back if (destroy_row_from != m_CharacterPositions.begin()) --destroy_row_from; destroy_row_from_used = true; current_line = destroy_row_from; } if (destroy_row_to_used == false) { destroy_row_to = m_CharacterPositions.end(); check_point_row_start = -1; destroy_row_from_used = true; } // 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; // 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; std::list::iterator temp_it = destroy_row_to; --temp_it; m_CharacterPositions.erase(destroy_row_from, destroy_row_to); // 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 (std::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 != (int)caption.length()) to += delta; } } int last_word_started=from; //int last_list_start=-1; // unused float x_pos = 0.f; //if (to_before != -1) // return; for (int i=from; i= GetTextAreaWidth() && multiline) { // 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. 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()) { // check all rows and see if any existing if (row.m_ListStart != check_point_row_start) { std::list::iterator destroy_row_from, destroy_row_to; // Are used to check if the above has been set to anything, // previously a comparison like: // destroy_row_from == std::list::iterator() // was used, but it didn't work with GCC. bool destroy_row_from_used=false, destroy_row_to_used=false; // 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 (std::list::iterator it=m_CharacterPositions.begin(); it!=m_CharacterPositions.end(); ++it, ++i) { if (destroy_row_from_used == false && 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_used = true; //--destroy_row_from; } if (destroy_row_to_used == false && it->m_ListStart > check_point_row_end) { destroy_row_to = it; destroy_row_to_used = true; // 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; } else check_point_row_start = check_point_row_end = -1; ++destroy_row_to; break; } } if (destroy_row_from_used == false) { destroy_row_from = m_CharacterPositions.end(); --destroy_row_from; destroy_row_from_used = true; current_line = destroy_row_from; } if (destroy_row_to_used == false) { destroy_row_to = m_CharacterPositions.end(); check_point_row_start = check_point_row_end = -1; destroy_row_to_used = true; } // 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(); // 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; std::list::iterator temp = destroy_row_to; --temp; m_CharacterPositions.erase(destroy_row_from, destroy_row_to); } // else, the for loop will end naturally. } } // This is kind of special, when we renew a some lines, then the last // one will sometimes end with a space (' '), that really should // be omitted when word-wrapping. So we'll check if the last row // we'll add has got the same value as the next row. if (current_line != m_CharacterPositions.end()) { if (row.m_ListStart + (int)row.m_ListOfX.size() == current_line->m_ListStart) row.m_ListOfX.resize( row.m_ListOfX.size()-1 ); } // add the final row (even if empty) m_CharacterPositions.insert(current_line, row); bool scrollbar; GUI::GetSetting(this, "scrollbar", scrollbar); // Update scollbar if (scrollbar) { GetScrollBar(0).SetScrollRange(m_CharacterPositions.size() * font.GetLineSpacing() + buffer_zone*2.f); GetScrollBar(0).SetScrollSpace(m_CachedActualSize.GetHeight()); } } int CInput::GetMouseHoveringTextPosition() { if (m_CharacterPositions.empty()) return 0; // Return position int retPosition; float buffer_zone; bool multiline; GUI::GetSetting(this, "buffer_zone", buffer_zone); GUI::GetSetting(this, "multiline", multiline); std::list::iterator current = m_CharacterPositions.begin(); CPos mouse = GetMousePos(); if (multiline) { CStrW font_name; bool scrollbar; GUI::GetSetting(this, "font", font_name); GUI::GetSetting(this, "scrollbar", scrollbar); float scroll=0.f; if (scrollbar) { scroll = GetScrollBar(0).GetPos(); } // Pointer to caption, will come in handy CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting; UNUSED2(pCaption); // Now get the height of the font. // TODO: Get the real font CFont font(font_name); float spacing = (float)font.GetLineSpacing(); //float height = (float)font.GetHeight(); // unused // Change mouse position relative to text. mouse -= m_CachedActualSize.TopLeft(); mouse.x -= buffer_zone; mouse.y += scroll - buffer_zone; //if ((m_CharacterPositions.size()-1) * spacing + height < mouse.y) // m_iBufferPos = pCaption->Length(); int row = (int)((mouse.y) / spacing);//m_CharachterPositions.size() if (row < 0) row = 0; if (row > (int)m_CharacterPositions.size()-1) row = (int)m_CharacterPositions.size()-1; // TODO Gee (2004-11-21): Okay, I need a 'std::list' for some reasons, but I would really like to // be able to get the specific element here. This is hopefully a temporary hack. for (int i=0; im_ListStart; // Okay, now loop through the glyphs to find the appropriate X position float dummy; retPosition += GetXTextPosition(current, mouse.x, dummy); return retPosition; } // Does not process horizontal scrolling, 'x' must be modified before inputted. int CInput::GetXTextPosition(const std::list::iterator ¤t, const float &x, float &wanted) { int ret=0; float previous=0.f; int i=0; for (std::vector::iterator it=current->m_ListOfX.begin(); it!=current->m_ListOfX.end(); ++it, ++i) { if (*it >= x) { if (x - previous >= *it - x) ret += i+1; else ret += i; break; } previous = *it; } // If a position wasn't found, we will assume the last // character of that line. if (i == (int)current->m_ListOfX.size()) { ret += i; wanted = x; } else wanted = 0.f; return ret; } void CInput::DeleteCurSelection() { CStrW *pCaption = (CStrW*)m_Settings["caption"].m_pSetting; int virtualFrom; int virtualTo; if (m_iBufferPos_Tail >= m_iBufferPos) { virtualFrom = m_iBufferPos; virtualTo = m_iBufferPos_Tail; } else { virtualFrom = m_iBufferPos_Tail; virtualTo = m_iBufferPos; } *pCaption = pCaption->Left( virtualFrom ) + pCaption->Right( (long) pCaption->length() - (virtualTo) ); UpdateText(virtualFrom, virtualTo, virtualFrom); // Remove selection m_iBufferPos_Tail = -1; m_iBufferPos = virtualFrom; } bool CInput::SelectingText() const { return m_iBufferPos_Tail != -1 && m_iBufferPos_Tail != m_iBufferPos; } float CInput::GetTextAreaWidth() { bool scrollbar; float buffer_zone; GUI::GetSetting(this, "scrollbar", scrollbar); GUI::GetSetting(this, "buffer_zone", buffer_zone); if (scrollbar && GetScrollBar(0).GetStyle()) return m_CachedActualSize.GetWidth() - buffer_zone*2.f - GetScrollBar(0).GetStyle()->m_Width; else return m_CachedActualSize.GetWidth() - buffer_zone*2.f; } void CInput::UpdateAutoScroll() { float buffer_zone; bool multiline; GUI::GetSetting(this, "buffer_zone", buffer_zone); GUI::GetSetting(this, "multiline", multiline); // Autoscrolling up and down if (multiline) { CStrW font_name; bool scrollbar; GUI::GetSetting(this, "font", font_name); GUI::GetSetting(this, "scrollbar", scrollbar); float scroll=0.f; if (!scrollbar) return; scroll = GetScrollBar(0).GetPos(); // Now get the height of the font. // TODO: Get the real font CFont font(font_name); float spacing = (float)font.GetLineSpacing(); //float height = font.GetHeight(); // TODO Gee (2004-11-21): Okay, I need a 'std::list' for some reasons, but I would really like to // be able to get the specific element here. This is hopefully a temporary hack. std::list::iterator current = m_CharacterPositions.begin(); int row=0; while (current != m_CharacterPositions.end()) { if (m_iBufferPos >= current->m_ListStart && m_iBufferPos <= current->m_ListStart+(int)current->m_ListOfX.size()) break; ++current; ++row; } // If scrolling down if (-scroll + (float)(row+1) * spacing + buffer_zone*2.f > m_CachedActualSize.GetHeight()) { // Scroll so the selected row is shown completely, also with buffer_zone length to the edge. GetScrollBar(0).SetPos((float)(row+1) * spacing - m_CachedActualSize.GetHeight() + buffer_zone*2.f); } else // If scrolling up if (-scroll + (float)row * spacing < 0.f) { // Scroll so the selected row is shown completely, also with buffer_zone length to the edge. GetScrollBar(0).SetPos((float)row * spacing); } } else // autoscrolling left and right { // Get X position of position: if (m_CharacterPositions.empty()) return; float x_position = 0.f; float x_total = 0.f; if (!m_CharacterPositions.begin()->m_ListOfX.empty()) { // Get position of m_iBufferPos if ((int)m_CharacterPositions.begin()->m_ListOfX.size() >= m_iBufferPos && m_iBufferPos != 0) x_position = m_CharacterPositions.begin()->m_ListOfX[m_iBufferPos-1]; // Get complete length: x_total = m_CharacterPositions.begin()->m_ListOfX[ m_CharacterPositions.begin()->m_ListOfX.size()-1 ]; } // Check if outside to the right if (x_position - m_HorizontalScroll + buffer_zone*2.f > m_CachedActualSize.GetWidth()) m_HorizontalScroll = x_position - m_CachedActualSize.GetWidth() + buffer_zone*2.f; // Check if outside to the left if (x_position - m_HorizontalScroll < 0.f) m_HorizontalScroll = x_position; // Check if the text doesn't even fill up to the right edge even though scrolling is done. if (m_HorizontalScroll != 0.f && x_total - m_HorizontalScroll + buffer_zone*2.f < m_CachedActualSize.GetWidth()) m_HorizontalScroll = x_total - m_CachedActualSize.GetWidth() + buffer_zone*2.f; // Now this is the fail-safe, if x_total isn't even the length of the control, // remove all scrolling if (x_total + buffer_zone*2.f < m_CachedActualSize.GetWidth()) m_HorizontalScroll = 0.f; } } Index: ps/trunk/source/gui/GUIRenderer.h =================================================================== --- ps/trunk/source/gui/GUIRenderer.h (revision 10984) +++ ps/trunk/source/gui/GUIRenderer.h (revision 10985) @@ -1,85 +1,85 @@ /* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef GUIRenderer_h #define GUIRenderer_h #include "graphics/Texture.h" #include "lib/res/handle.h" #include "ps/Overlay.h" #include "ps/CStr.h" #include struct SGUIImageEffects; struct SGUIImage; namespace GUIRenderer { class IGLState { public: virtual ~IGLState() {}; virtual void Set(const CTexturePtr& tex)=0; virtual void Unset()=0; }; struct SDrawCall { SDrawCall(const SGUIImage* image) : m_Image(image), m_Effects(NULL) {} CRect ComputeTexCoords() const; const SGUIImage* m_Image; bool m_HasTexture; CTexturePtr m_Texture; CRect m_ObjectSize; int m_CellID; bool m_EnableBlending; IGLState* m_Effects; CRect m_Vertices; float m_DeltaZ; CColor m_BorderColor; // == CColor() for no border CColor m_BackColor; }; class DrawCalls : public std::vector { public: void clear(); DrawCalls(); ~DrawCalls(); // Copy/assignment results in an empty list, not an actual copy DrawCalls(const DrawCalls&); const DrawCalls& operator=(const DrawCalls&); }; } #include "gui/CGUISprite.h" namespace GUIRenderer { void UpdateDrawCallCache(DrawCalls &Calls, const CStr& SpriteName, const CRect& Size, int CellID, std::map &Sprites); - void Draw(DrawCalls &Calls); + void Draw(DrawCalls &Calls, float Z); } #endif // GUIRenderer_h Index: ps/trunk/source/gui/CGUISprite.h =================================================================== --- ps/trunk/source/gui/CGUISprite.h (revision 10984) +++ ps/trunk/source/gui/CGUISprite.h (revision 10985) @@ -1,196 +1,196 @@ /* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* A GUI Sprite --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 INCLUDED_CGUISPRITE #define INCLUDED_CGUISPRITE //-------------------------------------------------------- // Includes / Compiler directives //-------------------------------------------------------- #include "GUIbase.h" #include "ps/Overlay.h" #include "lib/res/graphics/ogl_tex.h" //-------------------------------------------------------- // Macros //-------------------------------------------------------- //-------------------------------------------------------- // Types //-------------------------------------------------------- //-------------------------------------------------------- // Error declarations //-------------------------------------------------------- //-------------------------------------------------------- // Declarations //-------------------------------------------------------- struct SGUIImageEffects { SGUIImageEffects() : m_Greyscale(false) {} CColor m_AddColor; CColor m_MultiplyColor; bool m_Greyscale; }; /** * A CGUISprite is actually a collage of several real * sprites, this struct represents is such real sprite. */ struct SGUIImage { SGUIImage() : m_FixedHAspectRatio(0.f), m_RoundCoordinates(true), m_WrapMode(GL_REPEAT), m_Effects(NULL), m_Border(false), m_DeltaZ(0.f) { } // Filename of the texture VfsPath 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 cell-id will determine // which part of the texture is used. // Equal to CSize(0,0) for non-celled textures. CSize m_CellSize; /** * If non-zero, then the image's width will be adjusted when rendering so that * the width:height ratio equals this value. */ float m_FixedHAspectRatio; /** * If true, the image's coordinates will be rounded to integer pixels when * rendering, to avoid blurry filtering. */ bool m_RoundCoordinates; /** * Texture wrapping mode (GL_REPEAT, GL_CLAMP_TO_BORDER, etc) */ GLint m_WrapMode; // 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; }; /** * 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(const CStr& SpriteName); CGUISpriteInstance(const CGUISpriteInstance &Sprite); CGUISpriteInstance &operator=(const CStr& SpriteName); - void Draw(CRect Size, int CellID, std::map &Sprites) const; + void Draw(CRect Size, int CellID, std::map &Sprites, float Z) const; void Invalidate(); bool IsEmpty() const; const CStr& GetName() { return m_SpriteName; } private: CStr m_SpriteName; // Stored drawing calls, for more efficient rendering mutable GUIRenderer::DrawCalls m_DrawCallCache; // Relevant details of previously rendered sprite; the cache is invalidated // whenever any of these values changes. mutable CRect m_CachedSize; mutable int m_CachedCellID; }; #endif Index: ps/trunk/source/gui/GUIutil.h =================================================================== --- ps/trunk/source/gui/GUIutil.h (revision 10984) +++ ps/trunk/source/gui/GUIutil.h (revision 10985) @@ -1,380 +1,379 @@ /* Copyright (C) 2009 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* GUI util --Overview-- Contains help class GUI<>, which gives us templated parameter to all functions within GUI. --More info-- Check GUI.h */ #ifndef INCLUDED_GUIUTIL #define INCLUDED_GUIUTIL //-------------------------------------------------------- // Includes / Compiler directives //-------------------------------------------------------- #include "GUIbase.h" #include "ps/Parser.h" // TODO Gee: New #include "ps/Overlay.h" #include "CGUI.h" #include "CGUISprite.h" #include "IGUIObject.h" //-------------------------------------------------------- // Help Classes/Structs for the GUI //-------------------------------------------------------- class CClientArea; class CGUIString; +class CMatrix3D; template bool __ParseString(const CStrW& Value, T &tOutput); -// Load Identity matrix and -// adapt (origio) to being in top left corner and down -// just like the mouse position -void guiLoadIdentity(); +// Matrix with (0,0) in top-left of screen +CMatrix3D GetDefaultGuiMatrix(); //-------------------------------------------------------- // Forward declarations //-------------------------------------------------------- struct SGUIMessage; /** * 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, SGUIMessage &message); }; #ifndef NDEBUG // Used to ensure type-safety, sort of template void CheckType(const IGUIObject* obj, const CStr& setting); #endif /** * 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 PSRETURN 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 PSRETURN 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! * @param SkipMessage Does not send a GUIM_SETTINGS_UPDATED if true */ static PSRETURN SetSetting(IGUIObject *pObject, const CStr& Setting, const T &Value, const bool &SkipMessage=false); /** * Retrieves a setting by settings name and object name * * @param GUIinstance GUI Object const ref * @param Object Object name * @param Setting Setting by name * @param Value Stores value here, note type T! */ static PSRETURN GetSetting( const CGUI &GUIinstance, const CStr& Object, const CStr& Setting, T &Value) { if (!GUIinstance.ObjectExists(Object)) return PSRETURN_GUI_NullObjectProvided; // Retrieve pointer and call sibling function const IGUIObject *pObject = GetObjectPointer(GUIinstance, Object); return GetSetting(pObject, Setting, Value); } /** * 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 GUIinstance 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! * @param SkipMessage Does not send a GUIM_SETTINGS_UPDATED if true */ static PSRETURN SetSetting( CGUI &GUIinstance, const CStr& Object, const CStr& Setting, const T &Value, const bool& SkipMessage=false) { if (!GUIinstance.ObjectExists(Object)) return PSRETURN_GUI_NullObjectProvided; // 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, SkipMessage); } /** * 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 const CGUISpriteInstance& FallBackSprite( const CGUISpriteInstance& prim, const CGUISpriteInstance& sec) { 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 __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 __ParseString() */ static bool ParseString(const CStrW& Value, T &tOutput) { return __ParseString(Value, tOutput); } static bool ParseColor(const CStrW& Value, CColor &tOutput, float DefaultAlpha); 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 PSERROR Depends on what pFunc might throw. PSERROR is standard. * Itself doesn't throw anything. */ static void RecurseObject(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(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(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(int RR, IGUIObject *pObject) { // Statically initialise some strings, so we don't have to do // lots of allocation every time this function is called static CStr strHidden("hidden"); static CStr strEnabled("enabled"); static CStr strGhost("ghost"); if (RR & GUIRR_HIDDEN) { bool hidden = true; GUI::GetSetting(pObject, strHidden, hidden); if (hidden) return true; } if (RR & GUIRR_DISABLED) { bool enabled = false; GUI::GetSetting(pObject, strEnabled, enabled); if (!enabled) return true; } if (RR & GUIRR_GHOST) { bool ghost = true; GUI::GetSetting(pObject, strGhost, 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 10984) +++ ps/trunk/source/gui/CGUI.cpp (revision 10985) @@ -1,1946 +1,1905 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* CGUI */ #include "precompiled.h" #include #include #include "lib/res/graphics/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 "CList.h" #include "CDropDown.h" #include "CProgressBar.h" #include "CTooltip.h" #include "MiniMap.h" #include "scripting/JSInterface_GUITypes.h" -#include "ps/XML/Xeromyces.h" -#include "ps/Font.h" - -#include "ps/Pyrogenesis.h" +#include "graphics/TextRenderer.h" #include "lib/input.h" #include "lib/bits.h" #include "lib/timer.h" #include "lib/sysdep/sysdep.h" -// TODO Gee: Whatever include CRect/CPos/CSize +#include "ps/CLogger.h" +#include "ps/Filesystem.h" +#include "ps/Font.h" +#include "ps/Hotkey.h" +#include "ps/Globals.h" #include "ps/Overlay.h" #include "ps/Profile.h" - +#include "ps/Pyrogenesis.h" +#include "ps/XML/Xeromyces.h" #include "scripting/ScriptingHost.h" #include "scriptinterface/ScriptInterface.h" -#include "ps/Hotkey.h" -#include "ps/Globals.h" -#include "ps/Filesystem.h" + +extern int g_yres; const double SELECT_DBLCLICK_RATE = 0.5; -#include "ps/CLogger.h" void CGUI::ScriptingInit() { JSI_IGUIObject::init(); JSI_GUITypes::init(); } InReaction CGUI::HandleEvent(const SDL_Event_* ev) { InReaction ret = IN_PASS; if (ev->ev.type == SDL_HOTKEYDOWN) { const char* hotkey = static_cast(ev->ev.user.data1); std::map >::iterator it = m_HotkeyObjects.find(hotkey); if (it != m_HotkeyObjects.end()) { for (size_t i = 0; i < it->second.size(); ++i) { it->second[i]->SendEvent(GUIM_PRESSED, "press"); } } } else if (ev->ev.type == SDL_MOUSEMOTION) { // Yes the mouse position is stored as float to avoid // constant conversions when operating in a // float-based environment. m_MousePos = CPos((float)ev->ev.motion.x, (float)ev->ev.motion.y); SGUIMessage msg(GUIM_MOUSE_MOTION); GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::HandleMessage, msg); } // Update m_MouseButtons. (BUTTONUP is handled later.) else if (ev->ev.type == SDL_MOUSEBUTTONDOWN) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: case SDL_BUTTON_RIGHT: case SDL_BUTTON_MIDDLE: m_MouseButtons |= Bit(ev->ev.button.button); break; default: break; } } // Update m_MousePos (for delayed mouse button events) CPos oldMousePos = m_MousePos; if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP) { m_MousePos = CPos((float)ev->ev.button.x, (float)ev->ev.button.y); } // 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 { PROFILE( "mouse events" ); // TODO Gee: Optimizations needed! // these two recursive function are quite overhead heavy. // pNearest will after this point at the hovered object, possibly NULL pNearest = FindObjectUnderMouse(); // Is placed in the UpdateMouseOver function //if (ev->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->ev.type == SDL_MOUSEBUTTONDOWN) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: // Focus the clicked object (or focus none if nothing clicked on) SetFocusedObject(pNearest); if (pNearest) { ret = pNearest->SendEvent(GUIM_MOUSE_PRESS_LEFT, "mouseleftpress"); } break; case SDL_BUTTON_RIGHT: if (pNearest) { ret = pNearest->SendEvent(GUIM_MOUSE_PRESS_RIGHT, "mouserightpress"); } break; case SDL_BUTTON_WHEELDOWN: // wheel down if (pNearest) { ret = pNearest->SendEvent(GUIM_MOUSE_WHEEL_DOWN, "mousewheeldown"); } break; case SDL_BUTTON_WHEELUP: // wheel up if (pNearest) { ret = pNearest->SendEvent(GUIM_MOUSE_WHEEL_UP, "mousewheelup"); } break; default: break; } } else if (ev->ev.type == SDL_MOUSEBUTTONUP) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: if (pNearest) { double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_LEFT]; pNearest->m_LastClickTime[SDL_BUTTON_LEFT] = timer_Time(); //Double click? if (timeElapsed < SELECT_DBLCLICK_RATE) { ret = pNearest->SendEvent(GUIM_MOUSE_DBLCLICK_LEFT, "mouseleftdoubleclick"); } else { ret = pNearest->SendEvent(GUIM_MOUSE_RELEASE_LEFT, "mouseleftrelease"); } } break; case SDL_BUTTON_RIGHT: if (pNearest) { double timeElapsed = timer_Time() - pNearest->m_LastClickTime[SDL_BUTTON_RIGHT]; pNearest->m_LastClickTime[SDL_BUTTON_RIGHT] = timer_Time(); //Double click? if (timeElapsed < SELECT_DBLCLICK_RATE) { ret = pNearest->SendEvent(GUIM_MOUSE_DBLCLICK_RIGHT, "mouserightdoubleclick"); } else { ret = pNearest->SendEvent(GUIM_MOUSE_RELEASE_RIGHT, "mouserightrelease"); } } break; } // 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 (PSERROR_GUI& e) { UNUSED2(e); debug_warn(L"CGUI::HandleEvent error"); // TODO Gee: Handle } // BUTTONUP's effect on m_MouseButtons is handled after // everything else, so that e.g. 'press' handlers (activated // on button up) see which mouse button had been pressed. if (ev->ev.type == SDL_MOUSEBUTTONUP) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: case SDL_BUTTON_RIGHT: case SDL_BUTTON_MIDDLE: m_MouseButtons &= ~Bit(ev->ev.button.button); break; default: break; } } // Restore m_MousePos (for delayed mouse button events) if (ev->ev.type == SDL_MOUSEBUTTONDOWN || ev->ev.type == SDL_MOUSEBUTTONUP) { m_MousePos = oldMousePos; } // Handle keys for input boxes if (GetFocusedObject()) { if ( (ev->ev.type == SDL_KEYDOWN && ev->ev.key.keysym.sym != SDLK_ESCAPE && !g_keys[SDLK_LCTRL] && !g_keys[SDLK_RCTRL] && !g_keys[SDLK_LALT] && !g_keys[SDLK_RALT]) || ev->ev.type == SDL_HOTKEYDOWN ) { ret = GetFocusedObject()->ManuallyHandleEvent(ev); } // else will return IN_PASS because we never used the button. } return ret; } void CGUI::TickObjects() { CStr action = "tick"; GUI::RecurseObject(0, m_BaseObject, &IGUIObject::ScriptEvent, action); // Also update tooltips: m_Tooltip.Update(FindObjectUnderMouse(), m_MousePos, this); } void CGUI::SendEventToAll(const CStr& EventName) { // janwas 2006-03-03: spoke with Ykkrosh about EventName case. // when registering, case is converted to lower - this avoids surprise // if someone were to get the case wrong and then not notice their // handler is never called. however, until now, the other end // (sending events here) wasn't converting to lower case, // leading to a similar problem. // now fixed; case is irrelevant since all are converted to lower. GUI::RecurseObject(0, m_BaseObject, &IGUIObject::ScriptEvent, EventName.LowerCase()); } //------------------------------------------------------------------- // Constructor / Destructor //------------------------------------------------------------------- // To isolate the vars declared by each GUI page, we need to create // a pseudo-global object to declare them in. In particular, it must // have no parent object, so it must be declared with JS_NewGlobalObject. // But GUI scripts should have access to the real global's properties // (Array, undefined, getGUIObjectByName, etc), so we add a custom resolver // that defers to the real global object when necessary. static JSBool GetGlobalProperty(JSContext* cx, JSObject* UNUSED(obj), jsid id, jsval* vp) { return JS_GetPropertyById(cx, g_ScriptingHost.GetGlobalObject(), id, vp); } static JSBool SetGlobalProperty(JSContext* cx, JSObject* UNUSED(obj), jsid id, JSBool UNUSED(strict), jsval* vp) { return JS_SetPropertyById(cx, g_ScriptingHost.GetGlobalObject(), id, vp); } static JSBool ResolveGlobalProperty(JSContext* cx, JSObject* obj, jsid id, uintN flags, JSObject** objp) { // This gets called when the property can't be resolved in the page_global object. // Warning: The interaction between this resolution stuff and the JITs appears // to be quite fragile, and I don't quite understand what the constraints are. // If changing it, be careful to test with each JIT to make sure it works. // (This code is somewhat based on GPSEE's module system.) // Declarations and assignments shouldn't affect the real global if (flags & (JSRESOLVE_DECLARING | JSRESOLVE_ASSIGNING)) { // Can't be resolved - return NULL *objp = NULL; return JS_TRUE; } // Check whether the real global object defined this property uintN attrs; JSBool found; if (!JS_GetPropertyAttrsGetterAndSetterById(cx, g_ScriptingHost.GetGlobalObject(), id, &attrs, &found, NULL, NULL)) return JS_FALSE; if (!found) { // Not found on real global, so can't be resolved - return NULL *objp = NULL; return JS_TRUE; } // Retrieve the property value from the global jsval v; if (!JS_GetPropertyById(cx, g_ScriptingHost.GetGlobalObject(), id, &v)) return JS_FALSE; // Add the global's property value onto this object, with getter/setter that will // update the global's copy instead of this copy, and then return this object if (!JS_DefinePropertyById(cx, obj, id, v, GetGlobalProperty, SetGlobalProperty, attrs)) return JS_FALSE; *objp = obj; return JS_TRUE; } static JSClass page_global_class = { "page_global", JSCLASS_GLOBAL_FLAGS | JSCLASS_NEW_RESOLVE, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub, JS_EnumerateStub, (JSResolveOp)ResolveGlobalProperty, JS_ConvertStub, JS_FinalizeStub, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; CGUI::CGUI() : m_MouseButtons(0), m_FocusedObject(NULL), m_InternalNameNumber(0) { m_BaseObject = new CGUIDummyObject; m_BaseObject->SetGUI(this); // Construct the root object for all GUI JavaScript things m_ScriptObject = JS_NewGlobalObject(g_ScriptingHost.getContext(), &page_global_class); JS_AddObjectRoot(g_ScriptingHost.getContext(), &m_ScriptObject); } CGUI::~CGUI() { Destroy(); if (m_BaseObject) delete m_BaseObject; if (m_ScriptObject) { // Let it be garbage-collected JS_RemoveObjectRoot(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); AddObjectType("list", &CList::ConstructObject); AddObjectType("dropdown", &CDropDown::ConstructObject); } void CGUI::Draw() { // Clear the depth buffer, so the GUI is // drawn on top of everything else glClear(GL_DEPTH_BUFFER_BIT); - glPushMatrix(); - - guiLoadIdentity(); 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 (PSERROR_GUI& e) { LOGERROR(L"GUI draw error: %hs", e.what()); } - glPopMatrix(); } void CGUI::DrawSprite(const CGUISpriteInstance& Sprite, int CellID, const float& Z, const CRect& Rect, const CRect& UNUSED(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, CellID, m_Sprites); - - glPopMatrix(); - + Sprite.Draw(Rect, CellID, m_Sprites, Z); } 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 (PSERROR_GUI& e) { UNUSED2(e); debug_warn(L"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 SGUIMessage msg(GUIM_LOAD); GUI::RecurseObject(0, pObject, &IGUIObject::HandleMessage, msg); } catch (PSERROR_GUI&) { throw; } } 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 (PSERROR_GUI&) { // Throw the same error throw; } // Else actually update the real one m_pAllObjects.swap(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; } IGUIObject* CGUI::FindObjectUnderMouse() const { IGUIObject* pNearest = NULL; GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::ChooseMouseOverAndClosest, pNearest); return pNearest; } void CGUI::SetFocusedObject(IGUIObject* pObject) { if (pObject == m_FocusedObject) return; if (m_FocusedObject) { SGUIMessage msg(GUIM_LOST_FOCUS); m_FocusedObject->HandleMessage(msg); } m_FocusedObject = pObject; if (m_FocusedObject) { SGUIMessage msg(GUIM_GOT_FOCUS); m_FocusedObject->HandleMessage(msg); } } // private struct used only in GenerateText(...) struct SGenerateTextImage { 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, 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 CStrW& 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. std::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 (std::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 = std::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_SpriteName, BufferZone, icon.m_CellID); // Check if image is the lowest thing. Text.m_Size.cy = std::max(Text.m_Size.cy, Image.m_YTo); Images[j].push_back(Image); Text.m_SpriteCalls.push_back(SpriteCall); } } } pos_last_img = std::max(pos_last_img, i); x += Feedback.m_Size.cx; prelim_line_height = std::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 noticeably if one // structures 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 (std::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 = std::max(y, it->m_YFrom); union_to = std::min(y+prelim_line_height, it->m_YTo); // The union is not empty if (union_to > union_from) { if (j == From) width_range[From] = std::max(width_range[From], it->m_Indentation); else width_range[To] = std::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 = std::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; std::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 = std::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 = std::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, const CRect &clipping) { // TODO Gee: All these really necessary? Some // are defaults 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); if (clipping != CRect()) { - double eq[4][4] = - { - { 0.0, 1.0, 0.0, -clipping.top }, - { 1.0, 0.0, 0.0, -clipping.left }, - { 0.0, -1.0, 0.0, clipping.bottom }, - { -1.0, 0.0, 0.0, clipping.right } - }; - - for (int i=0; i<4; ++i) - { - glClipPlane(GL_CLIP_PLANE0+i, eq[i]); - glEnable(GL_CLIP_PLANE0+i); - } + glEnable(GL_SCISSOR_TEST); + glScissor(clipping.left, g_yres - clipping.bottom, clipping.GetWidth(), clipping.GetHeight()); } - CFont* font = NULL; - CStrW LastFontName; + CTextRenderer textRenderer; for (std::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); - glColor4fv(color.FloatArray()); - glwprintf(L"%ls", it->m_String.c_str()); // "%ls" is necessary in case m_String contains % symbols - - glPopMatrix(); - + textRenderer.ResetTransform(); + textRenderer.Translate((float)(int)(pos.x+it->m_Pos.x), (float)(int)(pos.y+it->m_Pos.y), z); + textRenderer.Color(color); + textRenderer.Font(it->m_Font); + textRenderer.Printf(L"%ls", it->m_String.c_str()); // "%ls" is necessary in case m_String contains % symbols } - if (font) - delete font; + textRenderer.Render(); for (std::list::iterator it=Text.m_SpriteCalls.begin(); it!=Text.m_SpriteCalls.end(); ++it) { 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 -- if (clipping != CRect()) - { - for (int i=0; i<4; ++i) - glDisable(GL_CLIP_PLANE0+i); - } + glDisable(GL_SCISSOR_TEST); + glDisable(GL_TEXTURE_2D); - // -- GL } bool CGUI::GetPreDefinedColor(const CStr& name, CColor &Output) { if (m_PreDefinedColors.count(name) == 0) { return false; } else { Output = m_PreDefinedColors[name]; return true; } } /** * @callgraph */ void CGUI::LoadXmlFile(const VfsPath& Filename, boost::unordered_set& Paths) { Paths.insert(Filename); CXeromyces XeroFile; if (XeroFile.Load(g_VFS, Filename) != 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, Paths); // 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(L"CGUI::LoadXmlFile error"); // TODO Gee: Output in log } } catch (PSERROR_GUI& e) { LOGERROR(L"Errors loading GUI file %ls (%u)", Filename.string().c_str(), e.getCode()); return; } } //=================================================================== // XML Reading Xeromyces Specific Sub-Routines //=================================================================== void CGUI::Xeromyces_ReadRootObjects(XMBElement Element, CXeromyces* pFile, boost::unordered_set& Paths) { int el_script = pFile->GetElementID("script"); std::vector > subst; // Iterate main children // they should all be or