16 #define GETTEXT_DOMAIN "wesnoth-lib"
38 #include <boost/format.hpp>
41 #define DBG_GUI_RL LOG_STREAM(debug, log_rich_label)
66 connect_signal<event::LEFT_BUTTON_CLICK>(
68 connect_signal<event::MOUSE_MOTION>(
70 connect_signal<event::MOUSE_LEAVE>(
82 tshape_ = std::make_unique<gui2::text_shape>(text_cfg, functions);
96 ishape_ = std::make_unique<gui2::image_shape>(img_cfg, functions);
102 size_t start = curr_item[
"text"].str().size();
104 curr_item[
"text"] = curr_item[
"text"].str() + text;
106 if (!attr_name.empty()) {
108 curr_item[
"attr_name"] = curr_item[
"attr_name"].str() + attr_name;
111 curr_item[
"attr_start"] = curr_item[
"attr_start"].str() + std::to_string(
start);
114 curr_item[
"attr_end"] = curr_item[
"attr_end"].str() + std::to_string(curr_item[
"text"].str().
size());
116 if (!extra_data.empty()) {
118 curr_item[
"attr_data"] = curr_item[
"attr_data"].str() + extra_data;
125 size_t start = curr_item[
"text"].str().size();
126 curr_item[
"text"] = curr_item[
"text"].str() + text;
128 if (!attr_names.empty()) {
130 curr_item[
"attr_name"] = curr_item[
"attr_name"].str() +
utils::join(attr_names);
132 for (
size_t i = 0;
i < attr_names.size();
i++) {
134 curr_item[
"attr_start"] = curr_item[
"attr_start"].str() + std::to_string(
start);
136 curr_item[
"attr_end"] = curr_item[
"attr_end"].str() + std::to_string(curr_item[
"text"].str().
size());
139 if (!extra_data.empty()) {
141 curr_item[
"attr_data"] = curr_item[
"attr_data"].str() +
utils::join(extra_data);
147 curr_item[
"name"] = name;
153 if (align ==
"right") {
154 curr_item[
"x"] = floating ?
"(width - image_width - img_x)" :
"(width - image_width - pos_x)";
155 }
else if (align ==
"middle" || align ==
"center") {
157 curr_item[
"x"] = floating ?
"(img_x + (width - image_width)/2.0)" :
"(pos_x + (width - image_width)/2.0)";
160 curr_item[
"x"] = floating ?
"(img_x)" :
"(pos_x)";
162 curr_item[
"y"] = floating ?
"(img_y + pos_y)" :
"(pos_y)";
163 curr_item[
"h"] =
"(image_height)";
164 curr_item[
"w"] =
"(image_width)";
179 if (align ==
"left") {
181 actions <<
"set_var('pos_x', image_width + padding)";
182 }
else if (align ==
"right") {
184 actions <<
"set_var('pos_x', 0)";
186 actions <<
"set_var('ww', image_width)";
190 actions <<
"," <<
"set_var('img_y', img_y + image_height + padding)";
194 actions <<
"set_var('pos_x', pos_x + image_width + padding)";
198 curr_item[
"actions"] =
actions.str();
205 DBG_GUI_RL <<
"add_link, x=" <<
x_ <<
" width=" << img_width;
212 std::string link_text = name.empty() ? dest : name;
225 if (t_end.x > t_start.x) {
226 point link_size = t_end - t_start;
233 links_.push_back(std::pair(link_rect, dest));
235 DBG_GUI_RL <<
"added link at rect: " << link_rect;
239 point t_size(
w_ - link_start.x - (
x_ == 0 ? img_width : 0), t_end.y - t_start.y);
241 point t_size2(t_end.x, t_end.y - t_start.y);
257 links_.push_back(std::pair(link_rect, dest));
258 links_.push_back(std::pair(link_rect2, dest));
260 DBG_GUI_RL <<
"added link at rect 1: " << link_rect;
261 DBG_GUI_RL <<
"added link at rect 2: " << link_rect2;
269 for (
int i = 0;
i < wrap_position.y;
i++) {
272 len += wrap_position.x;
276 while((
c = text.at(len)) !=
' ') {
293 std::vector<std::string> parsed_text = marked_up_text.
parsed_text();
295 config* curr_item =
nullptr;
298 bool is_image =
false;
299 bool floating =
false;
300 bool new_text_block =
false;
301 bool needs_size_update =
true;
302 bool in_table =
false;
304 unsigned col_width = 0;
305 unsigned max_col_height = 0;
309 for (
size_t i = 0;
i < parsed_text.size();
i++) {
310 bool last_entry = (
i == parsed_text.size() - 1);
311 std::string
line = parsed_text.at(
i);
313 if (!
line.empty() &&
line.at(0) ==
'[') {
319 std::string name =
child[
"src"];
320 floating =
child[
"float"].to_bool();
321 std::string align =
child[
"align"];
324 add_image(*curr_item, name, align, floating, img_size);
327 new_text_block =
true;
333 if (is_image && (!floating)) {
335 (*curr_item)[
"actions"] =
"([set_var('pos_x', 0), set_var('pos_y', pos_y + image_height + padding)])";
338 if (curr_item ==
nullptr || new_text_block) {
344 new_text_block =
false;
388 std::stringstream header_text;
389 header_text <<
child[
"text"].str() +
"\n";
390 std::vector<std::string> attrs = {
"color",
"size"};
391 std::vector<std::string> attr_data;
403 std::vector<std::string> attrs;
404 std::vector<std::string> attr_data;
409 for (
const auto& attr :
child.value().attribute_range()) {
410 if (attr.first !=
"text") {
411 attrs.push_back(attr.first);
412 attr_data.push_back(attr.second);
413 DBG_GUI_RL << attr.first <<
"=" << attr.second;
426 unsigned columns =
child[
"col"].to_int();
427 unsigned width =
child[
"width"].to_int();
428 width = width > 0 ? width :
w_;
429 col_width = width/columns;
432 (*curr_item)[
"actions"] = boost::str(
boost::format(
"([set_var('pos_x', 0), set_var('pos_y', pos_y + if(ih > text_height, ih, text_height)), set_var('tw', width - pos_x - %d), set_var('ih', 0)])") % col_width);
437 new_text_block =
true;
439 DBG_GUI_RL <<
"start table : " <<
"col=" << columns;
446 max_col_height = std::max(max_col_height,
txt_height_);
447 max_col_height = std::max(max_col_height,
static_cast<unsigned>(img_size.y));
453 (*curr_item)[
"actions"] = boost::str(
boost::format(
"([set_var('pos_x', pos_x + %d), set_var('tw', width - pos_x - %d)])") % col_width % col_width);
456 new_text_block =
true;
464 max_col_height = std::max(max_col_height,
txt_height_);
465 max_col_height = std::max(max_col_height,
static_cast<unsigned>(img_size.y));
473 (*curr_item)[
"actions"] = boost::str(
boost::format(
"([set_var('pos_x', 0), set_var('pos_y', pos_y + %d + %d), set_var('tw', width - pos_x - %d)])") % max_col_height %
padding_ % col_width);
477 DBG_GUI_RL <<
"linebreak: " << (in_table ? max_col_height :
w_);
480 new_text_block =
true;
485 DBG_GUI_RL <<
"end table: " << max_col_height;
486 max_col_height = std::max(max_col_height,
txt_height_);
487 max_col_height = std::max(max_col_height,
static_cast<unsigned>(img_size.y));
488 (*curr_item)[
"actions"] = boost::str(
boost::format(
"([set_var('pos_x', 0), set_var('pos_y', pos_y + %d), set_var('tw', 0)])") % max_col_height);
498 new_text_block =
true;
504 if (needs_size_update) {
515 }
else if (!
line.empty()) {
519 if (is_image && (!floating)) {
520 if (
line.at(0) ==
'\n') {
522 (*curr_item)[
"actions"] =
"([set_var('pos_x', 0), set_var('pos_y', pos_y + image_height + padding)])";
524 needs_size_update =
true;
526 needs_size_update =
false;
530 if (curr_item ==
nullptr || new_text_block) {
536 new_text_block =
false;
543 (*curr_item)[
"text"] = (*curr_item)[
"text"].str() +
line;
549 if ( floating && (img_size.y > 0) && (text_size.y > img_size.y) ) {
553 t_string* removed_part =
new t_string((*curr_item)[
"text"].str().substr(len+1));
554 (*curr_item)[
"text"] = (*curr_item)[
"text"].str().substr(0, len);
555 (*curr_item)[
"actions"] =
"([set_var('pos_x', 0), set_var('ww', 0), set_var('pos_y', pos_y + text_height)])";
562 img_size =
point(0,0);
570 }
else if ((img_size.y > 0) && (text_size.y < img_size.y)) {
573 (*curr_item)[
"actions"] =
"([set_var('pos_y', pos_y + image_height)])";
575 (*curr_item)[
"actions"] =
"([set_var('pos_y', pos_y + text_height)])";
591 if (!is_image && !floating && img_size.y > 0) {
592 if (needs_size_update) {
595 img_size =
point(0,0);
607 if (
static_cast<unsigned>(img_size.y) >
h_) {
614 break_cfg[
"text"] =
" ";
615 break_cfg[
"actions"] =
"([set_var('pos_x', 0), set_var('pos_y', 0), set_var('img_x', 0), set_var('img_y', 0)])";
625 if (txt_ptr !=
nullptr) {
626 (*txt_ptr)[
"text"] = text;
629 (*txt_ptr)[
"x"] =
"(pos_x)";
630 (*txt_ptr)[
"y"] =
"(pos_y)";
631 (*txt_ptr)[
"w"] =
"(text_width)";
632 (*txt_ptr)[
"h"] =
"(text_height)";
635 (*txt_ptr)[
"maximum_width"] =
"(width - pos_x - ww - tw)";
636 (*txt_ptr)[
"actions"] =
"([set_var('pos_y', pos_y+text_height)])";
651 tmp.set_variable(
"text_wrap_mode",
wfl::variant(PANGO_ELLIPSIZE_NONE));
712 DBG_GUI_RL <<
"(mouse)" << mouse.x <<
"," << mouse.y;
715 for (
const auto& entry :
links_) {
716 DBG_GUI_RL <<
"link [" << entry.first.x <<
"," << entry.first.y <<
","
717 << entry.first.x + entry.first.w <<
"," << entry.first.y + entry.first.h <<
"]";
719 if (entry.first.contains(mouse)) {
720 DBG_GUI_RL <<
"Clicked link! dst = " << entry.second;
724 DBG_GUI_RL <<
"No registered link handler found";
746 for (
const auto& entry :
links_) {
747 if (entry.first.contains(mouse)) {
791 load_resolutions<resolution>(cfg);
796 , link_color(cfg[
"link_color"].empty() ?
font::
YELLOW_COLOR :
color_t::from_rgba_string(cfg[
"link_color"].str()))
808 builder_rich_label::builder_rich_label(
const config& cfg)
811 , link_aware(cfg[
"link_aware"].to_bool(true))
812 , width(cfg[
"width"].to_int(500))
818 auto lbl = std::make_unique<rich_label>(*
this);
825 lbl->set_link_color(conf->link_color);
826 lbl->set_width(
width);
827 lbl->set_label(lbl->get_label());
829 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.
std::string debug() const
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 signal_handler_mouse_motion(bool &handled, const point &coordinate)
Mouse motion signal handler: checks if the cursor is on a hyperlink.
void append_if_not_empty(config_attribute_value *key, std::string suffix)
state_t state_
Current state of the widget.
void set_state(const state_t state)
void add_text_with_attribute(config &curr_item, std::string text, std::string attr_name="", std::string extra_data="")
size_t get_split_location(std::string text, int img_height)
virtual bool get_active() const override
Gets the active state of the styled_widget.
std::function< void(std::string)> link_handler_
wfl::map_formula_callable setup_text_renderer(config text_cfg, unsigned width=0)
virtual void update_canvas() override
Updates the canvas(ses).
unsigned w_
Width and height of the canvas.
void add_link(config &curr_item, std::string name, std::string dest, int img_width)
void default_text_config(config *txt_ptr, t_string text="")
template for canvas text config
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
std::unique_ptr< image_shape > ishape_
unsigned short text_alpha_
point get_image_size(config img_cfg)
point get_xy_from_offset(const unsigned offset) const
virtual void set_active(const bool active) override
Sets the styled_widget's state.
void add_image(config &curr_item, std::string name, std::string align, bool floating, point &img_size)
point get_text_size(config text_cfg, unsigned width=0)
size calculation functions
point get_column_line(const point &position) const
unsigned prev_blk_height_
Height of all previous blocks, combined.
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.
unsigned txt_height_
Height of current text block.
bool link_aware_
Whether the rich_label is link aware, rendering links with special formatting and handling click even...
void set_link_color(const color_t &color)
std::unique_ptr< text_shape > tshape_
shapes used for size calculation
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 add_text_with_attributes(config &curr_item, std::string text, std::vector< std::string > attr_names, std::vector< std::string > extra_data)
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 std::vector< std::string > & 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
pango_text & get_text_renderer()
Returns a reference to a static pango_text object.
constexpr float get_line_spacing_factor()
const color_t TITLE_COLOR
point get_mouse_position()
Returns the current mouse position.
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.
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.
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
Desktop environment interaction functions.
static lg::log_domain log_rich_label("gui/widget/rich_label")
void read(config &cfg, std::istream &in, abstract_validator *validator)
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)
An abstract description of a rectangle with integer coordinates.
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)