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);
134 auto& attr = curr_item[
"text"];
135 std::size_t
start = attr.str().size();
136 attr = attr.str() + text;
137 std::size_t end = attr.str().size();
138 return {
start, end };
143 const std::string& attr_name,
144 const std::string& extra_data,
153 cfg[
"name"] = attr_name;
161 if (!extra_data.empty()) {
162 cfg[
"value"] = extra_data;
168 const std::string& text,
169 const std::string& attr_name,
170 const std::string& extra_data)
174 return {
start, end };
179 const std::string& name,
180 const std::string& dest,
186 DBG_GUI_RL <<
"add_link: " << name <<
"->" << dest;
190 point t_start, t_end;
196 std::string link_text = name.empty() ? dest : name;
206 if(t_end.x > t_start.x) {
208 links_.emplace_back(link_rect, dest);
210 DBG_GUI_RL <<
"added link at rect: " << link_rect;
216 point t_size(
size_.x - t_start.x - (origin.x == 0 ? img_width : 0), t_end.y - t_start.y);
218 point t_size2(t_end.x, t_end.y - t_start.y);
220 rect link_rect{ t_start,
point{ t_size.x, text_height } };
221 rect link_rect2{ link_start2,
point{ t_size2.x, text_height } };
223 links_.emplace_back(link_rect, dest);
224 links_.emplace_back(link_rect2, dest);
226 DBG_GUI_RL <<
"added link at rect 1: " << link_rect;
227 DBG_GUI_RL <<
"added link at rect 2: " << link_rect2;
234 if (len >= text.size() - 1) {
235 return text.size() - 1;
240 while(!std::isspace(
c = text[len])) {
263 const config& parsed_text,
265 const unsigned init_width,
269 DBG_GUI_RL <<
"Initial width: " << init_width;
273 unsigned prev_blk_height = origin.y;
274 unsigned text_height = 0;
283 config* curr_item =
nullptr;
285 bool is_text =
false;
286 bool is_image =
false;
287 bool wrap_mode =
false;
288 bool new_text_block =
false;
291 point float_pos, float_size;
298 const std::string key = (orig_key ==
"img" && !
child[
"float"].to_bool(
false)) ?
"inline_image" : orig_key;
300 DBG_GUI_RL <<
"\n Trying to layout tag: " << key;
303 prev_blk_height += text_height;
306 const std::string& align =
child[
"align"].str(
"left");
308 curr_item = &(text_dom.
add_child(
"image"));
309 (*curr_item)[
"name"] =
child[
"src"];
310 (*curr_item)[
"x"] = 0;
311 (*curr_item)[
"y"] = 0;
312 (*curr_item)[
"w"] =
"(image_width)";
313 (*curr_item)[
"h"] =
"(image_height)";
317 if (align ==
"right") {
318 float_pos.x = init_width - curr_img_size.x;
319 }
else if (align ==
"middle" || align ==
"center") {
321 float_pos.x = float_size.x + (init_width - curr_img_size.x)/2;
325 float_pos.y += float_size.y;
328 (*curr_item)[
"x"] = float_pos.x;
329 (*curr_item)[
"y"] = pos.y + float_pos.y;
331 float_size.x = curr_img_size.x +
padding_;
332 float_size.y += curr_img_size.y +
padding_;
334 x = ((align ==
"left") ? float_size.x : 0);
335 pos.x += ((align ==
"left") ? float_size.x : 0);
343 new_text_block =
true;
345 DBG_GUI_RL << key <<
": src=" <<
child[
"src"] <<
", size=" << img_size;
347 }
else if(key ==
"clear") {
350 prev_blk_height += float_size.y;
351 pos.y += float_size.y;
352 float_size =
point(0, 0);
357 }
else if(key ==
"table") {
358 if(curr_item ==
nullptr) {
359 curr_item = &(text_dom.
add_child(
"text"));
361 new_text_block =
false;
365 img_size =
point(0,0);
366 float_size =
point(0,0);
368 prev_blk_height += text_height +
padding_;
373 unsigned col_idx = 0, row_idx = 0;
374 unsigned rows =
child.child_count(
"row");
375 unsigned columns = 1;
377 columns =
child.mandatory_child(
"row").child_count(
"col");
379 columns = (columns == 0) ? 1 : columns;
381 if(
child[
"width"] ==
"fill") {
382 init_cell_width = init_width/columns;
384 init_cell_width =
child[
"width"].to_int(init_width)/columns;
386 std::vector<int> col_widths(columns, 0);
387 std::vector<int> row_heights(rows, 0);
390 new_text_block =
true;
393 DBG_GUI_RL <<
"start table: " <<
"row=" << rows <<
" col=" << columns
394 <<
" width=" << init_cell_width*columns;
401 if(paddings.size() == 1) {
409 std::array<int, 2> row_paddings;
410 boost::multi_array<point, 2> cell_sizes(boost::extents[rows][columns]);
413 for(
const config& row :
child.child_range(
"row")) {
418 row_paddings = get_padding(row[
"padding"]);
420 pos.y += row_paddings[0];
422 DBG_GUI_RL <<
"table cell origin (pre-layout): " << pos.x <<
", " << pos.y;
429 std::array<int, 2> col_paddings = get_padding(col[
"padding"]);
430 int cell_width = init_cell_width - col_paddings[0] - col_paddings[1];
432 pos.x += col_paddings[0];
435 cell_sizes[row_idx][col_idx] =
get_parsed_text(col_cfg, pos, init_cell_width).second;
439 row_heights[row_idx] = std::max(row_heights[row_idx], cell_sizes[row_idx][col_idx].y);
440 if(!
child[
"width"].empty()) {
441 col_widths[col_idx] = cell_width;
443 col_widths[col_idx] = std::max(col_widths[col_idx], cell_sizes[row_idx][col_idx].x);
444 if(
child[
"width"].empty()) {
445 col_widths[col_idx] = std::min(col_widths[col_idx], cell_width);
448 DBG_GUI_RL <<
"table row " << row_idx <<
" height: " << row_heights[row_idx]
449 <<
"col " << col_idx <<
" width: " << col_widths[col_idx];
452 pos.x += col_paddings[1];
456 pos.y += row_heights[row_idx] + row_paddings[1];
462 pos =
point(origin.x, prev_blk_height);
463 for(
const config& row :
child.child_range(
"row")) {
467 if(!row[
"bgcolor"].
blank()) {
470 bgbox[
"x"] = origin.x;
472 bgbox[
"w"] = std::accumulate(col_widths.begin(), col_widths.end(), 0) + 2*(row_paddings[0] + row_paddings[1])*columns;
473 bgbox[
"h"] = row_paddings[0] + row_heights[row_idx] + row_paddings[1];
475 text_dom.
append(std::move(bg_base));
478 row_paddings = get_padding(row[
"padding"]);
479 pos.y += row_paddings[0];
482 DBG_GUI_RL <<
"table row " << row_idx <<
" height: " << row_heights[row_idx]
483 <<
"col " << col_idx <<
" width: " << col_widths[col_idx];
492 std::array<int, 2> col_paddings = get_padding(col[
"padding"]);
494 pos.x += col_paddings[0];
496 const std::string& valign = row[
"valign"].str(
"center");
497 const std::string& halign = col[
"halign"].str(
"left");
501 if (valign ==
"center" || valign ==
"middle") {
502 text_pos.y += (row_heights[row_idx] - cell_sizes[row_idx][col_idx].y)/2;
503 }
else if (valign ==
"bottom") {
504 text_pos.y += row_heights[row_idx] - cell_sizes[row_idx][col_idx].y;
506 if (halign ==
"center" || halign ==
"middle") {
507 text_pos.x += (col_widths[col_idx] - cell_sizes[row_idx][col_idx].x)/2;
508 }
else if (halign ==
"right") {
509 text_pos.x += col_widths[col_idx] - cell_sizes[row_idx][col_idx].x;
514 text_dom.
append(std::move(table_elem));
515 pos.x += col_widths[col_idx];
516 pos.x += col_paddings[1];
519 end_cfg[
"maximum_width"] = col_widths[col_idx];
524 new_text_block =
true;
530 pos.y += row_heights[row_idx];
531 pos.y += row_paddings[1];
532 DBG_GUI_RL <<
"row height: " << row_heights[row_idx];
536 w = std::max(
w,
static_cast<unsigned>(pos.x));
537 prev_blk_height = pos.y;
549 if (!finalize && (
line.empty() && key ==
"text")) {
553 if (curr_item ==
nullptr || new_text_block) {
554 curr_item = &(text_dom.
add_child(
"text"));
556 new_text_block =
false;
560 int tmp_h =
get_text_size(*curr_item, init_width - (x == 0 ? float_size.x : x)).y;
562 if(is_text && key ==
"text") {
569 if (key ==
"inline_image") {
580 }
else if(key ==
"ref") {
594 child_origin.y += prev_blk_height;
599 if(parsed_key ==
"text") {
600 const auto [
start, end] =
add_text(*curr_item, parsed_cfg[
"text"]);
602 add_attribute(*curr_item, attr[
"name"], attr[
"value"],
start + attr[
"start"].to_int(),
start + attr[
"end"].to_int());
606 text_dom.
add_child(parsed_key, parsed_cfg);
612 }
else if(key ==
"header" || key ==
"h") {
621 }
else if(key ==
"character_entity") {
631 }
else if(key ==
"span" || key ==
"format") {
637 for (
const auto& [key, value] :
child.attribute_range()) {
644 }
else if (key ==
"text") {
650 point text_size =
get_text_size(*curr_item, init_width - (x == 0 ? float_size.x : x));
655 if(wrap_mode && (float_size.y > 0) && (text_size.y > float_size.y)) {
659 DBG_GUI_RL <<
"wrap around area: " << float_size;
661 std::string removed_part = (*curr_item)[
"text"].str().substr(len+1);
666 (*curr_item)[
"text"] = (*curr_item)[
"text"].str().substr(0, len);
667 (*curr_item)[
"maximum_width"] = init_width - float_size.x;
668 float_size =
point(0,0);
671 int ah =
get_text_size(*curr_item, init_width - float_size.x).y;
675 text_height += ah - tmp_h;
676 prev_blk_height += text_height;
677 pos =
point(origin.x, prev_blk_height);
679 DBG_GUI_RL <<
"wrap: " << prev_blk_height <<
"," << text_height;
687 curr_item = &(text_dom.
add_child(
"text"));
692 }
else if((float_size.y > 0) && (text_size.y < float_size.y)) {
696 pos.y += text_size.y;
700 float_size =
point(0,0);
709 w = std::max(
w, x +
static_cast<unsigned>(
size.x));
711 text_height +=
size.y - tmp_h;
712 pos.y +=
size.y - tmp_h;
715 if(!is_image && !wrap_mode && img_size.y > 0) {
716 img_size =
point(0,0);
723 DBG_GUI_RL <<
"Prev block height: " << prev_blk_height <<
" Current text block height: " << text_height;
725 h = text_height + prev_blk_height;
734 #if LINK_DEBUG_BORDER
736 for(
const auto& entry :
links_) {
738 link_rect_cfg[
"x"] = entry.first.x;
739 link_rect_cfg[
"y"] = entry.first.y;
740 link_rect_cfg[
"w"] = entry.first.w;
741 link_rect_cfg[
"h"] = entry.first.h;
742 link_rect_cfg[
"border_thickness"] = 1;
743 link_rect_cfg[
"border_color"] =
"255, 180, 0, 255";
749 h = std::max(
static_cast<unsigned>(img_size.y),
h);
753 DBG_GUI_RL <<
"Width: " <<
w <<
" Height: " <<
h <<
" Origin: " << origin;
754 return { text_dom,
point(
w,
h - origin.y) };
763 if(txt_ptr !=
nullptr) {
764 (*txt_ptr)[
"text"] = text;
770 (*txt_ptr)[
"line_spacing"] = 0;
771 (*txt_ptr)[
"x"] = pos.x;
772 (*txt_ptr)[
"y"] = pos.y;
773 (*txt_ptr)[
"w"] =
"(text_width)";
774 (*txt_ptr)[
"h"] =
"(text_height)";
775 (*txt_ptr)[
"maximum_width"] = max_width;
776 (*txt_ptr)[
"parse_text_as_formula"] =
false;
790 tmp.set_variable(
"text_wrap_mode",
wfl::variant(PANGO_ELLIPSIZE_NONE));
843 connect_signal<event::LEFT_BUTTON_CLICK>(
845 connect_signal<event::MOUSE_MOTION>(
847 connect_signal<event::MOUSE_LEAVE>(
866 for(
const auto& entry :
links_) {
869 if(entry.first.contains(mouse)) {
870 DBG_GUI_RL <<
"Clicked link! dst = " << entry.second;
875 DBG_GUI_RL <<
"No registered link handler found";
894 for(
const auto& entry :
links_) {
895 if(entry.first.contains(mouse)) {
939 load_resolutions<resolution>(
cfg);
944 , text_color_enabled(
color_t::from_rgba_string(
cfg[
"text_font_color_enabled"].str()))
945 , text_color_disabled(
color_t::from_rgba_string(
cfg[
"text_font_color_disabled"].str()))
947 , font_family(
cfg[
"text_font_family"].str())
949 , font_style(
cfg[
"text_font_style"].str(
"normal"))
953 for(
const auto& [name, value] : colors_cfg->attribute_range()) {
968 builder_rich_label::builder_rich_label(
const config&
cfg)
971 , link_aware(
cfg[
"link_aware"].to_bool(true))
972 , width(
cfg[
"width"], 500)
973 , padding(
cfg[
"padding"].to_int(5))
979 DBG_GUI_G <<
"Window builder: placed rich_label '" <<
id <<
"' with definition '"
982 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.
void append(const config &cfg)
Append data from another config object to this one.
auto all_children_view() const
In-order iteration over all children.
child_itors child_range(config_key_type 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.
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
config & add_child(config_key_type key)
A simple canvas which can be drawn upon.
A rich_label takes marked up text and shows it correctly formatted and wrapped but no scrollbars are ...
void add_link(config &curr_item, const std::string &name, const std::string &dest, const point &origin, int img_width)
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.
void set_state(const state_t state)
color_t text_color_enabled_
Base text color, enabled state.
virtual bool get_active() const override
Gets the active state of the styled_widget.
point get_text_size(config &text_cfg, unsigned width=0) const
size calculation functions
std::function< void(std::string)> link_handler_
virtual void update_canvas() override
Updates the canvas(ses).
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 add_attribute(config &curr_item, const std::string &attr_name, const std::string &extra_data="", std::size_t start=0, std::size_t end=0)
int font_size_
Base font size.
point get_image_size(config &img_cfg) const
config shapes_
Final list of shapes to be drawn on the canvas.
unsigned short text_alpha_
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...
std::pair< config, point > get_parsed_text(const config &parsed_text, const point &origin, const unsigned init_width, const bool finalize=false)
virtual void set_active(const bool active) override
Sets the styled_widget's state.
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
bool link_aware_
Whether the rich_label is link aware, rendering links with special formatting and handling click even...
std::pair< std::size_t, std::size_t > add_text(config &curr_item, const std::string &text)
wfl::map_formula_callable setup_text_renderer(config text_cfg, unsigned width=0) const
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 default_text_config(config *txt_ptr, const point &pos, const int max_width, const t_string &text="")
Create template for text config that can be shown in canvas.
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
void update_mouse_cursor(bool enable)
Implementation detail for (re)setting the hyperlink cursor.
std::size_t get_split_location(std::string_view text, const point &pos)
std::pair< std::size_t, std::size_t > add_text_with_attribute(config &curr_item, const std::string &text, const std::string &attr_name="", const std::string &extra_data="")
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.
color_t string_to_color(const std::string &color_str)
Return the color the string represents.
std::string sound_button_click
void get_screen_size_variables(wfl::map_formula_callable &variable)
Gets a formula object with the screen size.
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.
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.
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.
std::string to_rgba_string() const
Returns the stored color as an "R,G,B,A" string.
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)