StaticText/EnrichedString: Styling support (#9187)
authorSmallJoker <SmallJoker@users.noreply.github.com>
Wed, 22 Jan 2020 18:09:11 +0000 (19:09 +0100)
committerGitHub <noreply@github.com>
Wed, 22 Jan 2020 18:09:11 +0000 (19:09 +0100)
* StaticText/EnrichedString: Styling support

* Fix tooltip fg/bgcolor

* Fix default color for substr(), add unittests

games/minimal/mods/test/formspec.lua
src/client/gameui.cpp
src/gui/guiButton.cpp
src/gui/guiFormSpecMenu.cpp
src/irrlicht_changes/static_text.cpp
src/irrlicht_changes/static_text.h
src/unittest/test_utilities.cpp
src/util/enriched_string.cpp
src/util/enriched_string.h

index 64b9ec0d5c7c15a9469607deded4e499c4c03302..bac82c965fcb6fa8ae6d6c5c41133b27ead582fc 100644 (file)
@@ -1,3 +1,5 @@
+local color = minetest.colorize\r
+\r
 local clip_fs = [[\r
        style_type[label;noclip=%c]\r
        style_type[button;noclip=%c]\r
@@ -31,8 +33,8 @@ local style_fs = [[
                bgcolor_pressed=purple]\r
        button[0,0;2.5,0.8;one_btn1;Button]\r
 \r
-       style[one_btn2;border=false;textcolor=cyan]\r
-       button[0,1.05;2.5,0.8;one_btn2;Text Button]\r
+       style[one_btn2;border=false;textcolor=cyan] ]]..\r
+       "button[0,1.05;2.5,0.8;one_btn2;Text " .. color("#FF0", "Yellow") .. [[]\r
 \r
        style[one_btn3;bgimg=bubble.png;bgimg_hovered=default_apple.png;\r
                bgimg_pressed=heart.png]\r
@@ -144,16 +146,18 @@ local pages = {
                list[current_player;main;6,8;3,2;1]\r
                button[9,0;2.5,1;name;]\r
                button[9,1;2.5,1;name;]\r
-               button[9,2;2.5,1;name;]\r
-               label[9,0;This is a label.\nLine\nLine\nLine\nEnd]\r
-               button[9,3;1,1;name;]\r
+               button[9,2;2.5,1;name;] ]]..\r
+               "label[9,0.5;This is a label.\nLine\nLine\nLine\nEnd]"..\r
+               [[button[9,3;1,1;name;]\r
                vertlabel[9,4;VERT]\r
                label[10,3;HORIZ]\r
                tabheader[6.5,0;6,0.65;name;Tab 1,Tab 2,Tab 3,Secrets;1;false;false]\r
        ]],\r
 \r
                "size[12,12]real_coordinates[true]" ..\r
-               "label[0.375,0.375;Styled]" ..\r
+               ("label[0.375,0.375;Styled - %s %s]"):format(\r
+                       color("#F00", "red text"),\r
+                       color("#77FF00CC", "green text")) ..\r
                "label[6.375,0.375;Unstyled]" ..\r
                "box[0,0.75;12,0.1;#999]" ..\r
                "box[6,0.85;0.1,11.15;#999]" ..\r
index 674d07fa629d9d2510b673bea77cb1aecba14158..3c7ed54b20f4be72fa729537b951f58f109e26c1 100644 (file)
@@ -155,7 +155,7 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_
 
        m_guitext2->setVisible(m_flags.show_debug);
 
-       setStaticText(m_guitext_info, translate_string(m_infotext).c_str());
+       setStaticText(m_guitext_info, m_infotext.c_str());
        m_guitext_info->setVisible(m_flags.show_hud && g_menumgr.menuCount() == 0);
 
        static const float statustext_time_max = 1.5f;
@@ -169,7 +169,7 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_
                }
        }
 
