16 #define GETTEXT_DOMAIN "wesnoth-lib"
41 #include <boost/format.hpp>
47 #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)
113 auto& attr = curr_item[
"text"];
114 size_t start = attr.str().size();
115 attr = attr.str() + std::move(text);
116 size_t end = attr.str().size();
117 return {
start, end };
124 "end" , end == 0 ? curr_item[
"text"].str().
size() : end,
125 "value" , std::move(extra_data)
130 const auto [
start, end] =
add_text(curr_item, std::move(text));
132 return {
start, end };
137 curr_item[
"name"] = name;
143 if (align ==
"right") {
144 curr_item[
"x"] = floating ?
"(width - image_width - img_x)" :
"(width - image_width - pos_x)";
145 }
else if (align ==
"middle" || align ==
"center") {
147 curr_item[
"x"] = floating ?
"(img_x + (width - image_width)/2.0)" :
"(pos_x + (width - image_width)/2.0)";
150 curr_item[
"x"] = floating ?
"(img_x)" :
"(pos_x)";
152 curr_item[
"y"] = (has_prev_image && floating) ?
"(img_y + pos_y)" :
"(pos_y)";
153 curr_item[
"h"] =
"(image_height)";
154 curr_item[
"w"] =
"(image_width)";
159 if (align ==
"left") {
160 actions <<
"set_var('pos_x', image_width + padding)";
161 }
else if (align ==
"right") {
162 actions <<
"set_var('pos_x', 0)";
164 actions <<
"set_var('ww', image_width + padding)";
167 actions <<
"," <<
"set_var('img_y', img_y + image_height + padding)";
169 actions <<
"set_var('pos_x', pos_x + image_width + padding)";
174 curr_item[
"actions"] =
actions.str();
181 DBG_GUI_RL <<
"add_link: " << name <<
"->" << dest;
185 point t_start, t_end;
191 std::string link_text = name.empty() ? dest : name;
201 if (t_end.x > t_start.x) {
203 links_.emplace_back(link_rect, dest);
205 DBG_GUI_RL <<
"added link at rect: " << link_rect;
209 point t_size(
size_.x - t_start.x - (origin.x == 0 ? img_width : 0), t_end.y - t_start.y);
211 point t_size2(t_end.x, t_end.y - t_start.y);
216 links_.emplace_back(link_rect, dest);
217 links_.emplace_back(link_rect2, dest);
219 DBG_GUI_RL <<
"added link at rect 1: " << link_rect;
220 DBG_GUI_RL <<
"added link at rect 2: " << link_rect2;
227 len = (len > text.size()-1) ? text.size()-1 : len;
231 while(!std::isspace(
c = text[len])) {
242 std::vector<std::string> res;
245 res.push_back(first_line);
246 if(
s.size() > first_line.size()) {
247 res.push_back(
s.substr(first_line.size()));
269 const config& parsed_text,
271 const unsigned init_width,
275 DBG_GUI_RL <<
"Initial width: " << init_width;
279 unsigned prev_blk_height = origin.y;
280 unsigned text_height = 0;
289 config* curr_item =
nullptr;
290 config* remaining_item =
nullptr;
292 bool is_text =
false;
293 bool is_image =
false;
294 bool is_float =
false;
295 bool wrap_mode =
false;
296 bool new_text_block =
false;
305 std::string name =
child[
"src"];
306 std::string align =
child[
"align"];
307 bool is_curr_float =
child[
"float"].to_bool(
false);
309 curr_item = &(text_dom.
add_child(
"image"));
310 add_image(*curr_item, name, align, is_image, is_curr_float);
314 x = (align ==
"left") ? float_size.x : 0;
315 float_size.x = curr_img_size.x +
padding_;
316 float_size.y += curr_img_size.y;
318 img_size.x += curr_img_size.x +
padding_;
320 img_size.y = std::max(img_size.y, curr_img_size.y);
321 if (!is_image || (is_image && is_float)) {
322 prev_blk_height += curr_img_size.y;
323 float_size.y -= curr_img_size.y;
334 is_float = is_curr_float;
336 new_text_block =
true;
338 DBG_GUI_RL <<
"image: src=" << name <<
", size=" << curr_img_size;
339 DBG_GUI_RL <<
"wrap mode: " << wrap_mode <<
", floating: " << is_float;
341 }
else if(key ==
"table") {
342 if (curr_item ==
nullptr) {
343 curr_item = &(text_dom.
add_child(
"text"));
345 new_text_block =
false;
349 img_size =
point(0,0);
350 float_size =
point(0,0);
352 prev_blk_height += text_height;
356 unsigned col_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;
363 unsigned width =
child[
"width"].to_int(init_width);
365 unsigned row_y = prev_blk_height;
366 unsigned max_row_height = 0;
367 std::vector<unsigned> col_widths(columns, 0);
370 (*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]);
373 new_text_block =
true;
376 DBG_GUI_RL << __LINE__ <<
"start table : " <<
"row= " << rows <<
" col=" << columns <<
" width=" << width;
379 for(
const config& row :
child.child_range(
"row")) {
394 col_widths[col_idx] = std::max(col_widths[col_idx],
static_cast<unsigned>(
size.x));
395 col_widths[col_idx] = std::min(col_widths[col_idx], width/columns);
397 col_x += width/columns;
405 row_y = prev_blk_height;
406 for(
const config& row :
child.child_range(
"row")) {
420 text_dom.
append(std::move(table_elem));
423 max_row_height = std::max(max_row_height,
static_cast<unsigned>(
size.y));
425 col_x += col_widths[col_idx] + 2 *
padding_;
427 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));
432 new_text_block =
true;
440 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]);
441 DBG_GUI_RL <<
"row height: " << max_row_height;
444 prev_blk_height = row_y;
448 end_cfg[
"actions"] = boost::str(
boost::format(
"([set_var('pos_x', 0), set_var('pos_y', %d), set_var('tw', 0)])") % row_y);
458 }
else if(key ==
"break" || key ==
"br") {
459 if (curr_item ==
nullptr) {
460 curr_item = &(text_dom.
add_child(
"text"));
462 new_text_block =
false;
466 if (is_image && !is_float) {
468 (*curr_item)[
"actions"] =
"([set_var('pos_x', 0), set_var('pos_y', pos_y + image_height + padding)])";
475 img_size =
point(0,0);
480 new_text_block =
true;
487 if (!finalize &&
line.empty()) {
492 if (is_image && (!is_float)) {
493 if (!
line.empty() &&
line.at(0) ==
'\n') {
496 (*curr_item)[
"actions"] =
"([set_var('pos_x', 0), set_var('pos_y', pos_y + image_height + padding)])";
498 }
else if (!
line.empty() &&
line.at(0) !=
'\n') {
501 if (!parts.front().empty()) {
502 line = parts.front();
505 std::string& part2 = parts.back();
506 if (!part2.empty() && parts.size() > 1) {
507 if (part2[0] ==
'\n') {
508 part2 = part2.substr(1);
511 part2_cfg.
add_child(
"text")[
"text"] = parts.back();
513 remaining_item = &part2_cfg;
516 if (parts.size() == 1) {
517 prev_blk_height -= img_size.y;
520 prev_blk_height -= img_size.y;
524 if (curr_item ==
nullptr || new_text_block) {
525 if (curr_item !=
nullptr) {
527 prev_blk_height += text_height;
531 curr_item = &(text_dom.
add_child(
"text"));
533 new_text_block =
false;
537 int tmp_h =
get_text_size(*curr_item, init_width - (x == 0 ? float_size.x : x)).y;
539 if (is_text && key ==
"text") {
551 }
else if(
std::find(format_tags.begin(), format_tags.end(), key) != format_tags.end()) {
556 for (
const auto [parsed_key, parsed_cfg] : parsed_children.
all_children_view()) {
557 if (parsed_key ==
"text") {
558 const auto [
start, end] =
add_text(*curr_item, parsed_cfg[
"text"]);
560 add_attribute(*curr_item, attr[
"name"],
start + attr[
"start"].to_int(),
start + attr[
"end"].to_int(), attr[
"value"]);
564 text_dom.
add_child(parsed_key, parsed_cfg);
572 }
else if(key ==
"header" || key ==
"h") {
583 }
else if(key ==
"character_entity") {
594 }
else if(key ==
"span" || key ==
"format") {
600 for (
const auto& [key, value] :
child.attribute_range()) {
609 }
else if (key ==
"text") {
615 point text_size =
get_text_size(*curr_item, init_width - (x == 0 ? float_size.x : x));
620 if (wrap_mode && (float_size.y > 0) && (text_size.y > float_size.y)) {
624 DBG_GUI_RL <<
"wrap around area: " << float_size;
627 std::string removed_part = (*curr_item)[
"text"].str().substr(len+1);
628 (*curr_item)[
"text"] = (*curr_item)[
"text"].str().substr(0, len);
629 (*curr_item)[
"maximum_width"] = init_width - float_size.x;
633 int ah =
get_text_size(*curr_item, init_width - float_size.x).y;
637 text_height += ah - tmp_h;
641 DBG_GUI_RL <<
"wrap: " << prev_blk_height <<
"," << text_height;
649 curr_item = &(text_dom.
add_child(
"text"));
654 }
else if ((float_size.y > 0) && (text_size.y < float_size.y)) {
658 (*curr_item)[
"actions"] =
"([set_var('pos_y', pos_y + text_height)])";
662 float_size =
point(0,0);
674 w = std::max(
w, x +
static_cast<unsigned>(
size.x));
676 text_height += ah - tmp_h;
678 if (remaining_item) {
680 (*curr_item)[
"actions"] =
"([set_var('pos_x', 0), set_var('pos_y', pos_y + " + std::to_string(img_size.y) +
")])";
681 text_dom.
append(*remaining_item);
682 remaining_item =
nullptr;
687 if (!is_image && !wrap_mode && img_size.y > 0) {
688 img_size =
point(0,0);
695 DBG_GUI_RL <<
"Prev block height: " << prev_blk_height <<
" Current text block height: " << text_height;
697 h = text_height + prev_blk_height;
709 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)])";
714 #if LINK_DEBUG_BORDER
716 for (
const auto& entry :
links_) {
718 link_rect_cfg[
"x"] = entry.first.x;
719 link_rect_cfg[
"y"] = entry.first.y;
720 link_rect_cfg[
"w"] = entry.first.w;
721 link_rect_cfg[
"h"] = entry.first.h;
722 link_rect_cfg[
"border_thickness"] = 1;
723 link_rect_cfg[
"border_color"] =
"255, 180, 0, 255";
729 h = std::max(
static_cast<unsigned>(img_size.y),
h);
731 DBG_GUI_RL <<
"Width: " <<
w <<
" Height: " <<
h <<
" Origin: " << origin;
732 return { text_dom,
point(
w,
h - origin.y) };
736 if (txt_ptr !=
nullptr) {
737 (*txt_ptr)[
"text"] = text;
743 (*txt_ptr)[
"x"] =
"(pos_x)";
744 (*txt_ptr)[
"y"] =
"(pos_y)";
745 (*txt_ptr)[
"w"] =
"(text_width)";
746 (*txt_ptr)[
"h"] =
"(text_height)";
750 (*txt_ptr)[
"maximum_width"] =
"(width - pos_x - ww - tw)";
751 (*txt_ptr)[
"actions"] =
"([set_var('pos_x', 0), set_var('pos_y', pos_y + text_height)])";
767 tmp.set_variable(
"text_wrap_mode",
wfl::variant(PANGO_ELLIPSIZE_NONE));
821 connect_signal<event::LEFT_BUTTON_CLICK>(
823 connect_signal<event::MOUSE_MOTION>(
825 connect_signal<event::MOUSE_LEAVE>(
844 for (
const auto& entry :
links_) {
847 if (entry.first.contains(mouse)) {
848 DBG_GUI_RL <<
"Clicked link! dst = " << entry.second;
853 DBG_GUI_RL <<
"No registered link handler found";
872 for (
const auto& entry :
links_) {
873 if (entry.first.contains(mouse)) {
917 load_resolutions<resolution>(cfg);
922 , text_color_enabled(
color_t::from_rgba_string(cfg[
"text_font_color_enabled"].str()))
923 , text_color_disabled(
color_t::from_rgba_string(cfg[
"text_font_color_disabled"].str()))
924 , link_color(cfg[
"link_color"].empty() ?
font::
YELLOW_COLOR :
color_t::from_rgba_string(cfg[
"link_color"].str()))
925 , font_family(cfg[
"text_font_family"].str())
927 , font_style(cfg[
"text_font_style"].str(
"normal"))
939 builder_rich_label::builder_rich_label(
const config& cfg)
942 , link_aware(cfg[
"link_aware"].to_bool(true))
943 , width(cfg[
"width"], 500)
949 auto lbl = std::make_unique<rich_label>(*
this);
955 lbl->set_text_color(conf->text_color_enabled,
true);
956 lbl->set_text_color(conf->text_color_enabled,
false);
957 lbl->set_link_color(conf->link_color);
958 lbl->set_font_family(conf->font_family);
959 lbl->set_font_size(conf->font_size);
960 lbl->set_font_style(conf->font_style);
961 lbl->set_label(lbl->get_label());
963 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 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)
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.
void add_attribute(config &curr_item, const std::string &attr_name, size_t start=0, size_t end=0, const std::string &extra_data="")
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
std::vector< std::string > split_in_width(const std::string &s, const int font_size, const unsigned width)
unsigned short text_alpha_
std::string font_family_
Base font family.
point get_xy_from_offset(const unsigned offset) const
void add_image(config &curr_item, const std::string &name, std::string align, bool has_prev_image, bool floating)
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.
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.
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 default_text_config(config *txt_ptr, const t_string &text="")
Create template for text config that can be shown in canvas.
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 PangoAlignment 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(std::string_view str)
Length in characters of a UTF-8 string.
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.
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.
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
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)