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 size_t start = attr.str().size();
136 attr = attr.str() + text;
137 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;
156 cfg[
"start"] =
start;
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])) {
261 const config& parsed_text,
263 const unsigned init_width,
267 DBG_GUI_RL <<
"Initial width: " << init_width;
271 unsigned prev_blk_height = origin.y;
272 unsigned text_height = 0;
281 config* curr_item =
nullptr;
283 bool is_text =
false;
284 bool is_image =
false;
285 bool wrap_mode =
false;
286 bool new_text_block =
false;
289 point float_pos, float_size;
296 const std::string key = (orig_key ==
"img" && !
child[
"float"].to_bool(
false)) ?
"inline_image" : orig_key;
298 DBG_GUI_RL <<
"\n Trying to layout tag: " << key;
301 prev_blk_height += text_height;
304 const std::string& align =
child[
"align"].str(
"left");
306 curr_item = &(text_dom.
add_child(
"image"));
307 (*curr_item)[
"name"] =
child[
"src"];
308 (*curr_item)[
"x"] = 0;
309 (*curr_item)[
"y"] = 0;
310 (*curr_item)[
"w"] =
"(image_width)";
311 (*curr_item)[
"h"] =
"(image_height)";
315 if (align ==
"right") {
316 float_pos.x = init_width - curr_img_size.x;
317 }
else if (align ==
"middle" || align ==
"center") {
319 float_pos.x = float_size.x + (init_width - curr_img_size.x)/2;
323 float_pos.y += float_size.y;
326 (*curr_item)[
"x"] = float_pos.x;
327 (*curr_item)[
"y"] = pos.y + float_pos.y;
329 float_size.x = curr_img_size.x +
padding_;
330 float_size.y += curr_img_size.y +
padding_;
332 x = ((align ==
"left") ? float_size.x : 0);
333 pos.x += ((align ==
"left") ? float_size.x : 0);
341 new_text_block =
true;
343 DBG_GUI_RL << key <<
": src=" <<
child[
"src"] <<
", size=" << img_size;
345 }
else if(key ==
"clear") {
348 prev_blk_height += float_size.y;
349 pos.y += float_size.y;
350 float_size =
point(0, 0);
355 }
else if(key ==
"table") {
356 if(curr_item ==
nullptr) {
357 curr_item = &(text_dom.
add_child(
"text"));
359 new_text_block =
false;
363 img_size =
point(0,0);
364 float_size =
point(0,0);
366 prev_blk_height += text_height +
padding_;
371 unsigned col_idx = 0, row_idx = 0;
372 unsigned rows =
child.child_count(
"row");
373 unsigned columns = 1;
375 columns =
child.mandatory_child(
"row").child_count(
"col");
377 columns = (columns == 0) ? 1 : columns;
379 if(
child[
"width"] ==
"fill") {
380 init_cell_width = init_width/columns;
382 init_cell_width =
child[
"width"].to_int(init_width)/columns;
384 std::vector<int> col_widths(columns, 0);
385 std::vector<int> row_heights(rows, 0);
388 new_text_block =
true;
391 DBG_GUI_RL <<
"start table : " <<
"row= " << rows <<
" col=" << columns
392 <<
" width=" << init_cell_width*columns;
399 if(paddings.size() == 1) {
407 std::array<int, 2> row_paddings;
408 boost::multi_array<point, 2> cell_sizes(boost::extents[rows][columns]);
411 for(
const config& row :
child.child_range(
"row")) {
416 row_paddings = get_padding(row[
"padding"]);
418 pos.y += row_paddings[0];
420 DBG_GUI_RL <<
"table cell origin (pre-layout): " << pos.x <<
", " << pos.y;
427 std::array<int, 2> col_paddings = get_padding(col[
"padding"]);
428 int cell_width = init_cell_width - col_paddings[0] - col_paddings[1];
430 pos.x += col_paddings[0];
433 cell_sizes[row_idx][col_idx] =
get_parsed_text(col_cfg, pos, init_cell_width).second;
437 row_heights[row_idx] = std::max(row_heights[row_idx], cell_sizes[row_idx][col_idx].y);
438 if(!
child[
"width"].empty()) {
439 col_widths[col_idx] = cell_width;
441 col_widths[col_idx] = std::max(col_widths[col_idx], cell_sizes[row_idx][col_idx].x);
442 if(
child[
"width"].empty()) {
443 col_widths[col_idx] = std::min(col_widths[col_idx], cell_width);
446 DBG_GUI_RL <<
"table row " << row_idx <<
" height: " << row_heights[row_idx]
447 <<
"col " << col_idx <<
" width: " << col_widths[col_idx];
450 pos.x += col_paddings[1];
454 pos.y += row_heights[row_idx] + row_paddings[1];
460 pos =
point(origin.x, prev_blk_height);
461 for(
const config& row :
child.child_range(
"row")) {
465 if(!row[
"bgcolor"].
blank()) {
468 bgbox[
"x"] = origin.x;
470 bgbox[
"w"] = std::accumulate(col_widths.begin(), col_widths.end(), 0) + 2*(row_paddings[0] + row_paddings[1])*columns;
471 bgbox[
"h"] = row_paddings[0] + row_heights[row_idx] + row_paddings[1];
473 text_dom.
append(std::move(bg_base));
476 pos.y += row_paddings[0];
479 DBG_GUI_RL <<
"table row " << row_idx <<
" height: " << row_heights[row_idx]
480 <<
"col " << col_idx <<
" width: " << col_widths[col_idx];
489 std::array<int, 2> col_paddings = get_padding(col[
"padding"]);
491 pos.x += col_paddings[0];
493 const std::string& valign = row[
"valign"].str(
"center");
494 const std::string& halign = col[
"halign"].str(
"left");
498 if (valign ==
"center" || valign ==
"middle") {
499 text_pos.y += (row_heights[row_idx] - cell_sizes[row_idx][col_idx].y)/2;
500 }
else if (valign ==
"bottom") {
501 text_pos.y += row_heights[row_idx] - cell_sizes[row_idx][col_idx].y;
503 if (halign ==
"center" || halign ==
"middle") {
504 text_pos.x += (col_widths[col_idx] - cell_sizes[row_idx][col_idx].x)/2;
505 }
else if (halign ==
"right") {
506 text_pos.x += col_widths[col_idx] - cell_sizes[row_idx][col_idx].x;
511 text_dom.
append(std::move(table_elem));
512 pos.x += col_widths[col_idx];
513 pos.x += col_paddings[1];
516 end_cfg[
"maximum_width"] = col_widths[col_idx];
521 new_text_block =
true;
527 pos.y += row_heights[row_idx];
528 pos.y += row_paddings[1];
529 DBG_GUI_RL <<
"row height: " << row_heights[row_idx];
533 w = std::max(
w,
static_cast<unsigned>(pos.x));
534 prev_blk_height = pos.y;
546 if (!finalize && (
line.empty() && key ==
"text")) {
550 if (curr_item ==
nullptr || new_text_block) {
551 curr_item = &(text_dom.
add_child(
"text"));
553 new_text_block =
false;
557 int tmp_h =
get_text_size(*curr_item, init_width - (x == 0 ? float_size.x : x)).y;
559 if(is_text && key ==
"text") {
566 if (key ==
"inline_image") {
577 }
else if(key ==
"ref") {
583 }
else if(
std::find(format_tags.begin(), format_tags.end(), key) != format_tags.end()) {
590 if(parsed_key ==
"text") {
591 const auto [
start, end] =
add_text(*curr_item, parsed_cfg[
"text"]);
593 add_attribute(*curr_item, attr[
"name"], attr[
"value"],
start + attr[
"start"].to_int(),
start + attr[
"end"].to_int());
597 text_dom.
add_child(parsed_key, parsed_cfg);
603 }
else if(key ==
"header" || key ==
"h") {
612 }
else if(key ==
"character_entity") {
622 }
else if(key ==
"span" || key ==
"format") {
628 for (
const auto& [key, value] :
child.attribute_range()) {
635 }
else if (key ==
"text") {
641 point text_size =
get_text_size(*curr_item, init_width - (x == 0 ? float_size.x : x));
645 if(wrap_mode && (float_size.y > 0) && (text_size.y > float_size.y)) {
649 DBG_GUI_RL <<
"wrap around area: " << float_size;
651 std::string removed_part = (*curr_item)[
"text"].str().substr(len+1);
656 (*curr_item)[
"text"] = (*curr_item)[
"text"].str().substr(0, len);
657 (*curr_item)[
"maximum_width"] = init_width - float_size.x;
658 float_size =
point(0,0);
661 int ah =
get_text_size(*curr_item, init_width - float_size.x).y;
665 text_height += ah - tmp_h;
666 prev_blk_height += text_height;
667 pos =
point(origin.x, prev_blk_height);
669 DBG_GUI_RL <<
"wrap: " << prev_blk_height <<
"," << text_height;
677 curr_item = &(text_dom.
add_child(
"text"));
682 }
else if((float_size.y > 0) && (text_size.y < float_size.y)) {
686 pos.y += text_size.y;
690 float_size =
point(0,0);
699 w = std::max(
w, x +
static_cast<unsigned>(
size.x));
701 text_height +=
size.y - tmp_h;
702 pos.y +=
size.y - tmp_h;
705 if(!is_image && !wrap_mode && img_size.y > 0) {
706 img_size =
point(0,0);
713 DBG_GUI_RL <<
"Prev block height: " << prev_blk_height <<
" Current text block height: " << text_height;
715 h = text_height + prev_blk_height;
724 #if LINK_DEBUG_BORDER
726 for(
const auto& entry :
links_) {
728 link_rect_cfg[
"x"] = entry.first.x;
729 link_rect_cfg[
"y"] = entry.first.y;
730 link_rect_cfg[
"w"] = entry.first.w;
731 link_rect_cfg[
"h"] = entry.first.h;
732 link_rect_cfg[
"border_thickness"] = 1;
733 link_rect_cfg[
"border_color"] =
"255, 180, 0, 255";
739 h = std::max(
static_cast<unsigned>(img_size.y),
h);
743 DBG_GUI_RL <<
"Width: " <<
w <<
" Height: " <<
h <<
" Origin: " << origin;
744 return { text_dom,
point(
w,
h - origin.y) };
753 if(txt_ptr !=
nullptr) {
754 (*txt_ptr)[
"text"] = text;
760 (*txt_ptr)[
"line_spacing"] = 0;
761 (*txt_ptr)[
"x"] = pos.x;
762 (*txt_ptr)[
"y"] = pos.y;
763 (*txt_ptr)[
"w"] =
"(text_width)";
764 (*txt_ptr)[
"h"] =
"(text_height)";
765 (*txt_ptr)[
"maximum_width"] = max_width;
766 (*txt_ptr)[
"parse_text_as_formula"] =
false;
780 tmp.set_variable(
"text_wrap_mode",
wfl::variant(PANGO_ELLIPSIZE_NONE));
833 connect_signal<event::LEFT_BUTTON_CLICK>(
835 connect_signal<event::MOUSE_MOTION>(
837 connect_signal<event::MOUSE_LEAVE>(
856 for(
const auto& entry :
links_) {
859 if(entry.first.contains(mouse)) {
860 DBG_GUI_RL <<
"Clicked link! dst = " << entry.second;
865 DBG_GUI_RL <<
"No registered link handler found";
884 for(
const auto& entry :
links_) {
885 if(entry.first.contains(mouse)) {
929 load_resolutions<resolution>(cfg);
934 , text_color_enabled(
color_t::from_rgba_string(cfg[
"text_font_color_enabled"].str()))
935 , text_color_disabled(
color_t::from_rgba_string(cfg[
"text_font_color_disabled"].str()))
936 , link_color(cfg[
"link_color"].empty() ?
font::
YELLOW_COLOR :
color_t::from_rgba_string(cfg[
"link_color"].str()))
937 , font_family(cfg[
"text_font_family"].str())
939 , font_style(cfg[
"text_font_style"].str(
"normal"))
943 for(
const auto& [name, value] : colors_cfg->attribute_range()) {
958 builder_rich_label::builder_rich_label(
const config& cfg)
961 , link_aware(cfg[
"link_aware"].to_bool(true))
962 , width(cfg[
"width"], 500)
963 , padding(cfg[
"padding"].to_int(5))
969 DBG_GUI_G <<
"Window builder: placed rich_label '" <<
id <<
"' with definition '"
972 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)
size_t get_split_location(std::string_view text, const point &pos)
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_
std::pair< size_t, size_t > add_text_with_attribute(config &curr_item, const std::string &text, const std::string &attr_name="", const std::string &extra_data="")
virtual void update_canvas() override
Updates the canvas(ses).
std::pair< size_t, size_t > add_text(config &curr_item, const std::string &text)
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
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.
void add_attribute(config &curr_item, const std::string &attr_name, const std::string &extra_data="", size_t start=0, size_t end=0)
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...
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.
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:
std::vector< std::string > split(const config_attribute_value &val)
auto * find(Container &container, const Value &value)
Convenience wrapper for using find on a container without needing to comare to end()
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)