-       setStaticText(m_guitext_status, translate_string(m_statustext).c_str());
+       setStaticText(m_guitext_status, m_statustext.c_str());
        m_guitext_status->setVisible(!m_statustext.empty());
 
        if (!m_statustext.empty()) {
index ed79999cf3e489885c0beecd057b01423d86cfb4..f7a0af2d99477e9d8d1c78fcbb38038a50129c11 100644 (file)
@@ -53,7 +53,6 @@ GUIButton::GUIButton(IGUIEnvironment* environment, IGUIElement* parent,
                        core::clamp<u32>(Colors[i].getGreen() * COLOR_PRESSED_MOD, 0, 255),\r
                        core::clamp<u32>(Colors[i].getBlue() * COLOR_PRESSED_MOD, 0, 255));\r
        }\r
-       \r
        StaticText = gui::StaticText::add(Environment, Text.c_str(), core::rect<s32>(0,0,rectangle.getWidth(),rectangle.getHeight()), false, false, this, id);\r
        StaticText->setTextAlignment(EGUIA_CENTER, EGUIA_CENTER);\r
        // END PATCH\r
index a91623f96715bfb43bf3ad629a738648a2a4a053..d03ce451657ead46c12d211d22da0d08c6c1b96e 100644 (file)
@@ -3417,19 +3417,16 @@ void GUIFormSpecMenu::drawMenu()
 void GUIFormSpecMenu::showTooltip(const std::wstring &text,
        const irr::video::SColor &color, const irr::video::SColor &bgcolor)
 {
-       const std::wstring ntext = translate_string(text);
-       m_tooltip_element->setOverrideColor(color);
-       m_tooltip_element->setBackgroundColor(bgcolor);
-       setStaticText(m_tooltip_element, ntext.c_str());
+       EnrichedString ntext(text);
+       ntext.setDefaultColor(color);
+       ntext.setBackground(bgcolor);
+
+       setStaticText(m_tooltip_element, ntext);
 
        // Tooltip size and offset
        s32 tooltip_width = m_tooltip_element->getTextWidth() + m_btn_height;
-#if (IRRLICHT_VERSION_MAJOR <= 1 && IRRLICHT_VERSION_MINOR <= 8 && IRRLICHT_VERSION_REVISION < 2) || USE_FREETYPE == 1
-       std::vector<std::wstring> text_rows = str_split(ntext, L'\n');
-       s32 tooltip_height = m_tooltip_element->getTextHeight() * text_rows.size() + 5;
-#else
        s32 tooltip_height = m_tooltip_element->getTextHeight() + 5;
-#endif
+
        v2u32 screenSize = Environment->getVideoDriver()->getScreenSize();
        int tooltip_offset_x = m_btn_height;
        int tooltip_offset_y = m_btn_height;
index 1375f033c344bccb0a3d8c20633533ebeb6b4483..39b34d17cf4bf4834d9bab6e1ddc1d06fa2df304 100644 (file)
@@ -32,21 +32,15 @@ StaticText::StaticText(const EnrichedString &text, bool border,
                        bool background)
 : IGUIStaticText(environment, parent, id, rectangle),
        HAlign(EGUIA_UPPERLEFT), VAlign(EGUIA_UPPERLEFT),
-       Border(border), OverrideColorEnabled(false), OverrideBGColorEnabled(false), WordWrap(false), Background(background),
+       Border(border), WordWrap(false), Background(background),
        RestrainTextInside(true), RightToLeft(false),
-       OverrideColor(video::SColor(101,255,255,255)), BGColor(video::SColor(101,210,210,210)),
        OverrideFont(0), LastBreakFont(0)
 {
        #ifdef _DEBUG
        setDebugName("StaticText");
        #endif
 
-       Text = text.c_str();
-       cText = text;
-       if (environment && environment->getSkin())
-       {
-               BGColor = environment->getSkin()->getColor(gui::EGDC_3D_FACE);
-       }
+       setText(text);
 }
 
 
@@ -73,12 +67,7 @@ void StaticText::draw()
        // draw background
 
        if (Background)
-       {
-               if ( !OverrideBGColorEnabled )  // skin-colors can change
-                       BGColor = skin->getColor(gui::EGDC_3D_FACE);
-
-               driver->draw2DRectangle(BGColor, frameRect, &AbsoluteClippingRect);
-       }
+               driver->draw2DRectangle(getBackgroundColor(), frameRect, &AbsoluteClippingRect);
 
        // draw the border
 
@@ -89,97 +78,60 @@ void StaticText::draw()
        }
 
        // draw the text
