Index: ps/trunk/source/gui/CDropDown.cpp =================================================================== --- ps/trunk/source/gui/CDropDown.cpp (revision 19544) +++ ps/trunk/source/gui/CDropDown.cpp (revision 19545) @@ -1,517 +1,517 @@ -/* Copyright (C) 2016 Wildfire Games. +/* Copyright (C) 2017 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 "CDropDown.h" #include "lib/external_libraries/libsdl.h" #include "lib/ogl.h" #include "lib/timer.h" #include "ps/CLogger.h" #include "soundmanager/ISoundManager.h" CDropDown::CDropDown() : m_Open(false), m_HideScrollBar(false), m_ElementHighlight(-1) { AddSetting(GUIST_float, "button_width"); AddSetting(GUIST_float, "dropdown_size"); AddSetting(GUIST_float, "dropdown_buffer"); // AddSetting(GUIST_CStrW, "font"); AddSetting(GUIST_CStrW, "sound_closed"); AddSetting(GUIST_CStrW, "sound_disabled"); AddSetting(GUIST_CStrW, "sound_enter"); AddSetting(GUIST_CStrW, "sound_leave"); AddSetting(GUIST_CStrW, "sound_opened"); AddSetting(GUIST_CGUISpriteInstance, "sprite"); // Background that sits around the size AddSetting(GUIST_CGUISpriteInstance, "sprite_disabled"); AddSetting(GUIST_CGUISpriteInstance, "sprite_list"); // Background of the drop down list AddSetting(GUIST_CGUISpriteInstance, "sprite2"); // Button that sits to the right AddSetting(GUIST_CGUISpriteInstance, "sprite2_over"); AddSetting(GUIST_CGUISpriteInstance, "sprite2_pressed"); AddSetting(GUIST_CGUISpriteInstance, "sprite2_disabled"); AddSetting(GUIST_EVAlign, "text_valign"); // Add these in CList! And implement TODO //AddSetting(GUIST_CColor, "textcolor_over"); //AddSetting(GUIST_CColor, "textcolor_pressed"); AddSetting(GUIST_CColor, "textcolor_selected"); AddSetting(GUIST_CColor, "textcolor_disabled"); // Scrollbar is forced to be true. GUI::SetSetting(this, "scrollbar", true); } CDropDown::~CDropDown() { } void CDropDown::SetupText() { SetupListRect(); CList::SetupText(); } void CDropDown::HandleMessage(SGUIMessage& Message) { // Important switch (Message.type) { case GUIM_SETTINGS_UPDATED: { // Update cached list rect if (Message.value == "size" || Message.value == "absolute" || Message.value == "dropdown_size" || Message.value == "dropdown_buffer" || Message.value == "scrollbar_style" || Message.value == "button_width") { SetupListRect(); } break; } case GUIM_MOUSE_MOTION: { if (!m_Open) break; CPos mouse = GetMousePos(); if (!GetListRect().PointInside(mouse)) break; bool scrollbar; CGUIList* pList; GUI::GetSetting(this, "scrollbar", scrollbar); GUI::GetSettingPointer(this, "list", pList); float scroll = 0.f; if (scrollbar) scroll = GetScrollBar(0).GetPos(); CRect rect = GetListRect(); mouse.y += scroll; int set = -1; for (int i = 0; i < (int)pList->m_Items.size(); ++i) { if (mouse.y >= rect.top + m_ItemsYPositions[i] && mouse.y < rect.top + m_ItemsYPositions[i+1] && // mouse is not over scroll-bar (m_HideScrollBar || mouse.x < GetScrollBar(0).GetOuterRect().left || mouse.x > GetScrollBar(0).GetOuterRect().right)) { set = i; } } if (set != -1) { m_ElementHighlight = set; //UpdateAutoScroll(); } break; } case GUIM_MOUSE_ENTER: { bool enabled; GUI::GetSetting(this, "enabled", enabled); if (!enabled) break; CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_enter", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); break; } case GUIM_MOUSE_LEAVE: { GUI::GetSetting(this, "selected", m_ElementHighlight); bool enabled; GUI::GetSetting(this, "enabled", enabled); if (!enabled) break; CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_leave", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); break; } // We can't inherent this routine from CList, because we need to include // a mouse click to open the dropdown, also the coordinates are changed. case GUIM_MOUSE_PRESS_LEFT: { bool enabled; GUI::GetSetting(this, "enabled", enabled); if (!enabled) { CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_disabled", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); break; } if (!m_Open) { CGUIList* pList; GUI::GetSettingPointer(this, "list", pList); if (pList->m_Items.empty()) return; m_Open = true; GetScrollBar(0).SetZ(GetBufferedZ()); GUI::GetSetting(this, "selected", m_ElementHighlight); // Start at the position of the selected item, if possible. GetScrollBar(0).SetPos(m_ItemsYPositions.empty() ? 0 : m_ItemsYPositions[m_ElementHighlight] - 60); CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_opened", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); return; // overshadow } else { CPos mouse = GetMousePos(); // If the regular area is pressed, then abort, and close. if (m_CachedActualSize.PointInside(mouse)) { m_Open = false; GetScrollBar(0).SetZ(GetBufferedZ()); CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_closed", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); return; // overshadow } if (m_HideScrollBar || mouse.x < GetScrollBar(0).GetOuterRect().left || mouse.x > GetScrollBar(0).GetOuterRect().right || mouse.y < GetListRect().top) { m_Open = false; GetScrollBar(0).SetZ(GetBufferedZ()); } } break; } case GUIM_MOUSE_WHEEL_DOWN: { bool enabled; GUI::GetSetting(this, "enabled", enabled); // Don't switch elements by scrolling when open, causes a confusing interaction between this and the scrollbar. if (m_Open || !enabled) break; GUI::GetSetting(this, "selected", m_ElementHighlight); if (m_ElementHighlight + 1 >= (int)m_ItemsYPositions.size() - 1) break; ++m_ElementHighlight; GUI::SetSetting(this, "selected", m_ElementHighlight); break; } case GUIM_MOUSE_WHEEL_UP: { bool enabled; GUI::GetSetting(this, "enabled", enabled); // Don't switch elements by scrolling when open, causes a confusing interaction between this and the scrollbar. if (m_Open || !enabled) break; GUI::GetSetting(this, "selected", m_ElementHighlight); if (m_ElementHighlight - 1 < 0) break; m_ElementHighlight--; GUI::SetSetting(this, "selected", m_ElementHighlight); break; } case GUIM_LOST_FOCUS: { if (m_Open) { CStrW soundPath; if (g_SoundManager && GUI::GetSetting(this, "sound_closed", soundPath) == PSRETURN_OK && !soundPath.empty()) g_SoundManager->PlayAsUI(soundPath.c_str(), false); } m_Open = false; break; } case GUIM_LOAD: SetupListRect(); break; default: break; } // Important that this is after, so that overshadowed implementations aren't processed CList::HandleMessage(Message); // As HandleMessage functions return void, we need to manually verify // whether the child list's items were modified. if (CList::GetModified()) SetupText(); } InReaction CDropDown::ManuallyHandleEvent(const SDL_Event_* ev) { InReaction result = IN_PASS; bool update_highlight = false; if (ev->ev.type == SDL_KEYDOWN) { int szChar = ev->ev.key.keysym.sym; switch (szChar) { case '\r': m_Open = false; result = IN_HANDLED; break; case SDLK_HOME: case SDLK_END: case SDLK_UP: case SDLK_DOWN: case SDLK_PAGEUP: case SDLK_PAGEDOWN: if (!m_Open) return IN_PASS; // Set current selected item to highlighted, before // then really processing these in CList::ManuallyHandleEvent() GUI::SetSetting(this, "selected", m_ElementHighlight); update_highlight = true; break; default: // If we have inputed a character try to get the closest element to it. // TODO: not too nice and doesn't deal with dashes. if (m_Open && ((szChar >= SDLK_a && szChar <= SDLK_z) || szChar == SDLK_SPACE || (szChar >= SDLK_0 && szChar <= SDLK_9) || (szChar >= SDLK_KP_0 && szChar <= SDLK_KP_9))) { // arbitrary 1 second limit to add to string or start fresh. // maximal amount of characters is 100, which imo is far more than enough. if (timer_Time() - m_TimeOfLastInput > 1.0 || m_InputBuffer.length() >= 100) m_InputBuffer = szChar; else m_InputBuffer += szChar; m_TimeOfLastInput = timer_Time(); CGUIList* pList; GUI::GetSettingPointer(this, "list", pList); // let's look for the closest element // basically it's alphabetic order and "as many letters as we can get". int closest = -1; int bestIndex = -1; int difference = 1250; for (int i = 0; i < (int)pList->m_Items.size(); ++i) { int indexOfDifference = 0; int diff = 0; for (size_t j = 0; j < m_InputBuffer.length(); ++j) { - diff = abs(pList->m_Items[i].GetOriginalString().LowerCase()[j] - (int)m_InputBuffer[j]); + diff = std::abs(pList->m_Items[i].GetRawString().LowerCase()[j] - (int)m_InputBuffer[j]); if (diff == 0) indexOfDifference = j+1; else break; } if (indexOfDifference > bestIndex || (indexOfDifference >= bestIndex && diff < difference)) { bestIndex = indexOfDifference; closest = i; difference = diff; } } // let's select the closest element. There should basically always be one. if (closest != -1) { GUI::SetSetting(this, "selected", closest); update_highlight = true; GetScrollBar(0).SetPos(m_ItemsYPositions[closest] - 60); } result = IN_HANDLED; } break; } } if (CList::ManuallyHandleEvent(ev) == IN_HANDLED) result = IN_HANDLED; if (update_highlight) GUI::GetSetting(this, "selected", m_ElementHighlight); return result; } void CDropDown::SetupListRect() { float size, buffer; GUI::GetSetting(this, "dropdown_size", size); GUI::GetSetting(this, "dropdown_buffer", buffer); if (m_ItemsYPositions.empty() || m_ItemsYPositions.back() >= size) { m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom+buffer, m_CachedActualSize.right, m_CachedActualSize.bottom+buffer + size); m_HideScrollBar = false; } else { m_CachedListRect = CRect(m_CachedActualSize.left, m_CachedActualSize.bottom+buffer, m_CachedActualSize.right, m_CachedActualSize.bottom+buffer + m_ItemsYPositions.back()); m_HideScrollBar = true; } } CRect CDropDown::GetListRect() const { return m_CachedListRect; } bool CDropDown::MouseOver() { if(!GetGUI()) throw PSERROR_GUI_OperationNeedsGUIObject(); if (m_Open) { CRect rect(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.right, GetListRect().bottom); return rect.PointInside(GetMousePos()); } else return m_CachedActualSize.PointInside(GetMousePos()); } void CDropDown::Draw() { if (!GetGUI()) return; float bz = GetBufferedZ(); float dropdown_size, button_width; GUI::GetSetting(this, "dropdown_size", dropdown_size); GUI::GetSetting(this, "button_width", button_width); CGUISpriteInstance* sprite; CGUISpriteInstance* sprite2; CGUISpriteInstance* sprite2_second; int cell_id, selected = 0; CColor color; bool enabled; GUI::GetSetting(this, "enabled", enabled); GUI::GetSettingPointer(this, "sprite2", sprite2); GUI::GetSetting(this, "cell_id", cell_id); GUI::GetSetting(this, "selected", selected); GUI::GetSetting(this, enabled ? "textcolor_selected" : "textcolor_disabled", color); GUI::GetSettingPointer(this, enabled ? "sprite" : "sprite_disabled", sprite); GetGUI()->DrawSprite(*sprite, cell_id, bz, m_CachedActualSize); if (button_width > 0.f) { CRect rect(m_CachedActualSize.right-button_width, m_CachedActualSize.top, m_CachedActualSize.right, m_CachedActualSize.bottom); if (!enabled) { GUI::GetSettingPointer(this, "sprite2_disabled", sprite2_second); GetGUI()->DrawSprite(GUI<>::FallBackSprite(*sprite2_second, *sprite2), cell_id, bz+0.05f, rect); } else if (m_Open) { GUI::GetSettingPointer(this, "sprite2_pressed", sprite2_second); GetGUI()->DrawSprite(GUI<>::FallBackSprite(*sprite2_second, *sprite2), cell_id, bz+0.05f, rect); } else if (m_MouseHovering) { GUI::GetSettingPointer(this, "sprite2_over", sprite2_second); GetGUI()->DrawSprite(GUI<>::FallBackSprite(*sprite2_second, *sprite2), cell_id, bz+0.05f, rect); } else GetGUI()->DrawSprite(*sprite2, cell_id, bz+0.05f, rect); } if (selected != -1) // TODO: Maybe check validity completely? { CRect cliparea(m_CachedActualSize.left, m_CachedActualSize.top, m_CachedActualSize.right-button_width, m_CachedActualSize.bottom); CPos pos(m_CachedActualSize.left, m_CachedActualSize.top); DrawText(selected, color, pos, bz+0.1f, cliparea); } bool* scrollbar = NULL; bool old; GUI::GetSettingPointer(this, "scrollbar", scrollbar); old = *scrollbar; if (m_Open) { if (m_HideScrollBar) *scrollbar = false; DrawList(m_ElementHighlight, "sprite_list", "sprite_selectarea", "textcolor"); if (m_HideScrollBar) *scrollbar = old; } } // When a dropdown list is opened, it needs to be visible above all the other // controls on the page. The only way I can think of to do this is to increase // its z value when opened, so that it's probably on top. float CDropDown::GetBufferedZ() const { float bz = CList::GetBufferedZ(); if (m_Open) return std::min(bz + 500.f, 1000.f); // TODO - don't use magic number for max z value else return bz; } Index: ps/trunk/source/gui/GUItext.h =================================================================== --- ps/trunk/source/gui/GUItext.h (revision 19544) +++ ps/trunk/source/gui/GUItext.h (revision 19545) @@ -1,336 +1,341 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2017 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 text, handles text stuff * * --Overview-- * Mainly contains struct SGUIText and friends. * Actual text processing is made in CGUI::GenerateText() * * --More info-- * Check GUI.h * */ #ifndef INCLUDED_GUITEXT #define INCLUDED_GUITEXT #include #include "CGUISprite.h" #include "ps/CStrIntern.h" class CGUI; /** * An SGUIText object is a parsed string, divided into * text-rendering components. Each component, being a * call to the Renderer. For instance, if you by tags * change the color, then the GUI will have to make * individual calls saying it want that color on the * text. * * For instance: * "Hello [b]there[/b] bunny!" * * That without word-wrapping would mean 3 components. * i.e. 3 calls to CRenderer. One drawing "Hello", * one drawing "there" in bold, and one drawing "bunny!". */ struct SGUIText { /** * A sprite call to the CRenderer */ struct SSpriteCall { SSpriteCall() : m_CellID(0) {} /** * Size and position of sprite */ CRect m_Area; /** * Sprite from global GUI sprite database. */ CGUISpriteInstance m_Sprite; int m_CellID; /** * Tooltip text */ CStrW m_Tooltip; /** * Tooltip style */ CStrW m_TooltipStyle; }; /** * A text call to the CRenderer */ struct STextCall { STextCall() : m_UseCustomColor(false), m_Bold(false), m_Italic(false), m_Underlined(false), m_pSpriteCall(NULL) {} /** * Position */ CPos m_Pos; /** * Size */ CSize m_Size; /** * The string that is suppose to be rendered. */ CStrW m_String; /** * Use custom color? If true then m_Color is used, * else the color inputted will be used. */ bool m_UseCustomColor; /** * Color setup */ CColor m_Color; /** * Font name */ CStrIntern m_Font; /** * Settings */ bool m_Bold, m_Italic, m_Underlined; /** * *IF* an icon, then this is not NULL. */ std::list::pointer m_pSpriteCall; }; /** * List of TextCalls, for instance "Hello", "there!" */ std::vector m_TextCalls; /** * List of sprites, or "icons" that should be rendered * along with the text. */ std::list m_SpriteCalls; // list for consistent mem addresses // so that we can point to elements. /** * Width and height of the whole output, used when setting up * scrollbars and such. */ CSize m_Size; }; /** * String class, substitute for CStr, but that parses * the tags and builds up a list of all text that will * be different when outputted. * * The difference between CGUIString and SGUIText is that * CGUIString is a string-class that parses the tags * when the value is set. The SGUIText is just a container * which stores the positions and settings of all text-calls * that will have to be made to the Renderer. */ class CGUIString { public: /** * A chunk of text that represents one call to the renderer. * In other words, all text in one chunk, will be drawn * exactly with the same settings. */ struct TextChunk { /** * A tag looks like this "Hello [b]there[/b] little" */ struct Tag { /** * Tag Type */ enum TagType { TAG_B, TAG_I, TAG_FONT, TAG_SIZE, TAG_COLOR, TAG_IMGLEFT, TAG_IMGRIGHT, TAG_ICON, TAG_INVALID }; struct TagAttribute { std::wstring attrib; std::wstring value; }; /** * Set tag from string * * @param tagtype TagType by string, like 'img' for [img] * @return True if m_TagType was set. */ bool SetTagType(const CStrW& tagtype); TagType GetTagType(const CStrW& tagtype) const; /** * In [b="Hello"][/b] * m_TagType is TAG_B */ TagType m_TagType; /** * In [b="Hello"][/b] * m_TagValue is 'Hello' */ std::wstring m_TagValue; /** * Some tags need an additional attributes */ std::vector m_TagAttributes; }; /** * m_From and m_To is the range of the string */ int m_From, m_To; /** * Tags that are present. [a][b] */ std::vector m_Tags; }; /** * All data generated in GenerateTextCall() */ struct SFeedback { // Constants static const int Left = 0; static const int Right = 1; /** * Reset all member data. */ void Reset(); /** * Image stacks, for left and right floating images. */ std::vector m_Images[2]; // left and right /** * Text and Sprite Calls. */ std::vector m_TextCalls; std::list m_SpriteCalls; // list for consistent mem addresses // so that we can point to elements. /** * Width and Height *feedback* */ CSize m_Size; /** * If the word inputted was a new line. */ bool m_NewLine; }; /** * Set the value, the string will automatically * be parsed when set. */ void SetValue(const CStrW& str); /** * Get String, with tags */ const CStrW& GetOriginalString() const { return m_OriginalString; } /** + * Get String, stripped of tags + */ + const CStrW& GetRawString() const { return m_RawString; } + + /** * Generate Text Call from specified range. The range * must span only within ONE TextChunk though. Otherwise * it can't be fit into a single Text Call * * Notice it won't make it complete, you will have to add * X/Y values and such. * * @param pGUI Pointer to CGUI object making this call, for e.g. icon retrieval. * @param Feedback contains all info that is generated. * @param DefaultFont Default Font * @param from From character n, * @param to to character n. * @param FirstLine Whether this is the first line of text, to calculate its height correctly * @param pObject Only for Error outputting, optional! If NULL * then no Errors will be reported! Useful when you need * to make several GenerateTextCall in different phases, * it avoids duplicates. */ void GenerateTextCall(const CGUI* pGUI, SFeedback& Feedback, CStrIntern DefaultFont, const int& from, const int& to, const bool FirstLine, const IGUIObject* pObject = NULL) const; /** * Words */ std::vector m_Words; private: /** * TextChunks */ std::vector m_TextChunks; /** * The full raw string. Stripped of tags. */ CStrW m_RawString; /** * The original string value passed to SetValue. */ CStrW m_OriginalString; }; #endif // INCLUDED_GUITEXT