1 #include "lxgui/gui_text.hpp"
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"
17 #define DEBUG_LOG(msg)
24 enum class color_action {
none, set, reset };
28 color_action action = color_action::none;
32 std::string file_name;
35 std::shared_ptr<material> mat;
38 using item = std::variant<char32_t, format, texture>;
41 std::vector<item> content;
46 parse_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) {
50 if (*iter_char == U
'|' && formatting_enabled) {
52 if (iter_char == caption.end())
55 if (*iter_char != U
'|') {
56 if (*iter_char == U
'r') {
58 format.action = color_action::reset;
59 content.push_back(format);
60 }
else if (*iter_char == U
'c') {
62 format.action = color_action::set;
64 auto read_two = [&](
float& out_value) {
66 if (iter_char == caption.end())
68 utils::ustring color_part(2, U
'0');
69 color_part[0] = *iter_char;
71 if (iter_char == caption.end())
73 color_part[1] = *iter_char;
74 out_value = utils::hex_to_uint(utils::unicode_to_utf8(color_part)) / 255.0f;
78 if (!read_two(format.col.a))
80 if (!read_two(format.col.r))
82 if (!read_two(format.col.g))
84 if (!read_two(format.col.b))
87 content.push_back(format);
88 }
else if (*iter_char == U
'T') {
91 const auto begin = iter_char - caption.begin();
92 const auto pos = caption.find(U
"|t", begin);
93 if (pos == caption.npos)
96 const std::string extracted =
97 utils::unicode_to_utf8(caption.substr(begin, pos - begin));
99 const auto words = utils::cut(extracted,
":");
100 if (!words.empty()) {
102 texture.mat = renderer.create_atlas_material(
"GUI", std::string{words[0]});
103 texture.width = texture.height = std::numeric_limits<float>::quiet_NaN();
105 if (words.size() == 2) {
107 utils::from_string<float>(words[1]).value_or(texture.width);
108 texture.height = texture.width;
109 }
else if (words.size() > 2) {
111 utils::from_string<float>(words[1]).value_or(texture.width);
113 utils::from_string<float>(words[2]).value_or(texture.height);
116 content.push_back(texture);
119 iter_char += extracted.size() + 1;
127 content.push_back(*iter_char);
133 bool is_whitespace(
const item& i) {
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);
146 bool is_word(
const item& i) {
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);
159 bool is_character(
const item& i) {
160 return i.index() == 0u;
163 bool is_format(
const item& i) {
164 return i.index() == 1u;
167 bool is_character(
const item& i, char32_t c) {
168 return i.index() == 0u && std::get<char32_t>(i) == c;
171 float get_width(
const text& text,
const item& i) {
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();
181 return value.width * text.get_scaling_factor();
189 float get_kerning(
const text& txt,
const item& i1,
const item& i2) {
191 [&](
const auto& value1) {
192 using type1 = std::decay_t<decltype(value1)>;
193 if constexpr (std::is_same_v<type1, char32_t>) {
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);
211 float get_tracking(
const text& txt,
const item& i) {
213 [&](
const auto& value) {
214 using type = std::decay_t<decltype(value)>;
215 if constexpr (std::is_same_v<type, char32_t>) {
217 return txt.get_tracking();
227 std::pair<float, float> get_advance(
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;
234 auto iter_prev = iter_char;
235 while (iter_prev != iter_begin) {
237 if (parser::is_format(*iter_prev))
240 kerning = parser::get_tracking(txt, *iter_char);
242 if (!parser::is_whitespace(*iter_char) && !parser::is_whitespace(*iter_prev))
243 kerning += parser::get_kerning(txt, *iter_prev, *iter_char);
248 return std::make_pair(kerning, advance);
251 float get_full_advance(
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;
259 float get_string_width(
const text& txt,
const std::vector<item>& content) {
261 float max_width = 0.0f;
264 if (parser::is_character(*iter_char, U
'\n')) {
265 if (width > max_width)
270 width += parser::get_full_advance(txt, iter_char, content.begin());
274 if (width > max_width)
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)) {}
289 return font_->get_size() * scaling_factor_;
295 if (scaling_factor_ == scaling_factor)
298 scaling_factor_ = scaling_factor;
299 notify_cache_dirty_();
303 return scaling_factor_;
307 if (unicode_text_ == content)
310 unicode_text_ = content;
312 notify_cache_dirty_();
316 return unicode_text_;
320 if (color_ == c && force_color_ == force_color)
324 force_color_ = force_color;
326 notify_vertex_cache_dirty_();
339 notify_vertex_cache_dirty_();
347 if (box_width_ == box_width && box_height_ == box_height)
350 box_width_ = box_width;
351 box_height_ = box_height;
353 notify_cache_dirty_();
357 if (box_width_ == box_width)
360 box_width_ = box_width;
362 notify_cache_dirty_();
366 if (box_height_ == box_height)
369 box_height_ = box_height;
371 notify_cache_dirty_();
400 std::size_t count = std::count(unicode_text_.begin(), unicode_text_.end(), U
'\n');
419 return parser::get_string_width(
420 *
this, parser::parse_string(renderer_, content, formatting_enabled_));
427 return 4.0f * font_->get_character_width(U
' ') * scaling_factor_;
429 return font_->get_character_width(c) * scaling_factor_;
433 return font_->get_character_kerning(c1, c2) * scaling_factor_;
437 if (align_x_ == align_x)
442 notify_cache_dirty_();
446 if (align_y_ == align_y)
451 notify_cache_dirty_();
463 if (tracking_ == tracking)
466 tracking_ = tracking;
468 notify_cache_dirty_();
476 if (line_spacing_ == line_spacing)
479 line_spacing_ = line_spacing;
481 notify_cache_dirty_();
485 return line_spacing_;
489 if (remove_starting_spaces_ == remove_starting_spaces)
492 remove_starting_spaces_ = remove_starting_spaces;
494 notify_cache_dirty_();
498 return remove_starting_spaces_;
502 if (word_wrap_enabled_ == wrap)
505 word_wrap_enabled_ = wrap;
507 notify_cache_dirty_();
511 return word_wrap_enabled_;
515 if (ellipsis_enabled_ == add_ellipsis)
518 ellipsis_enabled_ = add_ellipsis;
520 notify_cache_dirty_();
524 return ellipsis_enabled_;
528 if (formatting == formatting_enabled_)
531 formatting_enabled_ = formatting;
533 notify_vertex_cache_dirty_();
537 use_vertex_cache_flag_ = use_vertex_cache;
541 return use_vertex_cache_flag_;
544 bool text::use_vertex_cache_()
const {
549 if (!font_ || unicode_text_.empty())
554 bool use_vertex_cache = use_vertex_cache_();
555 if (use_vertex_cache) {
556 update_vertex_cache_();
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);
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_;
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);
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;
587 quad[i].col = color_;
590 quad[i].col.a *= alpha_;
597 for (
auto quad : icons_list_) {
598 for (std::size_t i = 0; i < 4; ++i) {
600 quad.
v[i].col.a *= alpha_;
608 void text::notify_cache_dirty_()
const {
609 update_cache_flag_ =
true;
612 void text::notify_vertex_cache_dirty_()
const {
613 update_vertex_cache_flag_ =
true;
620 void text::update_()
const {
621 if (!font_ || !update_cache_flag_)
625 std::vector<parser::line> line_list;
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_)) {
634 max_line_nbr = 1 +
static_cast<std::size_t
>(
638 max_line_nbr = std::numeric_limits<std::size_t>::max();
640 if (max_line_nbr != 0) {
641 auto manual_line_list = utils::cut_each(unicode_text_, U
"\n");
643 DEBUG_LOG(
" Line: '" + utils::unicode_to_utf8(*iterManual) +
"'");
646 std::vector<parser::item> parsed_content =
647 parser::parse_string(renderer_, *iter_manual, formatting_enabled_);
650 std::vector<parser::line> lines;
652 auto iter_line_begin = parsed_content.begin();
657 for (
auto iter_char1 = parsed_content.begin(); iter_char1 != parsed_content.end();
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);
663 if (round_to_pixel_(line.width - box_width_) > 0) {
665 " Box break " + utils::to_string(line.width) +
" > " +
666 utils::to_string(box_width_));
669 auto iter_space = std::find_if(
670 line.content.begin(), line.content.end(), &parser::is_whitespace);
672 if (iter_space != line.content.end() && word_wrap_enabled_) {
673 DEBUG_LOG(
" Spaced");
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) {
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);
692 line.width -= last_word_width;
693 last_word_width = 0.0f;
698 parser::get_full_advance(*
this, iter_char2, iter_line_begin);
699 erased_content.insert(erased_content.begin(), *iter_char2);
702 last_was_word =
true;
706 if (remove_starting_spaces_) {
707 while (iter_char2 != iter_char1 + 1 &&
708 parser::is_whitespace(*iter_char2)) {
710 erased_content.erase(erased_content.begin());
715 line.width -= last_word_width;
716 line.content.erase(line.content.end() - chars_to_erase, line.content.end());
717 lines.push_back(line);
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);
723 DEBUG_LOG(
" Single word");
728 if (ellipsis_enabled_) {
729 DEBUG_LOG(
" Ellipsis");
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) {
739 parser::get_full_advance(*
this, iter_char2, iter_line_begin);
744 " Char to erase: " + utils::to_string(chars_to_erase) +
745 " / " + utils::to_string(line.content.size()));
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;
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) {
760 parser::get_full_advance(*
this, iter_char2, iter_line_begin);
765 line.content.end() - chars_to_erase, line.content.end());
768 if (!word_wrap_enabled_) {
769 DEBUG_LOG(
" Display single line");
771 line_list.push_back(line);
777 lines.push_back(line);
779 line.content.clear();
781 DEBUG_LOG(
" Continue");
785 auto iter_temp = iter_char1;
787 std::find_if(iter_char1, parsed_content.end(), &parser::is_whitespace);
789 if (iter_char1 == parsed_content.end())
793 for (; iter_temp != iter_char1; ++iter_temp) {
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);
806 std::find_if(iter_char1, parsed_content.end(), &parser::is_word);
807 if (iter_char1 != parsed_content.end())
811 iter_line_begin = iter_char1;
821 lines.push_back(line);
824 for (
auto& l : lines) {
825 if (line_list.size() == max_line_nbr) {
829 line_list.push_back(std::move(l));
838 num_lines_ = line_list.size();
841 outline_quad_list_.clear();
844 if (!line_list.empty()) {
845 if (box_width_ == 0.0f || std::isinf(box_width_)) {
847 for (
const auto& line : line_list)
848 width_ = std::max(width_, line.width);
853 (1.0f +
static_cast<float>(line_list.size() - 1) * line_spacing_) *
get_line_height();
858 if (box_width_ != 0.0f && !std::isinf(box_width_)) {
867 if (!std::isinf(box_height_)) {
881 x0 = round_to_pixel_(x0);
882 y = round_to_pixel_(y);
884 std::vector<color> color_stack;
886 for (
const auto& line : line_list) {
894 x = round_to_pixel_(x) + x0;
897 const auto advance = parser::get_advance(*
this, iter_char, line.content.begin());
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;
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)) {
920 tex_width = round_to_pixel_(tex_width);
921 tex_height = round_to_pixel_(tex_height);
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);
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);
936 for (std::size_t i = 0; i < 4; ++i) {
937 icon.v[i].pos +=
vector2f(round_to_pixel_(x), round_to_pixel_(y));
940 icons_list_.push_back(icon);
941 }
else if constexpr (std::is_same_v<type, char32_t>) {
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));
951 outline_quad_list_.push_back(vertex_list);
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));
959 color_stack.empty() ?
color::empty : color_stack.back();
962 quad_list_.push_back(vertex_list);
977 update_cache_flag_ =
false;
979 notify_vertex_cache_dirty_();
982 void text::update_vertex_cache_()
const {
983 if (!update_vertex_cache_flag_)
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_;
996 quad[i].col.
a *= alpha_;
1000 vertex_cache_->update(quads_copy[0].data(), quads_copy.size() * 4);
1002 if (outline_font_) {
1003 if (!outline_vertex_cache_)
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_;
1013 outline_vertex_cache_->update(outline_quads_copy[0].data(), outline_quads_copy.size() * 4);
1016 update_vertex_cache_flag_ =
false;
1019 std::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_;
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();
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();
1037 std::array<vertex, 4> text::create_letter_quad_(char32_t c)
const {
1038 return create_letter_quad_(*font_, c);
1041 std::array<vertex, 4> text::create_outline_letter_quad_(char32_t c)
const {
1042 return create_letter_quad_(*outline_font_, c);
1047 output.
mat = font_->get_texture().lock();
1048 output.
v = create_letter_quad_(c);
1055 return quad_list_.size();
1061 if (index >= quad_list_.size())
1062 throw gui::exception(
"text",
"Trying to access letter at invalid index.");
1064 return quad_list_[index];
Holds a single color (float RGBA, 128 bits)
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).
bool is_word_ellipsis_enabled() const
Checks if word ellipsis is enabled.
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.
void render(const matrix4f &transform=matrix4f::identity) const
Renders this text at the given position.
alignment_y get_alignment_y() const
Returns the text vertical alignment.
float get_box_width() const
Returns the width of the text box.
void set_remove_starting_spaces(bool remove_starting_spaces)
Allows removal of a line's starting spaces.
float get_character_kerning(char32_t c1, char32_t c2) const
Returns the kerning between two characters.
void set_word_ellipsis_enabled(bool add_ellipsis)
Sets whether to show an ellipsis "..." if words don't fit in the text box.
void set_use_vertex_cache(bool use_vertex_cache)
Sets whether this text object should use vertex caches or not.
bool get_remove_starting_spaces() const
Checks if starting spaces removing is active.
bool get_use_vertex_cache() const
Checks if this text object is using vertex cache or not.
float get_string_width(const std::string &content) const
Returns the length of a provided string.
void set_tracking(float tracking)
Sets this text's tracking.
float get_width() const
Returns the width of the rendered text.
void set_box_height(float box_height)
Sets the height of the text box.
void set_scaling_factor(float scaling_factor)
Set the scaling factor to use when rendering glyphs.
alignment_x get_alignment_x() const
Returns the text horizontal alignment.
float get_text_height() const
Returns the height of the text.
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.
void set_color(const color &c, bool force_color=false)
Sets this text's default color.
void set_alpha(float alpha)
Sets this text's transparency (alpha).
void set_alignment_y(alignment_y align_y)
Sets text vertical alignment.
float get_height() const
Returns the height of the rendered text.
void set_alignment_x(alignment_x align_x)
Sets text horizontal alignment.
void set_box_dimensions(float box_width, float box_height)
Sets the dimensions of the text box.
std::size_t get_line_count() const
Returns the number of text lines.
void set_word_wrap_enabled(bool wrap)
Allows/disallows word wrap when the line is too long for the text box.
float get_box_height() const
Returns the height of the text box.
void set_formatting_enabled(bool formatting)
Enables color formatting.
const color & get_color() const
Returns this text's default color.
float get_tracking() const
Returns this text's tracking.
float get_line_height() const
Returns the height of one line (constant).
void set_text(const utils::ustring &content)
Sets the text to render (unicode character set).
void set_line_spacing(float line_spacing)
Sets this text's line spacing.
float get_scaling_factor() const
Returns the scaling factor used when rendering glyphs.
const utils::ustring & get_text() const
Returns the text that will be rendered (unicode character set).
text(renderer &rdr, std::shared_ptr< const font > fnt, std::shared_ptr< const font > outline_fnt=nullptr)
Constructor.
float get_character_width(char32_t c) const
Returns the length of a single character.
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.
bool is_word_wrap_enabled() const
Checks if word wrap is enabled.
@ 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::iterator_range< T > iterator(T &container)
Expose the iterator rather than the element.
range_impl::value_range< T > value(T &container)
Expose the value rather than the (key,value) pair.
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.
vector2< T > top_left() const noexcept
A 4x4 matrix, used for coordinate transformations.
Simple structure holding four vertices and a material.
std::shared_ptr< material > mat
std::array< vertex, 4 > v