Index: source/gui/CChart.cpp =================================================================== --- source/gui/CChart.cpp +++ source/gui/CChart.cpp @@ -20,6 +20,7 @@ #include "CChart.h" #include "gui/CGUIColor.h" +#include "gui/CGUIString.h" #include "gui/GUIMatrix.h" #include "graphics/ShaderManager.h" #include "i18n/L10n.h" Index: source/gui/CGUIList.h =================================================================== --- source/gui/CGUIList.h +++ source/gui/CGUIList.h @@ -15,11 +15,10 @@ * along with 0 A.D. If not, see . */ - #ifndef INCLUDED_CGUILIST #define INCLUDED_CGUILIST -#include "GUItext.h" +#include "gui/CGUIString.h" class CGUIList { Index: source/gui/CGUIString.h =================================================================== --- source/gui/CGUIString.h +++ source/gui/CGUIString.h @@ -27,143 +27,18 @@ * */ -#ifndef INCLUDED_GUITEXT -#define INCLUDED_GUITEXT +#ifndef INCLUDED_CGUISTRING +#define INCLUDED_CGUISTRING #include -#include "CGUISprite.h" -#include "gui/CGUIColor.h" +#include "gui/CGUISprite.h" +#include "gui/GUItext.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 - { - // The CGUISpriteInstance makes this uncopyable to avoid invalidating its draw cache - NONCOPYABLE(SSpriteCall); - MOVABLE(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 - { - NONCOPYABLE(STextCall); - MOVABLE(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 - */ - CGUIColor 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. @@ -351,4 +226,4 @@ CStrW m_OriginalString; }; -#endif // INCLUDED_GUITEXT +#endif // INCLUDED_CGUISTRING Index: source/gui/GUItext.h =================================================================== --- source/gui/GUItext.h +++ source/gui/GUItext.h @@ -15,28 +15,15 @@ * 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 "gui/CGUIColor.h" #include "ps/CStrIntern.h" - -class CGUI; +#include "ps/Shapes.h" +#include "gui/CGUISprite.h" /** * An SGUIText object is a parsed string, divided into @@ -163,192 +150,4 @@ 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 - { - // Avoid copying the vector and list containers. - NONCOPYABLE(SFeedback); - MOVABLE(SFeedback); - SFeedback() = default; - - // 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 Index: source/gui/GUItext.cpp =================================================================== --- source/gui/GUItext.cpp +++ source/gui/GUItext.cpp @@ -1,472 +0,0 @@ -/* Copyright (C) 2019 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 - -#include "gui/GUI.h" -#include "lib/utf8.h" -#include "graphics/FontMetrics.h" -#include "ps/CLogger.h" - - -// List of word delimiter bounds -// The list contains ranges of word delimiters. The odd indexed chars are the start -// of a range, the even are the end of a range. The list must be sorted in INCREASING ORDER -static const int NUM_WORD_DELIMITERS = 4*2; -static const u16 WordDelimiters[NUM_WORD_DELIMITERS] = { - ' ' , ' ', // spaces - '-' , '-', // hyphens - 0x3000, 0x31FF, // ideographic symbols - 0x3400, 0x9FFF -// TODO add unicode blocks of other languages that don't use spaces -}; - -void CGUIString::SFeedback::Reset() -{ - m_Images[Left].clear(); - m_Images[Right].clear(); - m_TextCalls.clear(); - m_SpriteCalls.clear(); - m_Size = CSize(); - m_NewLine = false; -} - -void CGUIString::GenerateTextCall(const CGUI* pGUI, SFeedback& Feedback, CStrIntern DefaultFont, const int& from, const int& to, const bool FirstLine, const IGUIObject* pObject) const -{ - // Reset width and height, because they will be determined with incrementation - // or comparisons. - Feedback.Reset(); - - // Check out which text chunk this is within. - for (const TextChunk& textChunk : m_TextChunks) - { - // Get the area that is overlapped by both the TextChunk and - // by the from/to inputted. - int _from = std::max(from, textChunk.m_From); - int _to = std::min(to, textChunk.m_To); - - // If from is larger than to, then they are not overlapping - if (_to == _from && textChunk.m_From == textChunk.m_To) - { - // These should never be able to have more than one tag. - ENSURE(textChunk.m_Tags.size() == 1); - - // Icons and images are placed on exactly one position - // in the words-list, and they can be counted twice if placed - // on an edge. But there is always only one logical preference - // that we want. This check filters the unwanted. - - // it's in the end of one word, and the icon - // should really belong to the beginning of the next one - if (_to == to && to >= 1 && to < (int)m_RawString.length()) - { - if (m_RawString[to-1] == ' ' || - m_RawString[to-1] == '-' || - m_RawString[to-1] == '\n') - continue; - } - // This std::string is just a break - if (_from == from && from >= 1) - { - if (m_RawString[from] == '\n' && - m_RawString[from-1] != '\n' && - m_RawString[from-1] != ' ' && - m_RawString[from-1] != '-') - continue; - } - - const TextChunk::Tag& tag = textChunk.m_Tags[0]; - ENSURE(tag.m_TagType == TextChunk::Tag::TAG_IMGLEFT || - tag.m_TagType == TextChunk::Tag::TAG_IMGRIGHT || - tag.m_TagType == TextChunk::Tag::TAG_ICON); - - const std::string& path = utf8_from_wstring(tag.m_TagValue); - if (!pGUI->HasIcon(path)) - { - if (pObject) - LOGERROR("Trying to use an icon, imgleft or imgright-tag with an undefined icon (\"%s\").", path.c_str()); - continue; - } - - switch (tag.m_TagType) - { - case TextChunk::Tag::TAG_IMGLEFT: - Feedback.m_Images[SFeedback::Left].push_back(path); - break; - case TextChunk::Tag::TAG_IMGRIGHT: - Feedback.m_Images[SFeedback::Right].push_back(path); - break; - case TextChunk::Tag::TAG_ICON: - { - // We'll need to setup a text-call that will point - // to the icon, this is to be able to iterate - // through the text-calls without having to - // complex the structure virtually for nothing more. - SGUIText::STextCall TextCall; - - // Also add it to the sprites being rendered. - SGUIText::SSpriteCall SpriteCall; - - // Get Icon from icon database in pGUI - const SGUIIcon& icon = pGUI->GetIcon(path); - - const CSize& size = icon.m_Size; - - // append width, and make maximum height the height. - Feedback.m_Size.cx += size.cx; - Feedback.m_Size.cy = std::max(Feedback.m_Size.cy, size.cy); - - // These are also needed later - TextCall.m_Size = size; - SpriteCall.m_Area = size; - - // Handle additional attributes - for (const TextChunk::Tag::TagAttribute& tagAttrib : tag.m_TagAttributes) - { - if (tagAttrib.attrib == L"displace" && !tagAttrib.value.empty()) - { - // Displace the sprite - CSize displacement; - // Parse the value - if (!GUI::ParseString(tagAttrib.value, displacement)) - LOGERROR("Error parsing 'displace' value for tag [ICON]"); - else - SpriteCall.m_Area += displacement; - } - else if (tagAttrib.attrib == L"tooltip") - SpriteCall.m_Tooltip = tagAttrib.value; - else if (tagAttrib.attrib == L"tooltip_style") - SpriteCall.m_TooltipStyle = tagAttrib.value; - } - - SpriteCall.m_Sprite = icon.m_SpriteName; - SpriteCall.m_CellID = icon.m_CellID; - - // Add sprite call - Feedback.m_SpriteCalls.push_back(std::move(SpriteCall)); - - // Finalize text call - TextCall.m_pSpriteCall = &Feedback.m_SpriteCalls.back(); - - // Add text call - Feedback.m_TextCalls.emplace_back(std::move(TextCall)); - - break; - } - NODEFAULT; - } - } - else if (_to > _from && !Feedback.m_NewLine) - { - SGUIText::STextCall TextCall; - - // Set defaults - TextCall.m_Font = DefaultFont; - TextCall.m_UseCustomColor = false; - - TextCall.m_String = m_RawString.substr(_from, _to-_from); - - // Go through tags and apply changes. - for (const TextChunk::Tag& tag : textChunk.m_Tags) - { - switch (tag.m_TagType) - { - case TextChunk::Tag::TAG_COLOR: - TextCall.m_UseCustomColor = true; - - if (!GUI::ParseString(tag.m_TagValue, TextCall.m_Color) && pObject) - LOGERROR("Error parsing the value of a [color]-tag in GUI text when reading object \"%s\".", pObject->GetPresentableName().c_str()); - break; - case TextChunk::Tag::TAG_FONT: - // TODO Gee: (2004-08-15) Check if Font exists? - TextCall.m_Font = CStrIntern(utf8_from_wstring(tag.m_TagValue)); - break; - default: - LOGERROR("Encountered unexpected tag applied to text"); - break; - } - } - - // Calculate the size of the font - CSize size; - int cx, cy; - CFontMetrics font (TextCall.m_Font); - font.CalculateStringSize(TextCall.m_String.c_str(), cx, cy); - // For anything other than the first line, the line spacing - // needs to be considered rather than just the height of the text - if (!FirstLine) - cy = font.GetLineSpacing(); - - size.cx = (float)cx; - size.cy = (float)cy; - - // Append width, and make maximum height the height. - Feedback.m_Size.cx += size.cx; - Feedback.m_Size.cy = std::max(Feedback.m_Size.cy, size.cy); - - // These are also needed later - TextCall.m_Size = size; - - if (!TextCall.m_String.empty() && TextCall.m_String[0] == '\n') - Feedback.m_NewLine = true; - - // Add text-chunk - Feedback.m_TextCalls.emplace_back(std::move(TextCall)); - } - } -} - -bool CGUIString::TextChunk::Tag::SetTagType(const CStrW& tagtype) -{ - TagType t = GetTagType(tagtype); - if (t == TAG_INVALID) - return false; - - m_TagType = t; - return true; -} - -CGUIString::TextChunk::Tag::TagType CGUIString::TextChunk::Tag::GetTagType(const CStrW& tagtype) const -{ - if (tagtype == L"color") - return TAG_COLOR; - if (tagtype == L"font") - return TAG_FONT; - if (tagtype == L"icon") - return TAG_ICON; - if (tagtype == L"imgleft") - return TAG_IMGLEFT; - if (tagtype == L"imgright") - return TAG_IMGRIGHT; - - return TAG_INVALID; -} - -void CGUIString::SetValue(const CStrW& str) -{ - m_OriginalString = str; - - m_TextChunks.clear(); - m_Words.clear(); - m_RawString.clear(); - - // Current Text Chunk - CGUIString::TextChunk CurrentTextChunk; - CurrentTextChunk.m_From = 0; - - int l = str.length(); - int rawpos = 0; - CStrW tag; - std::vector tags; - bool closing = false; - for (int p = 0; p < l; ++p) - { - TextChunk::Tag tag_; - switch (str[p]) - { - case L'[': - CurrentTextChunk.m_To = rawpos; - // Add the current chunks if it is not empty - if (CurrentTextChunk.m_From != rawpos) - m_TextChunks.push_back(CurrentTextChunk); - CurrentTextChunk.m_From = rawpos; - - closing = false; - if (++p == l) - { - LOGERROR("Partial tag at end of string '%s'", utf8_from_wstring(str)); - break; - } - if (str[p] == L'/') - { - closing = true; - if (tags.empty()) - { - LOGERROR("Encountered closing tag without having any open tags. At %d in '%s'", p, utf8_from_wstring(str)); - break; - } - if (++p == l) - { - LOGERROR("Partial closing tag at end of string '%s'", utf8_from_wstring(str)); - break; - } - } - tag.clear(); - // Parse tag - for (; p < l && str[p] != L']'; ++p) - { - CStrW name, param; - switch (str[p]) - { - case L' ': - if (closing) // We still parse them to make error handling cleaner - LOGERROR("Closing tags do not support parameters (at pos %d '%s')", p, utf8_from_wstring(str)); - - // parse something="something else" - for (++p; p < l && str[p] != L'='; ++p) - name.push_back(str[p]); - - if (p == l) - { - LOGERROR("Parameter without value at pos %d '%s'", p, utf8_from_wstring(str)); - break; - } - FALLTHROUGH; - case L'=': - // parse a quoted parameter - if (closing) // We still parse them to make error handling cleaner - LOGERROR("Closing tags do not support parameters (at pos %d '%s')", p, utf8_from_wstring(str)); - - if (++p == l) - { - LOGERROR("Expected parameter, got end of string '%s'", utf8_from_wstring(str)); - break; - } - if (str[p] != L'"') - { - LOGERROR("Unquoted parameters are not supported (at pos %d '%s')", p, utf8_from_wstring(str)); - break; - } - for (++p; p < l && str[p] != L'"'; ++p) - { - switch (str[p]) - { - case L'\\': - if (++p == l) - { - LOGERROR("Escape character at end of string '%s'", utf8_from_wstring(str)); - break; - } - // NOTE: We do not support \n in tag parameters - FALLTHROUGH; - default: - param.push_back(str[p]); - } - } - - if (!name.empty()) - { - TextChunk::Tag::TagAttribute a = {name, param}; - tag_.m_TagAttributes.push_back(a); - } - else - tag_.m_TagValue = param; - break; - default: - tag.push_back(str[p]); - break; - } - } - - if (!tag_.SetTagType(tag)) - { - LOGERROR("Invalid tag '%s' at %d in '%s'", utf8_from_wstring(tag), p, utf8_from_wstring(str)); - break; - } - if (!closing) - { - if (tag_.m_TagType == TextChunk::Tag::TAG_IMGRIGHT - || tag_.m_TagType == TextChunk::Tag::TAG_IMGLEFT - || tag_.m_TagType == TextChunk::Tag::TAG_ICON) - { - TextChunk FreshTextChunk = { rawpos, rawpos }; - FreshTextChunk.m_Tags.push_back(tag_); - m_TextChunks.push_back(FreshTextChunk); - } - else - { - tags.push_back(tag); - CurrentTextChunk.m_Tags.push_back(tag_); - } - } - else - { - if (tag != tags.back()) - { - LOGERROR("Closing tag '%s' does not match last opened tag '%s' at %d in '%s'", utf8_from_wstring(tag), utf8_from_wstring(tags.back()), p, utf8_from_wstring(str)); - break; - } - - tags.pop_back(); - CurrentTextChunk.m_Tags.pop_back(); - } - break; - case L'\\': - if (++p == l) - { - LOGERROR("Escape character at end of string '%s'", utf8_from_wstring(str)); - break; - } - if (str[p] == L'n') - { - ++rawpos; - m_RawString.push_back(L'\n'); - break; - } - FALLTHROUGH; - default: - ++rawpos; - m_RawString.push_back(str[p]); - break; - } - } - - // Add the chunk after the last tag - if (CurrentTextChunk.m_From != rawpos) - { - CurrentTextChunk.m_To = rawpos; - m_TextChunks.push_back(CurrentTextChunk); - } - - - // Add a delimiter at start and at end, it helps when - // processing later, because we don't have make exceptions for - // those cases. - m_Words.push_back(0); - - // Add word boundaries in increasing order - for (u32 i = 0; i < m_RawString.length(); ++i) - { - wchar_t c = m_RawString[i]; - if (c == '\n') - { - m_Words.push_back((int)i); - m_Words.push_back((int)i+1); - continue; - } - for (int n = 0; n < NUM_WORD_DELIMITERS; n += 2) - { - if (c <= WordDelimiters[n+1]) - { - if (c >= WordDelimiters[n]) - m_Words.push_back((int)i+1); - // assume the WordDelimiters list is stored in increasing order - break; - } - } - } - - m_Words.push_back((int)m_RawString.length()); - - // Remove duplicates (only if larger than 2) - if (m_Words.size() <= 2) - return; - - m_Words.erase(std::unique(m_Words.begin(), m_Words.end()), m_Words.end()); -}