16 #define GETTEXT_DOMAIN "wesnoth-lib"
41 #include <boost/format.hpp>
44 #define DBG_GUI_RL LOG_STREAM(debug, log_rich_label)
46 #define LINK_DEBUG_BORDER false
52 using namespace std::string_literals;
55 const std::array format_tags{
"bold"s,
"b"s,
"italic"s,
"i"s,
"underline"s,
"u"s };
66 , link_aware_(builder.link_aware)
75 connect_signal<event::LEFT_BUTTON_CLICK>(
77 connect_signal<event::MOUSE_MOTION>(
79 connect_signal<event::MOUSE_LEAVE>(
115 auto& attr = curr_item[
"text"];
116 size_t start = attr.str().size();
117 attr = attr.str() + std::move(text);
118 size_t end = attr.str().size();
119 return {
start, end };
126 "end" , end == 0 ? curr_item[
"text"].str().
size() : end,
127 "value" , std::move(extra_data)
132 const auto [
start, end] =
add_text(curr_item, std::move(text));
134 return {
start, end };
139 curr_item[
"name"] = name;
145 if (align ==
"right") {
146 curr_item[
"x"] = floating ?
"(width - image_width - img_x)" :
"(width - image_width - pos_x)";
147 }
else if (align ==
"middle" || align ==
"center") {
149 curr_item[
"x"] = floating ?
"(img_x + (width - image_width)/2.0)" :
"(pos_x + (width - image_width)/2.0)";
152 curr_item[
"x"] = floating ?
"(img_x)" :
"(pos_x)";
154 curr_item[
"y"] = (has_prev_image && floating) ?
"(img_y + pos_y)" :
"(pos_y)";
155 curr_item[
"h"] =
"(image_height)";
156 curr_item[
"w"] =
"(image_width)";
161 if (align ==
"left") {
162 actions <<
"set_var('pos_x', image_width + padding)";
163 }
else if (align ==
"right") {
164 actions <<
"set_var('pos_x', 0)";
166 actions <<
"set_var('ww', image_width + padding)";
169 actions <<
"," <<
"set_var('img_y', img_y + image_height + padding)";
171 actions <<
"set_var('pos_x', pos_x + image_width + padding)";
176 curr_item[
"actions"] =
actions.str();
183 DBG_GUI_RL <<
"add_link: " << name <<
"->" << dest;
187 point t_start, t_end;
193 std::string link_text = name.empty() ? dest : name;
203 if (t_end.x > t_start.x) {
205 links_.emplace_back(link_rect, dest);
207 DBG_GUI_RL <<
"added link at rect: " << link_rect;
211 point t_size(
size_.x - t_start.x - (origin.x == 0 ? img_width : 0), t_end.y - t_start.y);
213 point t_size2(t_end.x, t_end.y - t_start.y);
218 links_.emplace_back(link_rect, dest);
219 links_.emplace_back(link_rect2, dest);
221 DBG_GUI_RL <<
"added link at rect 1: " << link_rect;
222 DBG_GUI_RL <<
"added link at rect 2: " << link_rect2;
229 len = (len > text.size()-1) ? text.size()-1 : len;
233 while(!std::isspace(
c = text[len])) {
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;
276 config* curr_item =
nullptr;
277 config* remaining_item =
nullptr;
279 bool is_text =
false;
280 bool is_image =
false;
281 bool is_float =
false;
282 bool wrap_mode =
false;
283 bool new_text_block =
false;
292 std::string name =
child[
"src"];
293 std::string align =
child[
"align"];
294 bool is_curr_float =
child[
"float"].to_bool(
false);
296 curr_item = &(text_dom.
add_child(
"image"));
297 add_image(*curr_item, name, align, is_image, is_curr_float);
301 x = (align ==
"left") ? float_size.x : 0;
302 float_size.x = curr_img_size.x +
padding_;
303 float_size.y += curr_img_size.y;
305 img_size.x += curr_img_size.x +
padding_;
307 img_size.y = std::max(img_size.y, curr_img_size.y);
308 if (!is_image || (is_image && is_float)) {
309 prev_blk_height += curr_img_size.y;
310 float_size.y -= curr_img_size.y;
321 is_float = is_curr_float;
323 new_text_block =
true;
325 DBG_GUI_RL <<
"image: src=" << name <<
", size=" << curr_img_size;
326 DBG_GUI_RL <<
"wrap mode: " << wrap_mode <<
", floating: " << is_float;
328 }
else if(key ==
"table") {
329 if (curr_item ==
nullptr) {
330 curr_item = &(text_dom.
add_child(
"text"));
332 new_text_block =
false;
336 img_size =
point(0,0);
337 float_size =
point(0,0);
339 prev_blk_height += text_height;
343 unsigned col_idx = 0, row_idx = 0;
344 unsigned rows =
child.child_count(
"row");
345 unsigned columns = 1;
347 columns =
child.mandatory_child(
"row").child_count(
"col");
349 columns = (columns == 0) ? 1 : columns;
350 unsigned width =
child[
"width"].to_int(init_width);
352 unsigned row_y = prev_blk_height;
353 unsigned max_row_height = 0;
354 std::vector<unsigned> col_widths(columns, 0);
357 (*curr_item)[
"actions"] = boost::str(
boost::format(
"([set_var('pos_x', 0), set_var('pos_y', %d), set_var('tw', width - pos_x - %d)])") % row_y % col_widths[col_idx]);
360 new_text_block =
true;
363 DBG_GUI_RL << __LINE__ <<
"start table : " <<
"row= " << rows <<
" col=" << columns <<
" width=" << width;
366 for(
const config& row :
child.child_range(
"row")) {
379 col_widths[col_idx] = std::max(col_widths[col_idx],
static_cast<unsigned>(
size.x));
380 col_widths[col_idx] = std::min(col_widths[col_idx], width/columns);
382 col_x += width/columns;
391 row_y = prev_blk_height;
393 for(
const config& row :
child.child_range(
"row")) {
407 text_dom.
append(std::move(table_elem));
410 max_row_height = std::max(max_row_height,
static_cast<unsigned>(
size.y));
412 col_x += col_widths[col_idx] + 2 *
padding_;
414 end_cfg[
"actions"] = boost::str(
boost::format(
"([set_var('pos_x', %d), set_var('pos_y', %d), set_var('tw', width - %d - %d)])") % col_x % row_y % col_x % (width/columns));
419 new_text_block =
true;
427 end_cfg[
"actions"] = boost::str(
boost::format(
"([set_var('pos_x', 0), set_var('pos_y', %d), set_var('tw', width - %d - %d)])") % row_y % col_x % col_widths[columns-1]);
428 DBG_GUI_RL <<
"row height: " << max_row_height;
431 prev_blk_height = row_y;
435 end_cfg[
"actions"] = boost::str(
boost::format(
"([set_var('pos_x', 0), set_var('pos_y', %d), set_var('tw', 0)])") % row_y);
445 }
else if(key ==
"break" || key ==
"br") {
446 if (curr_item ==
nullptr) {
447 curr_item = &(text_dom.
add_child(
"text"));
449 new_text_block =
false;
453 if (is_image && !is_float) {
455 (*curr_item)[
"actions"] =
"([set_var('pos_x', 0), set_var('pos_y', pos_y + image_height + padding)])";
462 img_size =
point(0,0);
467 new_text_block =
true;
474 if (!finalize &&
line.empty()) {
479 if (is_image && (!is_float)) {
480 if (!
line.empty() &&
line.at(0) ==
'\n') {
483 (*curr_item)[
"actions"] =
"([set_var('pos_x', 0), set_var('pos_y', pos_y + image_height + padding)])";
485 }
else if (!
line.empty() &&
line.at(0) !=
'\n') {
488 if (!parts.front().empty()) {
489 line = parts.front();
492 std::string& part2 = parts.back();
493 if (!part2.empty() && parts.size() > 1) {
494 if (part2[0] ==
'\n') {
495 part2 = part2.substr(1);
498 part2_cfg.
add_child(
"text")[
"text"] = parts.back();
500 remaining_item = &part2_cfg;
503 if (parts.size() == 1) {
504 prev_blk_height -= img_size.y;
507 prev_blk_height -= img_size.y;
511 if (curr_item ==
nullptr || new_text_block) {
512 if (curr_item !=
nullptr) {
514 prev_blk_height += text_height;
518 curr_item = &(text_dom.
add_child(
"text"));
520 new_text_block =
false;
524 int tmp_h =
get_text_size(*curr_item, init_width - (x == 0 ? float_size.x : x)).y;
526 if (is_text && key ==
"text") {
538 }
else if(std::find(format_tags.begin(), format_tags.end(), key) != format_tags.end()) {
544 if (parsed_key ==
"text") {
545 const auto [
start, end] =
add_text(*curr_item, parsed_cfg[
"text"]);
547 add_attribute(*curr_item, attr[
"name"],
start + attr[
"start"].to_int(),
start + attr[
"end"].to_int(), attr[
"value"]);
551 text_dom.
add_child(parsed_key, parsed_cfg);
559 }
else if(key ==
"header" || key ==
"h") {
570 }
else if(key ==
"character_entity") {
581 }
else if(key ==
"span" || key ==
"format") {
587 for (
const auto& [key, value] :
child.attribute_range()) {
596 }
else if (key ==
"text") {
602 point text_size =
get_text_size(*curr_item, init_width - (x == 0 ? float_size.x : x));
607 if (wrap_mode && (float_size.y > 0) && (text_size.y > float_size.y)) {
611 DBG_GUI_RL <<
"wrap around area: " << float_size;
614 std::string removed_part = (*curr_item)[
"text"].str().substr(len+1);
615 (*curr_item)[
"text"] = (*curr_item)[
"text"].str().substr(0, len);
616 (*curr_item)[
"maximum_width"] = init_width - float_size.x;
620 int ah =
get_text_size(*curr_item, init_width - float_size.x).y;
624 text_height += ah - tmp_h;
628 DBG_GUI_RL <<
"wrap: " << prev_blk_height <<
"," << text_height;
636 curr_item = &(text_dom.
add_child(
"text"));
641 }
else if ((float_size.y > 0) && (text_size.y < float_size.y)) {
645 (*curr_item)[
"actions"] =
"([set_var('pos_y', pos_y + text_height)])";
649 float_size =
point(0,0);
661 w = std::max(
w, x +
static_cast<unsigned>(
size.x));
663 text_height += ah - tmp_h;
665 if (remaining_item) {
667 (*curr_item)[
"actions"] =
"([set_var('pos_x', 0), set_var('pos_y', pos_y + " + std::to_string(img_size.y) +
")])";
668 text_dom.
append(*remaining_item);
669 remaining_item =
nullptr;
674 if (!is_image && !wrap_mode && img_size.y > 0) {
675 img_size =
point(0,0);
682 DBG_GUI_RL <<
"Prev block height: " << prev_blk_height <<
" Current text block height: " << text_height;
684 h = text_height + prev_blk_height;
696 break_cfg[
"actions"] =
"([set_var('pos_x', 0), set_var('pos_y', 0), set_var('img_x', 0), set_var('img_y', 0), set_var('ww', 0), set_var('tw', 0)])";
701 #if LINK_DEBUG_BORDER
703 for (
const auto& entry :
links_) {
705 link_rect_cfg[
"x"] = entry.first.x;
706 link_rect_cfg[
"y"] = entry.first.y;
707 link_rect_cfg[
"w"] = entry.first.w;
708 link_rect_cfg[
"h"] = entry.first.h;
709 link_rect_cfg[
"border_thickness"] = 1;
710 link_rect_cfg[
"border_color"] =
"255, 180, 0, 255";
716 h = std::max(
static_cast<unsigned>(img_size.y),
h);
718 DBG_GUI_RL <<
"Width: " <<
w <<
" Height: " <<
h <<
" Origin: " << origin;
719 return { text_dom,
point(
w,
h - origin.y) };
723 if (txt_ptr !=
nullptr) {
724 (*txt_ptr)[
"text"] = text;
727 (*txt_ptr)[
"x"] =
"(pos_x)";
728 (*txt_ptr)[
"y"] =
"(pos_y)";
729 (*txt_ptr)[
"w"] =
"(text_width)";
730 (*txt_ptr)[
"h"] =
"(text_height)";
734 (*txt_ptr)[
"maximum_width"] =
"(width - pos_x - ww - tw)";
735 (*txt_ptr)[
"actions"] =
"([set_var('pos_y', pos_y + text_height)])";
751 tmp.set_variable(
"text_wrap_mode",
wfl::variant(PANGO_ELLIPSIZE_NONE));
812 for (
const auto& entry :
links_) {
815 if (entry.first.contains(mouse)) {
816 DBG_GUI_RL <<
"Clicked link! dst = " << entry.second;
821 DBG_GUI_RL <<
"No registered link handler found";
840 for (
const auto& entry :
links_) {
841 if (entry.first.contains(mouse)) {
885 load_resolutions<resolution>(cfg);
890 , link_color(cfg[
"link_color"].empty() ?
font::
YELLOW_COLOR :
color_t::from_rgba_string(cfg[
"link_color"].str()))
902 builder_rich_label::builder_rich_label(
const config& cfg)
905 , link_aware(cfg[
"link_aware"].to_bool(true))
906 , width(cfg[
"width"], 500)
912 auto lbl = std::make_unique<rich_label>(*
this);
918 lbl->set_link_color(conf->link_color);
919 lbl->set_label(lbl->get_label());
921 DBG_GUI_G <<
"Window builder: placed rich_label '" <<
id <<
"' with definition '"
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.
const_all_children_itors all_children_range() 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.
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 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)
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).
std::pair< size_t, size_t > add_text(config &curr_item, std::string text)
void default_text_config(config *txt_ptr, t_string text="")
Create template for text config that can be shown in canvas.
t_string unparsed_text_
The unparsed/raw 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
void add_link(config &curr_item, std::string name, std::string dest, const point &origin, int img_width)
std::pair< size_t, size_t > add_text_with_attribute(config &curr_item, std::string text, std::string attr_name="", std::string extra_data="")
point get_image_size(config &img_cfg) const
void add_attribute(config &curr_item, std::string attr_name, size_t start=0, size_t end=0, std::string extra_data="")
unsigned short text_alpha_
point get_xy_from_offset(const unsigned offset) const
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.
const unsigned init_w_
Width and height of the canvas.
state_t
Possible states of the widget.
void signal_handler_left_button_click(bool &handled)
Left click signal handler: checks if we clicked on a hyperlink.
void set_topic(const help::topic *topic)
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...
void add_image(config &curr_item, std::string name, std::string align, bool has_prev_image, bool floating)
wfl::map_formula_callable setup_text_renderer(config text_cfg, unsigned width=0) const
void set_link_color(const color_t &color)
unsigned padding_
Padding.
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 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.
config text_dom_
structure tree of the marked up text after parsing
The text displayed in a topic.
const config & parsed_text() const
constexpr uint8_t ALPHA_OPAQUE
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.
Collection of helper functions relating to Pango formatting.
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.
const color_t YELLOW_COLOR
color_t string_to_color(const std::string &cmp_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 text alignment to its string representation.
std::vector< std::string > split_in_width(const std::string &s, const int font_size, const unsigned width)
Make a best effort to word wrap s.
Contains the implementation details for lexical_cast and shouldn't be used directly.
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(const std::string &str)
Length in characters of a UTF-8 string.
int get_pixel_scale()
Get the current active pixel scale multiplier.
Desktop environment interaction functions.
static lg::log_domain log_rich_label("gui/widget/rich_label")
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.
virtual std::unique_ptr< widget > build() const override
PangoAlignment text_alignment
std::vector< state_definition > state
resolution(const config &cfg)
rich_label_definition(const config &cfg)
A topic contains a title, an id and some text.
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)