├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── RichText.cpp ├── RichText.hpp └── demo ├── demo.pro └── main.cpp /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | [Dd]ebug/ 46 | [Rr]elease/ 47 | *_i.c 48 | *_p.c 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.vspscc 63 | .builds 64 | *.dotCover 65 | 66 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 67 | #packages/ 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | 76 | # Visual Studio profiler 77 | *.psess 78 | *.vsp 79 | 80 | # ReSharper is a .NET coding add-in 81 | _ReSharper* 82 | 83 | # Installshield output folder 84 | [Ee]xpress 85 | 86 | # DocProject is a documentation generator add-in 87 | DocProject/buildhelp/ 88 | DocProject/Help/*.HxT 89 | DocProject/Help/*.HxC 90 | DocProject/Help/*.hhc 91 | DocProject/Help/*.hhk 92 | DocProject/Help/*.hhp 93 | DocProject/Help/Html2 94 | DocProject/Help/html 95 | 96 | # Click-Once directory 97 | publish 98 | 99 | # Others 100 | [Bb]in 101 | [Oo]bj 102 | sql 103 | TestResults 104 | *.Cache 105 | ClientBin 106 | stylecop.* 107 | ~$* 108 | *.dbmdl 109 | Generated_Code #added for RIA/Silverlight projects 110 | 111 | # Backup & report files from converting an old project file to a newer 112 | # Visual Studio version. Backup files are not needed, because we have git ;-) 113 | _UpgradeReport_Files/ 114 | Backup*/ 115 | UpgradeLog*.XML 116 | 117 | 118 | 119 | ############ 120 | ## Windows 121 | ############ 122 | 123 | # Windows image file caches 124 | Thumbs.db 125 | 126 | # Folder config file 127 | Desktop.ini 128 | 129 | 130 | ############# 131 | ## Python 132 | ############# 133 | 134 | *.py[co] 135 | 136 | # Packages 137 | *.egg 138 | *.egg-info 139 | dist 140 | build 141 | eggs 142 | parts 143 | bin 144 | var 145 | sdist 146 | develop-eggs 147 | .installed.cfg 148 | 149 | # Installer logs 150 | pip-log.txt 151 | 152 | # Unit test / coverage reports 153 | .coverage 154 | .tox 155 | 156 | #Translations 157 | *.mo 158 | 159 | #Mr Developer 160 | .mr.developer.cfg 161 | 162 | # Mac crap 163 | .DS_Store 164 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is available under 2 licenses -- choose whichever you prefer. 2 | 3 | ALTERNATIVE A - MIT License 4 | Copyright (c) 2017 Cristian Pallarés 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | 21 | ALTERNATIVE B - Public Domain (www.unlicense.org) 22 | This is free and unencumbered software released into the public domain. 23 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 24 | software, either in source code form or as a compiled binary, for any purpose, 25 | commercial or non-commercial, and by any means. 26 | In jurisdictions that recognize copyright laws, the author or authors of this 27 | software dedicate any and all copyright interest in the software to the public 28 | domain. We make this dedication for the benefit of the public at large and to 29 | the detriment of our heirs and successors. We intend this dedication to be an 30 | overt act of relinquishment in perpetuity of all present and future rights to 31 | this software under copyright law. 32 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 33 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 34 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 35 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 36 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 37 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RichText 2 | 3 | Rich text class for [SFML 2](https://github.com/SFML/SFML/). Allows the 4 | user to draw lines of text with different styles and colors. 5 | 6 | ## Authors 7 | 8 | * [Cristian Pallarés](https://github.com/Skyrpex/) - Original code 9 | * [Lukas Dürrenberger](https://github.com/eXpl0it3r/) - Conversion to the new SFML 2 API 10 | 11 | ## How to use 12 | 13 | 1. Include the header and the source to your project. 14 | 2. Link to SFML 2.4.x. 15 | 3. Use a C++11 ready compiler. 16 | 17 | ## Support branches 18 | 19 | **Notice:** There's no guarantee that these branches are fully updated. 20 | 21 | * For a non C++11 ready compilers, there is a [support branch](https://github.com/Skyrpex/RichText/tree/support/no-c%2B%2B11). 22 | * For earlier SFML versions than 2.4.x, see the [support branch](https://github.com/Skyrpex/RichText/tree/support/pre-sfml-2.4). 23 | 24 | ## Repository 25 | 26 | You can get the current development version from the [git repository](https://github.com/Skyrpex/RichText). 27 | 28 | ## Example 29 | 30 | ```cpp 31 | #include 32 | #include "RichText.hpp" 33 | 34 | int main() 35 | { 36 | sf::RenderWindow window(sf::VideoMode(800, 600), "sfe::RichText"); 37 | window.setFramerateLimit(30); 38 | 39 | sf::Font font; 40 | font.loadFromFile("FreeMono.ttf"); 41 | 42 | sfe::RichText text(font); 43 | text << sf::Text::Bold << sf::Color::Cyan << "This " 44 | << sf::Text::Italic << sf::Color::White << "is\nan\n" 45 | << sf::Text::Regular << sf::Color::Green << "example" 46 | << sf::Color::White << ".\n" 47 | << sf::Text::Underlined << "It looks good!"; 48 | 49 | text.setCharacterSize(25); 50 | text.setPosition(400, 300); 51 | text.setOrigin(text.getGlobalBounds().width / 2.f, text.getGlobalBounds().height / 2.f); 52 | 53 | while (window.isOpen()) 54 | { 55 | sf::Event event; 56 | while (window.pollEvent(event)) 57 | { 58 | if (event.type == sf::Event::Closed) 59 | window.close(); 60 | } 61 | 62 | window.clear(); 63 | window.draw(text); 64 | window.display(); 65 | } 66 | } 67 | ``` -------------------------------------------------------------------------------- /RichText.cpp: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // Headers 3 | //////////////////////////////////////////////////////////////////////////////// 4 | #include 5 | #include 6 | 7 | #include "RichText.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | namespace sfe 16 | { 17 | 18 | //////////////////////////////////////////////////////////////////////////////// 19 | void RichText::Line::setCharacterColor(std::size_t pos, sf::Color color) 20 | { 21 | assert(pos < getLength()); 22 | isolateCharacter(pos); 23 | std::size_t stringToFormat = convertLinePosToLocal(pos); 24 | m_texts[stringToFormat].setFillColor(color); 25 | updateGeometry(); 26 | } 27 | 28 | //////////////////////////////////////////////////////////////////////////////// 29 | void RichText::Line::setCharacterStyle(std::size_t pos, sf::Text::Style style) 30 | { 31 | assert(pos < getLength()); 32 | isolateCharacter(pos); 33 | std::size_t stringToFormat = convertLinePosToLocal(pos); 34 | m_texts[stringToFormat].setStyle(style); 35 | updateGeometry(); 36 | } 37 | //////////////////////////////////////////////////////////////////////////////// 38 | void RichText::Line::setCharacter(std::size_t pos, sf::Uint32 character) 39 | { 40 | assert(pos < getLength()); 41 | sf::Text& text = m_texts[convertLinePosToLocal(pos)]; 42 | sf::String string = text.getString(); 43 | string[pos] = character; 44 | text.setString(string); 45 | updateGeometry(); 46 | } 47 | 48 | 49 | //////////////////////////////////////////////////////////////////////////////// 50 | void RichText::Line::setCharacterSize(unsigned int size) 51 | { 52 | for (sf::Text &text : m_texts) 53 | text.setCharacterSize(size); 54 | 55 | updateGeometry(); 56 | } 57 | 58 | 59 | //////////////////////////////////////////////////////////////////////////////// 60 | void RichText::Line::setFont(const sf::Font &font) 61 | { 62 | for (sf::Text &text : m_texts) 63 | text.setFont(font); 64 | 65 | updateGeometry(); 66 | } 67 | 68 | 69 | //////////////////////////////////////////////////////////////////////////////// 70 | std::size_t RichText::Line::getLength() const 71 | { 72 | std::size_t count = 0; 73 | for (sf::Text &text : m_texts) 74 | { 75 | count += text.getString().getSize(); 76 | } 77 | return count; 78 | } 79 | 80 | 81 | //////////////////////////////////////////////////////////////////////////////// 82 | sf::Color RichText::Line::getCharacterColor(std::size_t pos) const 83 | { 84 | assert(pos < getLength()); 85 | return m_texts[convertLinePosToLocal(pos)].getFillColor(); 86 | } 87 | 88 | 89 | //////////////////////////////////////////////////////////////////////////////// 90 | sf::Uint32 RichText::Line::getCharacterStyle(std::size_t pos) const 91 | { 92 | assert(pos < getLength()); 93 | return m_texts[convertLinePosToLocal(pos)].getStyle(); 94 | } 95 | 96 | 97 | //////////////////////////////////////////////////////////////////////////////// 98 | sf::Uint32 RichText::Line::getCharacter(std::size_t pos) const 99 | { 100 | assert(pos < getLength()); 101 | sf::Text& text = m_texts[convertLinePosToLocal(pos)]; 102 | return text.getString()[pos]; 103 | } 104 | 105 | 106 | //////////////////////////////////////////////////////////////////////////////// 107 | const std::vector &RichText::Line::getTexts() const 108 | { 109 | return m_texts; 110 | } 111 | 112 | //////////////////////////////////////////////////////////////////////////////// 113 | void RichText::Line::appendText(sf::Text text) 114 | { 115 | updateTextAndGeometry(text); 116 | m_texts.push_back(std::move(text)); 117 | } 118 | 119 | 120 | //////////////////////////////////////////////////////////////////////////////// 121 | sf::FloatRect RichText::Line::getLocalBounds() const 122 | { 123 | return m_bounds; 124 | } 125 | 126 | 127 | //////////////////////////////////////////////////////////////////////////////// 128 | sf::FloatRect RichText::Line::getGlobalBounds() const 129 | { 130 | return getTransform().transformRect(getLocalBounds()); 131 | } 132 | 133 | 134 | //////////////////////////////////////////////////////////////////////////////// 135 | void RichText::Line::draw(sf::RenderTarget &target, sf::RenderStates states) const 136 | { 137 | states.transform *= getTransform(); 138 | 139 | for (const sf::Text &text : m_texts) 140 | target.draw(text, states); 141 | } 142 | 143 | 144 | //////////////////////////////////////////////////////////////////////////////// 145 | std::size_t RichText::Line::convertLinePosToLocal(std::size_t& pos) const 146 | { 147 | assert(pos < getLength()); 148 | std::size_t arrayIndex = 0; 149 | for (; pos >= m_texts[arrayIndex].getString().getSize(); ++arrayIndex) 150 | { 151 | pos -= m_texts[arrayIndex].getString().getSize(); 152 | } 153 | return arrayIndex; 154 | } 155 | 156 | 157 | //////////////////////////////////////////////////////////////////////////////// 158 | void RichText::Line::isolateCharacter(std::size_t pos) 159 | { 160 | std::size_t localPos = pos; 161 | std::size_t index = convertLinePosToLocal(localPos); 162 | sf::Text temp = m_texts[index]; 163 | 164 | sf::String string = temp.getString(); 165 | if (string.getSize() == 1) 166 | return; 167 | 168 | m_texts.erase(m_texts.begin() + index); 169 | if (localPos != string.getSize() - 1) 170 | { 171 | temp.setString(string.substring(localPos+1)); 172 | m_texts.insert(m_texts.begin() + index, temp); 173 | } 174 | 175 | temp.setString(string.substring(localPos, 1)); 176 | m_texts.insert(m_texts.begin() + index, temp); 177 | 178 | if (localPos != 0) 179 | { 180 | temp.setString(string.substring(0, localPos)); 181 | m_texts.insert(m_texts.begin() + index, temp); 182 | } 183 | } 184 | 185 | 186 | //////////////////////////////////////////////////////////////////////////////// 187 | void RichText::Line::updateGeometry() const 188 | { 189 | m_bounds = sf::FloatRect(); 190 | 191 | for (sf::Text& text : m_texts) 192 | updateTextAndGeometry(text); 193 | } 194 | 195 | 196 | //////////////////////////////////////////////////////////////////////////////// 197 | void RichText::Line::updateTextAndGeometry(sf::Text& text) const 198 | { 199 | // Set text offset 200 | text.setPosition(m_bounds.width, 0.f); 201 | 202 | // Update bounds 203 | float lineSpacing = std::floor(text.getFont()->getLineSpacing(text.getCharacterSize())); 204 | m_bounds.height = std::max(m_bounds.height, lineSpacing); 205 | m_bounds.width += text.getGlobalBounds().width; 206 | } 207 | 208 | 209 | //////////////////////////////////////////////////////////////////////////////// 210 | RichText::RichText() 211 | : RichText(nullptr) 212 | { 213 | 214 | } 215 | 216 | 217 | //////////////////////////////////////////////////////////////////////////////// 218 | RichText::RichText(const sf::Font& font) 219 | : RichText(&font) 220 | { 221 | 222 | } 223 | 224 | //////////////////////////////////////////////////////////////////////////////// 225 | RichText& RichText::operator << (const TextStroke& stroke) 226 | { 227 | m_currentStroke = stroke; 228 | return *this; 229 | } 230 | 231 | RichText& RichText::operator << (const Outline& outline) 232 | { 233 | m_currentStroke.outline = outline.outline; 234 | m_currentStroke.thickness = outline.thickness; 235 | return *this; 236 | } 237 | 238 | //////////////////////////////////////////////////////////////////////////////// 239 | RichText & RichText::operator << (const sf::Color& color) 240 | { 241 | m_currentStroke.fill = color; 242 | return *this; 243 | } 244 | 245 | 246 | //////////////////////////////////////////////////////////////////////////////// 247 | RichText & RichText::operator << (sf::Text::Style style) 248 | { 249 | m_currentStyle = style; 250 | return *this; 251 | } 252 | 253 | 254 | //////////////////////////////////////////////////////////////////////////////// 255 | std::vector explode(const sf::String& string, sf::Uint32 delimiter) 256 | { 257 | if (string.isEmpty()) 258 | return std::vector(); 259 | 260 | // For each character in the string 261 | std::vector result; 262 | sf::String buffer; 263 | for (sf::Uint32 character : string) { 264 | // If we've hit the delimiter character 265 | if (character == delimiter) { 266 | // Add them to the result vector 267 | result.push_back(buffer); 268 | buffer.clear(); 269 | } else { 270 | // Accumulate the next character into the sequence 271 | buffer += character; 272 | } 273 | } 274 | 275 | // Add to the result if buffer still has contents or if the last character is a delimiter 276 | if (!buffer.isEmpty() || string[string.getSize() - 1] == delimiter) 277 | result.push_back(buffer); 278 | 279 | return result; 280 | } 281 | 282 | 283 | //////////////////////////////////////////////////////////////////////////////// 284 | RichText & RichText::operator << (const sf::String& string) 285 | { 286 | // Maybe skip 287 | if (string.isEmpty()) 288 | return *this; 289 | 290 | // Explode into substrings 291 | std::vector subStrings = explode(string, '\n'); 292 | 293 | // Append first substring using the last line 294 | auto it = subStrings.begin(); 295 | if (it != subStrings.end()) { 296 | // If there isn't any line, just create it 297 | if (m_lines.empty()) 298 | m_lines.resize(1); 299 | 300 | // Remove last line's height 301 | Line &line = m_lines.back(); 302 | m_bounds.height -= line.getGlobalBounds().height; 303 | 304 | // Append text 305 | line.appendText(createText(*it)); 306 | 307 | // Update bounds 308 | m_bounds.height += line.getGlobalBounds().height; 309 | m_bounds.width = std::max(m_bounds.width, line.getGlobalBounds().width); 310 | } 311 | 312 | // Append the rest of substrings as new lines 313 | while (++it != subStrings.end()) { 314 | Line line; 315 | line.setPosition(0.f, m_bounds.height); 316 | line.appendText(createText(*it)); 317 | m_lines.push_back(std::move(line)); 318 | 319 | // Update bounds 320 | m_bounds.height += line.getGlobalBounds().height; 321 | m_bounds.width = std::max(m_bounds.width, line.getGlobalBounds().width); 322 | } 323 | 324 | // Return 325 | return *this; 326 | } 327 | 328 | 329 | //////////////////////////////////////////////////////////////////////////////// 330 | void RichText::setCharacterColor(std::size_t line, std::size_t pos, sf::Color color) 331 | { 332 | assert(line < m_lines.size()); 333 | m_lines[line].setCharacterColor(pos, color); 334 | updateGeometry(); 335 | } 336 | 337 | 338 | //////////////////////////////////////////////////////////////////////////////// 339 | void RichText::setCharacterStyle(std::size_t line, std::size_t pos, sf::Text::Style style) 340 | { 341 | assert(line < m_lines.size()); 342 | m_lines[line].setCharacterStyle(pos, style); 343 | updateGeometry(); 344 | } 345 | 346 | 347 | //////////////////////////////////////////////////////////////////////////////// 348 | void RichText::setCharacter(std::size_t line, std::size_t pos, sf::Uint32 character) 349 | { 350 | assert(line < m_lines.size()); 351 | m_lines[line].setCharacter(pos, character); 352 | updateGeometry(); 353 | } 354 | 355 | //////////////////////////////////////////////////////////////////////////////// 356 | void RichText::setCharacterSize(unsigned int size) 357 | { 358 | // Maybe skip 359 | if (m_characterSize == size) 360 | return; 361 | 362 | // Update character size 363 | m_characterSize = size; 364 | 365 | // Set texts character size 366 | for (Line &line : m_lines) 367 | line.setCharacterSize(size); 368 | 369 | updateGeometry(); 370 | } 371 | 372 | 373 | //////////////////////////////////////////////////////////////////////////////// 374 | void RichText::setFont(const sf::Font& font) 375 | { 376 | // Maybe skip 377 | if (m_font == &font) 378 | return; 379 | 380 | // Update font 381 | m_font = &font; 382 | 383 | // Set texts font 384 | for (Line &line : m_lines) 385 | line.setFont(font); 386 | 387 | updateGeometry(); 388 | } 389 | 390 | 391 | //////////////////////////////////////////////////////////////////////////////// 392 | void RichText::clear() 393 | { 394 | // Clear texts 395 | m_lines.clear(); 396 | 397 | // Reset bounds 398 | m_bounds = sf::FloatRect(); 399 | } 400 | 401 | 402 | //////////////////////////////////////////////////////////////////////////////// 403 | sf::Color RichText::getCharacterColor(std::size_t line, std::size_t pos) const 404 | { 405 | assert(line < m_lines.size()); 406 | return m_lines[line].getCharacterColor(pos); 407 | } 408 | 409 | 410 | //////////////////////////////////////////////////////////////////////////////// 411 | sf::Uint32 RichText::getCharacterStyle(std::size_t line, std::size_t pos) const 412 | { 413 | assert(line < m_lines.size()); 414 | return m_lines[line].getCharacterStyle(pos); 415 | } 416 | 417 | 418 | //////////////////////////////////////////////////////////////////////////////// 419 | sf::Uint32 RichText::getCharacter(std::size_t line, std::size_t pos) const 420 | { 421 | assert(line < m_lines.size()); 422 | return m_lines[line].getCharacter(pos); 423 | } 424 | 425 | 426 | //////////////////////////////////////////////////////////////////////////////// 427 | const std::vector &RichText::getLines() const 428 | { 429 | return m_lines; 430 | } 431 | 432 | 433 | //////////////////////////////////////////////////////////////////////////////// 434 | unsigned int RichText::getCharacterSize() const 435 | { 436 | return m_characterSize; 437 | } 438 | 439 | 440 | //////////////////////////////////////////////////////////////////////////////// 441 | const sf::Font *RichText::getFont() const 442 | { 443 | return m_font; 444 | } 445 | 446 | 447 | //////////////////////////////////////////////////////////// 448 | sf::FloatRect RichText::getLocalBounds() const 449 | { 450 | return m_bounds; 451 | } 452 | 453 | 454 | //////////////////////////////////////////////////////////// 455 | sf::FloatRect RichText::getGlobalBounds() const 456 | { 457 | return getTransform().transformRect(getLocalBounds()); 458 | } 459 | 460 | 461 | //////////////////////////////////////////////////////////////////////////////// 462 | void RichText::draw(sf::RenderTarget& target, sf::RenderStates states) const 463 | { 464 | states.transform *= getTransform(); 465 | 466 | for (const Line &line : m_lines) 467 | target.draw(line, states); 468 | } 469 | 470 | 471 | //////////////////////////////////////////////////////////////////////////////// 472 | RichText::RichText(const sf::Font* font) 473 | : m_font(font), 474 | m_characterSize(30), 475 | m_currentStroke{ sf::Color::White, sf::Color::Transparent }, 476 | m_currentStyle(sf::Text::Regular) 477 | { 478 | 479 | } 480 | 481 | 482 | //////////////////////////////////////////////////////////////////////////////// 483 | sf::Text RichText::createText(const sf::String& string) const 484 | { 485 | sf::Text text; 486 | text.setString(string); 487 | text.setFillColor(m_currentStroke.fill); 488 | text.setOutlineColor(m_currentStroke.outline); 489 | text.setOutlineThickness(m_currentStroke.thickness); 490 | text.setStyle(m_currentStyle); 491 | text.setCharacterSize(m_characterSize); 492 | if (m_font) 493 | text.setFont(*m_font); 494 | 495 | return text; 496 | } 497 | 498 | 499 | //////////////////////////////////////////////////////////////////////////////// 500 | void RichText::updateGeometry() const 501 | { 502 | m_bounds = sf::FloatRect(); 503 | 504 | for (Line &line : m_lines) { 505 | line.setPosition(0.f, m_bounds.height); 506 | 507 | m_bounds.height += line.getGlobalBounds().height; 508 | m_bounds.width = std::max(m_bounds.width, line.getGlobalBounds().width); 509 | } 510 | } 511 | 512 | } 513 | -------------------------------------------------------------------------------- /RichText.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | ////////////////////////////////////////////////////////////////////////// 4 | // Headers 5 | ////////////////////////////////////////////////////////////////////////// 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | namespace sf 16 | { 17 | class Font; 18 | class String; 19 | template class Rect; 20 | typedef Rect FloatRect; 21 | } 22 | 23 | namespace sfe 24 | { 25 | struct TextStroke 26 | { 27 | sf::Color fill = sf::Color::White; 28 | sf::Color outline = sf::Color::Transparent; 29 | float thickness = 0.f; 30 | }; 31 | 32 | struct Outline 33 | { 34 | sf::Color outline = sf::Color::Transparent; 35 | float thickness = 0.f; 36 | }; 37 | 38 | class RichText : public sf::Drawable, public sf::Transformable 39 | { 40 | public: 41 | ////////////////////////////////////////////////////////////////////////// 42 | // Nested class that represents a single line 43 | ////////////////////////////////////////////////////////////////////////// 44 | class Line : public sf::Transformable, public sf::Drawable 45 | { 46 | public: 47 | ////////////////////////////////////////////////////////////////////// 48 | // Set a character's color. 49 | // NOTE: Attempting to access a character outside of the line bounds 50 | // causes a crash. 51 | ////////////////////////////////////////////////////////////////////// 52 | void setCharacterColor(std::size_t pos, sf::Color color); 53 | 54 | ////////////////////////////////////////////////////////////////////// 55 | // Set a character's style 56 | // NOTE: Attempting to access a character outside of the line bounds 57 | // causes a crash. 58 | ////////////////////////////////////////////////////////////////////// 59 | void setCharacterStyle(std::size_t pos, sf::Text::Style style); 60 | 61 | ////////////////////////////////////////////////////////////////////// 62 | // Set a character 63 | // NOTE: Attempting to access a character outside of the line bounds 64 | // causes a crash. 65 | ////////////////////////////////////////////////////////////////////// 66 | void setCharacter(std::size_t pos, sf::Uint32 character); 67 | 68 | ////////////////////////////////////////////////////////////////////// 69 | // Set character size 70 | ////////////////////////////////////////////////////////////////////// 71 | void setCharacterSize(unsigned int size); 72 | 73 | ////////////////////////////////////////////////////////////////////// 74 | // Set font 75 | ////////////////////////////////////////////////////////////////////// 76 | void setFont(const sf::Font &font); 77 | 78 | ////////////////////////////////////////////////////////////////////// 79 | // Get the length of the line 80 | ////////////////////////////////////////////////////////////////////// 81 | std::size_t getLength() const; 82 | 83 | ////////////////////////////////////////////////////////////////////// 84 | // Get a character's color 85 | // NOTE: Attempting to access a character outside of the line bounds 86 | // causes a crash. 87 | ////////////////////////////////////////////////////////////////////// 88 | sf::Color getCharacterColor(std::size_t pos) const; 89 | 90 | ////////////////////////////////////////////////////////////////////// 91 | // Get a character's style 92 | // NOTE: Attempting to access a character outside of the line bounds 93 | // causes a crash. 94 | ////////////////////////////////////////////////////////////////////// 95 | sf::Uint32 getCharacterStyle(std::size_t pos) const; 96 | 97 | ////////////////////////////////////////////////////////////////////// 98 | // Get a character 99 | // NOTE: Attempting to access a character outside of the line bounds 100 | // causes a crash 101 | ////////////////////////////////////////////////////////////////////// 102 | sf::Uint32 getCharacter(std::size_t pos) const; 103 | 104 | ////////////////////////////////////////////////////////////////////// 105 | // Get texts 106 | ////////////////////////////////////////////////////////////////////// 107 | const std::vector &getTexts() const; 108 | 109 | ////////////////////////////////////////////////////////////////////// 110 | // Append text 111 | ////////////////////////////////////////////////////////////////////// 112 | void appendText(sf::Text text); 113 | 114 | ////////////////////////////////////////////////////////////////////// 115 | // Get local bounds 116 | ////////////////////////////////////////////////////////////////////// 117 | sf::FloatRect getLocalBounds() const; 118 | 119 | ////////////////////////////////////////////////////////////////////// 120 | // Get global bounds 121 | ////////////////////////////////////////////////////////////////////// 122 | sf::FloatRect getGlobalBounds() const; 123 | 124 | protected: 125 | ////////////////////////////////////////////////////////////////////// 126 | // Draw 127 | ////////////////////////////////////////////////////////////////////// 128 | void draw(sf::RenderTarget &target, sf::RenderStates states) const override; 129 | 130 | private: 131 | ////////////////////////////////////////////////////////////////////// 132 | // Get the index of the sf::Text containing the pos'th character. 133 | // Also changes pos to the position of the character in the sf::Text. 134 | ////////////////////////////////////////////////////////////////////// 135 | std::size_t convertLinePosToLocal(std::size_t &pos) const; 136 | 137 | ////////////////////////////////////////////////////////////////////// 138 | // Split a string to isolate the given character 139 | // into a string of its own for individual formatting 140 | ////////////////////////////////////////////////////////////////////// 141 | void isolateCharacter(std::size_t pos); 142 | 143 | ////////////////////////////////////////////////////////////////////// 144 | // Update geometry 145 | ////////////////////////////////////////////////////////////////////// 146 | void updateGeometry() const; 147 | 148 | ////////////////////////////////////////////////////////////////////// 149 | // Update geometry for a given text 150 | ////////////////////////////////////////////////////////////////////// 151 | void updateTextAndGeometry(sf::Text &text) const; 152 | 153 | ////////////////////////////////////////////////////////////////////// 154 | // Member data 155 | ////////////////////////////////////////////////////////////////////// 156 | mutable std::vector m_texts; ///< List of texts 157 | mutable sf::FloatRect m_bounds; ///< Local bounds 158 | }; 159 | 160 | ////////////////////////////////////////////////////////////////////////// 161 | // Constructor 162 | ////////////////////////////////////////////////////////////////////////// 163 | RichText(); 164 | 165 | ////////////////////////////////////////////////////////////////////////// 166 | // Constructor 167 | ////////////////////////////////////////////////////////////////////////// 168 | RichText(const sf::Font &font); 169 | 170 | ////////////////////////////////////////////////////////////////////////// 171 | // Operators 172 | ////////////////////////////////////////////////////////////////////////// 173 | RichText & operator << (const TextStroke &stroke); 174 | RichText & operator << (const Outline &outline); 175 | RichText & operator << (const sf::Color &color); 176 | RichText & operator << (sf::Text::Style style); 177 | RichText & operator << (const sf::String &string); 178 | 179 | ////////////////////////////////////////////////////////////////////////// 180 | // Set the color of a character. 181 | // Attempting to access a character outside of the bounds causes a crash. 182 | ////////////////////////////////////////////////////////////////////////// 183 | void setCharacterColor(std::size_t line, std::size_t pos, sf::Color color); 184 | 185 | ////////////////////////////////////////////////////////////////////////// 186 | // Set the style of a character. 187 | // Attempting to access a character outside of the bounds causes a crash. 188 | ////////////////////////////////////////////////////////////////////////// 189 | void setCharacterStyle(std::size_t line, std::size_t pos, sf::Text::Style style); 190 | 191 | ////////////////////////////////////////////////////////////////////////// 192 | // Set a character 193 | // Attempting to access a character outside of the bounds causes a crash. 194 | ////////////////////////////////////////////////////////////////////////// 195 | void setCharacter(std::size_t line, std::size_t pos, sf::Uint32 character); 196 | 197 | ////////////////////////////////////////////////////////////////////////// 198 | // Set character size 199 | ////////////////////////////////////////////////////////////////////////// 200 | void setCharacterSize(unsigned int size); 201 | 202 | ////////////////////////////////////////////////////////////////////////// 203 | // Set font 204 | ////////////////////////////////////////////////////////////////////////// 205 | void setFont(const sf::Font &font); 206 | 207 | ////////////////////////////////////////////////////////////////////////// 208 | // Clear 209 | ////////////////////////////////////////////////////////////////////////// 210 | void clear(); 211 | 212 | ////////////////////////////////////////////////////////////////////////// 213 | // Get the color of a character. 214 | // Attempting to access a character outside of the bounds causes a crash. 215 | ////////////////////////////////////////////////////////////////////////// 216 | sf::Color getCharacterColor(std::size_t line, std::size_t pos) const; 217 | 218 | ////////////////////////////////////////////////////////////////////////// 219 | // Get the style of a character. 220 | // Attempting to access a character outside of the bounds causes a crash. 221 | ////////////////////////////////////////////////////////////////////////// 222 | sf::Uint32 getCharacterStyle(std::size_t line, std::size_t pos) const; 223 | 224 | ////////////////////////////////////////////////////////////////////////// 225 | // Get a character 226 | // Attempting to access a character outside of the bounds causes a crash. 227 | ////////////////////////////////////////////////////////////////////////// 228 | sf::Uint32 getCharacter(std::size_t line, std::size_t pos) const; 229 | 230 | ////////////////////////////////////////////////////////////////////////// 231 | // Get text list 232 | ////////////////////////////////////////////////////////////////////////// 233 | const std::vector &getLines() const; 234 | 235 | ////////////////////////////////////////////////////////////////////////// 236 | // Get character size 237 | ////////////////////////////////////////////////////////////////////////// 238 | unsigned int getCharacterSize() const; 239 | 240 | ////////////////////////////////////////////////////////////////////////// 241 | // Get font 242 | ////////////////////////////////////////////////////////////////////////// 243 | const sf::Font *getFont() const; 244 | 245 | ////////////////////////////////////////////////////////////////////////// 246 | // Get local bounds 247 | ////////////////////////////////////////////////////////////////////////// 248 | sf::FloatRect getLocalBounds() const; 249 | 250 | ////////////////////////////////////////////////////////////////////////// 251 | // Get global bounds 252 | ////////////////////////////////////////////////////////////////////////// 253 | sf::FloatRect getGlobalBounds() const; 254 | 255 | protected: 256 | ////////////////////////////////////////////////////////////////////////// 257 | // Render 258 | ////////////////////////////////////////////////////////////////////////// 259 | void draw(sf::RenderTarget &target, sf::RenderStates states) const override; 260 | 261 | private: 262 | ////////////////////////////////////////////////////////////////////////// 263 | // Delegate constructor 264 | ////////////////////////////////////////////////////////////////////////// 265 | RichText(const sf::Font *font); 266 | 267 | ////////////////////////////////////////////////////////////////////////// 268 | // Creates a sf::Text instance using the current styles 269 | ////////////////////////////////////////////////////////////////////////// 270 | sf::Text createText(const sf::String &string) const; 271 | 272 | ////////////////////////////////////////////////////////////////////////// 273 | // Update geometry 274 | ////////////////////////////////////////////////////////////////////////// 275 | void updateGeometry() const; 276 | 277 | ////////////////////////////////////////////////////////////////////////// 278 | // Member data 279 | ////////////////////////////////////////////////////////////////////////// 280 | mutable std::vector m_lines; ///< List of lines 281 | const sf::Font *m_font; ///< Font 282 | unsigned int m_characterSize; ///< Character size 283 | mutable sf::FloatRect m_bounds; ///< Local bounds 284 | TextStroke m_currentStroke; ///< Last used stroke 285 | sf::Text::Style m_currentStyle; ///< Last style used 286 | }; 287 | 288 | } 289 | -------------------------------------------------------------------------------- /demo/demo.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | CONFIG -= qt 3 | CONFIG -= app_bundle 4 | CONFIG -= console 5 | CONFIG += C++11 6 | 7 | INCLUDEPATH += SFML 8 | 9 | LIBS += -lsfml-graphics-d -lsfml-window-d -lsfml-graphics-d 10 | 11 | SOURCES += main.cpp \ 12 | ../RichText.cpp 13 | 14 | HEADERS += \ 15 | ../RichText.hpp 16 | -------------------------------------------------------------------------------- /demo/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../RichText.hpp" 3 | 4 | int main() 5 | { 6 | sf::RenderWindow window(sf::VideoMode(800, 600), "sfe::RichText"); 7 | window.setFramerateLimit(30); 8 | 9 | sf::Font font; 10 | font.loadFromFile("FreeMono.ttf"); 11 | 12 | sfe::RichText text(font); 13 | text << sf::Text::Bold << sf::Color::Cyan << "This " 14 | << sf::Text::Italic << sf::Color::White << "is\nan\n" 15 | << sf::Text::Regular << sf::Color::Green << "example" 16 | << sf::Color::White << ".\n" 17 | << sf::Text::Underlined << "It looks good!\n" << sf::Text::StrikeThrough 18 | << sfe::Outline{ sf::Color::Blue, 3.f } << "Really good!"; 19 | 20 | text.setCharacterSize(25); 21 | text.setPosition(400, 300); 22 | text.setOrigin(text.getGlobalBounds().width / 2.f, text.getGlobalBounds().height / 2.f); 23 | 24 | while (window.isOpen()) 25 | { 26 | sf::Event event; 27 | while (window.pollEvent(event)) 28 | { 29 | if (event.type == sf::Event::Closed) 30 | window.close(); 31 | } 32 | 33 | window.clear(); 34 | window.draw(text); 35 | window.display(); 36 | } 37 | } 38 | --------------------------------------------------------------------------------