Implement ui::label link tag
This commit is contained in:
parent
9300dc6779
commit
f0cf916b85
4 changed files with 172 additions and 10 deletions
|
|
@ -62,9 +62,16 @@ namespace psemek::ui
|
|||
virtual struct image_provider * set_image_provider(struct image_provider * provider);
|
||||
virtual struct image_provider * image_provider() const { return image_provider_; }
|
||||
|
||||
using link_callback = std::function<void(std::string_view)>;
|
||||
|
||||
virtual void on_link_click(link_callback callback);
|
||||
|
||||
struct shape const & shape() const override { return shape_; }
|
||||
void reshape(geom::box<float, 2> const & bbox) override;
|
||||
|
||||
bool on_event(ui::mouse_move const & e) override;
|
||||
bool on_event(ui::mouse_click const & e) override;
|
||||
|
||||
geom::box<float, 2> size_constraints() const override;
|
||||
|
||||
geom::interval<float> width_constraints(float height) const override;
|
||||
|
|
@ -89,6 +96,8 @@ namespace psemek::ui
|
|||
|
||||
struct image_provider * image_provider_ = nullptr;
|
||||
|
||||
link_callback link_callback_;
|
||||
|
||||
box_shape shape_;
|
||||
|
||||
struct text_chunk
|
||||
|
|
@ -96,12 +105,14 @@ namespace psemek::ui
|
|||
std::string_view text;
|
||||
text_style style; // to be OR'd with base style
|
||||
std::optional<gfx::color_rgba> color;
|
||||
std::optional<std::string_view> link;
|
||||
};
|
||||
|
||||
struct image_chunk
|
||||
{
|
||||
std::string_view id;
|
||||
std::optional<gfx::color_rgba> color;
|
||||
std::optional<std::string_view> link;
|
||||
};
|
||||
|
||||
using chunk = std::variant<text_chunk, image_chunk>;
|
||||
|
|
@ -127,10 +138,14 @@ namespace psemek::ui
|
|||
|
||||
std::vector<batch> batches;
|
||||
geom::vector<float, 2> size{0.f, 0.f};
|
||||
|
||||
std::vector<std::pair<geom::box<float, 2>, std::string_view>> link_bboxes;
|
||||
};
|
||||
|
||||
mutable std::optional<cached_state> cached_state_;
|
||||
mutable std::optional<cached_state> cached_state_inf_;
|
||||
std::optional<std::string_view> selected_link_;
|
||||
bool mouse_down_ = false;
|
||||
|
||||
void update_cached_state() const;
|
||||
cached_state cached_state_for(geom::box<float, 2> const & bbox) const;
|
||||
|
|
|
|||
|
|
@ -58,9 +58,17 @@ namespace psemek::ui
|
|||
|
||||
std::optional<gfx::color_rgba> text_color;
|
||||
std::optional<int> text_scale;
|
||||
std::optional<ui::text_style> text_style;
|
||||
std::optional<geom::vector<int, 2>> text_shadow_offset;
|
||||
|
||||
std::optional<ui::text_style> text_style;
|
||||
std::optional<gfx::color_rgba> link_color;
|
||||
std::optional<ui::text_style> link_style;
|
||||
|
||||
std::optional<gfx::color_rgba> link_hover_color;
|
||||
std::optional<ui::text_style> link_hover_style;
|
||||
|
||||
std::optional<gfx::color_rgba> link_click_color;
|
||||
std::optional<ui::text_style> link_click_style;
|
||||
|
||||
std::shared_ptr<struct font> font;
|
||||
std::shared_ptr<struct font> bold_font;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#include <psemek/ui/label.hpp>
|
||||
|
||||
#include <psemek/geom/contains.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <cctype>
|
||||
#include <unordered_map>
|
||||
|
|
@ -17,7 +19,7 @@ namespace psemek::ui
|
|||
text_ = std::move(text);
|
||||
|
||||
chunks_.clear();
|
||||
chunks_.push_back(text_chunk{{text_.data(), text_.size()}, text_style{}, std::nullopt});
|
||||
chunks_.push_back(text_chunk{{text_.data(), text_.size()}, text_style{}, std::nullopt, std::nullopt});
|
||||
|
||||
on_state_changed();
|
||||
}
|
||||
|
|
@ -28,6 +30,7 @@ namespace psemek::ui
|
|||
"uline",
|
||||
"strike",
|
||||
"color",
|
||||
"link",
|
||||
};
|
||||
|
||||
void check_attribute(std::string_view tag, std::optional<std::string_view> const & attribute)
|
||||
|
|
@ -44,11 +47,17 @@ namespace psemek::ui
|
|||
if (!gfx::parse_color(*attribute))
|
||||
throw std::runtime_error("failed to parse color \"" + std::string(*attribute) + "\"");
|
||||
}
|
||||
else if (tag == "link")
|
||||
{
|
||||
if (!attribute)
|
||||
throw std::runtime_error("tag [link] requires an attribute");
|
||||
}
|
||||
}
|
||||
|
||||
void label::set_tagged_text(std::string text)
|
||||
{
|
||||
text_ = std::move(text);
|
||||
selected_link_ = std::nullopt;
|
||||
|
||||
chunks_.clear();
|
||||
|
||||
|
|
@ -69,6 +78,8 @@ namespace psemek::ui
|
|||
chunk.style.set(text_style_flag::strikethrough);
|
||||
if (!tags_stack["color"].empty())
|
||||
chunk.color = *gfx::parse_color(*tags_stack["color"].back());
|
||||
if (!tags_stack["link"].empty())
|
||||
chunk.link = tags_stack["link"].back();
|
||||
chunk.text = *text;
|
||||
chunks_.push_back(chunk);
|
||||
}
|
||||
|
|
@ -82,11 +93,15 @@ namespace psemek::ui
|
|||
chunk.id = *tag->attribute;
|
||||
if (!tags_stack["color"].empty())
|
||||
chunk.color = *gfx::parse_color(*tags_stack["color"].back());
|
||||
if (!tags_stack["link"].empty())
|
||||
chunk.link = tags_stack["link"].back();
|
||||
chunks_.push_back(chunk);
|
||||
}
|
||||
else if (known_tags.contains(tag->type))
|
||||
{
|
||||
check_attribute(tag->type, tag->attribute);
|
||||
if (tag->type == "link" && !tags_stack["link"].empty())
|
||||
throw std::runtime_error("tag [link] cannot be nested");
|
||||
tags_stack[tag->type].push_back(tag->attribute);
|
||||
}
|
||||
else
|
||||
|
|
@ -136,6 +151,11 @@ namespace psemek::ui
|
|||
return provider;
|
||||
}
|
||||
|
||||
void label::on_link_click(link_callback callback)
|
||||
{
|
||||
link_callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
void label::set_overflow(overflow_mode value)
|
||||
{
|
||||
overflow_ = value;
|
||||
|
|
@ -148,6 +168,63 @@ namespace psemek::ui
|
|||
cached_state_.reset();
|
||||
}
|
||||
|
||||
bool label::on_event(ui::mouse_move const & e)
|
||||
{
|
||||
if (cached_state_)
|
||||
{
|
||||
std::optional<std::string_view> new_selected_link;
|
||||
auto const p = geom::cast<float>(e.position);
|
||||
for (auto const & b : cached_state_->link_bboxes)
|
||||
{
|
||||
if (geom::contains(b.first, p))
|
||||
{
|
||||
new_selected_link = b.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (new_selected_link != selected_link_)
|
||||
{
|
||||
selected_link_ = new_selected_link;
|
||||
mouse_down_ = false;
|
||||
on_state_changed();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool label::on_event(ui::mouse_click const & e)
|
||||
{
|
||||
if (e.button == mouse_button::left)
|
||||
{
|
||||
if (e.down)
|
||||
{
|
||||
if (selected_link_)
|
||||
{
|
||||
if (!mouse_down_)
|
||||
{
|
||||
mouse_down_ = true;
|
||||
if (link_callback_)
|
||||
post([cb = link_callback_, text = *selected_link_]{ cb(text); });
|
||||
on_state_changed();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mouse_down_)
|
||||
{
|
||||
mouse_down_ = false;
|
||||
on_state_changed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
geom::box<float, 2> label::size_constraints() const
|
||||
{
|
||||
static float const inf = std::numeric_limits<float>::infinity();
|
||||
|
|
@ -256,6 +333,7 @@ namespace psemek::ui
|
|||
text_style style;
|
||||
gfx::color_rgba color;
|
||||
gfx::texture_view_2d image;
|
||||
std::optional<std::string_view> link;
|
||||
};
|
||||
|
||||
std::vector<item> items;
|
||||
|
|
@ -270,21 +348,50 @@ namespace psemek::ui
|
|||
{
|
||||
if (auto text = std::get_if<text_chunk>(&chunk))
|
||||
{
|
||||
auto const merged_text_style = *st->text_style | text->style;
|
||||
auto text_style = *st->text_style | text->style;
|
||||
|
||||
bool const bold = merged_text_style.is_set(text_style_flag::bold);
|
||||
if (text->link)
|
||||
{
|
||||
if (selected_link_ && *text->link == *selected_link_)
|
||||
{
|
||||
if (mouse_down_)
|
||||
text_style |= *st->link_click_style;
|
||||
else
|
||||
text_style |= *st->link_hover_style;
|
||||
}
|
||||
else
|
||||
text_style |= *st->link_style;
|
||||
}
|
||||
|
||||
gfx::color_rgba color;
|
||||
if (text->color)
|
||||
color = *text->color;
|
||||
else if (text->link)
|
||||
{
|
||||
if (selected_link_ && *text->link == *selected_link_)
|
||||
{
|
||||
if (mouse_down_)
|
||||
color = *st->link_click_color;
|
||||
else
|
||||
color = *st->link_hover_color;
|
||||
}
|
||||
else
|
||||
color = *st->link_color;
|
||||
}
|
||||
else
|
||||
color = *st->text_color;
|
||||
|
||||
bool const bold = text_style.is_set(text_style_flag::bold);
|
||||
|
||||
auto font = bold ? st->bold_font.get() : st->font.get();
|
||||
|
||||
auto color = text->color ? *text->color : *st->text_color;
|
||||
|
||||
auto glyphs = font->shape(text->text, opts, pen);
|
||||
|
||||
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, {}});
|
||||
item_chunks.push_back({items.size(), text_style, color, {}, text->link});
|
||||
}
|
||||
else if (auto image = std::get_if<image_chunk>(&chunk))
|
||||
{
|
||||
|
|
@ -306,7 +413,7 @@ namespace psemek::ui
|
|||
pen[0] = position[0].max;
|
||||
|
||||
items.push_back({position, {}});
|
||||
item_chunks.push_back({items.size(), {}, color, texture});
|
||||
item_chunks.push_back({items.size(), {}, color, texture, image->link});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -590,6 +697,24 @@ namespace psemek::ui
|
|||
}
|
||||
}
|
||||
|
||||
// Generate link bboxes
|
||||
{
|
||||
std::size_t begin = 0;
|
||||
for (auto const & ch : item_chunks)
|
||||
{
|
||||
if (ch.link)
|
||||
{
|
||||
geom::box<float, 2> bbox;
|
||||
for (std::size_t i = begin; i < ch.end; ++i)
|
||||
bbox |= items[i].position;
|
||||
|
||||
state.link_bboxes.push_back({bbox, *ch.link});
|
||||
}
|
||||
|
||||
begin = ch.end;
|
||||
}
|
||||
}
|
||||
|
||||
state.size = text_size;
|
||||
|
||||
return state;
|
||||
|
|
|
|||
|
|
@ -71,8 +71,14 @@ namespace psemek::ui
|
|||
merge(dst.outer_margin, src.outer_margin);
|
||||
merge(dst.text_color, src.text_color);
|
||||
merge(dst.text_scale, src.text_scale);
|
||||
merge(dst.text_shadow_offset, src.text_shadow_offset);
|
||||
merge(dst.text_style, src.text_style);
|
||||
merge(dst.text_shadow_offset, src.text_shadow_offset);
|
||||
merge(dst.link_color, src.link_color);
|
||||
merge(dst.link_style, src.link_style);
|
||||
merge(dst.link_hover_color, src.link_hover_color);
|
||||
merge(dst.link_hover_style, src.link_hover_style);
|
||||
merge(dst.link_click_color, src.link_click_color);
|
||||
merge(dst.link_click_style, src.link_click_style);
|
||||
merge(dst.font, src.font);
|
||||
merge(dst.bold_font, src.bold_font);
|
||||
}
|
||||
|
|
@ -125,9 +131,17 @@ namespace psemek::ui
|
|||
|
||||
s.text_color = {255, 255, 255, 255};
|
||||
s.text_scale = 1;
|
||||
s.text_style = text_style{};
|
||||
s.text_shadow_offset = {1, 1};
|
||||
|
||||
s.text_style = text_style{};
|
||||
s.link_color = {0, 0, 255, 255};
|
||||
s.link_style = text_style{};
|
||||
|
||||
s.link_hover_color = {127, 127, 255, 255};
|
||||
s.link_hover_style = text_style{};
|
||||
|
||||
s.link_click_color = {0, 0, 127, 255};
|
||||
s.link_click_style = text_style{};
|
||||
|
||||
return s;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue