psemek/libs/ui/source/label.cpp

227 lines
5.2 KiB
C++

#include <psemek/ui/label.hpp>
#include <stdexcept>
#include <cctype>
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<float, 2> const & bbox)
{
shape_.box = bbox;
cached_state_.reset();
}
geom::box<float, 2> label::size_constraints() const
{
if (!cached_state_)
update_cached_state();
static float const inf = std::numeric_limits<float>::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<float>(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<float, 2> 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<std::pair<std::size_t, std::size_t>> 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<float> 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<std::size_t>(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<float> x_range;
for (std::size_t i = lines[l].first; i < lines[l].second; ++i)
x_range |= glyphs[i].position[0];
geom::vector<float, 2> 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();
}
}