ui::label tagged text support: implement color tag
This commit is contained in:
parent
7b99158b4d
commit
183f5283e2
2 changed files with 82 additions and 35 deletions
|
|
@ -89,6 +89,7 @@ namespace psemek::ui
|
|||
{
|
||||
std::string_view text;
|
||||
text_style style; // to be OR'd with base style
|
||||
std::optional<gfx::color_rgba> color;
|
||||
};
|
||||
|
||||
using chunk = std::variant<text_chunk>;
|
||||
|
|
@ -107,6 +108,7 @@ namespace psemek::ui
|
|||
struct batch
|
||||
{
|
||||
gfx::texture_2d const * texture;
|
||||
gfx::color_rgba color;
|
||||
std::vector<image> images;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -17,11 +17,35 @@ namespace psemek::ui
|
|||
text_ = std::move(text);
|
||||
|
||||
chunks_.clear();
|
||||
chunks_.push_back(text_chunk{{text_.data(), text_.size()}, text_style{}});
|
||||
chunks_.push_back(text_chunk{{text_.data(), text_.size()}, text_style{}, std::nullopt});
|
||||
|
||||
on_state_changed();
|
||||
}
|
||||
|
||||
static std::unordered_set<std::string_view> const known_tags
|
||||
{
|
||||
"bold",
|
||||
"uline",
|
||||
"strike",
|
||||
"color",
|
||||
};
|
||||
|
||||
void check_attribute(std::string_view tag, std::optional<std::string_view> const & attribute)
|
||||
{
|
||||
if (tag == "bold" || tag == "uline" || tag == "strike")
|
||||
{
|
||||
if (attribute)
|
||||
throw std::runtime_error("tag [" + std::string(tag) + " doesn't support attributes");
|
||||
}
|
||||
else if (tag == "color")
|
||||
{
|
||||
if (!attribute)
|
||||
throw std::runtime_error("tag [color] requires an attribute");
|
||||
if (!gfx::parse_color(*attribute))
|
||||
throw std::runtime_error("failed to parse color \"" + std::string(*attribute) + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
void label::set_tagged_text(std::string text)
|
||||
{
|
||||
text_ = std::move(text);
|
||||
|
|
@ -30,47 +54,41 @@ namespace psemek::ui
|
|||
|
||||
auto parse_result = tagged_text::parse(text_);
|
||||
|
||||
std::unordered_map<std::string_view, int> tags_stack;
|
||||
tags_stack["bold"] = 0;
|
||||
tags_stack["uline"] = 0;
|
||||
tags_stack["strike"] = 0;
|
||||
std::unordered_map<std::string_view, std::vector<std::optional<std::string_view>>> tags_stack;
|
||||
|
||||
for (auto const & token : parse_result.tokens)
|
||||
{
|
||||
if (auto text = std::get_if<std::string_view>(&token))
|
||||
{
|
||||
text_chunk chunk;
|
||||
if (tags_stack["bold"] > 0)
|
||||
if (!tags_stack["bold"].empty())
|
||||
chunk.style.set(text_style_flag::bold);
|
||||
if (tags_stack["uline"] > 0)
|
||||
if (!tags_stack["uline"].empty())
|
||||
chunk.style.set(text_style_flag::underline);
|
||||
if (tags_stack["strike"] > 0)
|
||||
if (!tags_stack["strike"].empty())
|
||||
chunk.style.set(text_style_flag::strikethrough);
|
||||
if (!tags_stack["color"].empty())
|
||||
chunk.color = *gfx::parse_color(*tags_stack["color"].back());
|
||||
chunk.text = *text;
|
||||
chunks_.push_back(chunk);
|
||||
}
|
||||
else if (auto tag = std::get_if<tagged_text::opening_tag>(&token))
|
||||
{
|
||||
if (tag->type == "bold")
|
||||
if (known_tags.contains(tag->type))
|
||||
{
|
||||
tags_stack["bold"] += 1;
|
||||
}
|
||||
else if (tag->type == "uline")
|
||||
{
|
||||
tags_stack["uline"] += 1;
|
||||
}
|
||||
else if (tag->type == "strike")
|
||||
{
|
||||
tags_stack["strike"] += 1;
|
||||
check_attribute(tag->type, tag->attribute);
|
||||
tags_stack[tag->type].push_back(tag->attribute);
|
||||
}
|
||||
else
|
||||
throw std::runtime_error("unknown tag [" + std::string(tag->type) + "]");
|
||||
}
|
||||
else if (auto tag = std::get_if<tagged_text::closing_tag>(&token))
|
||||
{
|
||||
if (tags_stack[tag->type] == 0)
|
||||
if (!known_tags.contains(tag->type))
|
||||
throw std::runtime_error("unknown tag [" + std::string(tag->type) + "]");
|
||||
if (tags_stack[tag->type].empty())
|
||||
throw std::runtime_error("mismatched opening & closing tags for [" + std::string(tag->type) + "]");
|
||||
tags_stack[tag->type] -= 1;
|
||||
tags_stack[tag->type].pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -165,13 +183,16 @@ namespace psemek::ui
|
|||
{
|
||||
auto const offset = geom::cast<float>(*st->text_shadow_offset);
|
||||
for (auto const & batch : cached_state_->batches)
|
||||
{
|
||||
if (batch.color[3] < 255) continue;
|
||||
for (auto const & image : batch.images)
|
||||
p.draw_image(image.position + offset, gfx::texture_view_2d{batch.texture, image.texcoords}, {*st->shadow_color, painter::color_mode::multiply});
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const & batch : cached_state_->batches)
|
||||
for (auto const & image : batch.images)
|
||||
p.draw_image(image.position, gfx::texture_view_2d{batch.texture, image.texcoords}, {*st->text_color, painter::color_mode::multiply});
|
||||
p.draw_image(image.position, gfx::texture_view_2d{batch.texture, image.texcoords}, {batch.color, painter::color_mode::multiply});
|
||||
}
|
||||
|
||||
void label::on_state_changed()
|
||||
|
|
@ -209,6 +230,7 @@ namespace psemek::ui
|
|||
{
|
||||
std::size_t end;
|
||||
text_style style;
|
||||
gfx::color_rgba color;
|
||||
};
|
||||
|
||||
std::vector<glyph> glyphs;
|
||||
|
|
@ -231,10 +253,12 @@ namespace psemek::ui
|
|||
|
||||
auto chunk_glyphs = font->shape(text->text, opts, pen);
|
||||
|
||||
auto color = text->color ? *text->color : *st->text_color;
|
||||
|
||||
glyphs.reserve(glyphs.size() + chunk_glyphs.size());
|
||||
glyphs.insert(glyphs.end(), chunk_glyphs.begin(), chunk_glyphs.end());
|
||||
|
||||
glyph_chunks.push_back({glyphs.size(), merged_text_style});
|
||||
glyph_chunks.push_back({glyphs.size(), merged_text_style, color});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -400,6 +424,7 @@ namespace psemek::ui
|
|||
auto font = ch.style.is_set(text_style_flag::bold) ? st->bold_font.get() : st->font.get();
|
||||
|
||||
batch.texture = &font->atlas();
|
||||
batch.color = ch.color;
|
||||
|
||||
for (std::size_t i = begin; i < ch.end; ++i)
|
||||
{
|
||||
|
|
@ -424,9 +449,6 @@ namespace psemek::ui
|
|||
{
|
||||
float const line_width = 1.f * (*st->text_scale);
|
||||
|
||||
auto & batch = state.batches.emplace_back();
|
||||
batch.texture = single_white_pixel_texture().get();
|
||||
|
||||
std::size_t ch_begin = 0;
|
||||
std::size_t ch = 0;
|
||||
for (std::size_t l = 0; l < lines.size(); ++l)
|
||||
|
|
@ -434,19 +456,30 @@ namespace psemek::ui
|
|||
float underline_y = std::round(line_offset[l][1] + font_height - line_width);
|
||||
float strikethrough_y = std::round(line_offset[l][1] + font_height / 2.f - line_width / 2.f);
|
||||
|
||||
std::optional<geom::interval<float>> current_underline;
|
||||
std::optional<geom::interval<float>> current_strikethrough;
|
||||
std::optional<std::pair<geom::interval<float>, gfx::color_rgba>> current_underline;
|
||||
std::optional<std::pair<geom::interval<float>, gfx::color_rgba>> current_strikethrough;
|
||||
|
||||
auto batch = [&](gfx::color_rgba const & color) -> cached_state::batch & {
|
||||
auto texture = single_white_pixel_texture().get();
|
||||
if (state.batches.empty() || state.batches.back().texture != texture || state.batches.back().color != color)
|
||||
{
|
||||
auto & b = state.batches.emplace_back();
|
||||
b.texture = texture;
|
||||
b.color = color;
|
||||
}
|
||||
return state.batches.back();
|
||||
};
|
||||
|
||||
auto flush_underline = [&]{
|
||||
auto & image = batch.images.emplace_back();
|
||||
image.position[0] = *current_underline;
|
||||
auto & image = batch(current_underline->second).images.emplace_back();
|
||||
image.position[0] = current_underline->first;
|
||||
image.position[1] = {underline_y, underline_y + line_width};
|
||||
current_underline = std::nullopt;
|
||||
};
|
||||
|
||||
auto flush_strikethrough = [&]{
|
||||
auto & image = batch.images.emplace_back();
|
||||
image.position[0] = *current_strikethrough;
|
||||
auto & image = batch(current_strikethrough->second).images.emplace_back();
|
||||
image.position[0] = current_strikethrough->first;
|
||||
image.position[1] = {strikethrough_y, strikethrough_y + line_width};
|
||||
current_strikethrough = std::nullopt;
|
||||
};
|
||||
|
|
@ -469,11 +502,17 @@ namespace psemek::ui
|
|||
|
||||
if (underline && current_underline)
|
||||
{
|
||||
*current_underline |= x_range;
|
||||
if (current_underline->second != chunk.color)
|
||||
{
|
||||
flush_underline();
|
||||
current_underline = {x_range, chunk.color};
|
||||
}
|
||||
else
|
||||
current_underline->first |= x_range;
|
||||
}
|
||||
else if (underline && !current_underline)
|
||||
{
|
||||
current_underline = x_range;
|
||||
current_underline = {x_range, chunk.color};
|
||||
}
|
||||
else if (!underline && current_underline)
|
||||
{
|
||||
|
|
@ -482,11 +521,17 @@ namespace psemek::ui
|
|||
|
||||
if (strikethrough && current_strikethrough)
|
||||
{
|
||||
*current_strikethrough |= x_range;
|
||||
if (current_strikethrough->second != chunk.color)
|
||||
{
|
||||
flush_strikethrough();
|
||||
current_strikethrough = {x_range, chunk.color};
|
||||
}
|
||||
else
|
||||
current_strikethrough->first |= x_range;
|
||||
}
|
||||
else if (strikethrough && !current_strikethrough)
|
||||
{
|
||||
current_strikethrough = x_range;
|
||||
current_strikethrough = {x_range, chunk.color};
|
||||
}
|
||||
else if (!strikethrough && current_strikethrough)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue