Implement multiline modes in label

This commit is contained in:
Nikita Lisitsa 2021-02-26 11:34:32 +03:00
parent 92a395c293
commit c9ec53952c
2 changed files with 138 additions and 64 deletions

View file

@ -6,13 +6,19 @@
#include <psemek/ui/controller.hpp>
#include <psemek/ui/default_element_factory.hpp>
#include <psemek/ui/font.hpp>
#include <psemek/ui/frame.hpp>
#include <psemek/async/event_loop.hpp>
#include <psemek/util/recursive.hpp>
#include <psemek/geom/camera.hpp>
using namespace psemek;
static std::string const lorem_ipsum =
R"(Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.)";
struct ui_example
: app::app
{
@ -26,11 +32,30 @@ struct ui_example
element_factory.set_style(style);
auto screen = element_factory.make_screen();
auto button = element_factory.make_button("Test");
button->on_click([button = button.get()]{
button->label()->set_text(std::string(button->label()->text()) + "0");
// auto button = element_factory.make_button("Test");
// button->on_click([button = button.get()]{
// button->label()->set_text(std::string(button->label()->text()) + "0");
// });
// screen->add(button, ui::screen::x_policy::center, ui::screen::y_policy::center);
// auto text = element_factory.make_label(lorem_ipsum);
// screen->add(text, ui::screen::x_policy::center, ui::screen::y_policy::center);
auto text = element_factory.make_label(lorem_ipsum);
text->set_overflow(ui::label::overflow_mode::ellipsis);
text->set_multiline(ui::label::multiline_mode::minimize_lines);
auto frame = element_factory.make_frame();
frame->set_child(text);
frame->set_fixed_size({200.f, 200.f});
screen->add(frame, ui::screen::x_policy::center, ui::screen::y_policy::center);
auto updater = util::recursive([this, frame, i = 0](auto && self) mutable -> void {
frame->set_fixed_size({200.f + i * 2.f, 200.f});
++i;
loop.post_at(async::clock::now() + std::chrono::milliseconds{20}, self);
});
screen->add(button, ui::screen::x_policy::center, ui::screen::y_policy::center);
updater();
ui_controller.set_root(std::move(screen));
}

View file

@ -1,5 +1,7 @@
#include <psemek/ui/label.hpp>
#include <stdexcept>
namespace psemek::ui
{
@ -82,82 +84,129 @@ namespace psemek::ui
shape_options opts;
opts.scale = st->text_scale;
cached_state_->glyphs = st->font->shape(text_, opts);
auto glyphs = st->font->shape(text_, opts);
int lines = 1;
geom::box<float, 2> raw_bbox;
for (auto const & g : glyphs)
raw_bbox |= g.position;
switch (overflow_)
std::size_t max_lines = 1;
switch (multiline_)
{
case overflow_mode::ignore:
case multiline_mode::none:
break;
case overflow_mode::drop:
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)
{
std::size_t line_begin = current_glyph;
std::size_t line_end = line_begin;
geom::interval<float> x_range;
while (line_end < glyphs.size())
{
geom::interval<float> x;
std::size_t end = 0;
for (; end < cached_state_->glyphs.size(); ++end)
{
x |= cached_state_->glyphs[end].position[0];
if (x.length() > shape_.box[0].length())
break;
}
cached_state_->glyphs.resize(end);
x_range |= glyphs[line_end].position[0];
if (x_range.length() > shape_.box[0].length())
break;
++line_end;
}
break;
case overflow_mode::ellipsis:
if (line + 1 == max_lines && line_end < glyphs.size())
{
geom::interval<float> x;
std::size_t end = 0;
for (; end < cached_state_->glyphs.size(); ++end)
switch (overflow_)
{
x |= cached_state_->glyphs[end].position[0];
if (x.length() > shape_.box[0].length())
break;
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;
}
std::string text = text_.substr(0, end);
for (std::size_t i = std::max<std::size_t>(3, end) - 3; i < end; ++i)
text[i] = '.';
cached_state_->glyphs = st->font->shape(text, opts);
}
break;
current_glyph = line_end;
lines.push_back({line_begin, line_end});
if (line_end == glyphs.size())
break;
}
geom::box<float, 2> bbox;
for (auto const & g : cached_state_->glyphs)
bbox |= g.position;
geom::interval<float> x_range_full;
geom::vector<float, 2> offset;
switch (halign_)
for (std::size_t l = 0; l < lines.size(); ++l)
{
case halignment::left:
offset[0] = shape_.box[0].min;
break;
case halignment::center:
offset[0] = shape_.box[0].center() - bbox[0].length() / 2.f;
break;
case halignment::right:
offset[0] = shape_.box[0].max - bbox[0].length();
break;
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;
float prev_x = x_range_full.empty() ? 0.f : x_range_full.length();
switch (halign_)
{
case halignment::left:
offset[0] = shape_.box[0].min - prev_x;
break;
case halignment::center:
offset[0] = shape_.box[0].center() - x_range.length() / 2.f - prev_x;
break;
case halignment::right:
offset[0] = shape_.box[0].max - x_range.length() - prev_x;
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;
x_range_full |= x_range;
}
switch (valign_)
{
case valignment::top:
offset[1] = shape_.box[1].min;
break;
case valignment::center:
offset[1] = shape_.box[1].center() - lines * st->text_scale * st->font->size()[1] / 2.f;
break;
case valignment::bottom:
offset[1] = shape_.box[1].max - lines * st->text_scale * st->font->size()[1];
break;
}
for (auto & g : cached_state_->glyphs)
g.position += offset;
cached_state_->size = bbox.dimensions();
cached_state_->glyphs = std::move(glyphs);
cached_state_->size = raw_bbox.dimensions();
}
}