16 #define GETTEXT_DOMAIN "wesnoth-lib"
43 #include <boost/format.hpp>
46 #define DBG_GUI_RL LOG_STREAM(debug, log_rich_label)
48 #define LINK_DEBUG_BORDER false
54 using namespace std::string_literals;
57 const std::array format_tags{
"bold"s,
"b"s,
"italic"s,
"i"s,
"underline"s,
"u"s };
68 , link_aware_(builder.link_aware)
112 auto& attr = curr_item[
"text"];
113 size_t start = attr.str().size();
114 attr = attr.str() + std::move(text);
115 size_t end = attr.str().size();
116 return {
start, end };
123 "end" , end == 0 ? curr_item[
"text"].str().
size() : end,
124 "value" , std::move(extra_data)
129 const auto [
start, end] =
add_text(curr_item, std::move(text));
131 return {
start, end };
136 curr_item[
"name"] = name;
142 if (align ==
"right") {
143 curr_item[
"x"] = floating ?
"(width - image_width - img_x)" :
"(width - image_width - pos_x)";
144 }
else if (align ==
"middle" || align ==
"center") {
146 curr_item[
"x"] = floating ?
"(img_x + (width - image_width)/2.0)" :
"(pos_x + (width - image_width)/2.0)";
149 curr_item[
"x"] = floating ?
"(img_x)" :
"(pos_x)";
151 curr_item[
"y"] = (has_prev_image && floating) ?
"(img_y + pos_y)" :
"(pos_y)";
152 curr_item[
"h"] =
"(image_height)";
153 curr_item[
"w"] =
"(image_width)";
158 if (align ==
"left") {
159 actions <<
"set_var('pos_x', image_width + padding)";
160 }
else if (align ==
"right") {
161 actions <<
"set_var('pos_x', 0)";
163 actions <<
"set_var('ww', image_width + padding)";
166 actions <<
"," <<
"set_var('img_y', img_y + image_height + padding)";
168 actions <<
"set_var('pos_x', pos_x + image_width + padding)";
173 curr_item[
"actions"] =
actions.str();
180 DBG_GUI_RL <<
"add_link: " << name <<
"->" << dest;
184 point t_start, t_end;
190 std::string link_text = name.empty() ? dest : name;
200 if (t_end.x > t_start.x) {
202 links_.emplace_back(link_rect, dest);
204 DBG_GUI_RL <<
"added link at rect: " << link_rect;
208 point t_size(
size_.x - t_start.x - (origin.x == 0 ? img_width : 0), t_end.y - t_start.y);
210 point t_size2(t_end.x, t_end.y - t_start.y);
215 links_.emplace_back(link_rect, dest);
216 links_.emplace_back(link_rect2, dest);
218 DBG_GUI_RL <<
"added link at rect 1: " << link_rect;
219 DBG_GUI_RL <<
"added link at rect 2: " << link_rect2;
226 len = (len > text.size()-1) ? text.size()-1 : len;
230 while(!std::isspace(
c = text[len])) {
241 std::vector<std::string> res;
244 res.push_back(first_line);
245 if(
s.size() > first_line.size()) {
246 res.push_back(
s.substr(first_line.size()));
268 const config& parsed_text,
270 const unsigned init_width,
274 DBG_GUI_RL <<
"Initial width: " << init_width;
278 unsigned prev_blk_height = origin.y;
279 unsigned text_height = 0;
288 config* curr_item =
nullptr;
289 config* remaining_item =
nullptr;
291 bool is_text =
false;
292 bool is_image =
false;
293 bool is_float =
false;
294 bool wrap_mode =
false;
295 bool new_text_block =
false;
304 std::string name =
child[
"src"];
305 std::string align =
child[
"align"];
306 bool is_curr_float =
child[
"float"].to_bool(
false);
308 curr_item = &(text_dom.
add_child(
"image"));
309 add_image(*curr_item, name, align, is_image, is_curr_float);
313 x = (align ==
"left") ? float_size.x : 0;
314 float_size.x = curr_img_size.x +
padding_;
315 float_size.y += curr_img_size.y;
317 img_size.x += curr_img_size.x +
padding_;
319 img_size.y = std::max(img_size.y, curr_img_size.y);
320 if (!is_image || (is_image && is_float)) {
321 prev_blk_height += curr_img_size.y;
322 float_size.y -= curr_img_size.y;
333 is_float = is_curr_float;
335 new_text_block =
true;
337 DBG_GUI_RL <<
"image: src=" << name <<
", size=" << curr_img_size;
338 DBG_GUI_RL <<
"wrap mode: " << wrap_mode <<
", floating: " << is_float;
340 }
else if(key ==
"table") {
341 if (curr_item ==
nullptr) {
342 curr_item = &(text_dom.
add_child(
"text"));
344 new_text_block =
false;
348 img_size =
point(0,0);
349 float_size =
point(0,0);
351 prev_blk_height += text_height;
355 unsigned col_idx = 0;
356 unsigned rows =
child.child_count(
"row");
357 unsigned columns = 1;
359 columns =
child.mandatory_child(
"row").child_count(
"col");
361 columns = (columns == 0) ? 1 : columns;
362 unsigned width =
child[
"width"].to_int(init_width);
364 unsigned row_y = prev_blk_height;
365 unsigned max_row_height = 0;
366 std::vector<unsigned> col_widths(columns, 0);
369 (*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]);
372 new_text_block =
true;
375 DBG_GUI_RL << __LINE__ <<
"start table : " <<
"row= " << rows <<
" col=" << columns <<
" width=" << width;
378 for(
const config& row :
child.child_range(
"row")) {
393 col_widths[col_idx] = std::max(col_widths[col_idx],
static_cast<unsigned>(
size.x));
394 col_widths[col_idx] = std::min(col_widths[col_idx], width/columns);
396 col_x += width/columns;
404 row_y = prev_blk_height;
405 for(
const config& row :
child.child_range(
"row")) {
419 text_dom.
append(std::move(table_elem));
422 max_row_height = std::max(max_row_height,
static_cast<unsigned>(
size.y));
424 col_x += col_widths[col_idx] + 2 *
padding_;
426 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));
431 new_text_block =
true;
439 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]);
440 DBG_GUI_RL <<
"row height: " << max_row_height;
443 prev_blk_height = row_y;
447 end_cfg[
"actions"] = boost::str(
boost::format(
"([set_var('pos_x', 0), set_var('pos_y', %d), set_var('tw', 0)])") % row_y);
457 }
else if(key ==
"break" || key ==
"br") {
458 if (curr_item ==
nullptr) {
459 curr_item = &(text_dom.
add_child(
"text"));
461 new_text_block =
false;
465 if (is_image && !is_float) {
467 (*curr_item)[
"actions"] =
"([set_var('pos_x', 0), set_var('pos_y', pos_y + image_height + padding)])";
474 img_size =
point(0,0);
479 new_text_block =
true;
486 if (!finalize &&
line.empty()) {
491 if (is_image && (!is_float)) {
492 if (!
line.empty() &&
line.at(0) ==
'\n') {
495 (*curr_item)[
"actions"] =
"([set_var('pos_x', 0), set_var('pos_y', pos_y + image_height + padding)])";
497 }
else if (!
line.empty() &&
line.at(0) !=
'\n') {
500 if (!parts.front().empty()) {
501 line = parts.front();
504 std::string& part2 = parts.back();
505 if (!part2.empty() && parts.size() > 1) {
506 if (part2[0] ==
'\n') {
507 part2 = part2.substr(1);
510 part2_cfg.
add_child(
"text")[
"text"] = parts.back();
512 remaining_item = &part2_cfg;
515 if (parts.size() == 1) {
516 prev_blk_height -= img_size.y;
519 prev_blk_height -= img_size.y;
523 if (curr_item ==
nullptr || new_text_block) {
524 if (curr_item !=
nullptr) {
526 prev_blk_height += text_height;
530 curr_item = &(text_dom.
add_child(
"text"));
532 new_text_block =
false;
536 int tmp_h =
get_text_size(*curr_item, init_width - (x == 0 ? float_size.x : x)).y;
538 if (is_text && key ==
"text") {
550 }
else if(std::find(format_tags.begin(), format_tags.end(), key) != format_tags.end()) {
555 for (
const auto [parsed_key, parsed_cfg] : parsed_children.
all_children_view()) {
556 if (parsed_key ==
"text") {
557 const auto [
start, end] =
add_text(*curr_item, parsed_cfg[
"text"]);
559 add_attribute(*curr_item, attr[
"name"],
start + attr[
"start"].to_int(),
start + attr[
"end"].to_int(), attr[
"value"]);
563 text_dom.
add_child(parsed_key, parsed_cfg);
571 }
else if(key ==
"header" || key ==
"h") {
582 }
else if(key ==
"character_entity") {
593 }
else if(key ==
"span" || key ==
"format") {
599 for (
const auto& [key, value] :
child.attribute_range()) {
608 }
else if (key ==
"text") {
614 point text_size =
get_text_size(*curr_item, init_width - (x == 0 ? float_size.x : x));
619 if (wrap_mode && (float_size.y > 0) && (text_size.y > float_size.y)) {
623 DBG_GUI_RL <<
"wrap around area: " << float_size;
626 std::string removed_part = (*curr_item)[
"text"].str().substr(len+1);
627 (*curr_item)[
"text"] = (*curr_item)[
"text"].str().substr(0, len);
628 (*curr_item)[
"maximum_width"] = init_width - float_size.x;
632 int ah =
get_text_size(*curr_item, init_width - float_size.x).y;
636 text_height += ah - tmp_h;
640 DBG_GUI_RL <<
"wrap: " << prev_blk_height <<
"," << text_height;
648 curr_item = &(text_dom.
add_child(
"text"));
653 }
else if ((float_size.y > 0) && (text_size.y < float_size.y)) {
657 (*curr_item)[
"actions"] =
"([set_var('pos_y', pos_y + text_height)])";
661 float_size =
point(0,0);
673 w = std::max(
w, x +
static_cast<unsigned>(
size.x));
675 text_height += ah - tmp_h;
677 if (remaining_item) {
679 (*curr_item)[
"actions"] =
"([set_var('pos_x', 0), set_var('pos_y', pos_y + " + std::to_string(img_size.y) +
")])";
680 text_dom.
append(*remaining_item);
681 remaining_item =
nullptr;
686 if (!is_image && !wrap_mode && img_size.y > 0) {
687 img_size =
point(0,0);
694 DBG_GUI_RL <<
"Prev block height: " << prev_blk_height <<
" Current text block height: " << text_height;
696 h = text_height + prev_blk_height;
708 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)])";
713 #if LINK_DEBUG_BORDER
715 for (
const auto& entry :
links_) {
717 link_rect_cfg[
"x"] = entry.first.x;
718 link_rect_cfg[
"y"] = entry.first.y;
719 link_rect_cfg[
"w"] = entry.first.w;
720 link_rect_cfg[
"h"] = entry.first.h;
721 link_rect_cfg[
"border_thickness"] = 1;
722 link_rect_cfg[
"border_color"] =
"255, 180, 0, 255";
728 h = std::max(
static_cast<unsigned>(img_size.y),
h);
730 DBG_GUI_RL <<
"Width: " <<
w <<
" Height: " <<
h <<
" Origin: " << origin;
731 return { text_dom,
point(
w,
h - origin.y) };
735 if (txt_ptr !=
nullptr) {
736 (*txt_ptr)[
"text"] = text;
739 (*txt_ptr)[
"x"] =
"(pos_x)";
740 (*txt_ptr)[
"y"] =
"(pos_y)";
741 (*txt_ptr)[
"w"] =
"(text_width)";
742 (*txt_ptr)[
"h"] =
"(text_height)";
746 (*txt_ptr)[
"maximum_width"] =
"(width - pos_x - ww - tw)";
747 (*txt_ptr)[
"actions"] =
"([set_var('pos_x', 0), set_var('pos_y', pos_y + text_height)])";
763 tmp.set_variable(
"text_wrap_mode",
wfl::variant(PANGO_ELLIPSIZE_NONE));
817 connect_signal<event::LEFT_BUTTON_CLICK>(
819 connect_signal<event::MOUSE_MOTION>(
821 connect_signal<event::MOUSE_LEAVE>(
840 for (
const auto& entry :
links_) {
843 if (entry.first.contains(mouse)) {
844 DBG_GUI_RL <<
"Clicked link! dst = " << entry.second;
849 DBG_GUI_RL <<
"No registered link handler found";
868 for (
const auto& entry :
links_) {
869 if (entry.first.contains(mouse)) {
913 load_resolutions<resolution>(cfg);
918 , link_color(cfg[
"link_color"].empty() ?
font::
YELLOW_COLOR :
color_t::from_rgba_string(cfg[
"link_color"].str()))
931 builder_rich_label::builder_rich_label(
const config& cfg)
934 , link_aware(cfg[
"link_aware"].to_bool(true))
935 , width(cfg[
"width"], 500)
941 auto lbl = std::make_unique<rich_label>(*
this);
947 lbl->set_link_color(conf->link_color);
948 lbl->set_font_size(conf->font_size);
949 lbl->set_label(lbl->get_label());
951 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.
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.
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)
int font_size_
Base font size.
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
std::vector< std::string > split_in_width(const std::string &s, const int font_size, const unsigned width)
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 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.
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
Thrown by operations encountering invalid UTF-8 data.
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.
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 pango_word_wrap(const std::string &unwrapped_text, int font_size, int max_width, int max_height, int max_lines, bool)
Uses Pango to word wrap text.
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.
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")
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.
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.
Thrown when the help system fails to parse something.
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)