diff --git a/Global.h b/Global.h index 675a3eff4..2eb54db45 100644 --- a/Global.h +++ b/Global.h @@ -122,6 +122,7 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size."); #include #include #include +#include #include #include #include @@ -147,6 +148,7 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size."); #define BOOST_ASIO_ENABLE_OLD_SERVICES #endif +#include #include #include #include diff --git a/client/render/Colors.cpp b/client/render/Colors.cpp index c7f5c1f4b..0c239e879 100644 --- a/client/render/Colors.cpp +++ b/client/render/Colors.cpp @@ -10,6 +10,7 @@ #include "StdInc.h" #include "Colors.h" +#include "../../lib/JsonNode.h" const ColorRGBA Colors::YELLOW = { 229, 215, 123, ColorRGBA::ALPHA_OPAQUE }; const ColorRGBA Colors::WHITE = { 255, 243, 222, ColorRGBA::ALPHA_OPAQUE }; @@ -23,3 +24,29 @@ const ColorRGBA Colors::RED = {255, 0, 0, ColorRGBA::ALPHA_OPAQUE}; const ColorRGBA Colors::PURPLE = {255, 75, 125, ColorRGBA::ALPHA_OPAQUE}; const ColorRGBA Colors::BLACK = {0, 0, 0, ColorRGBA::ALPHA_OPAQUE}; const ColorRGBA Colors::TRANSPARENCY = {0, 0, 0, ColorRGBA::ALPHA_TRANSPARENT}; + +std::optional Colors::parseColor(std::string text) +{ + std::smatch match; + std::regex expr("^#([0-9a-fA-F]{6})$"); + ui8 rgb[3] = {0, 0, 0}; + if(std::regex_search(text, match, expr)) + { + std::string tmp = boost::algorithm::unhex(match[1].str()); + std::copy(tmp.begin(), tmp.end(), rgb); + return ColorRGBA(rgb[0], rgb[1], rgb[2]); + } + + static const JsonNode config(JsonPath::builtin("CONFIG/textColors")); + auto colors = config["colors"].Struct(); + for(auto & color : colors) { + if(boost::algorithm::to_lower_copy(color.first) == boost::algorithm::to_lower_copy(text)) + { + std::string tmp = boost::algorithm::unhex(color.second.String()); + std::copy(tmp.begin(), tmp.end(), rgb); + return ColorRGBA(rgb[0], rgb[1], rgb[2]); + } + } + + return std::nullopt; +} \ No newline at end of file diff --git a/client/render/Colors.h b/client/render/Colors.h index 5db94e4ed..a73b5a856 100644 --- a/client/render/Colors.h +++ b/client/render/Colors.h @@ -49,4 +49,7 @@ public: static const ColorRGBA BLACK; static const ColorRGBA TRANSPARENCY; + + /// parse color + static std::optional parseColor(std::string text); }; diff --git a/client/widgets/TextControls.cpp b/client/widgets/TextControls.cpp index e1d1e5a84..e6cce7c1a 100644 --- a/client/widgets/TextControls.cpp +++ b/client/widgets/TextControls.cpp @@ -153,6 +153,17 @@ void CTextContainer::blitLine(Canvas & to, Rect destRect, std::string what) //We should count delimiters length from string to correct centering later. delimitersCount *= f->getStringWidth(delimeters)/2; + std::smatch match; + std::regex expr("\\{(.*?)\\|"); + std::string::const_iterator searchStart( what.cbegin() ); + while(std::regex_search(searchStart, what.cend(), match, expr)) + { + std::string colorText = match[1].str(); + if(auto c = Colors::parseColor(colorText)) + delimitersCount += f->getStringWidth(colorText + "|"); + searchStart = match.suffix().first; + } + // input is rect in which given text should be placed // calculate proper position for top-left corner of the text if(alignment == ETextAlignment::TOPLEFT) @@ -189,8 +200,25 @@ void CTextContainer::blitLine(Canvas & to, Rect destRect, std::string what) { std::string toPrint = what.substr(begin, end - begin); - if(currDelimeter % 2) // Enclosed in {} text - set to yellow - to.drawText(where, font, Colors::YELLOW, ETextAlignment::TOPLEFT, toPrint); + if(currDelimeter % 2) // Enclosed in {} text - set to yellow or defined color + { + std::smatch match; + std::regex expr("^(.*?)\\|"); + if(std::regex_search(toPrint, match, expr)) + { + std::string colorText = match[1].str(); + + if(auto color = Colors::parseColor(colorText)) + { + toPrint = toPrint.substr(colorText.length() + 1, toPrint.length() - colorText.length()); + to.drawText(where, font, *color, ETextAlignment::TOPLEFT, toPrint); + } + else + to.drawText(where, font, Colors::YELLOW, ETextAlignment::TOPLEFT, toPrint); + } + else + to.drawText(where, font, Colors::YELLOW, ETextAlignment::TOPLEFT, toPrint); + } else // Non-enclosed text, use default color to.drawText(where, font, color, ETextAlignment::TOPLEFT, toPrint); diff --git a/client/windows/CMessage.cpp b/client/windows/CMessage.cpp index d1c2bce2c..ce508fa66 100644 --- a/client/windows/CMessage.cpp +++ b/client/windows/CMessage.cpp @@ -131,6 +131,7 @@ std::vector CMessage::breakText( std::string text, size_t maxLineWi ui32 wordBreak = -1; //last position for line break (last space character) ui32 currPos = 0; //current position in text bool opened = false; //set to true when opening brace is found + std::string color = ""; //color found size_t symbolSize = 0; // width of character, in bytes size_t glyphWidth = 0; // width of printable glyph, pixels @@ -147,9 +148,27 @@ std::vector CMessage::breakText( std::string text, size_t maxLineWi /* We don't count braces in string length. */ if (text[currPos] == '{') + { opened=true; + + std::smatch match; + std::regex expr("^\\{(.*?)\\|"); + std::string tmp = text.substr(currPos); + if(std::regex_search(tmp, match, expr)) + { + std::string colorText = match[1].str(); + if(auto c = Colors::parseColor(colorText)) + { + color = colorText + "|"; + currPos += colorText.length() + 1; + } + } + } else if (text[currPos]=='}') + { opened=false; + color = ""; + } else lineWidth += (ui32)glyphWidth; currPos += (ui32)symbolSize; @@ -196,7 +215,7 @@ std::vector CMessage::breakText( std::string text, size_t maxLineWi { /* Add an opening brace for the next line. */ if (text.length() != 0) - text.insert(0, "{"); + text.insert(0, "{" + color); } } diff --git a/client/windows/CMessage.h b/client/windows/CMessage.h index 22fae44b5..430c87a5b 100644 --- a/client/windows/CMessage.h +++ b/client/windows/CMessage.h @@ -16,6 +16,10 @@ struct SDL_Surface; class CInfoWindow; class CComponent; +VCMI_LIB_NAMESPACE_BEGIN +class ColorRGBA; +VCMI_LIB_NAMESPACE_END + /// Class which draws formatted text messages and generates chat windows class CMessage diff --git a/config/textColors.json b/config/textColors.json new file mode 100644 index 000000000..6873a64ab --- /dev/null +++ b/config/textColors.json @@ -0,0 +1,171 @@ +{ + "colors": { + "r": "F80000", + "g": "00FC00", + "b": "0000F8", + "y": "F8FC00", + "o": "F88000", + "p": "F800F8", + "c": "00FCF8", + "k": "000000", + "w": "FFFFFF", + "Regular": "F8F0D8", + "Highlight": "E8D478", + "H3Red": "F80000", + "H3Cyan": "00FCF8", + "H3Green": "00FC00", + "H3Blue": "0000F8", + "H3Yellow": "F8FC00", + "H3Orange": "F88000", + "H3Purple": "F800F8", + "H3Pink": "C07888", + "Black": "000000", + "Navy": "000080", + "DarkBlue": "00008B", + "MediumBlue": "0000CD", + "Blue": "0000FF", + "DarkGreen": "006400", + "Green": "008000", + "Teal": "008080", + "DarkCyan": "008B8B", + "DeepSkyBlue": "00BFFF", + "DarkTurquoise": "00CED1", + "MediumSpringGreen": "00FA9A", + "Lime": "00FF00", + "SpringGreen": "00FF7F", + "Aqua": "00FFFF", + "Cyan": "00FFFF", + "MidnightBlue": "191970", + "DodgerBlue": "1E90FF", + "LightSeaGreen": "20B2AA", + "ForestGreen": "228B22", + "SeaGreen": "2E8B57", + "DarkSlateGray": "2F4F4F", + "DarkSlateGrey": "2F4F4F", + "LimeGreen": "32CD32", + "MediumSeaGreen": "3CB371", + "Turquoise": "40E0D0", + "RoyalBlue": "4169E1", + "SteelBlue": "4682B4", + "DarkSlateBlue": "483D8B", + "MediumTurquoise": "48D1CC", + "Indigo": "4B0082", + "DarkOliveGreen": "556B2F", + "CadetBlue": "5F9EA0", + "CornflowerBlue": "6495ED", + "RebeccaPurple": "663399", + "MediumAquaMarine": "66CDAA", + "DimGray": "696969", + "DimGrey": "696969", + "SlateBlue": "6A5ACD", + "OliveDrab": "6B8E23", + "SlateGray": "708090", + "SlateGrey": "708090", + "LightSlateGray": "778899", + "LightSlateGrey": "778899", + "MediumSlateBlue": "7B68EE", + "LawnGreen": "7CFC00", + "Chartreuse": "7FFF00", + "Aquamarine": "7FFFD4", + "Maroon": "800000", + "Purple": "800080", + "Olive": "808000", + "Gray": "808080", + "Grey": "808080", + "SkyBlue": "87CEEB", + "LightSkyBlue": "87CEFA", + "BlueViolet": "8A2BE2", + "DarkRed": "8B0000", + "DarkMagenta": "8B008B", + "SaddleBrown": "8B4513", + "DarkSeaGreen": "8FBC8F", + "LightGreen": "90EE90", + "MediumPurple": "9370DB", + "DarkViolet": "9400D3", + "PaleGreen": "98FB98", + "DarkOrchid": "9932CC", + "YellowGreen": "9ACD32", + "Sienna": "A0522D", + "Brown": "A52A2A", + "DarkGray": "A9A9A9", + "DarkGrey": "A9A9A9", + "LightBlue": "ADD8E6", + "GreenYellow": "ADFF2F", + "PaleTurquoise": "AFEEEE", + "LightSteelBlue": "B0C4DE", + "PowderBlue": "B0E0E6", + "FireBrick": "B22222", + "DarkGoldenRod": "B8860B", + "MediumOrchid": "BA55D3", + "RosyBrown": "BC8F8F", + "DarkKhaki": "BDB76B", + "Silver": "C0C0C0", + "MediumVioletRed": "C71585", + "IndianRed": "CD5C5C", + "Peru": "CD853F", + "Chocolate": "D2691E", + "Tan": "D2B48C", + "LightGray": "D3D3D3", + "LightGrey": "D3D3D3", + "Thistle": "D8BFD8", + "Orchid": "DA70D6", + "GoldenRod": "DAA520", + "PaleVioletRed": "DB7093", + "Crimson": "DC143C", + "Gainsboro": "DCDCDC", + "Plum": "DDA0DD", + "BurlyWood": "DEB887", + "LightCyan": "E0FFFF", + "Lavender": "E6E6FA", + "DarkSalmon": "E9967A", + "Violet": "EE82EE", + "PaleGoldenRod": "EEE8AA", + "LightCoral": "F08080", + "Khaki": "F0E68C", + "AliceBlue": "F0F8FF", + "HoneyDew": "F0FFF0", + "Azure": "F0FFFF", + "SandyBrown": "F4A460", + "Wheat": "F5DEB3", + "Beige": "F5F5DC", + "WhiteSmoke": "F5F5F5", + "MintCream": "F5FFFA", + "GhostWhite": "F8F8FF", + "Salmon": "FA8072", + "AntiqueWhite": "FAEBD7", + "Linen": "FAF0E6", + "LightGoldenRodYellow": "FAFAD2", + "OldLace": "FDF5E6", + "Red": "FF0000", + "Fuchsia": "FF00FF", + "Magenta": "FF00FF", + "DeepPink": "FF1493", + "OrangeRed": "FF4500", + "Tomato": "FF6347", + "HotPink": "FF69B4", + "Coral": "FF7F50", + "DarkOrange": "FF8C00", + "LightSalmon": "FFA07A", + "Orange": "FFA500", + "LightPink": "FFB6C1", + "Pink": "FFC0CB", + "Gold": "FFD700", + "PeachPuff": "FFDAB9", + "NavajoWhite": "FFDEAD", + "Moccasin": "FFE4B5", + "Bisque": "FFE4C4", + "MistyRose": "FFE4E1", + "BlanchedAlmond": "FFEBCD", + "PapayaWhip": "FFEFD5", + "LavenderBlush": "FFF0F5", + "SeaShell": "FFF5EE", + "Cornsilk": "FFF8DC", + "LemonChiffon": "FFFACD", + "FloralWhite": "FFFAF0", + "Snow": "FFFAFA", + "Yellow": "FFFF00", + "LightYellow": "FFFFE0", + "Ivory": "FFFFF0", + "White": "FFFFFF" + } +} diff --git a/docs/players/Game_Mechanics.md b/docs/players/Game_Mechanics.md index 5291c267c..d360acb86 100644 --- a/docs/players/Game_Mechanics.md +++ b/docs/players/Game_Mechanics.md @@ -45,6 +45,31 @@ Some of H3 mechanics can't be straight considered as bug, but default VCMI behav Mouse click on castle icon in the town screen open quick recruitment window, where we can purhase in fast way units. +## Color support + +Additional color are supported for text fields (e.g. map description). Uses HTML color syntax (e.g. #abcdef) / HTML predefined colors (e.g. green). + +##### Original Heroes III Support + +`This is white` + +This is white + +`{This is yellow}` + +This is yellow + +##### New + +`{#ff0000|This is red}` + +This is red + +`{green|This is green}` + +This is green + + # Manuals and guides -- https://heroes.thelazy.net//index.php/Main_Page Wiki that aims to be a complete reference to Heroes of Might and Magic III. +- https://heroes.thelazy.net/index.php/Main_Page Wiki that aims to be a complete reference to Heroes of Might and Magic III.