Support color tag in ui::label
This commit is contained in:
parent
e51480db0a
commit
9300dc6779
2 changed files with 126 additions and 55 deletions
|
|
@ -98,7 +98,13 @@ namespace psemek::ui
|
|||
std::optional<gfx::color_rgba> color;
|
||||
};
|
||||
|
||||
using chunk = std::variant<text_chunk>;
|
||||
struct image_chunk
|
||||
{
|
||||
std::string_view id;
|
||||
std::optional<gfx::color_rgba> color;
|
||||
};
|
||||
|
||||
using chunk = std::variant<text_chunk, image_chunk>;
|
||||
|
||||
std::string text_;
|
||||
std::vector<chunk> chunks_;
|
||||
|
|
@ -115,6 +121,7 @@ namespace psemek::ui
|
|||
{
|
||||
gfx::texture_2d const * texture;
|
||||
gfx::color_rgba color;
|
||||
bool text;
|
||||
std::vector<image> images;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -74,7 +74,17 @@ namespace psemek::ui
|
|||
}
|
||||
else if (auto tag = std::get_if<tagged_text::opening_tag>(&token))
|
||||
{
|
||||
if (known_tags.contains(tag->type))
|
||||
if (tag->type == "image")
|
||||
{
|
||||
if (!tag->attribute)
|
||||
throw std::runtime_error("tag [image] requires an attribute");
|
||||
image_chunk chunk;
|
||||
chunk.id = *tag->attribute;
|
||||
if (!tags_stack["color"].empty())
|
||||
chunk.color = *gfx::parse_color(*tags_stack["color"].back());
|
||||
chunks_.push_back(chunk);
|
||||
}
|
||||
else if (known_tags.contains(tag->type))
|
||||
{
|
||||
check_attribute(tag->type, tag->attribute);
|
||||
tags_stack[tag->type].push_back(tag->attribute);
|
||||
|
|
@ -85,7 +95,7 @@ namespace psemek::ui
|
|||
else if (auto tag = std::get_if<tagged_text::closing_tag>(&token))
|
||||
{
|
||||
if (!known_tags.contains(tag->type))
|
||||
throw std::runtime_error("unknown tag [" + std::string(tag->type) + "]");
|
||||
throw std::runtime_error("unknown closing 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].pop_back();
|
||||
|
|
@ -191,6 +201,7 @@ namespace psemek::ui
|
|||
auto const offset = geom::cast<float>(*st->text_shadow_offset);
|
||||
for (auto const & batch : cached_state_->batches)
|
||||
{
|
||||
if (!batch.text) continue;
|
||||
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});
|
||||
|
|
@ -199,7 +210,7 @@ namespace psemek::ui
|
|||
|
||||
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}, {batch.color, painter::color_mode::multiply});
|
||||
p.draw_image(image.position, gfx::texture_view_2d{batch.texture, image.texcoords}, {batch.color, batch.text ? painter::color_mode::multiply : painter::color_mode::mix});
|
||||
}
|
||||
|
||||
void label::on_state_changed()
|
||||
|
|
@ -229,19 +240,26 @@ namespace psemek::ui
|
|||
|
||||
auto const font_height = st->font->size()[1] * (*st->text_scale);
|
||||
|
||||
// Shape glyphs chunk by chunk
|
||||
// All glyphs within a chunk have
|
||||
// the exact same font properties
|
||||
// Shape items (glyphs/images) chunk by chunk
|
||||
// All items within a chunk have
|
||||
// the exact same properties
|
||||
|
||||
struct glyph_chunk
|
||||
struct item
|
||||
{
|
||||
geom::box<float, 2> position;
|
||||
std::optional<char32_t> character;
|
||||
};
|
||||
|
||||
struct item_chunk
|
||||
{
|
||||
std::size_t end;
|
||||
text_style style;
|
||||
gfx::color_rgba color;
|
||||
gfx::texture_view_2d image;
|
||||
};
|
||||
|
||||
std::vector<glyph> glyphs;
|
||||
std::vector<glyph_chunk> glyph_chunks;
|
||||
std::vector<item> items;
|
||||
std::vector<item_chunk> item_chunks;
|
||||
|
||||
{
|
||||
geom::point<float, 2> pen{0.f, 0.f};
|
||||
|
|
@ -258,19 +276,42 @@ namespace psemek::ui
|
|||
|
||||
auto font = bold ? st->bold_font.get() : st->font.get();
|
||||
|
||||
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());
|
||||
auto glyphs = font->shape(text->text, opts, pen);
|
||||
|
||||
glyph_chunks.push_back({glyphs.size(), merged_text_style, color});
|
||||
items.reserve(items.size() + glyphs.size());
|
||||
for (auto const & g : glyphs)
|
||||
items.push_back(item{g.position, g.character});
|
||||
|
||||
item_chunks.push_back({items.size(), merged_text_style, color, {}});
|
||||
}
|
||||
else if (auto image = std::get_if<image_chunk>(&chunk))
|
||||
{
|
||||
auto color = image->color ? *image->color : gfx::color_rgba{255, 255, 255, 0};
|
||||
|
||||
if (!image_provider_)
|
||||
throw std::runtime_error("cannot use [image] tag without image provider");
|
||||
|
||||
auto texture = image_provider_->get(image->id);
|
||||
|
||||
float const scale = *st->text_scale;
|
||||
|
||||
geom::box<float, 2> position;
|
||||
position[0].min = pen[0];
|
||||
position[0].max = pen[0] + texture.width() * scale;
|
||||
position[1].min = pen[1] + font_height / 2.f - texture.height() * scale / 2.f;
|
||||
position[1].max = position[1].min + texture.height() * scale;
|
||||
|
||||
pen[0] = position[0].max;
|
||||
|
||||
items.push_back({position, {}});
|
||||
item_chunks.push_back({items.size(), {}, color, texture});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Break glyphs into lines
|
||||
// Break items into lines
|
||||
|
||||
struct line_range
|
||||
{
|
||||
|
|
@ -281,30 +322,30 @@ namespace psemek::ui
|
|||
std::vector<line_range> lines;
|
||||
|
||||
{
|
||||
std::size_t current_glyph = 0;
|
||||
while (current_glyph < glyphs.size())
|
||||
std::size_t current_item = 0;
|
||||
while (current_item < items.size())
|
||||
{
|
||||
if (skip_spaces_)
|
||||
{
|
||||
while (current_glyph < glyphs.size() && std::isspace(glyphs[current_glyph].character))
|
||||
++current_glyph;
|
||||
while (current_item < items.size() && items[current_item].character && std::isspace(*items[current_item].character))
|
||||
++current_item;
|
||||
}
|
||||
|
||||
std::size_t line_begin = current_glyph;
|
||||
std::size_t line_begin = current_item;
|
||||
|
||||
bool newline_end = false;
|
||||
bool wrap_end = false;
|
||||
|
||||
geom::interval<float> x_range;
|
||||
while (current_glyph < glyphs.size())
|
||||
while (current_item < items.size())
|
||||
{
|
||||
if (is_newline(glyphs[current_glyph].character))
|
||||
if (items[current_item].character && is_newline(*items[current_item].character))
|
||||
{
|
||||
newline_end = true;
|
||||
break;
|
||||
}
|
||||
|
||||
x_range |= glyphs[current_glyph].position[0];
|
||||
x_range |= items[current_item].position[0];
|
||||
|
||||
if (x_range.length() > bbox[0].length() && wrap_)
|
||||
{
|
||||
|
|
@ -312,26 +353,26 @@ namespace psemek::ui
|
|||
break;
|
||||
}
|
||||
|
||||
++current_glyph;
|
||||
++current_item;
|
||||
}
|
||||
|
||||
if (wrap_end && current_glyph == line_begin)
|
||||
if (wrap_end && current_item == line_begin)
|
||||
break;
|
||||
|
||||
if (wrap_end && current_glyph > line_begin)
|
||||
if (wrap_end && current_item > line_begin)
|
||||
{
|
||||
std::size_t space_pos = current_glyph - 1;
|
||||
while (space_pos > line_begin && !std::isspace(glyphs[space_pos].character))
|
||||
std::size_t space_pos = current_item - 1;
|
||||
while (space_pos > line_begin && (!items[current_item].character || !std::isspace(*items[space_pos].character)))
|
||||
--space_pos;
|
||||
|
||||
if (space_pos > line_begin)
|
||||
current_glyph = space_pos;
|
||||
current_item = space_pos;
|
||||
}
|
||||
|
||||
lines.push_back({line_begin, current_glyph});
|
||||
lines.push_back({line_begin, current_item});
|
||||
|
||||
if (newline_end)
|
||||
++current_glyph;
|
||||
++current_item;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -345,7 +386,7 @@ namespace psemek::ui
|
|||
// hardcode font height as min line height
|
||||
bbox[1] = {0.f, 1.f * font_height};
|
||||
for (std::size_t i = lines[l].begin; i < lines[l].end; ++i)
|
||||
bbox |= glyphs[i].position;
|
||||
bbox |= items[i].position;
|
||||
line_bbox[l] = bbox;
|
||||
}
|
||||
|
||||
|
|
@ -360,7 +401,7 @@ namespace psemek::ui
|
|||
|
||||
// TODO: handle text overflow
|
||||
|
||||
// Position lines & glyphs inside label bbox
|
||||
// Position lines & items inside label bbox
|
||||
|
||||
std::vector<geom::vector<float, 2>> line_offset(lines.size());
|
||||
{
|
||||
|
|
@ -383,7 +424,7 @@ namespace psemek::ui
|
|||
int spaces = 0;
|
||||
|
||||
for (std::size_t i = lines[l].begin; i < lines[l].end; ++i)
|
||||
if (std::isspace(glyphs[i].character))
|
||||
if (items[i].character && std::isspace(*items[i].character))
|
||||
++spaces;
|
||||
|
||||
geom::vector<float, 2> offset{0.f, current_y};
|
||||
|
|
@ -411,8 +452,8 @@ namespace psemek::ui
|
|||
|
||||
for (std::size_t i = lines[l].begin; i < lines[l].end; ++i)
|
||||
{
|
||||
glyphs[i].position += offset;
|
||||
if (std::isspace(glyphs[i].character))
|
||||
items[i].position += offset;
|
||||
if (items[i].character && std::isspace(*items[i].character))
|
||||
offset[0] += space_extra;
|
||||
}
|
||||
|
||||
|
|
@ -424,27 +465,49 @@ namespace psemek::ui
|
|||
|
||||
{
|
||||
std::size_t begin = 0;
|
||||
for (auto const & ch : glyph_chunks)
|
||||
for (auto const & ch : item_chunks)
|
||||
{
|
||||
auto & batch = state.batches.emplace_back();
|
||||
|
||||
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)
|
||||
if (ch.image)
|
||||
{
|
||||
auto & g = glyphs[i];
|
||||
auto tc = font->texcoords(g.character);
|
||||
if (!tc) continue;
|
||||
batch.texture = ch.image.texture;
|
||||
batch.color = ch.color;
|
||||
batch.text = false;
|
||||
|
||||
g.position[0] += (std::round(g.position[0].min) - g.position[0].min);
|
||||
g.position[1] += (std::round(g.position[1].min) - g.position[1].min);
|
||||
for (std::size_t i = begin; i < ch.end; ++i)
|
||||
{
|
||||
auto & g = items[i];
|
||||
|
||||
auto & image = batch.images.emplace_back();
|
||||
image.position = g.position;
|
||||
image.texcoords = *tc;
|
||||
g.position[0] += (std::round(g.position[0].min) - g.position[0].min);
|
||||
g.position[1] += (std::round(g.position[1].min) - g.position[1].min);
|
||||
|
||||
auto & image = batch.images.emplace_back();
|
||||
image.position = g.position;
|
||||
image.texcoords = ch.image.part;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
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;
|
||||
batch.text = true;
|
||||
|
||||
for (std::size_t i = begin; i < ch.end; ++i)
|
||||
{
|
||||
auto & g = items[i];
|
||||
auto tc = font->texcoords(*g.character);
|
||||
if (!tc) continue;
|
||||
|
||||
g.position[0] += (std::round(g.position[0].min) - g.position[0].min);
|
||||
g.position[1] += (std::round(g.position[1].min) - g.position[1].min);
|
||||
|
||||
auto & image = batch.images.emplace_back();
|
||||
image.position = g.position;
|
||||
image.texcoords = *tc;
|
||||
}
|
||||
}
|
||||
|
||||
begin = ch.end;
|
||||
|
|
@ -463,6 +526,7 @@ namespace psemek::ui
|
|||
auto & b = state.batches.emplace_back();
|
||||
b.texture = texture;
|
||||
b.color = color;
|
||||
b.text = true;
|
||||
}
|
||||
return state.batches.back();
|
||||
};
|
||||
|
|
@ -477,9 +541,9 @@ namespace psemek::ui
|
|||
std::optional<float> last_underline_end;
|
||||
std::optional<float> last_strikethrough_end;
|
||||
|
||||
for (; ch < glyph_chunks.size(); ++ch)
|
||||
for (; ch < item_chunks.size(); ++ch)
|
||||
{
|
||||
auto const & chunk = glyph_chunks[ch];
|
||||
auto const & chunk = item_chunks[ch];
|
||||
|
||||
bool const underline = chunk.style.is_set(text_style_flag::underline);
|
||||
bool const strikethrough = chunk.style.is_set(text_style_flag::strikethrough);
|
||||
|
|
@ -491,7 +555,7 @@ namespace psemek::ui
|
|||
{
|
||||
geom::interval<float> x_range;
|
||||
for (std::size_t i = ibegin; i < iend; ++i)
|
||||
x_range |= glyphs[i].position[0];
|
||||
x_range |= items[i].position[0];
|
||||
|
||||
if (underline)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue