lxgui
Loading...
Searching...
No Matches
gui_text.cpp
1#include "lxgui/gui_text.hpp"
2
3#include "lxgui/gui_exception.hpp"
4#include "lxgui/gui_font.hpp"
5#include "lxgui/gui_material.hpp"
6#include "lxgui/gui_matrix4.hpp"
7#include "lxgui/gui_out.hpp"
8#include "lxgui/gui_quad.hpp"
9#include "lxgui/gui_renderer.hpp"
10#include "lxgui/gui_vertex_cache.hpp"
11#include "lxgui/utils.hpp"
12#include "lxgui/utils_range.hpp"
13
14#include <map>
15
16// #define DEBUG_LOG(msg) gui::out << (msg) << std::endl
17#define DEBUG_LOG(msg)
18
19namespace lxgui::gui {
20
23namespace parser {
24enum class color_action { none, set, reset };
25
26struct format {
27 color col = color::white;
28 color_action action = color_action::none;
29};
30
31struct texture {
32 std::string file_name;
33 float width = 0.0f;
34 float height = 0.0f;
35 std::shared_ptr<material> mat;
36};
37
38using item = std::variant<char32_t, format, texture>;
39
40struct line {
41 std::vector<item> content;
42 float width = 0.0f;
43};
44
45std::vector<item>
46parse_string(renderer& renderer, const utils::ustring_view& caption, bool formatting_enabled) {
47 std::vector<item> content;
48 for (auto iter_char = caption.begin(); iter_char != caption.end(); ++iter_char) {
49 // Read format tags
50 if (*iter_char == U'|' && formatting_enabled) {
51 ++iter_char;
52 if (iter_char == caption.end())
53 break;
54
55 if (*iter_char != U'|') {
56 if (*iter_char == U'r') {
57 format format;
58 format.action = color_action::reset;
59 content.push_back(format);
60 } else if (*iter_char == U'c') {
61 format format;
62 format.action = color_action::set;
63
64 auto read_two = [&](float& out_value) {
65 ++iter_char;
66 if (iter_char == caption.end())
67 return false;
68 utils::ustring color_part(2, U'0');
69 color_part[0] = *iter_char;
70 ++iter_char;
71 if (iter_char == caption.end())
72 return false;
73 color_part[1] = *iter_char;
74 out_value = utils::hex_to_uint(utils::unicode_to_utf8(color_part)) / 255.0f;
75 return true;
76 };
77
78 if (!read_two(format.col.a))
79 break;
80 if (!read_two(format.col.r))
81 break;
82 if (!read_two(format.col.g))
83 break;
84 if (!read_two(format.col.b))
85 break;
86
87 content.push_back(format);
88 } else if (*iter_char == U'T') {
89 ++iter_char;
90
91 const auto begin = iter_char - caption.begin();
92 const auto pos = caption.find(U"|t", begin);
93 if (pos == caption.npos)
94 break;
95
96 const std::string extracted =
97 utils::unicode_to_utf8(caption.substr(begin, pos - begin));
98
99 const auto words = utils::cut(extracted, ":");
100 if (!words.empty()) {
101 texture texture;
102 texture.mat = renderer.create_atlas_material("GUI", std::string{words[0]});
103 texture.width = texture.height = std::numeric_limits<float>::quiet_NaN();
104
105 if (words.size() == 2) {
106 texture.width =
107 utils::from_string<float>(words[1]).value_or(texture.width);
108 texture.height = texture.width;
109 } else if (words.size() > 2) {
110 texture.width =
111 utils::from_string<float>(words[1]).value_or(texture.width);
112 texture.height =
113 utils::from_string<float>(words[2]).value_or(texture.height);
114 }
115
116 content.push_back(texture);
117 }
118
119 iter_char += extracted.size() + 1;
120 }
121
122 continue;
123 }
124 }
125
126 // Add characters
127 content.push_back(*iter_char);
128 }
129
130 return content;
131}
132
133bool is_whitespace(const item& i) {
134 return std::visit(
135 [](const auto& value) {
136 using type = std::decay_t<decltype(value)>;
137 if constexpr (std::is_same_v<type, char32_t>) {
138 return utils::is_whitespace(value);
139 } else {
140 return false;
141 }
142 },
143 i);
144}
145
146bool is_word(const item& i) {
147 return std::visit(
148 [](const auto& value) {
149 using type = std::decay_t<decltype(value)>;
150 if constexpr (std::is_same_v<type, char32_t>) {
151 return !utils::is_whitespace(value);
152 } else {
153 return false;
154 }
155 },
156 i);
157}
158
159bool is_character(const item& i) {
160 return i.index() == 0u;
161}
162
163bool is_format(const item& i) {
164 return i.index() == 1u;
165}
166
167bool is_character(const item& i, char32_t c) {
168 return i.index() == 0u && std::get<char32_t>(i) == c;
169}
170
171float get_width(const text& text, const item& i) {
172 return std::visit(
173 [&](const auto& value) {
174 using type = std::decay_t<decltype(value)>;
175 if constexpr (std::is_same_v<type, char32_t>) {
176 return text.get_character_width(value);
177 } else if constexpr (std::is_same_v<type, texture>) {
178 if (std::isnan(value.width))
179 return text.get_line_height();
180 else
181 return value.width * text.get_scaling_factor();
182 } else {
183 return 0.0f;
184 }
185 },
186 i);
187}
188
189float get_kerning(const text& txt, const item& i1, const item& i2) {
190 return std::visit(
191 [&](const auto& value1) {
192 using type1 = std::decay_t<decltype(value1)>;
193 if constexpr (std::is_same_v<type1, char32_t>) {
194 return std::visit(
195 [&](const auto& value2) {
196 using type2 = std::decay_t<decltype(value2)>;
197 if constexpr (std::is_same_v<type2, char32_t>) {
198 return txt.get_character_kerning(value1, value2);
199 } else {
200 return 0.0f;
201 }
202 },
203 i2);
204 } else {
205 return 0.0f;
206 }
207 },
208 i1);
209}
210
211float get_tracking(const text& txt, const item& i) {
212 return std::visit(
213 [&](const auto& value) {
214 using type = std::decay_t<decltype(value)>;
215 if constexpr (std::is_same_v<type, char32_t>) {
216 if (value != U'\n')
217 return txt.get_tracking();
218 else
219 return 0.0f;
220 } else {
221 return 0.0f;
222 }
223 },
224 i);
225}
226
227std::pair<float, float> get_advance(
228 const text& txt,
229 std::vector<item>::const_iterator iter_char,
230 std::vector<item>::const_iterator iter_begin) {
231 float advance = parser::get_width(txt, *iter_char);
232 float kerning = 0.0f;
233
234 auto iter_prev = iter_char;
235 while (iter_prev != iter_begin) {
236 --iter_prev;
237 if (parser::is_format(*iter_prev))
238 continue;
239
240 kerning = parser::get_tracking(txt, *iter_char);
241
242 if (!parser::is_whitespace(*iter_char) && !parser::is_whitespace(*iter_prev))
243 kerning += parser::get_kerning(txt, *iter_prev, *iter_char);
244
245 break;
246 }
247
248 return std::make_pair(kerning, advance);
249}
250
251float get_full_advance(
252 const text& txt,
253 std::vector<item>::const_iterator iter_char,
254 std::vector<item>::const_iterator iter_begin) {
255 const auto advance = get_advance(txt, iter_char, iter_begin);
256 return advance.first + advance.second;
257}
258
259float get_string_width(const text& txt, const std::vector<item>& content) {
260 float width = 0.0f;
261 float max_width = 0.0f;
262
263 for (auto iter_char : utils::range::iterator(content)) {
264 if (parser::is_character(*iter_char, U'\n')) {
265 if (width > max_width)
266 max_width = width;
267
268 width = 0.0f;
269 } else {
270 width += parser::get_full_advance(txt, iter_char, content.begin());
271 }
272 }
273
274 if (width > max_width)
275 max_width = width;
276
277 return max_width;
278}
279} // namespace parser
284 renderer& rdr, std::shared_ptr<const font> fnt, std::shared_ptr<const font> outline_font) :
285 renderer_(rdr), font_(std::move(fnt)), outline_font_(std::move(outline_font)) {}
286
288 if (font_)
289 return font_->get_size() * scaling_factor_;
290 else
291 return 0.0;
292}
293
294void text::set_scaling_factor(float scaling_factor) {
295 if (scaling_factor_ == scaling_factor)
296 return;
297
298 scaling_factor_ = scaling_factor;
299 notify_cache_dirty_();
300}
301
303 return scaling_factor_;
304}
305
306void text::set_text(const utils::ustring& content) {
307 if (unicode_text_ == content)
308 return;
309
310 unicode_text_ = content;
311
312 notify_cache_dirty_();
313}
314
315const utils::ustring& text::get_text() const {
316 return unicode_text_;
317}
318
319void text::set_color(const color& c, bool force_color) {
320 if (color_ == c && force_color_ == force_color)
321 return;
322
323 color_ = c;
324 force_color_ = force_color;
325
326 notify_vertex_cache_dirty_();
327}
328
329const color& text::get_color() const {
330 return color_;
331}
332
333void text::set_alpha(float alpha) {
334 if (alpha == alpha_)
335 return;
336
337 alpha_ = alpha;
338
339 notify_vertex_cache_dirty_();
340}
341
342float text::get_alpha() const {
343 return alpha_;
344}
345
346void text::set_box_dimensions(float box_width, float box_height) {
347 if (box_width_ == box_width && box_height_ == box_height)
348 return;
349
350 box_width_ = box_width;
351 box_height_ = box_height;
352
353 notify_cache_dirty_();
354}
355
356void text::set_box_width(float box_width) {
357 if (box_width_ == box_width)
358 return;
359
360 box_width_ = box_width;
361
362 notify_cache_dirty_();
363}
364
365void text::set_box_height(float box_height) {
366 if (box_height_ == box_height)
367 return;
368
369 box_height_ = box_height;
370
371 notify_cache_dirty_();
372}
373
374float text::get_width() const {
375 update_();
376 return width_;
377}
378
379float text::get_height() const {
380 update_();
381 return height_;
382}
383
384float text::get_box_width() const {
385 return box_width_;
386}
387
388float text::get_box_height() const {
389 return box_height_;
390}
391
392float text::get_text_width() const {
393 return get_string_width(unicode_text_);
394}
395
397 if (!font_)
398 return 0.0f;
399
400 std::size_t count = std::count(unicode_text_.begin(), unicode_text_.end(), U'\n');
401 float height = (1.0f + count * line_spacing_) * get_line_height();
402
403 return height;
404}
405
406std::size_t text::get_line_count() const {
407 update_();
408 return num_lines_;
409}
410
411float text::get_string_width(const std::string& content) const {
412 return get_string_width(utils::utf8_to_unicode(content));
413}
414
415float text::get_string_width(const utils::ustring& content) const {
416 if (!font_)
417 return 0.0f;
418
419 return parser::get_string_width(
420 *this, parser::parse_string(renderer_, content, formatting_enabled_));
421}
422
423float text::get_character_width(char32_t c) const {
424 if (!font_)
425 return 0.0f;
426 else if (c == U'\t')
427 return 4.0f * font_->get_character_width(U' ') * scaling_factor_;
428 else
429 return font_->get_character_width(c) * scaling_factor_;
430}
431
432float text::get_character_kerning(char32_t c1, char32_t c2) const {
433 return font_->get_character_kerning(c1, c2) * scaling_factor_;
434}
435
437 if (align_x_ == align_x)
438 return;
439
440 align_x_ = align_x;
441
442 notify_cache_dirty_();
443}
444
446 if (align_y_ == align_y)
447 return;
448
449 align_y_ = align_y;
450
451 notify_cache_dirty_();
452}
453
455 return align_x_;
456}
457
459 return align_y_;
460}
461
462void text::set_tracking(float tracking) {
463 if (tracking_ == tracking)
464 return;
465
466 tracking_ = tracking;
467
468 notify_cache_dirty_();
469}
470
471float text::get_tracking() const {
472 return tracking_;
473}
474
475void text::set_line_spacing(float line_spacing) {
476 if (line_spacing_ == line_spacing)
477 return;
478
479 line_spacing_ = line_spacing;
480
481 notify_cache_dirty_();
482}
483
485 return line_spacing_;
486}
487
488void text::set_remove_starting_spaces(bool remove_starting_spaces) {
489 if (remove_starting_spaces_ == remove_starting_spaces)
490 return;
491
492 remove_starting_spaces_ = remove_starting_spaces;
493
494 notify_cache_dirty_();
495}
496
498 return remove_starting_spaces_;
499}
500
502 if (word_wrap_enabled_ == wrap)
503 return;
504
505 word_wrap_enabled_ = wrap;
506
507 notify_cache_dirty_();
508}
509
511 return word_wrap_enabled_;
512}
513
514void text::set_word_ellipsis_enabled(bool add_ellipsis) {
515 if (ellipsis_enabled_ == add_ellipsis)
516 return;
517
518 ellipsis_enabled_ = add_ellipsis;
519
520 notify_cache_dirty_();
521}
522
524 return ellipsis_enabled_;
525}
526
527void text::set_formatting_enabled(bool formatting) {
528 if (formatting == formatting_enabled_)
529 return;
530
531 formatting_enabled_ = formatting;
532
533 notify_vertex_cache_dirty_();
534}
535
536void text::set_use_vertex_cache(bool use_vertex_cache) {
537 use_vertex_cache_flag_ = use_vertex_cache;
538}
539
541 return use_vertex_cache_flag_;
542}
543
544bool text::use_vertex_cache_() const {
545 return renderer_.is_vertex_cache_supported() && use_vertex_cache_flag_;
546}
547
548void text::render(const matrix4f& transform) const {
549 if (!font_ || unicode_text_.empty())
550 return;
551
552 update_();
553
554 bool use_vertex_cache = use_vertex_cache_();
555 if (use_vertex_cache) {
556 update_vertex_cache_();
557 }
558
559 if (outline_font_) {
560 if (const auto mat = outline_font_->get_texture().lock()) {
561 if (use_vertex_cache && outline_vertex_cache_) {
562 renderer_.render_cache(mat.get(), *outline_vertex_cache_, transform);
563 } else {
564 std::vector<std::array<vertex, 4>> quads_copy = outline_quad_list_;
565 for (auto& quad : quads_copy) {
566 for (std::size_t i = 0; i < 4; ++i) {
567 quad[i].pos = quad[i].pos * transform;
568 quad[i].col.a *= alpha_;
569 }
570 }
571
572 renderer_.render_quads(mat.get(), quads_copy);
573 }
574 }
575 }
576
577 if (const auto mat = font_->get_texture().lock()) {
578 if (use_vertex_cache && vertex_cache_) {
579 renderer_.render_cache(mat.get(), *vertex_cache_, transform);
580 } else {
581 std::vector<std::array<vertex, 4>> quads_copy = quad_list_;
582 for (auto& quad : quads_copy) {
583 for (std::size_t i = 0; i < 4; ++i) {
584 quad[i].pos = quad[i].pos * transform;
585
586 if (!formatting_enabled_ || force_color_ || quad[i].col == color::empty) {
587 quad[i].col = color_;
588 }
589
590 quad[i].col.a *= alpha_;
591 }
592 }
593
594 renderer_.render_quads(mat.get(), quads_copy);
595 }
596
597 for (auto quad : icons_list_) {
598 for (std::size_t i = 0; i < 4; ++i) {
599 quad.v[i].pos = quad.v[i].pos * transform;
600 quad.v[i].col.a *= alpha_;
601 }
602
603 renderer_.render_quad(quad);
604 }
605 }
606}
607
608void text::notify_cache_dirty_() const {
609 update_cache_flag_ = true;
610}
611
612void text::notify_vertex_cache_dirty_() const {
613 update_vertex_cache_flag_ = true;
614}
615
616float text::round_to_pixel_(float value, utils::rounding_method method) const {
617 return utils::round(value, scaling_factor_, method);
618}
619
620void text::update_() const {
621 if (!font_ || !update_cache_flag_)
622 return;
623
624 // Update the line list, read format tags, do word wrapping, ...
625 std::vector<parser::line> line_list;
626
627 DEBUG_LOG(" Get max line nbr");
628 std::size_t max_line_nbr = 0;
629 if (box_height_ != 0.0f && !std::isinf(box_height_)) {
630 if (box_height_ < get_line_height()) {
631 max_line_nbr = 0;
632 } else {
633 float remaining = box_height_ - get_line_height();
634 max_line_nbr = 1 + static_cast<std::size_t>(
635 std::floor(remaining / (get_line_height() * line_spacing_)));
636 }
637 } else
638 max_line_nbr = std::numeric_limits<std::size_t>::max();
639
640 if (max_line_nbr != 0) {
641 auto manual_line_list = utils::cut_each(unicode_text_, U"\n");
642 for (auto iter_manual : utils::range::iterator(manual_line_list)) {
643 DEBUG_LOG(" Line: '" + utils::unicode_to_utf8(*iterManual) + "'");
644
645 // Parse the line
646 std::vector<parser::item> parsed_content =
647 parser::parse_string(renderer_, *iter_manual, formatting_enabled_);
648
649 // Make a temporary line array
650 std::vector<parser::line> lines;
651
652 auto iter_line_begin = parsed_content.begin();
653 parser::line line;
654 line.width = 0.0f;
655
656 bool done = false;
657 for (auto iter_char1 = parsed_content.begin(); iter_char1 != parsed_content.end();
658 ++iter_char1) {
659 DEBUG_LOG(" Get width");
660 line.width += parser::get_full_advance(*this, iter_char1, iter_line_begin);
661 line.content.push_back(*iter_char1);
662
663 if (round_to_pixel_(line.width - box_width_) > 0) {
664 DEBUG_LOG(
665 " Box break " + utils::to_string(line.width) + " > " +
666 utils::to_string(box_width_));
667
668 // Whoops, the line is too long...
669 auto iter_space = std::find_if(
670 line.content.begin(), line.content.end(), &parser::is_whitespace);
671
672 if (iter_space != line.content.end() && word_wrap_enabled_) {
673 DEBUG_LOG(" Spaced");
674 // There are several words on this line, we'll
675 // be able to put the last one on the next line
676 auto iter_char2 = iter_char1 + 1;
677 std::vector<parser::item> erased_content;
678 std::size_t chars_to_erase = 0;
679 float last_word_width = 0.0f;
680 bool last_was_word = false;
681 while (line.width > box_width_ && iter_char2 != iter_line_begin) {
682 --iter_char2;
683
684 if (parser::is_whitespace(*iter_char2)) {
685 if (!last_was_word || remove_starting_spaces_ ||
686 line.width - last_word_width > box_width_) {
687 last_word_width += parser::get_full_advance(
688 *this, iter_char2, iter_line_begin);
689 erased_content.insert(erased_content.begin(), *iter_char2);
690 ++chars_to_erase;
691
692 line.width -= last_word_width;
693 last_word_width = 0.0f;
694 } else
695 break;
696 } else {
697 last_word_width +=
698 parser::get_full_advance(*this, iter_char2, iter_line_begin);
699 erased_content.insert(erased_content.begin(), *iter_char2);
700 ++chars_to_erase;
701
702 last_was_word = true;
703 }
704 }
705
706 if (remove_starting_spaces_) {
707 while (iter_char2 != iter_char1 + 1 &&
708 parser::is_whitespace(*iter_char2)) {
709 --chars_to_erase;
710 erased_content.erase(erased_content.begin());
711 ++iter_char2;
712 }
713 }
714
715 line.width -= last_word_width;
716 line.content.erase(line.content.end() - chars_to_erase, line.content.end());
717 lines.push_back(line);
718
719 line.width = parser::get_string_width(*this, erased_content);
720 line.content = erased_content;
721 iter_line_begin = iter_char1 - (line.content.size() - 1u);
722 } else {
723 DEBUG_LOG(" Single word");
724 // There is only one word on this line, or word
725 // wrap is disabled. Anyway, this line is just
726 // too long for the text box: our only option
727 // is to truncate it.
728 if (ellipsis_enabled_) {
729 DEBUG_LOG(" Ellipsis");
730 // FIXME: this doesn't account for kerning between the "..." and prev
731 // char
732 float word_width = get_string_width(U"...");
733 auto iter_char2 = iter_char1 + 1;
734 std::size_t chars_to_erase = 0;
735 while (line.width + word_width > box_width_ &&
736 iter_char2 != iter_line_begin) {
737 --iter_char2;
738 line.width -=
739 parser::get_full_advance(*this, iter_char2, iter_line_begin);
740 ++chars_to_erase;
741 }
742
743 DEBUG_LOG(
744 " Char to erase: " + utils::to_string(chars_to_erase) +
745 " / " + utils::to_string(line.content.size()));
746
747 line.content.erase(
748 line.content.end() - chars_to_erase, line.content.end());
749 line.content.push_back(U'.');
750 line.content.push_back(U'.');
751 line.content.push_back(U'.');
752 line.width += word_width;
753 } else {
754 DEBUG_LOG(" Truncate");
755 auto iter_char2 = iter_char1 + 1;
756 std::size_t chars_to_erase = 0;
757 while (line.width > box_width_ && iter_char2 != iter_line_begin) {
758 --iter_char2;
759 line.width -=
760 parser::get_full_advance(*this, iter_char2, iter_line_begin);
761 ++chars_to_erase;
762 }
763
764 line.content.erase(
765 line.content.end() - chars_to_erase, line.content.end());
766 }
767
768 if (!word_wrap_enabled_) {
769 DEBUG_LOG(" Display single line");
770 // Word wrap is disabled, so we can only display one line anyway.
771 line_list.push_back(line);
772 done = true;
773 break;
774 }
775
776 // Add the line
777 lines.push_back(line);
778 line.width = 0.0f;
779 line.content.clear();
780
781 DEBUG_LOG(" Continue");
782
783 // Skip all following content (which we cannot display) until next
784 // whitespace
785 auto iter_temp = iter_char1;
786 iter_char1 =
787 std::find_if(iter_char1, parsed_content.end(), &parser::is_whitespace);
788
789 if (iter_char1 == parsed_content.end())
790 break;
791
792 // Apply the format tags that were cut
793 for (; iter_temp != iter_char1; ++iter_temp) {
794 std::visit(
795 [&](const auto& value) {
796 using type = std::decay_t<decltype(value)>;
797 if constexpr (std::is_same_v<type, parser::format>) {
798 line.content.push_back(value);
799 }
800 },
801 *iter_temp);
802 }
803
804 // Look for the next word
805 iter_char1 =
806 std::find_if(iter_char1, parsed_content.end(), &parser::is_word);
807 if (iter_char1 != parsed_content.end())
808 break;
809
810 --iter_char1;
811 iter_line_begin = iter_char1;
812 }
813 }
814 }
815
816 if (done)
817 break;
818
819 DEBUG_LOG(" End");
820
821 lines.push_back(line);
822
823 // Add the maximum number of line to this text
824 for (auto& l : lines) {
825 if (line_list.size() == max_line_nbr) {
826 done = true;
827 break;
828 }
829 line_list.push_back(std::move(l));
830 }
831
832 if (done)
833 break;
834 DEBUG_LOG(" .");
835 }
836 }
837
838 num_lines_ = line_list.size();
839
840 quad_list_.clear();
841 outline_quad_list_.clear();
842 icons_list_.clear();
843
844 if (!line_list.empty()) {
845 if (box_width_ == 0.0f || std::isinf(box_width_)) {
846 width_ = 0.0f;
847 for (const auto& line : line_list)
848 width_ = std::max(width_, line.width);
849 } else
850 width_ = box_width_;
851
852 height_ =
853 (1.0f + static_cast<float>(line_list.size() - 1) * line_spacing_) * get_line_height();
854
855 float y = 0.0f;
856 float x0 = 0.0f;
857
858 if (box_width_ != 0.0f && !std::isinf(box_width_)) {
859 switch (align_x_) {
860 case alignment_x::left: x0 = 0.0f; break;
861 case alignment_x::center: x0 = box_width_ * 0.5f; break;
862 case alignment_x::right: x0 = box_width_; break;
863 }
864 } else
865 x0 = 0.0f;
866
867 if (!std::isinf(box_height_)) {
868 switch (align_y_) {
869 case alignment_y::top: y = 0.0f; break;
870 case alignment_y::middle: y = (box_height_ - height_) * 0.5f; break;
871 case alignment_y::bottom: y = (box_height_ - height_); break;
872 }
873 } else {
874 switch (align_y_) {
875 case alignment_y::top: y = 0.0f; break;
876 case alignment_y::middle: y = -height_ * 0.5f; break;
877 case alignment_y::bottom: y = -height_; break;
878 }
879 }
880
881 x0 = round_to_pixel_(x0);
882 y = round_to_pixel_(y);
883
884 std::vector<color> color_stack;
885
886 for (const auto& line : line_list) {
887 float x = 0.0f;
888 switch (align_x_) {
889 case alignment_x::left: x = 0.0f; break;
890 case alignment_x::center: x = -line.width * 0.5f; break;
891 case alignment_x::right: x = -line.width; break;
892 }
893
894 x = round_to_pixel_(x) + x0;
895
896 for (auto iter_char : utils::range::iterator(line.content)) {
897 const auto advance = parser::get_advance(*this, iter_char, line.content.begin());
898
899 x += advance.first;
900
901 std::visit(
902 [&](const auto& value) {
903 using type = std::decay_t<decltype(value)>;
904 if constexpr (std::is_same_v<type, parser::format>) {
905 switch (value.action) {
906 case parser::color_action::set: color_stack.push_back(value.col); break;
907 case parser::color_action::reset: color_stack.pop_back(); break;
908 default: break;
909 }
910 } else if constexpr (std::is_same_v<type, parser::texture>) {
911 float tex_width = 0.0f, tex_height = 0.0f;
912 if (std::isnan(value.width)) {
913 tex_width = get_line_height();
914 tex_height = get_line_height();
915 } else {
916 tex_width = value.width * get_scaling_factor();
917 tex_height = value.height * get_scaling_factor();
918 }
919
920 tex_width = round_to_pixel_(tex_width);
921 tex_height = round_to_pixel_(tex_height);
922
923 quad icon;
924 icon.mat = value.mat;
925 icon.v[0].pos = vector2f(0.0f, 0.0f);
926 icon.v[1].pos = vector2f(tex_width, 0.0f);
927 icon.v[2].pos = vector2f(tex_width, tex_height);
928 icon.v[3].pos = vector2f(0.0f, tex_height);
929 if (icon.mat) {
930 icon.v[0].uvs = icon.mat->get_canvas_uv(vector2f(0.0f, 0.0f), true);
931 icon.v[1].uvs = icon.mat->get_canvas_uv(vector2f(1.0f, 0.0f), true);
932 icon.v[2].uvs = icon.mat->get_canvas_uv(vector2f(1.0f, 1.0f), true);
933 icon.v[3].uvs = icon.mat->get_canvas_uv(vector2f(0.0f, 1.0f), true);
934 }
935
936 for (std::size_t i = 0; i < 4; ++i) {
937 icon.v[i].pos += vector2f(round_to_pixel_(x), round_to_pixel_(y));
938 }
939
940 icons_list_.push_back(icon);
941 } else if constexpr (std::is_same_v<type, char32_t>) {
942 if (outline_font_) {
943 std::array<vertex, 4> vertex_list =
944 create_outline_letter_quad_(value);
945 for (std::size_t i = 0; i < 4; ++i) {
946 vertex_list[i].pos +=
947 vector2f(round_to_pixel_(x), round_to_pixel_(y));
948 vertex_list[i].col = color::black;
949 }
950
951 outline_quad_list_.push_back(vertex_list);
952 }
953
954 std::array<vertex, 4> vertex_list = create_letter_quad_(value);
955 for (std::size_t i = 0; i < 4; ++i) {
956 vertex_list[i].pos +=
957 vector2f(round_to_pixel_(x), round_to_pixel_(y));
958 vertex_list[i].col =
959 color_stack.empty() ? color::empty : color_stack.back();
960 }
961
962 quad_list_.push_back(vertex_list);
963 }
964 },
965 *iter_char);
966
967 x += advance.second;
968 }
969
970 y += get_line_height() * line_spacing_;
971 }
972 } else {
973 width_ = 0.0f;
974 height_ = 0.0f;
975 }
976
977 update_cache_flag_ = false;
978
979 notify_vertex_cache_dirty_();
980}
981
982void text::update_vertex_cache_() const {
983 if (!update_vertex_cache_flag_)
984 return;
985
986 if (!vertex_cache_)
987 vertex_cache_ = renderer_.create_vertex_cache(vertex_cache::type::quads);
988
989 std::vector<std::array<vertex, 4>> quads_copy = quad_list_;
990 for (auto& quad : quads_copy) {
991 for (std::size_t i = 0; i < 4; ++i) {
992 if (!formatting_enabled_ || force_color_ || quad[i].col == color::empty) {
993 quad[i].col = color_;
994 }
995
996 quad[i].col.a *= alpha_;
997 }
998 }
999
1000 vertex_cache_->update(quads_copy[0].data(), quads_copy.size() * 4);
1001
1002 if (outline_font_) {
1003 if (!outline_vertex_cache_)
1004 outline_vertex_cache_ = renderer_.create_vertex_cache(vertex_cache::type::quads);
1005
1006 std::vector<std::array<vertex, 4>> outline_quads_copy = outline_quad_list_;
1007 for (auto& quad : outline_quads_copy) {
1008 for (std::size_t i = 0; i < 4; ++i) {
1009 quad[i].col.a *= alpha_;
1010 }
1011 }
1012
1013 outline_vertex_cache_->update(outline_quads_copy[0].data(), outline_quads_copy.size() * 4);
1014 }
1015
1016 update_vertex_cache_flag_ = false;
1017}
1018
1019std::array<vertex, 4> text::create_letter_quad_(const gui::font& font, char32_t c) const {
1020 bounds2f quad = font.get_character_bounds(c) * scaling_factor_;
1021
1022 std::array<vertex, 4> vertex_list;
1023 vertex_list[0].pos = quad.top_left();
1024 vertex_list[1].pos = quad.top_right();
1025 vertex_list[2].pos = quad.bottom_right();
1026 vertex_list[3].pos = quad.bottom_left();
1027
1028 bounds2f uvs = font.get_character_uvs(c);
1029 vertex_list[0].uvs = uvs.top_left();
1030 vertex_list[1].uvs = uvs.top_right();
1031 vertex_list[2].uvs = uvs.bottom_right();
1032 vertex_list[3].uvs = uvs.bottom_left();
1033
1034 return vertex_list;
1035}
1036
1037std::array<vertex, 4> text::create_letter_quad_(char32_t c) const {
1038 return create_letter_quad_(*font_, c);
1039}
1040
1041std::array<vertex, 4> text::create_outline_letter_quad_(char32_t c) const {
1042 return create_letter_quad_(*outline_font_, c);
1043}
1044
1046 quad output;
1047 output.mat = font_->get_texture().lock();
1048 output.v = create_letter_quad_(c);
1049
1050 return output;
1051}
1052
1053std::size_t text::get_letter_count() const {
1054 update_();
1055 return quad_list_.size();
1056}
1057
1058const std::array<vertex, 4>& text::get_letter_quad(std::size_t index) const {
1059 update_();
1060
1061 if (index >= quad_list_.size())
1062 throw gui::exception("text", "Trying to access letter at invalid index.");
1063
1064 return quad_list_[index];
1065}
1066
1067} // namespace lxgui::gui
Holds a single color (float RGBA, 128 bits)
Definition gui_color.hpp:13
static const color black
Definition gui_color.hpp:45
static const color empty
Definition gui_color.hpp:43
Exception to be thrown by GUI code.
Abstract type for implementation specific management.
virtual std::shared_ptr< vertex_cache > create_vertex_cache(gui::vertex_cache::type type)=0
Creates a new empty vertex cache.
void render_quad(const quad &q)
Renders a quad.
void render_cache(const material *mat, const vertex_cache &cache, const matrix4f &model_transform=matrix4f::identity)
Renders a vertex cache.
virtual bool is_vertex_cache_supported() const =0
Checks if the renderer supports vertex caches.
void render_quads(const material *mat, const std::vector< std::array< vertex, 4 > > &quad_list)
Renders a set of quads.
float get_alpha() const
Returns this text's transparency (alpha).
Definition gui_text.cpp:342
bool is_word_ellipsis_enabled() const
Checks if word ellipsis is enabled.
Definition gui_text.cpp:523
quad create_letter_quad(char32_t c) const
Creates a quad that contains the provided character.
float get_line_spacing() const
Returns this text's line spacing.
Definition gui_text.cpp:484
void render(const matrix4f &transform=matrix4f::identity) const
Renders this text at the given position.
Definition gui_text.cpp:548
alignment_y get_alignment_y() const
Returns the text vertical alignment.
Definition gui_text.cpp:458
float get_box_width() const
Returns the width of the text box.
Definition gui_text.cpp:384
void set_remove_starting_spaces(bool remove_starting_spaces)
Allows removal of a line's starting spaces.
Definition gui_text.cpp:488
float get_character_kerning(char32_t c1, char32_t c2) const
Returns the kerning between two characters.
Definition gui_text.cpp:432
void set_word_ellipsis_enabled(bool add_ellipsis)
Sets whether to show an ellipsis "..." if words don't fit in the text box.
Definition gui_text.cpp:514
void set_use_vertex_cache(bool use_vertex_cache)
Sets whether this text object should use vertex caches or not.
Definition gui_text.cpp:536
bool get_remove_starting_spaces() const
Checks if starting spaces removing is active.
Definition gui_text.cpp:497
bool get_use_vertex_cache() const
Checks if this text object is using vertex cache or not.
Definition gui_text.cpp:540
float get_string_width(const std::string &content) const
Returns the length of a provided string.
Definition gui_text.cpp:411
void set_tracking(float tracking)
Sets this text's tracking.
Definition gui_text.cpp:462
float get_width() const
Returns the width of the rendered text.
Definition gui_text.cpp:374
void set_box_height(float box_height)
Sets the height of the text box.
Definition gui_text.cpp:365
void set_scaling_factor(float scaling_factor)
Set the scaling factor to use when rendering glyphs.
Definition gui_text.cpp:294
alignment_x get_alignment_x() const
Returns the text horizontal alignment.
Definition gui_text.cpp:454
float get_text_height() const
Returns the height of the text.
Definition gui_text.cpp:396
const std::array< vertex, 4 > & get_letter_quad(std::size_t index) const
Returns the quad for the letter at the provided index (position, texture coords, color).
float get_text_width() const
Returns the length of the text.
Definition gui_text.cpp:392
void set_color(const color &c, bool force_color=false)
Sets this text's default color.
Definition gui_text.cpp:319
void set_alpha(float alpha)
Sets this text's transparency (alpha).
Definition gui_text.cpp:333
void set_alignment_y(alignment_y align_y)
Sets text vertical alignment.
Definition gui_text.cpp:445
float get_height() const
Returns the height of the rendered text.
Definition gui_text.cpp:379
void set_alignment_x(alignment_x align_x)
Sets text horizontal alignment.
Definition gui_text.cpp:436
void set_box_dimensions(float box_width, float box_height)
Sets the dimensions of the text box.
Definition gui_text.cpp:346
std::size_t get_line_count() const
Returns the number of text lines.
Definition gui_text.cpp:406
void set_word_wrap_enabled(bool wrap)
Allows/disallows word wrap when the line is too long for the text box.
Definition gui_text.cpp:501
float get_box_height() const
Returns the height of the text box.
Definition gui_text.cpp:388
void set_formatting_enabled(bool formatting)
Enables color formatting.
Definition gui_text.cpp:527
const color & get_color() const
Returns this text's default color.
Definition gui_text.cpp:329
float get_tracking() const
Returns this text's tracking.
Definition gui_text.cpp:471
float get_line_height() const
Returns the height of one line (constant).
Definition gui_text.cpp:287
void set_text(const utils::ustring &content)
Sets the text to render (unicode character set).
Definition gui_text.cpp:306
void set_line_spacing(float line_spacing)
Sets this text's line spacing.
Definition gui_text.cpp:475
float get_scaling_factor() const
Returns the scaling factor used when rendering glyphs.
Definition gui_text.cpp:302
const utils::ustring & get_text() const
Returns the text that will be rendered (unicode character set).
Definition gui_text.cpp:315
text(renderer &rdr, std::shared_ptr< const font > fnt, std::shared_ptr< const font > outline_fnt=nullptr)
Constructor.
Definition gui_text.cpp:283
float get_character_width(char32_t c) const
Returns the length of a single character.
Definition gui_text.cpp:423
std::size_t get_letter_count() const
Returns the number of letters currently displayed.
void set_box_width(float box_width)
Sets the width of the text box.
Definition gui_text.cpp:356
bool is_word_wrap_enabled() const
Checks if word wrap is enabled.
Definition gui_text.cpp:510
@ quads
3 vertices per element
vector2< float > vector2f
Holds 2D coordinates (as floats)
bounds2< float > bounds2f
Holds 2D bounds of a region (as floats).
range_impl::value_range< T > value(T &container)
Expose the value rather than the (key,value) pair.
range_impl::iterator_range< T > iterator(T &container)
Expose the iterator rather than the element.
float round(float value, float unit, rounding_method method)
Round a floating point value to a specific unit and using a specific rounding method.
rounding_method
Rounding method for points to pixels conversions.
STL namespace.
vector2< T > top_left() const noexcept
A 4x4 matrix, used for coordinate transformations.
Simple structure holding four vertices and a material.
Definition gui_quad.hpp:18
std::shared_ptr< material > mat
Definition gui_quad.hpp:20
std::array< vertex, 4 > v
Definition gui_quad.hpp:19