#include #include #include namespace psemek::ui { void label::set_text(std::string text) { text_ = std::move(text); on_state_changed(); } void label::set_halign(halignment value) { halign_ = value; on_state_changed(); } void label::set_valign(valignment value) { valign_ = value; on_state_changed(); } void label::set_multiline(multiline_mode value) { multiline_ = value; on_state_changed(); } void label::set_overflow(overflow_mode value) { overflow_ = value; on_state_changed(); } void label::reshape(geom::box const & bbox) { shape_.box = bbox; cached_state_.reset(); } geom::box label::size_constraints() const { if (!cached_state_) update_cached_state(); static float const inf = std::numeric_limits::infinity(); return {{{cached_state_->size[0], inf}, {cached_state_->size[1], inf}}}; } void label::draw(painter & p) const { if (!cached_state_) update_cached_state(); if (!cached_state_->font) return; auto st = style(); if (!st) return; if (st->text_shadow_offset != geom::vector{0, 0}) for (auto & g : cached_state_->glyphs) p.draw_glyph(*(cached_state_->font), g.character, g.position + geom::cast(st->text_shadow_offset), st->shadow_color); for (auto & g : cached_state_->glyphs) p.draw_glyph(*(cached_state_->font), g.character, g.position, st->text_color); } void label::on_state_changed() { cached_state_.reset(); post_reshape(); } void label::update_cached_state() const { cached_state_ = cached_state{}; if (text_.empty()) return; auto st = style(); if (!st) return; if (!st->font) return; cached_state_->font = st->font.get(); shape_options opts; opts.scale = st->text_scale; auto glyphs = st->font->shape(text_, opts); geom::box raw_bbox; for (auto const & g : glyphs) raw_bbox |= g.position; std::size_t max_lines = 1; switch (multiline_) { case multiline_mode::none: break; case multiline_mode::minimize_lines: max_lines = std::max(1.f, std::floor(shape_.box[1].length() / st->font->size()[1] / st->text_scale)); break; case multiline_mode::minimize_area: throw std::runtime_error("multiline_mode::minimize_area is not supported yet"); } std::vector> lines; std::size_t current_glyph = 0; for (std::size_t line = 0; line < max_lines; ++line) { if (line != 0) { while (current_glyph < glyphs.size() && std::isspace(glyphs[current_glyph].character)) ++current_glyph; } std::size_t line_begin = current_glyph; std::size_t line_end = line_begin; geom::interval x_range; while (line_end < glyphs.size()) { x_range |= glyphs[line_end].position[0]; if (x_range.length() > shape_.box[0].length()) break; ++line_end; } if (line_end < glyphs.size()) { std::size_t space_pos = line_end; while (space_pos > line_begin && !std::isspace(glyphs[space_pos].character)) --space_pos; if (space_pos > line_begin) line_end = space_pos; } if (line + 1 == max_lines && line_end < glyphs.size()) { switch (overflow_) { case overflow_mode::ignore: line_end = glyphs.size(); break; case overflow_mode::drop: glyphs.erase(glyphs.begin() + line_end, glyphs.end()); break; case overflow_mode::ellipsis: { std::size_t el_start = std::max(3, line_end) - 3; float x_offset = 0.f; if (el_start > 0) x_offset = glyphs[el_start - 1].position[0].max; char const el_str[] = "..."; auto els = st->font->shape(el_str, opts); for (std::size_t i = el_start; i < line_end; ++i) { glyphs[i] = els[i - el_start]; glyphs[i].position[0] += x_offset; } glyphs.erase(glyphs.begin() + line_end, glyphs.end()); } break; } } current_glyph = line_end; lines.push_back({line_begin, line_end}); if (line_end == glyphs.size()) break; } for (std::size_t l = 0; l < lines.size(); ++l) { geom::interval x_range; for (std::size_t i = lines[l].first; i < lines[l].second; ++i) x_range |= glyphs[i].position[0]; geom::vector offset; switch (halign_) { case halignment::left: offset[0] = shape_.box[0].min - x_range.min; break; case halignment::center: offset[0] = shape_.box[0].center() - x_range.length() / 2.f - x_range.min; break; case halignment::right: offset[0] = shape_.box[0].max - x_range.length() - x_range.min; break; } switch (valign_) { case valignment::top: offset[1] = shape_.box[1].min + l * st->text_scale * st->font->size()[1]; break; case valignment::center: offset[1] = shape_.box[1].center() + (l - lines.size() / 2.f) * st->text_scale * st->font->size()[1]; break; case valignment::bottom: offset[1] = shape_.box[1].max + (l - lines.size() * 1.f) * st->text_scale * st->font->size()[1]; break; } for (std::size_t i = lines[l].first; i < lines[l].second; ++i) glyphs[i].position += offset; } cached_state_->glyphs = std::move(glyphs); cached_state_->size = raw_bbox.dimensions(); } }