16 #define GETTEXT_DOMAIN "wesnoth-lib"
37 #include <boost/format.hpp>
38 #include <boost/multi_array.hpp>
45 #define DBG_GUI_RL LOG_STREAM(debug, log_rich_label)
49 #define LINK_DEBUG_BORDER false
55 using namespace std::string_literals;
58 const std::array format_tags{
"bold"s,
"b"s,
"italic"s,
"i"s,
"underline"s,
"u"s };
69 , link_aware_(builder.link_aware)
77 , padding_(builder.padding)
79 const auto conf = cast_config_to<rich_label_definition>();
81 text_color_enabled_ = conf->text_color_enabled;
82 text_color_disabled_ = conf->text_color_disabled;
83 font_family_ = conf->font_family;
84 font_size_ = conf->font_size;
85 font_style_ = conf->font_style;
86 link_color_ = conf->link_color;
87 predef_colors_.insert(conf->colors.begin(), conf->colors.end());
88 set_text_alignment(builder.text_alignment);
89 set_label(builder.label_string);
106 tshape.
draw(variables);
127 const std::string& attr_name,
128 const std::string& extra_data)
132 return {
start, end };
137 const std::string& name,
138 const std::string& dest,
144 DBG_GUI_RL <<
"add_link: " << name <<
"->" << dest;
148 point t_start, t_end;
154 std::string link_text = name.empty() ? dest : name;
164 if(t_end.x > t_start.x) {
166 links_.emplace_back(link_rect, dest);
168 DBG_GUI_RL <<
"added link at rect: " << link_rect;
174 point t_size(
size_.x - t_start.x - (origin.x == 0 ? img_width : 0), t_end.y - t_start.y);
176 point t_size2(t_end.x, t_end.y - t_start.y);
178 rect link_rect{ t_start,
point{ t_size.x, text_height } };
179 rect link_rect2{ link_start2,
point{ t_size2.x, text_height } };
181 links_.emplace_back(link_rect, dest);
182 links_.emplace_back(link_rect2, dest);
184 DBG_GUI_RL <<
"added link at rect 1: " << link_rect;
185 DBG_GUI_RL <<
"added link at rect 2: " << link_rect2;
196 std::size_t len =
static_cast<size_t>(offset);
197 if (len >= text.size() - 1) {
198 return text.size() - 1;
203 while(!std::isspace(
c = text[len])) {
234 if(maximum_width !=
static_cast<unsigned>(
size_.x)) {
256 const config& parsed_text,
258 const unsigned init_width,
262 DBG_GUI_RL <<
"Initial width: " << init_width;
266 unsigned prev_blk_height = origin.y;
267 unsigned text_height = 0;
275 std::vector<shape_ptr> shapes;
276 std::unique_ptr<gui2::text_shape> curr_item =
nullptr;
278 bool is_text =
false;
279 bool is_image =
false;
280 bool wrap_mode =
false;
281 bool new_text_block =
false;
284 point float_pos, float_size;
291 const std::string key = (orig_key ==
"img" && !
child[
"float"].to_bool(
false)) ?
"inline_image" : orig_key;
293 DBG_GUI_RL <<
"\n Trying to layout tag: " << key;
296 prev_blk_height += text_height;
299 const std::string& align =
child[
"align"].str(
"left");
302 if (align ==
"right") {
303 float_pos.x = init_width - curr_img_size.x;
304 }
else if (align ==
"middle" || align ==
"center") {
306 float_pos.x = float_size.x + (init_width - curr_img_size.x)/2;
310 float_pos.y += float_size.y;
313 shapes.emplace_back(std::make_unique<image_shape>(
point{float_pos.x, pos.y + float_pos.y},
child[
"src"]));
315 float_size.x = curr_img_size.x +
padding_;
316 float_size.y += curr_img_size.y +
padding_;
318 x = ((align ==
"left") ? float_size.x : 0);
319 pos.x += ((align ==
"left") ? float_size.x : 0);
327 new_text_block =
true;
329 DBG_GUI_RL << key <<
": src=" <<
child[
"src"] <<
", size=" << img_size;
331 }
else if(key ==
"clear") {
334 prev_blk_height += float_size.y;
335 pos.y += float_size.y;
336 float_size =
point(0, 0);
341 }
else if(key ==
"table") {
342 if(curr_item ==
nullptr) {
344 new_text_block =
false;
348 img_size =
point(0,0);
349 float_size =
point(0,0);
351 prev_blk_height += text_height +
padding_;
356 unsigned col_idx = 0, row_idx = 0;
357 unsigned rows =
child.child_count(
"row");
358 unsigned columns = 1;
360 columns =
child.mandatory_child(
"row").child_count(
"col");
362 columns = (columns == 0) ? 1 : columns;
364 if(
child[
"width"] ==
"fill") {
365 init_cell_width = init_width/columns;
367 init_cell_width =
child[
"width"].to_int(init_width)/columns;
369 std::vector<int> col_widths(columns, 0);
370 std::vector<int> row_heights(rows, 0);
373 new_text_block =
true;
376 DBG_GUI_RL <<
"start table: " <<
"row=" << rows <<
" col=" << columns
377 <<
" width=" << init_cell_width*columns;
384 if(paddings.size() == 1) {
392 std::array<int, 2> row_paddings;
393 boost::multi_array<point, 2> cell_sizes(boost::extents[rows][columns]);
396 for(
const config& row :
child.child_range(
"row")) {
401 row_paddings = get_padding(row[
"padding"]);
403 pos.y += row_paddings[0];
405 DBG_GUI_RL <<
"table cell origin (pre-layout): " << pos.x <<
", " << pos.y;
412 std::array<int, 2> col_paddings = get_padding(col[
"padding"]);
413 int cell_width = init_cell_width - col_paddings[0] - col_paddings[1];
415 pos.x += col_paddings[0];
418 cell_sizes[row_idx][col_idx] =
get_parsed_text(col_cfg, pos, init_cell_width).second;
422 row_heights[row_idx] = std::max(row_heights[row_idx], cell_sizes[row_idx][col_idx].y);
423 if(!
child[
"width"].empty()) {
424 col_widths[col_idx] = cell_width;
426 col_widths[col_idx] = std::max(col_widths[col_idx], cell_sizes[row_idx][col_idx].x);
427 if(
child[
"width"].empty()) {
428 col_widths[col_idx] = std::min(col_widths[col_idx], cell_width);
431 DBG_GUI_RL <<
"table row " << row_idx <<
" height: " << row_heights[row_idx]
432 <<
"col " << col_idx <<
" width: " << col_widths[col_idx];
435 pos.x += col_paddings[1];
439 pos.y += row_heights[row_idx] + row_paddings[1];
445 pos =
point(origin.x, prev_blk_height);
446 for(
const config& row :
child.child_range(
"row")) {
450 if(!row[
"bgcolor"].
blank()) {
451 unsigned width = std::accumulate(col_widths.begin(), col_widths.end(), 0) + 2*(row_paddings[0] + row_paddings[1])*columns;
452 unsigned height = row_paddings[0] + row_heights[row_idx] + row_paddings[1];
453 shapes.emplace_back(std::make_unique<rectangle_shape>(
454 rect{origin.x, pos.y,
static_cast<int>(width),
static_cast<int>(height)},
458 row_paddings = get_padding(row[
"padding"]);
459 pos.y += row_paddings[0];
462 DBG_GUI_RL <<
"table row " << row_idx <<
" height: " << row_heights[row_idx]
463 <<
"col " << col_idx <<
" width: " << col_widths[col_idx];
472 std::array<int, 2> col_paddings = get_padding(col[
"padding"]);
474 pos.x += col_paddings[0];
476 const std::string& valign = row[
"valign"].str(
"center");
477 const std::string& halign = col[
"halign"].str(
"left");
481 if (valign ==
"center" || valign ==
"middle") {
482 text_pos.y += (row_heights[row_idx] - cell_sizes[row_idx][col_idx].y)/2;
483 }
else if (valign ==
"bottom") {
484 text_pos.y += row_heights[row_idx] - cell_sizes[row_idx][col_idx].y;
486 if (halign ==
"center" || halign ==
"middle") {
487 text_pos.x += (col_widths[col_idx] - cell_sizes[row_idx][col_idx].x)/2;
488 }
else if (halign ==
"right") {
489 text_pos.x += col_widths[col_idx] - cell_sizes[row_idx][col_idx].x;
493 std::vector<shape_ptr> table_shapes =
get_parsed_text(col_cfg, text_pos, col_widths[col_idx]).first;
494 std::move(table_shapes.begin(), table_shapes.end(), std::back_inserter(shapes));
495 pos.x += col_widths[col_idx];
496 pos.x += col_paddings[1];
498 if(!shapes.empty()) {
499 if(
auto* tshape =
dynamic_cast<text_shape*
>(shapes.back().get())) {
500 tshape->set_wrap_width(col_widths[col_idx]);
507 new_text_block =
true;
513 pos.y += row_heights[row_idx];
514 pos.y += row_paddings[1];
515 DBG_GUI_RL <<
"row height: " << row_heights[row_idx];
519 w = std::max(
w,
static_cast<unsigned>(pos.x));
520 prev_blk_height = pos.y;
532 if(!finalize && (
line.empty() && key ==
"text")) {
536 if(curr_item ==
nullptr || new_text_block) {
537 if (new_text_block && curr_item !=
nullptr) {
538 shapes.emplace_back(std::move(curr_item));
541 curr_item =
new_text_shape(pos, init_width - pos.x - float_size.x);
542 new_text_block =
false;
546 int tmp_h =
get_text_size(*curr_item, init_width - (x == 0 ? float_size.x : x)).y;
548 if(is_text && key ==
"text") {
555 if(key ==
"inline_image") {
561 curr_item->add_text(
"\u200b");
566 }
else if(key ==
"ref") {
580 child_origin.y += prev_blk_height;
583 for(
auto&& shape : child_shapes) {
584 if(
auto* tshape =
dynamic_cast<text_shape*
>(shape.get())) {
585 const auto [
start, end] = curr_item->add_text(tshape->get_text());
586 curr_item->add_attributes_from(*tshape,
start);
587 curr_item->add_attribute(key,
"",
start, end);
589 shapes.emplace_back(std::move(shape));
595 }
else if(key ==
"header" || key ==
"h") {
597 const auto [
start, end] = curr_item->add_text(
line);
598 curr_item->add_attribute(
"weight",
"heavy",
start, end);
599 curr_item->add_attribute(
"color",
"white",
start, end);
604 }
else if(key ==
"character_entity") {
608 const auto [
start, end] = curr_item->add_text(
line);
609 curr_item->add_attribute(
"face",
"monospace",
start, end);
610 curr_item->add_attribute(
"color",
"red",
start, end);
614 }
else if(key ==
"span" || key ==
"format") {
616 const auto [
start, end] = curr_item->add_text(
line);
620 for (
const auto& [key, value] :
child.attribute_range()) {
622 curr_item->add_attribute(key, value,
start, end);
627 }
else if (key ==
"text") {
631 curr_item->add_text(
line);
633 point text_size =
get_text_size(*curr_item, init_width - (x == 0 ? float_size.x : x));
638 if(wrap_mode && (float_size.y > 0) && (text_size.y > float_size.y)) {
641 const std::string full_text = curr_item->get_text();
645 DBG_GUI_RL <<
"wrap around area: " << float_size;
649 curr_item->set_text(full_text.substr(0, len));
652 curr_item->set_wrap_width(init_width - float_size.x);
653 float_size =
point(0,0);
656 int ah =
get_text_size(*curr_item, init_width - float_size.x).y;
660 text_height += ah - tmp_h;
661 prev_blk_height += text_height;
662 pos =
point(origin.x, prev_blk_height);
664 DBG_GUI_RL <<
"wrap: " << prev_blk_height <<
"," << text_height;
673 shapes.emplace_back(std::move(curr_item));
680 }
else if((float_size.y > 0) && (text_size.y < float_size.y)) {
684 pos.y += text_size.y;
688 float_size =
point(0,0);
697 w = std::max(
w, x +
static_cast<unsigned>(
size.x));
699 text_height +=
size.y - tmp_h;
700 pos.y +=
size.y - tmp_h;
703 if(!is_image && !wrap_mode && img_size.y > 0) {
704 img_size =
point(0,0);
708 DBG_GUI_RL <<
"Prev block height: " << prev_blk_height <<
" Current text block height: " << text_height;
710 h = text_height + prev_blk_height;
715 #if LINK_DEBUG_BORDER
717 for(
const auto& entry :
links_) {
718 shapes.emplace_back(std::make_unique<rectangle_shape>(
719 entry.first.x, entry.first.y, entry.first.w, entry.first.h,
725 if(curr_item !=
nullptr) {
726 shapes.emplace_back(std::move(curr_item));
733 h = std::max(
static_cast<unsigned>(img_size.y),
h);
735 DBG_GUI_RL <<
"Width: " <<
w <<
" Height: " <<
h <<
" Origin: " << origin;
736 return { std::move(shapes),
point(
w,
h - origin.y) };
743 auto tshape = std::make_unique<gui2::text_shape>(
762 tmp.set_variable(
"text_wrap_mode",
wfl::variant(PANGO_ELLIPSIZE_NONE));
813 connect_signal<event::LEFT_BUTTON_CLICK>(
815 connect_signal<event::MOUSE_MOTION>(
817 connect_signal<event::MOUSE_LEAVE>(
836 std::optional<std::string> click_target;
837 for(
const auto& entry :
links_) {
840 if(entry.first.contains(mouse)) {
841 click_target = entry.second;
847 DBG_GUI_RL <<
"Clicked link! dst = " << *click_target;
852 DBG_GUI_RL <<
"No registered link handler found";
869 for(
const auto& entry :
links_) {
870 if(entry.first.contains(mouse)) {
914 load_resolutions<resolution>(
cfg);
919 , text_color_enabled(
color_t::from_rgba_string(
cfg[
"text_font_color_enabled"].str()))
920 , text_color_disabled(
color_t::from_rgba_string(
cfg[
"text_font_color_disabled"].str()))
922 , font_family(
cfg[
"text_font_family"].str())
924 , font_style(
cfg[
"text_font_style"].str(
"normal"))
928 for(
const auto& [name, value] : colors_cfg->attribute_range()) {
942 builder_rich_label::builder_rich_label(
const config&
cfg)
945 , link_aware(
cfg[
"link_aware"].to_bool(true))
946 , padding(
cfg[
"padding"].to_int(5))
952 DBG_GUI_G <<
"Window builder: placed rich_label '" <<
id <<
"' with definition '"
955 return std::make_unique<rich_label>(*
this);
Variant for storing WML attributes.
A config object defines a single node in a WML file, with access to child nodes.
config & add_child(std::string_view key)
optional_config_impl< config > optional_child(std::string_view key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
auto all_children_view() const
In-order iteration over all children.
child_itors child_range(std::string_view key)
void append_attributes(const config &cfg)
Adds attributes from cfg.
std::string debug() const
void append_children(const config &cfg)
Adds children from cfg.
A simple canvas which can be drawn upon.
void set_shapes(const config &cfg, const bool force=false)
Sets the config.
A rich_label takes marked up text and shows it correctly formatted and wrapped but no scrollbars are ...
point calculate_best_size() const override
See widget::calculate_best_size.
void signal_handler_mouse_motion(bool &handled, const point &coordinate)
Mouse motion signal handler: checks if the cursor is on a hyperlink.
state_t state_
Current state of the widget.
virtual void set_active(const bool) override
Sets the styled_widget's state.
void set_state(const state_t state)
std::function< void(std::string)> link_handler_
virtual void update_canvas() override
Updates the canvas(ses).
std::pair< std::vector< shape_ptr >, point > get_parsed_text(const config &parsed_text, const point &origin, const unsigned init_width, const bool finalize=false)
void signal_handler_mouse_leave(bool &handled)
Mouse leave signal handler: checks if the cursor left a hyperlink.
std::vector< std::pair< rect, std::string > > links_
link variables and functions
void place(const point &origin, const point &size) override
See widget::place.
int font_size_
Base font size.
unsigned short text_alpha_
std::unique_ptr< gui2::text_shape > new_text_shape(const point &origin, const int max_width)
Create template for text config that can be shown in canvas.
std::string font_family_
Base font family.
unsigned init_w_
Width and height of the canvas.
void set_dom(const config &dom)
point get_xy_from_offset(const unsigned offset) const
color_t get_color(const std::string &color)
If color is a predefined color set in resolution, return it, otherwise decode using font::string_to_c...
wfl::map_formula_callable setup_text_renderer(text_shape &tshape, unsigned width) const
size calculation functions
void request_reduce_width(const unsigned maximum_width) override
See widget::request_reduce_width.
std::string font_style_
Base font style.
state_t
Possible states of the widget.
void register_link_callback(std::function< void(std::string)> link_handler)
void signal_handler_left_button_click(bool &handled)
Left click signal handler: checks if we clicked on a hyperlink.
int get_offset_from_xy(const point &position) const
point get_image_size(const std::string &path) const
bool link_aware_
Whether the rich_label is link aware, rendering links with special formatting and handling click even...
void set_link_color(const color_t &color)
std::map< std::string, color_t > predef_colors_
Color variables that can be used in place of colors strings, like <row bgcolor=color1>
void set_link_aware(bool l)
virtual bool get_link_aware() const override
Returns whether the label should be link_aware, in in rendering and in searching for links with get_l...
void add_link(text_shape &tshape, const std::string &name, const std::string &dest, const point &origin, int img_width)
void set_text_alpha(unsigned short alpha)
color_t link_color_
What color links will be rendered in.
void set_label(const t_string &text) override
point get_text_size(text_shape &tshape, unsigned width) const
void update_mouse_cursor(bool enable)
Implementation detail for (re)setting the hyperlink cursor.
std::pair< std::size_t, std::size_t > add_text_with_attribute(text_shape &tshape, const t_string &text, const std::string &attr_name="", const std::string &extra_data="")
std::size_t get_split_location(std::string_view text, const point &pos)
t_string get_text() const
void draw(wfl::map_formula_callable &variables) override
Draws the canvas.
std::pair< std::size_t, std::size_t > add_text(const t_string &text)
void add_attribute(const std::string &attr_name, const std::string &extra_data="", std::size_t start=PANGO_ATTR_INDEX_FROM_TEXT_BEGINNING, std::size_t end=PANGO_ATTR_INDEX_TO_TEXT_END)
Generic locator abstracting the location of an image.
int as_int(int fallback=0) const
Returns the variant's value as an integer.
constexpr uint8_t ALPHA_OPAQUE
static std::string _(const char *str)
Define the common log macros for the gui toolkit.
Standard logging facilities (interface).
void set(CURSOR_TYPE type)
Use the default parameter to reset cursors.
void point(int x, int y)
Draw a single point.
void line(int from_x, int from_y, int to_x, int to_y)
Draw a line.
EXIT_STATUS start(bool clear_id, const std::string &filename, bool take_screenshot, const std::string &screenshot_filename)
Main interface for launching the editor from the title screen.
const color_t YELLOW_COLOR
constexpr float get_line_spacing_factor()
int get_max_height(unsigned size, font::family_class fclass, pango_text::FONT_STYLE style)
Returns the maximum glyph height of a font, in pixels.
family_class decode_family_class(const std::string &str)
color_t string_to_color(const std::string &color_str)
Return the color the string represents.
std::string sound_button_click
point get_mouse_position()
Returns the current mouse position.
std::string_view debug_truncate(std::string_view text)
Returns a truncated version of the text.
font::pango_text::FONT_STYLE decode_font_style(const std::string &style)
Converts a font style string to a font style.
PangoAlignment decode_text_alignment(const std::string &alignment)
Converts a text alignment string to a text alignment.
std::string encode_text_alignment(const PangoAlignment alignment)
Converts a PangoAlignment to its string representation.
point get_size(const locator &i_locator, bool skip_cache)
Returns the width and height of an image.
Contains the implementation details for lexical_cast and shouldn't be used directly.
static log_domain dom("general")
config parse_text(const std::string &text)
Parse a xml style marked up text string.
void play_UI_sound(const std::string &files)
map_location coordinate
Contains an x and y coordinate used for starting positions in maps.
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
int stoi(std::string_view str)
Same interface as std::stoi and meant as a drop in replacement, except:
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
std::vector< std::string > split(const config_attribute_value &val)
int get_pixel_scale()
Get the current active pixel scale multiplier.
static lg::log_domain log_rich_label("gui/widget/rich_label")
Transitional API for porting SDL_ttf-based code to Pango.
This file contains the settings handling of the widget library.
The basic class for representing 8-bit RGB or RGBA colour values.
std::string to_hex_string() const
Returns the stored color in rrggbb hex format.
static color_t from_rgba_string(std::string_view c)
Creates a new color_t object from a string variable in "R,G,B,A" format.
virtual std::unique_ptr< widget > build() const override
std::vector< state_definition > state
std::map< std::string, color_t > colors
resolution(const config &cfg)
rich_label_definition(const config &cfg)
An abstract description of a rectangle with integer coordinates.
static map_location::direction s
std::string missing_mandatory_wml_tag(const std::string §ion, const std::string &tag)
Returns a standard message for a missing wml child (tag).
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define VALIDATE_WML_CHILD(cfg, key, message)