-       if (cText.size())
-       {
-               IGUIFont* font = getActiveFont();
-
-               if (font)
+       IGUIFont *font = getActiveFont();
+       if (font && BrokenText.size()) {
+               if (font != LastBreakFont)
+                       updateText();
+
+               core::rect<s32> r = frameRect;
+               s32 height_line = font->getDimension(L"A").Height + font->getKerningHeight();
+               s32 height_total = height_line * BrokenText.size();
+               if (VAlign == EGUIA_CENTER && WordWrap)
                {
-                       if (!WordWrap)
-                       {
-                               // TODO: add colors here
-                               if (VAlign == EGUIA_LOWERRIGHT)
-                               {
-                                       frameRect.UpperLeftCorner.Y = frameRect.LowerRightCorner.Y -
-                                               font->getDimension(L"A").Height - font->getKerningHeight();
-                               }
-                               if (HAlign == EGUIA_LOWERRIGHT)
-                               {
-                                       frameRect.UpperLeftCorner.X = frameRect.LowerRightCorner.X -
-                                               font->getDimension(cText.c_str()).Width;
-                               }
+                       r.UpperLeftCorner.Y = r.getCenter().Y - (height_total / 2);
+               }
+               else if (VAlign == EGUIA_LOWERRIGHT)
+               {
+                       r.UpperLeftCorner.Y = r.LowerRightCorner.Y - height_total;
+               }
+               if (HAlign == EGUIA_LOWERRIGHT)
+               {
+                       r.UpperLeftCorner.X = r.LowerRightCorner.X -
+                               getTextWidth();
+               }
 
-#if USE_FREETYPE
-                               if (font->getType() == irr::gui::EGFT_CUSTOM) {
-                                       irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(font);
-                                       tmp->draw(Text, frameRect,
-                                               OverrideColorEnabled ? OverrideColor :
-                                                       skin->getColor(isEnabled() ? EGDC_BUTTON_TEXT : EGDC_GRAY_TEXT),
-                                               HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER,
-                                               (RestrainTextInside ? &AbsoluteClippingRect : NULL));
-                               } else
-#endif
-                               {
-                                       font->draw(Text.c_str(), frameRect,
-                                               skin->getColor(EGDC_BUTTON_TEXT),
-                                               HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER,
-                                               (RestrainTextInside ? &AbsoluteClippingRect : NULL));
-                               }
-                       }
-                       else
+               irr::video::SColor previous_color(255, 255, 255, 255);
+               for (const EnrichedString &str : BrokenText) {
+                       if (HAlign == EGUIA_LOWERRIGHT)
                        {
-                               if (font != LastBreakFont)
-                                       breakText();
-
-                               core::rect<s32> r = frameRect;
-                               s32 height = font->getDimension(L"A").Height + font->getKerningHeight();
-                               s32 totalHeight = height * BrokenText.size();
-                               if (VAlign == EGUIA_CENTER)
-                               {
-                                       r.UpperLeftCorner.Y = r.getCenter().Y - (totalHeight / 2);
-                               }
-                               else if (VAlign == EGUIA_LOWERRIGHT)
-                               {
-                                       r.UpperLeftCorner.Y = r.LowerRightCorner.Y - totalHeight;
-                               }
-
-                               irr::video::SColor previous_color(255, 255, 255, 255);
-                               for (u32 i=0; i<BrokenText.size(); ++i)
-                               {
-                                       if (HAlign == EGUIA_LOWERRIGHT)
-                                       {
-                                               r.UpperLeftCorner.X = frameRect.LowerRightCorner.X -
-                                                       font->getDimension(BrokenText[i].c_str()).Width;
-                                       }
-
-                                       EnrichedString str = BrokenText[i];
+                               r.UpperLeftCorner.X = frameRect.LowerRightCorner.X -
+                                       font->getDimension(str.c_str()).Width;
+                       }
 
-                                       //str = colorizeText(BrokenText[i].c_str(), colors, previous_color);
-                                       //if (!colors.empty())
-                                       //      previous_color = colors[colors.size() - 1];
+                       //str = colorizeText(BrokenText[i].c_str(), colors, previous_color);
+                       //if (!colors.empty())
+                       //      previous_color = colors[colors.size() - 1];
 
 #if USE_FREETYPE
-                                       if (font->getType() == irr::gui::EGFT_CUSTOM) {
-                                               irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(font);
-                                               tmp->draw(str,
-                                                       r, previous_color, // FIXME
-                                                       HAlign == EGUIA_CENTER, false,
-                                                       (RestrainTextInside ? &AbsoluteClippingRect : NULL));
-                                       } else
+                       if (font->getType() == irr::gui::EGFT_CUSTOM) {
+                               irr::gui::CGUITTFont *tmp = static_cast<irr::gui::CGUITTFont*>(font);
+                               tmp->draw(str,
+                                       r, previous_color, // FIXME
+                                       HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER,
+                                       (RestrainTextInside ? &AbsoluteClippingRect : NULL));
+                       } else
 #endif
-                                       {
-                                               // Draw non-colored text
-                                               font->draw(str.c_str(),
-                                                       r, skin->getColor(EGDC_BUTTON_TEXT),
-                                                       HAlign == EGUIA_CENTER, false,
-                                                       (RestrainTextInside ? &AbsoluteClippingRect : NULL));
-                                       }
+                       {
+                               // Draw non-colored text
+                               font->draw(str.c_str(),
+                                       r, str.getDefaultColor(), // TODO: Implement colorization
+                                       HAlign == EGUIA_CENTER, VAlign == EGUIA_CENTER,
+                                       (RestrainTextInside ? &AbsoluteClippingRect : NULL));
+                       }
 
 
-                                       r.LowerRightCorner.Y += height;
-                                       r.UpperLeftCorner.Y += height;
-                               }
-                       }
+                       r.LowerRightCorner.Y += height_line;
+                       r.UpperLeftCorner.Y += height_line;
                }
        }
 
@@ -201,7 +153,7 @@ void StaticText::setOverrideFont(IGUIFont* font)
        if (OverrideFont)
                OverrideFont->grab();
 
-       breakText();
+       updateText();
 }
 
 //! Gets the override font (if any)
@@ -224,16 +176,15 @@ IGUIFont* StaticText::getActiveFont() const
 //! Sets another color for the text.
 void StaticText::setOverrideColor(video::SColor color)
 {
-       OverrideColor = color;
-       OverrideColorEnabled = true;
+       ColoredText.setDefaultColor(color);
+       updateText();
 }
 
 
 //! Sets another color for the text.
 void StaticText::setBackgroundColor(video::SColor color)
 {
-       BGColor = color;
-       OverrideBGColorEnabled = true;
+       ColoredText.setBackground(color);
        Background = true;
 }
 
@@ -248,7 +199,10 @@ void StaticText::setDrawBackground(bool draw)
 //! Gets the background color
 video::SColor StaticText::getBackgroundColor() const
 {
-       return BGColor;
+       IGUISkin *skin = Environment->getSkin();
+
+       return (ColoredText.hasBackground() || !skin) ?
+               ColoredText.getBackground() : skin->getColor(gui::EGDC_3D_FACE);
 }
 
 
@@ -298,7 +252,7 @@ const video::SColor& StaticText::getOverrideColor() const
 video::SColor StaticText::getOverrideColor() const
 #endif
 {
-       return OverrideColor;
+       return ColoredText.getDefaultColor();
 }
 
 
@@ -306,13 +260,13 @@ video::SColor StaticText::getOverrideColor() const
 //! color in the gui skin.
 void StaticText::enableOverrideColor(bool enable)
 {
-       OverrideColorEnabled = enable;
+       // TODO
 }
 
 
 bool StaticText::isOverrideColorEnabled() const
 {
-       return OverrideColorEnabled;
+       return true;
 }
 
 
@@ -321,7 +275,7 @@ bool StaticText::isOverrideColorEnabled() const
 void StaticText::setWordWrap(bool enable)
 {
        WordWrap = enable;
-       breakText();
+       updateText();
 }
 
 
@@ -336,7 +290,7 @@ void StaticText::setRightToLeft(bool rtl)
        if (RightToLeft != rtl)
        {
                RightToLeft = rtl;
-               breakText();
+               updateText();
        }
 }
 
@@ -348,12 +302,22 @@ bool StaticText::isRightToLeft() const
 
 
 //! Breaks the single text line.
-void StaticText::breakText()
+// Updates the font colors
+void StaticText::updateText()
 {
-       if (!WordWrap)
+       const EnrichedString &cText = ColoredText;
+       BrokenText.clear();
+
+       if (cText.hasBackground()) {
+               setBackgroundColor(cText.getBackground());
+       }
+
+       if (!WordWrap) {
+               BrokenText.push_back(cText);
                return;
+       }
 
-       BrokenText.clear();
+       // Update word wrap
 
        IGUISkin* skin = Environment->getSkin();
        IGUIFont* font = getActiveFont();
@@ -574,25 +538,20 @@ void StaticText::breakText()
 //! Sets the new caption of this element.
 void StaticText::setText(const wchar_t* text)
 {
-       setText(EnrichedString(text));
+       setText(EnrichedString(text, getOverrideColor()));
 }
 
-//! Sets the new caption of this element.
 void StaticText::setText(const EnrichedString &text)
 {
-       IGUIElement::setText(text.c_str());
-       cText = text;
-       if (text.hasBackground()) {
-               setBackgroundColor(text.getBackground());
-       }
-       breakText();
+       ColoredText = text;
+       IGUIElement::setText(ColoredText.c_str());
+       updateText();
 }
 
-
 void StaticText::updateAbsolutePosition()
 {
        IGUIElement::updateAbsolutePosition();
-       breakText();
+       updateText();
 }
 
 
@@ -603,39 +562,31 @@ s32 StaticText::getTextHeight() const
        if (!font)
                return 0;
 
-       s32 height = font->getDimension(L"A").Height + font->getKerningHeight();
-
-       if (WordWrap)
-               height *= BrokenText.size();
-
-       return height;
+       if (WordWrap) {
+               s32 height = font->getDimension(L"A").Height + font->getKerningHeight();
+               return height * BrokenText.size();
+       }
+       // There may be intentional new lines without WordWrap
+       return font->getDimension(BrokenText[0].c_str()).Height;
 }
 
 
 s32 StaticText::getTextWidth() const
 {
-       IGUIFont * font = getActiveFont();
-       if(!font)
+       IGUIFont *font = getActiveFont();
+       if (!font)
                return 0;
 
-       if(WordWrap)
-       {
-               s32 widest = 0;
+       s32 widest = 0;
 
-               for(u32 line = 0; line < BrokenText.size(); ++line)
-               {
-                       s32 width = font->getDimension(BrokenText[line].c_str()).Width;
-
-                       if(width > widest)
-                               widest = width;
-               }
+       for (const EnrichedString &line : BrokenText) {
+               s32 width = font->getDimension(line.c_str()).Width;
 
-               return widest;
-       }
-       else
-       {
-               return font->getDimension(cText.c_str()).Width;
+               if (width > widest)
+                       widest = width;
        }
+
+       return widest;
 }
 
 
@@ -647,14 +598,14 @@ void StaticText::serializeAttributes(io::IAttributes* out, io::SAttributeReadWri
        IGUIStaticText::serializeAttributes(out,options);
 
        out->addBool    ("Border",              Border);
-       out->addBool    ("OverrideColorEnabled",OverrideColorEnabled);
-       out->addBool    ("OverrideBGColorEnabled",OverrideBGColorEnabled);
+       out->addBool    ("OverrideColorEnabled",true);
+       out->addBool    ("OverrideBGColorEnabled",ColoredText.hasBackground());
        out->addBool    ("WordWrap",            WordWrap);
        out->addBool    ("Background",          Background);
        out->addBool    ("RightToLeft",         RightToLeft);
        out->addBool    ("RestrainTextInside",  RestrainTextInside);
-       out->addColor   ("OverrideColor",       OverrideColor);
-       out->addColor   ("BGColor",             BGColor);
+       out->addColor   ("OverrideColor",       ColoredText.getDefaultColor());
+       out->addColor   ("BGColor",             ColoredText.getBackground());
        out->addEnum    ("HTextAlign",          HAlign, GUIAlignmentNames);
        out->addEnum    ("VTextAlign",          VAlign, GUIAlignmentNames);
 
@@ -668,14 +619,14 @@ void StaticText::deserializeAttributes(io::IAttributes* in, io::SAttributeReadWr
        IGUIStaticText::deserializeAttributes(in,options);
 
        Border = in->getAttributeAsBool("Border");
-       enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled"));
-       OverrideBGColorEnabled = in->getAttributeAsBool("OverrideBGColorEnabled");
        setWordWrap(in->getAttributeAsBool("WordWrap"));
        Background = in->getAttributeAsBool("Background");
        RightToLeft = in->getAttributeAsBool("RightToLeft");
        RestrainTextInside = in->getAttributeAsBool("RestrainTextInside");
-       OverrideColor = in->getAttributeAsColor("OverrideColor");
-       BGColor = in->getAttributeAsColor("BGColor");
+       if (in->getAttributeAsBool("OverrideColorEnabled"))
+               ColoredText.setDefaultColor(in->getAttributeAsColor("OverrideColor"));
+       if (in->getAttributeAsBool("OverrideBGColorEnabled"))
+               ColoredText.setBackground(in->getAttributeAsColor("BGColor"));
 
        setTextAlignment( (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("HTextAlign", GUIAlignmentNames),
                       (EGUI_ALIGNMENT) in->getAttributeAsEnumeration("VTextAlign", GUIAlignmentNames));
index 43c5872845e61bd75bf02cb88fab2afb951145e9..1f111ea56e810d7e68dd75938efc29e088a21391 100644 (file)
@@ -34,7 +34,8 @@ namespace gui
        {
        public:
 
-               //! constructor
+               // StaticText is translated by EnrichedString.
+               // No need to use translate_string()
                StaticText(const EnrichedString &text, bool border, IGUIEnvironment* environment,
                        IGUIElement* parent, s32 id, const core::rect<s32>& rectangle,
                        bool background = false);
@@ -201,23 +202,20 @@ namespace gui
        private:
 
                //! Breaks the single text line.
-               void breakText();
+               void updateText();
 
                EGUI_ALIGNMENT HAlign, VAlign;
                bool Border;
-               bool OverrideColorEnabled;
-               bool OverrideBGColorEnabled;
                bool WordWrap;
                bool Background;
                bool RestrainTextInside;
                bool RightToLeft;
 
-               video::SColor OverrideColor, BGColor;
                gui::IGUIFont* OverrideFont;
                gui::IGUIFont* LastBreakFont; // stored because: if skin changes, line break must be recalculated.
 
-               EnrichedString cText;
-               core::array< EnrichedString > BrokenText;
+               EnrichedString ColoredText;
+               std::vector<EnrichedString> BrokenText;
        };
 
 
@@ -274,10 +272,7 @@ inline void setStaticText(irr::gui::IGUIStaticText *static_text, const EnrichedS
 
 inline void setStaticText(irr::gui::IGUIStaticText *static_text, const wchar_t *text)
 {
-       auto color = static_text->isOverrideColorEnabled()
-                                    ? static_text->getOverrideColor()
-                                    : irr::video::SColor(255, 255, 255, 255);
-       setStaticText(static_text, EnrichedString(text, color));
+       setStaticText(static_text, EnrichedString(text, static_text->getOverrideColor()));
 }
 
 #endif // _IRR_COMPILE_WITH_GUI_
index 8e8958d18e159776dcd3ef3ce188284e42f1964b..447b591e18b587f1d4606dc8da8eab44036d76a0 100644 (file)
@@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "test.h"
 
 #include <cmath>
+#include "util/enriched_string.h"
 #include "util/numeric.h"
 #include "util/string.h"
 
@@ -49,6 +50,7 @@ public:
        void testUTF8();
        void testRemoveEscapes();
        void testWrapRows();
+       void testEnrichedString();
        void testIsNumber();
        void testIsPowerOfTwo();
        void testMyround();
@@ -79,6 +81,7 @@ void TestUtilities::runTests(IGameDef *gamedef)
        TEST(testUTF8);
        TEST(testRemoveEscapes);
        TEST(testWrapRows);
+       TEST(testEnrichedString);
        TEST(testIsNumber);
        TEST(testIsPowerOfTwo);
        TEST(testMyround);
@@ -344,6 +347,23 @@ void TestUtilities::testWrapRows()
        }
 }
 
+void TestUtilities::testEnrichedString()
+{
+       EnrichedString str(L"Test bar");
+       irr::video::SColor color(0xFF, 0, 0, 0xFF);
+
+       UASSERT(str.substr(1, 3).getString() == L"est");
+       str += L" BUZZ";
+       UASSERT(str.substr(9, std::string::npos).getString() == L"BUZZ");
+       str.setDefaultColor(color); // Blue foreground
+       UASSERT(str.getColors()[5] == color);
+       // Green background, then white and yellow text
+       str = L"\x1b(b@#0F0)Regular \x1b(c@#FF0)yellow";
+       UASSERT(str.getColors()[2] == 0xFFFFFFFF);
+       str.setDefaultColor(color); // Blue foreground
+       UASSERT(str.getColors()[13] == 0xFFFFFF00); // Still yellow text
+       UASSERT(str.getBackground() == 0xFF00FF00); // Green background
+}
 
 void TestUtilities::testIsNumber()
 {
index 642188a52bcd4db3d631147187d536ba773395be..d5f8aa66103b782088dc80816339e7a251081808 100644 (file)
@@ -45,15 +45,27 @@ EnrichedString::EnrichedString(const wchar_t *str, const SColor &color)
        addAtEnd(translate_string(std::wstring(str)), color);
 }
 
+void EnrichedString::clear()
+{
+       m_string.clear();
+       m_colors.clear();
+       m_has_background = false;
+       m_default_length = 0;
+       m_default_color = irr::video::SColor(255, 255, 255, 255);
+}
+
 void EnrichedString::operator=(const wchar_t *str)
 {
        clear();
-       addAtEnd(translate_string(std::wstring(str)), SColor(255, 255, 255, 255));
+       addAtEnd(translate_string(std::wstring(str)), m_default_color);
 }
 
 void EnrichedString::addAtEnd(const std::wstring &s, const SColor &initial_color)
 {
        SColor color(initial_color);
+       bool use_default = (m_default_length == m_string.size() &&
+               color == m_default_color);
+
        size_t i = 0;
        while (i < s.length()) {
                if (s[i] != L'\x1b') {
@@ -90,6 +102,12 @@ void EnrichedString::addAtEnd(const std::wstring &s, const SColor &initial_color
                                continue;
                        }
                        parseColorString(wide_to_utf8(parts[1]), color, true);
+
+                       // No longer use default color after first escape
+                       if (use_default) {
+                               m_default_length = m_string.size();
+                               use_default = false;
+                       }
                } else if (parts[0] == L"b") {
                        if (parts.size() < 2) {
                                continue;
@@ -98,6 +116,10 @@ void EnrichedString::addAtEnd(const std::wstring &s, const SColor &initial_color
                        m_has_background = true;
                }
        }
+
+       // Update if no escape character was found
+       if (use_default)
+               m_default_length = m_string.size();
 }
 
 void EnrichedString::addChar(const EnrichedString &source, size_t i)
@@ -110,7 +132,7 @@ void EnrichedString::addCharNoColor(wchar_t c)
 {
        m_string += c;
        if (m_colors.empty()) {
-               m_colors.emplace_back(255, 255, 255, 255);
+               m_colors.emplace_back(m_default_color);
        } else {
                m_colors.push_back(m_colors[m_colors.size() - 1]);
        }
@@ -118,35 +140,40 @@ void EnrichedString::addCharNoColor(wchar_t c)
 
 EnrichedString EnrichedString::operator+(const EnrichedString &other) const
 {
-       std::vector<SColor> result;
-       result.insert(result.end(), m_colors.begin(), m_colors.end());
-       result.insert(result.end(), other.m_colors.begin(), other.m_colors.end());
-       return EnrichedString(m_string + other.m_string, result);
+       EnrichedString result = *this;
+       result += other;
+       return result;
 }
 
 void EnrichedString::operator+=(const EnrichedString &other)
 {
+       bool update_default_color = m_default_length == m_string.size();
+
        m_string += other.m_string;
        m_colors.insert(m_colors.end(), other.m_colors.begin(), other.m_colors.end());
+
+       if (update_default_color) {
+               m_default_length += other.m_default_length;
+               updateDefaultColor();
+       }
 }
 
 EnrichedString EnrichedString::substr(size_t pos, size_t len) const
 {
-       if (pos == m_string.length()) {
+       if (pos >= m_string.length())
                return EnrichedString();
-       }
-       if (len == std::string::npos || pos + len > m_string.length()) {
-               return EnrichedString(
-                       m_string.substr(pos, std::string::npos),
-                       std::vector<SColor>(m_colors.begin() + pos, m_colors.end())
-               );
-       }
 
-       return EnrichedString(
+       if (len == std::string::npos || pos + len > m_string.length())
+               len = m_string.length() - pos;
+
+       EnrichedString str(
                m_string.substr(pos, len),
                std::vector<SColor>(m_colors.begin() + pos, m_colors.begin() + pos + len)
        );
-
+       if (pos < m_default_length)
+               str.m_default_length = m_default_length - pos;
+       str.setDefaultColor(m_default_color);
+       return str;
 }
 
 const wchar_t *EnrichedString::c_str() const
@@ -163,3 +190,15 @@ const std::wstring &EnrichedString::getString() const
 {
        return m_string;
 }
+
+void EnrichedString::setDefaultColor(const irr::video::SColor &color)
+{
+       m_default_color = color;
+       updateDefaultColor();
+}
+
+void EnrichedString::updateDefaultColor()
+{
+       for (size_t i = 0; i < m_default_length; ++i)
+               m_colors[i] = m_default_color;
+}
index 202d84cb07c5ea117017f4217bcae3b55d3c8546..eaab3bd917326fef193b380f051cb598edbba0aa 100644 (file)
@@ -32,6 +32,7 @@ public:
                const irr::video::SColor &color = irr::video::SColor(255, 255, 255, 255));
        EnrichedString(const std::wstring &string,
                const std::vector<irr::video::SColor> &colors);
+       void clear();
        void operator=(const wchar_t *str);
        void addAtEnd(const std::wstring &s, const irr::video::SColor &color);
 
@@ -50,6 +51,14 @@ public:
        const wchar_t *c_str() const;
        const std::vector<irr::video::SColor> &getColors() const;
        const std::wstring &getString() const;
+
+       void setDefaultColor(const irr::video::SColor &color);
+       void updateDefaultColor();
+       inline const irr::video::SColor &getDefaultColor() const
+       {
+               return m_default_color;
+       }
+
        inline bool operator==(const EnrichedString &other) const
        {
                return (m_string == other.m_string && m_colors == other.m_colors);
@@ -58,12 +67,6 @@ public:
        {
                return !(*this == other);
        }
-       inline void clear()
-       {
-               m_string.clear();
-               m_colors.clear();
-               m_has_background = false;
-       }
        inline bool empty() const
        {
                return m_string.empty();
@@ -72,6 +75,7 @@ public:
        {
                return m_string.size();
        }
+
        inline bool hasBackground() const
        {
                return m_has_background;
@@ -80,9 +84,19 @@ public:
        {
                return m_background;
        }
+       inline void setBackground(const irr::video::SColor &color)
+       {
+               m_background = color;
+               m_has_background = true;
+       }
+
 private:
        std::wstring m_string;
        std::vector<irr::video::SColor> m_colors;
-       bool m_has_background = false;
+       bool m_has_background;
+       irr::video::SColor m_default_color;
        irr::video::SColor m_background;
+       // This variable defines the length of the default-colored text.
+       // Change this to a std::vector if an "end coloring" tag is wanted.
+       size_t m_default_length;
 };