The Battle for Wesnoth  1.19.23+dev
canvas.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2007 - 2025
3  by Mark de Wever <koraq@xs4all.nl>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 /**
17  * @file
18  * Implementation of canvas.hpp.
19  */
20 
21 #define GETTEXT_DOMAIN "wesnoth-lib"
22 
23 #include "gui/core/canvas.hpp"
25 
26 #include "draw.hpp"
27 #include "draw_manager.hpp"
28 #include "font/text.hpp"
29 #include "font/attributes.hpp"
30 #include "font/font_options.hpp"
31 #include "formatter.hpp"
32 #include "gettext.hpp"
34 #include "gui/core/log.hpp"
35 #include "gui/widgets/helper.hpp"
36 #include "font/font_config.hpp"
37 #include "font/standard_colors.hpp"
38 #include "picture.hpp"
39 #include "sdl/point.hpp"
40 #include "sdl/rect.hpp"
41 #include "sdl/surface.hpp"
42 #include "sdl/texture.hpp"
43 #include "sdl/utils.hpp" // blur_surface
44 #include "utils/general.hpp"
45 #include "video.hpp" // read_pixels_low_res, only used for blurring
46 #include "wml_exception.hpp"
47 
48 namespace gui2
49 {
50 
51 /***** ***** ***** ***** ***** LINE ***** ***** ***** ***** *****/
52 
54  : shape(cfg)
55  , x1_(cfg["x1"])
56  , y1_(cfg["y1"])
57  , x2_(cfg["x2"])
58  , y2_(cfg["y2"])
59  , color_(cfg["color"])
60  , thickness_(cfg["thickness"].to_unsigned())
61 {
62  const std::string& debug = (cfg["debug"]);
63  if(!debug.empty()) {
64  DBG_GUI_P << "Line: found debug message '" << debug << "'.";
65  }
66 }
67 
69 {
70  /**
71  * @todo formulas are now recalculated every draw cycle which is a bit silly
72  * unless there has been a resize. So to optimize we should use an extra
73  * flag or do the calculation in a separate routine.
74  */
75 
76  const unsigned x1 = x1_(variables);
77  const unsigned y1 = y1_(variables);
78  const unsigned x2 = x2_(variables);
79  const unsigned y2 = y2_(variables);
80 
81  DBG_GUI_D << "Line: draw from " << x1 << ',' << y1 << " to " << x2 << ',' << y2 << ".";
82 
83  // @todo FIXME respect the thickness.
84 
85  draw::line(x1, y1, x2, y2, color_(variables));
86 }
87 
88 /***** ***** ***** ***** ***** Rectangle ***** ***** ***** ***** *****/
89 
92  , border_thickness_(cfg["border_thickness"].to_int())
93  , border_color_(cfg["border_color"], color_t::null_color())
94  , fill_color_(cfg["fill_color"], color_t::null_color())
95 {
96  // Check if a raw color string evaluates to a null color.
97  if(!border_color_.has_formula() && border_color_().null()) {
99  }
100 
101  const std::string& debug = (cfg["debug"]);
102  if(!debug.empty()) {
103  DBG_GUI_P << "Rectangle: found debug message '" << debug << "'.";
104  }
105 }
106 
108  const rect& bounds,
109  const color_t& border_color,
110  const unsigned thickness,
111  const color_t& fill_color)
112  : rect_bounded_shape(bounds)
113  , border_thickness_(thickness)
114  , border_color_(border_color)
115  , fill_color_(fill_color)
116 {
117 }
118 
120 {
121  const rect area {
122  x_(variables),
123  y_(variables),
124  w_(variables),
125  h_(variables)
126  };
127 
128  const color_t fill_color = fill_color_(variables);
129 
130  // Fill the background, if applicable
131  if(!fill_color.null()) {
132  DBG_GUI_D << "fill " << fill_color;
133  draw::set_color(fill_color);
134  draw::fill(area.padded_by(-border_thickness_));
135  }
136 
137  const color_t border_color = border_color_(variables);
138 
139  // Draw the border
140  draw::set_color(border_color);
141  DBG_GUI_D << "border thickness " << border_thickness_ << ", colour " << border_color;
142  for(int i = 0; i < border_thickness_; ++i) {
143  draw::rect(area.padded_by(-i));
144  }
145 }
146 
147 /***** ***** ***** ***** ***** Rounded Rectangle ***** ***** ***** ***** *****/
148 
151  , r_(cfg["corner_radius"])
152  , border_thickness_(cfg["border_thickness"].to_int())
153  , border_color_(cfg["border_color"], color_t::null_color())
154  , fill_color_(cfg["fill_color"], color_t::null_color())
155 {
156  // Check if a raw color string evaluates to a null color.
157  if(!border_color_.has_formula() && border_color_().null()) {
158  border_thickness_ = 0;
159  }
160 
161  const std::string& debug = (cfg["debug"]);
162  if(!debug.empty()) {
163  DBG_GUI_P << "Rounded Rectangle: found debug message '" << debug << "'.";
164  }
165 }
166 
168 {
169  const int x = x_(variables);
170  const int y = y_(variables);
171  const int w = w_(variables);
172  const int h = h_(variables);
173  const int r = r_(variables);
174 
175  DBG_GUI_D << "Rounded Rectangle: draw from " << x << ',' << y << " width " << w << " height " << h << ".";
176 
177  const color_t fill_color = fill_color_(variables);
178 
179  // Fill the background, if applicable
180  if(!fill_color.null() && w && h) {
181  draw::set_color(fill_color);
182 
183  draw::fill(rect{x + r, y + border_thickness_, w - r * 2, r - border_thickness_ + 1});
184  draw::fill(rect{x + border_thickness_, y + r + 1, w - border_thickness_ * 2, h - r * 2});
185  draw::fill(rect{x + r, y - r + h + 1, w - r * 2, r - border_thickness_});
186 
187  draw::disc(x + r, y + r, r, 0xc0);
188  draw::disc(x + w - r, y + r, r, 0x03);
189  draw::disc(x + r, y + h - r, r, 0x30);
190  draw::disc(x + w - r, y + h - r, r, 0x0c);
191  }
192 
193  const color_t border_color = border_color_(variables);
194 
195  // Draw the border
196  draw::set_color(border_color);
197 
198  for(int i = 0; i < border_thickness_; ++i) {
199  draw::line(x + r, y + i, x + w - r, y + i);
200  draw::line(x + r, y + h - i, x + w - r, y + h - i);
201 
202  draw::line(x + i, y + r, x + i, y + h - r);
203  draw::line(x + w - i, y + r, x + w - i, y + h - r);
204 
205  draw::circle(x + r, y + r, r - i, 0xc0);
206  draw::circle(x + w - r, y + r, r - i, 0x03);
207  draw::circle(x + r, y + h - r, r - i, 0x30);
208  draw::circle(x + w - r, y + h - r, r - i, 0x0c);
209  }
210 }
211 
212 /***** ***** ***** ***** ***** CIRCLE ***** ***** ***** ***** *****/
213 
215  : shape(cfg)
216  , x_(cfg["x"])
217  , y_(cfg["y"])
218  , radius_(cfg["radius"])
219  , border_color_(cfg["border_color"])
220  , fill_color_(cfg["fill_color"])
221  , border_thickness_(cfg["border_thickness"].to_int(1))
222 {
223  const std::string& debug = (cfg["debug"]);
224  if(!debug.empty()) {
225  DBG_GUI_P << "Circle: found debug message '" << debug << "'.";
226  }
227 }
228 
230 {
231  /**
232  * @todo formulas are now recalculated every draw cycle which is a bit
233  * silly unless there has been a resize. So to optimize we should use an
234  * extra flag or do the calculation in a separate routine.
235  */
236  const int x = x_(variables);
237  const int y = y_(variables);
238  const unsigned radius = radius_(variables);
239  const color_t fill_color = fill_color_(variables);
240  if (!fill_color.null()) {
241  draw::cairo_disc(x, y, radius, fill_color);
242  }
243 
244  const color_t border_color = border_color_(variables);
245  draw::cairo_circle(x, y, radius, border_color, border_thickness_);
246 
247  DBG_GUI_D << "Circle: drawn at " << x << ',' << y << " radius " << radius << ".";
248 }
249 
250 /***** ***** ***** ***** ***** IMAGE ***** ***** ***** ***** *****/
251 
253  : shape(cfg)
254  , x_(cfg["x"])
255  , y_(cfg["y"])
256  , w_(cfg["w"])
257  , h_(cfg["h"])
258  , image_name_(cfg["name"], "") // avoid ambiguous ctor error
259  , resize_mode_(get_resize_mode(cfg["resize_mode"]))
260  , mirror_(cfg.get_old_attribute("mirror", "vertical_mirror", "image"))
261  , actions_formula_(cfg["actions"], &functions)
262  , failure_logged_(false)
263 {
264  const std::string& debug = (cfg["debug"]);
265  if(!debug.empty()) {
266  DBG_GUI_P << "Image: found debug message '" << debug << "'.";
267  }
268 }
269 
271  const point& origin,
272  const std::string& img_path)
273  : shape()
274  , x_(origin.x)
275  , y_(origin.y)
276  , w_("(image_width)")
277  , h_("(image_height)")
278  , image_name_(img_path, img_path) // avoid ambiguous ctor error
279  , resize_mode_(get_resize_mode("scale_sharp"))
280  , mirror_(false)
281  , actions_formula_("")
282 {
283 }
284 
285 void image_shape::dimension_validation(unsigned value, const std::string& name, const std::string& key)
286 {
287  const int as_int = static_cast<int>(value);
288 
289  VALIDATE_WITH_DEV_MESSAGE(as_int >= 0, _("Image doesn’t fit on canvas."),
290  formatter() << "Image '" << name << "', " << key << " = " << as_int << "."
291  );
292 }
293 
295 {
296  DBG_GUI_D << "Image: draw.";
297 
298  // The name will be passed to the texture loading code on each draw, any caching is left to the
299  // image loading code itself. A name can point to a different image file due to i18n support,
300  // even when the image_name_ isn't a formula.
301  const std::string& name = image_name_(variables);
302 
303  if(name.empty()) {
304  if(!failure_logged_) {
305  DBG_GUI_D << "Image: name is empty or contains invalid formula, will not be drawn.";
306  failure_logged_ = true;
307  }
308  return;
309  }
310 
311  // Texture filtering mode must be set on texture creation,
312  // so check whether we need smooth scaling or not here.
316  {
318  }
320 
321  if(!tex) {
322  if(!failure_logged_) {
323  ERR_GUI_D << "Image: '" << name << "' not found and won't be drawn.";
324  failure_logged_ = true;
325  }
326  return;
327  }
328 
329  wfl::map_formula_callable local_variables(variables);
330  local_variables.add("image_original_width", wfl::variant(tex.w()));
331  local_variables.add("image_original_height", wfl::variant(tex.h()));
332 
333  int w = w_(local_variables);
334  dimension_validation(w, name, "w");
335 
336  int h = h_(local_variables);
337  dimension_validation(h, name, "h");
338 
339  local_variables.add("image_width", wfl::variant(w ? w : tex.w()));
340  local_variables.add("image_height", wfl::variant(h ? h : tex.h()));
341 
342  const int x = x_(local_variables);
343  const int y = y_(local_variables);
344 
345  // used in gui/dialogs/story_viewer.cpp
346  local_variables.add("clip_x", wfl::variant(x));
347  local_variables.add("clip_y", wfl::variant(y));
348 
349  if (variables.has_key("fake_draw") && variables.query_value("fake_draw").as_bool()) {
350  variables.add("image_original_width", wfl::variant(tex.w()));
351  variables.add("image_original_height", wfl::variant(tex.h()));
352  variables.add("image_width", wfl::variant(w ? w : tex.w()));
353  variables.add("image_height", wfl::variant(h ? h : tex.h()));
354  return;
355  }
356 
357  // Execute the provided actions for this context.
358  wfl::execute_actions(actions_formula_.evaluate(local_variables), wfl::variant(variables.fake_ptr()));
359 
360  // If w or h is 0, assume it means the whole image.
361  if (!w) { w = tex.w(); }
362  if (!h) { h = tex.h(); }
363 
364  const rect dst_rect { x, y, w, h };
365 
366  // What to do with the image depends on whether we need to tile it or not.
367  switch(resize_mode_) {
368  case resize_mode::tile:
369  draw::tiled(tex, dst_rect, false, mirror_(variables));
370  break;
372  draw::tiled(tex, dst_rect, true, mirror_(variables));
373  break;
375  draw::tiled_highres(tex, dst_rect, false, mirror_(variables));
376  break;
378  // Stretching is identical to scaling in terms of handling.
379  // Is this intended? That's what previous code was doing.
380  case resize_mode::scale:
381  // Filtering mode is set on texture creation above.
382  // Handling is otherwise identical to sharp scaling.
384  if(mirror_(variables)) {
385  draw::flipped(tex, dst_rect);
386  } else {
387  draw::blit(tex, dst_rect);
388  }
389  break;
390  default:
391  ERR_GUI_D << "Image: unrecognized resize mode.";
392  break;
393  }
394 }
395 
397 {
398  if(resize_mode == "tile") {
399  return resize_mode::tile;
400  } else if(resize_mode == "tile_center") {
402  } else if(resize_mode == "tile_highres") {
404  } else if(resize_mode == "stretch") {
405  return resize_mode::stretch;
406  } else if(resize_mode == "scale_sharp") {
408  } else if(resize_mode == "scale") {
409  return resize_mode::scale;
410  } else {
411  if(!resize_mode.empty()) {
412  ERR_GUI_E << "Invalid resize mode '" << resize_mode << "' falling back to 'scale'.";
413  }
414 
415  // Linear scaling just looks horrible as a default, especially on HDPI screens, and even
416  // for some non-pixel art (the logo, for example). Nearest-neighbor isn't perfect for those
417  // usecases, but it's definitely better, in my opinion.
418  //
419  // -- vultraz, 2022-08-20
421  }
422 }
423 
424 /***** ***** ***** ***** ***** TEXT ***** ***** ***** ***** *****/
425 
426 namespace
427 {
428 /** Populates the attribute list from the given config child range. */
429 auto parse_attributes(const config::const_child_itors& range)
430 {
431  // TODO: most of the time this will be empty, unless you're using rich_label.
432  // It's a lot of memory allocations to always have a valid object here...
433  // Do we need store it as an optional?
434  font::attribute_list text_attributes;
435 
436  for(const config& attr : range) {
437  const std::string name = attr["name"];
438 
439  if(name.empty()) {
440  continue;
441  }
442 
443  const unsigned start = attr["start"].to_int(PANGO_ATTR_INDEX_FROM_TEXT_BEGINNING);
444  const unsigned end = attr["end"].to_int(PANGO_ATTR_INDEX_TO_TEXT_END);
445 
446  // Attributes with start == end set won't do anything, so skip
447  if (start == end) {
448  WRN_GUI_D << "attribute " << name << " has equal start and end indices, will not be added.";
449  continue;
450  }
451 
452  if (name == "color" || name == "fgcolor" || name == "foreground") {
453  add_attribute_fg_color(text_attributes, start, end, attr["value"].empty() ? font::NORMAL_COLOR : font::string_to_color(attr["value"]));
454  } else if (name == "bgcolor" || name == "background") {
455  add_attribute_bg_color(text_attributes, start, end, attr["value"].empty() ? font::GOOD_COLOR : font::string_to_color(attr["value"]));
456  } else if (name == "font_size" || name == "size") {
457  add_attribute_size(text_attributes, start, end, attr["value"].to_int(font::SIZE_NORMAL));
458  } else if (name == "font_family" || name == "face") {
459  add_attribute_font_family(text_attributes, start, end, font::decode_family_class(attr["value"]));
460  } else if (name == "weight") {
461  add_attribute_weight(text_attributes, start, end, decode_text_weight(attr["value"]));
462  } else if (name == "style") {
463  add_attribute_style(text_attributes, start, end, decode_text_style(attr["value"]));
464  } else if (name == "bold" || name == "b") {
465  add_attribute_weight(text_attributes, start, end, PANGO_WEIGHT_BOLD);
466  } else if (name == "italic" || name == "i") {
467  add_attribute_style(text_attributes, start, end, PANGO_STYLE_ITALIC);
468  } else if (name == "underline" || name == "u") {
469  add_attribute_underline(text_attributes, start, end, PANGO_UNDERLINE_SINGLE);
470  } else if (name == "line_height") {
471  add_attribute_line_height(text_attributes, start, end, attr["value"].to_double());
472  } else if (name == "image") { // An inline image that behave as a custom text glyph
473  add_attribute_image_shape(text_attributes, start, end, attr["value"]);
474  } else {
475  // Unsupported formatting or normal text
476  add_attribute_weight(text_attributes, start, end, PANGO_WEIGHT_NORMAL);
477  add_attribute_style(text_attributes, start, end, PANGO_STYLE_NORMAL);
478  }
479  }
480 
481  return text_attributes;
482 }
483 
484 } // anon namespace
485 
488  , font_family_(font::decode_family_class(cfg["font_family"]))
489  , font_size_(cfg["font_size"], font::SIZE_NORMAL)
490  , font_style_(decode_font_style(cfg["font_style"]))
491  , text_alignment_(cfg["text_alignment"])
492  , color_(cfg["color"], font::NORMAL_COLOR)
493  , text_(cfg["text"])
494  , parse_text_as_formula_(cfg["parse_text_as_formula"].to_bool(true))
495  , text_markup_(cfg["text_markup"], false)
496  , link_aware_(cfg["text_link_aware"], false)
497  , link_color_(cfg["text_link_color"], font::YELLOW_COLOR)
498  , maximum_width_(cfg["maximum_width"], -1)
499  , characters_per_line_(cfg["text_characters_per_line"].to_unsigned())
500  , maximum_height_(cfg["maximum_height"], -1)
501  , highlight_start_(cfg["highlight_start"])
502  , highlight_end_(cfg["highlight_end"])
503  , highlight_color_(cfg["highlight_color"], color_t::from_hex_string("215380"))
504  , line_spacing_(cfg["line_spacing"].to_double(font::get_line_spacing_factor()))
505  , outline_(cfg["outline"], false)
506  , actions_formula_(cfg["actions"], &functions)
507  , text_attributes_(parse_attributes(cfg.child_range("attribute")))
508 {
509  const std::string& debug = (cfg["debug"]);
510  if(!debug.empty()) {
511  DBG_GUI_P << "Text: found debug message '" << debug << "'.";
512  }
513 }
514 
516  const point& origin,
517  font::family_class family,
518  const unsigned size,
520  const std::string& align,
521  const unsigned width)
522  : rect_bounded_shape(origin, "(text_width)", "(text_height)")
523  , font_family_(family)
524  , font_size_(size)
525  , font_style_(style)
526  , text_alignment_(align)
527  , color_(font::NORMAL_COLOR)
528  , parse_text_as_formula_(false)
529  , text_markup_(false)
530  , link_aware_(false)
531  , link_color_(font::YELLOW_COLOR)
532  , maximum_width_(width)
533  , characters_per_line_(0)
534  , maximum_height_(-1)
535  , highlight_start_("")
536  , highlight_end_("")
537  , highlight_color_(color_t::from_hex_string("215380"))
538  , line_spacing_(0)
539  , outline_(false)
540  , actions_formula_("")
541  , text_attributes_()
542 {
543 }
544 
546  return text_.t_str();
547 }
548 
549 void text_shape::set_text(const t_string& text) {
550  text_ = text;
551 }
552 
553 std::pair<std::size_t, std::size_t> text_shape::add_text(const t_string& text)
554 {
555  t_string old_text = get_text();
556  std::size_t start = old_text.size();
557  set_text(old_text + text);
558  std::size_t end = start + text.size();
559  return { start, end };
560 }
561 
562 void text_shape::set_wrap_width(const unsigned wrap_width) {
563  maximum_width_.set_value(wrap_width);
564 }
565 
567  const std::string& name,
568  const std::string& extra_data,
569  std::size_t start,
570  std::size_t end)
571 {
572  if(name.empty()) {
573  return;
574  }
575 
576  // Attributes with start == end set won't do anything, so skip
577  if (start == end) {
578  WRN_GUI_D << "attribute " << name << " has equal start and end indices, will not be added.";
579  return;
580  }
581 
582  if (name == "color" || name == "fgcolor" || name == "foreground") {
583  add_attribute_fg_color(text_attributes_, start, end, extra_data.empty() ? font::NORMAL_COLOR : font::string_to_color(extra_data));
584  } else if (name == "bgcolor" || name == "background") {
585  add_attribute_bg_color(text_attributes_, start, end, extra_data.empty() ? font::GOOD_COLOR : font::string_to_color(extra_data));
586  } else if (name == "font_size" || name == "size") {
588  } else if (name == "font_family" || name == "face") {
590  } else if (name == "weight") {
592  } else if (name == "style") {
594  } else if (name == "bold" || name == "b") {
595  add_attribute_weight(text_attributes_, start, end, PANGO_WEIGHT_BOLD);
596  } else if (name == "italic" || name == "i") {
597  add_attribute_style(text_attributes_, start, end, PANGO_STYLE_ITALIC);
598  } else if (name == "underline" || name == "u") {
599  add_attribute_underline(text_attributes_, start, end, PANGO_UNDERLINE_SINGLE);
600  } else if (name == "line_height") {
602  } else if (name == "image") { // An inline image that behave as a custom text glyph
604  } else {
605  // Unsupported formatting or normal text
606  add_attribute_weight(text_attributes_, start, end, PANGO_WEIGHT_NORMAL);
607  add_attribute_style(text_attributes_, start, end, PANGO_STYLE_NORMAL);
608  }
609 }
610 
611 void text_shape::add_attributes_from(text_shape& tshape2, const unsigned attr_start)
612 {
613  text_attributes_.splice_into(tshape2.text_attributes_, attr_start);
614 }
615 
617 {
618  assert(variables.has_key("text"));
619 
620  // We first need to determine the size of the text which need the rendered
621  // text. So resolve and render the text first and then start to resolve
622  // the other formulas.
623  const auto text = parse_text_as_formula_
624  ? typed_formula<t_string>{text_}(variables)
625  : text_.t_str();
626 
627  if(text.empty()) {
628  DBG_GUI_D << "Text: no text to render, leave.";
629  return;
630  }
631 
632  //
633  // Highlight
634  //
635  const int highlight_start = highlight_start_(variables);
636  const int highlight_end = highlight_end_(variables);
637 
638  if(highlight_start != highlight_end) {
639  add_attribute_bg_color(text_attributes_, highlight_start, highlight_end, highlight_color_(variables));
640  }
641 
642  font::pango_text& text_renderer = font::get_text_renderer();
643  text_renderer.clear_attributes();
644 
645  text_renderer
646  .set_link_aware(link_aware_(variables))
647  .set_link_color(link_color_(variables))
648  .set_text(text, text_markup_(variables));
649 
650  text_renderer
652  .set_font_size(font_size_(variables))
654  .set_alignment(text_alignment_(variables))
655  .set_foreground_color(color_(variables))
656  .set_maximum_width(maximum_width_(variables))
657  .set_maximum_height(maximum_height_(variables), true)
658  .set_ellipse_mode(variables.has_key("text_wrap_mode")
659  ? static_cast<PangoEllipsizeMode>(variables.query_value("text_wrap_mode").as_int())
660  : PANGO_ELLIPSIZE_END)
662  .set_add_outline(outline_(variables))
664 
665  // Do this last so it can merge with attributes from markup
666  text_renderer.apply_attributes(text_attributes_);
667 
668  wfl::map_formula_callable local_variables(variables);
669  const auto [tw, th] = text_renderer.get_size();
670 
671  // Translate text width and height back to draw-space, rounding up.
672  local_variables.add("text_width", wfl::variant(tw));
673  local_variables.add("text_height", wfl::variant(th));
674 
675  if (variables.has_key("fake_draw") && variables.query_value("fake_draw").as_bool()) {
676  variables.add("text_width", wfl::variant(tw));
677  variables.add("text_height", wfl::variant(th));
678  return;
679  }
680 
681  const int x = x_(local_variables);
682  const int y = y_(local_variables);
683  const int w = w_(local_variables);
684  const int h = h_(local_variables);
685  rect dst_rect{x, y, w, h};
686 
687  // Execute the provided actions for this context.
688  wfl::execute_actions(actions_formula_.evaluate(local_variables), wfl::variant(variables.fake_ptr()));
689 
690  texture tex = text_renderer.render_and_get_texture();
691  if(!tex) {
692  DBG_GUI_D << "Text: Rendering '" << text << "' resulted in an empty canvas, leave.";
693  return;
694  }
695 
696  dst_rect.w = std::min(dst_rect.w, tex.w());
697  dst_rect.h = std::min(dst_rect.h, tex.h());
698 
699  draw::blit(tex, dst_rect);
700 }
701 
702 /***** ***** ***** ***** ***** CANVAS ***** ***** ***** ***** *****/
703 
705  : shapes_()
706  , blur_depth_(0)
707  , blur_region_()
708  , deferred_(false)
709  , w_(0)
710  , h_(0)
711  , variables_()
712  , functions_()
713 {
714  parse_cfg(cfg);
715 }
716 
717 // It would be better if the blur effect was managed at a higher level.
718 // But for now this works and should be both general and robust.
719 bool canvas::update_blur(const rect& screen_region, bool force)
720 {
721  if(!blur_depth_) {
722  // No blurring needed.
723  return true;
724  }
725 
726  if(screen_region != blur_region_) {
727  DBG_GUI_D << "blur region changed from " << blur_region_
728  << " to " << screen_region;
729  // something has changed. regenerate the texture.
731  blur_region_ = screen_region;
732  }
733 
734  if(blur_texture_ && !force) {
735  // We already made the blur. It's expensive, so don't do it again.
736  return true;
737  }
738 
739  // To blur what is underneath us, it must already be rendered somewhere.
740  // This is okay for sub-elements of an opaque window (panels on the main
741  // title screen for example) as the window will already have rendered
742  // its background to the render buffer before we get here.
743  // If however we are blurring elements behind the window, such as if
744  // the window itself is translucent (objectives popup), or it is
745  // transparent with a translucent element (character dialogue),
746  // then we need to render what will be behind it before capturing that
747  // and rendering a blur.
748  // We could use the previous render frame, but there could well have been
749  // another element there last frame such as a popup window which we
750  // don't want to be part of the blur.
751  // The stable solution is to render in multiple passes,
752  // so that is what we shall do.
753 
754  // For the first pass, this element and its children are not rendered.
755  if(!deferred_) {
756  DBG_GUI_D << "Deferring blur at " << screen_region;
757  deferred_ = true;
759  return false;
760  }
761 
762  // For the second pass we read the result of the first pass at
763  // this widget's location, and blur it.
764  DBG_GUI_D << "Blurring " << screen_region << " depth " << blur_depth_;
765  rect read_region = screen_region;
766  auto setter = draw::set_render_target({});
767  surface s = video::read_pixels_low_res(&read_region);
768  blur_surface(s, {0, 0, s->w, s->h}, blur_depth_);
770  deferred_ = false;
771  return true;
772 }
773 
775 {
777 }
778 
780 {
781  // This early-return has to come before the `validate(rect.w <= w_)` check, as during the boost_unit_tests execution
782  // the debug_clock widget will have no shapes, 0x0 size, yet be given a larger rect to draw.
783  if(shapes_.empty()) {
784  DBG_GUI_D << "Canvas: empty (no shapes to draw).";
785  return;
786  }
787 
788  if(deferred_) {
789  // We will draw next frame.
790  return;
791  }
792 
793  // Draw blurred background.
794  // TODO: hwaccel - this should be able to be removed at some point with shaders
795  if(blur_depth_ && blur_texture_) {
796  DBG_GUI_D << "blitting blur size " << blur_texture_.draw_size();
798  }
799 
800  // Draw items
801  for(auto& shape : shapes_) {
802  assert(shape);
803  const lg::scope_logger inner_scope_logging_object__{log_gui_draw, "Canvas: draw shape."};
805  }
806 }
807 
809 {
810  log_scope2(log_gui_parse, "Canvas: parsing config.");
811 
812  for(const auto [type, data] : cfg.all_children_view())
813  {
814  DBG_GUI_P << "Canvas: found shape of the type " << type << ".";
815 
816  if(type == "line") {
817  shapes_.emplace_back(std::make_unique<line_shape>(data));
818  } else if(type == "rectangle") {
819  shapes_.emplace_back(std::make_unique<rectangle_shape>(data));
820  } else if(type == "round_rectangle") {
821  shapes_.emplace_back(std::make_unique<round_rectangle_shape>(data));
822  } else if(type == "circle") {
823  shapes_.emplace_back(std::make_unique<circle_shape>(data));
824  } else if(type == "image") {
825  shapes_.emplace_back(std::make_unique<image_shape>(data, functions_));
826  } else if(type == "text") {
827  shapes_.emplace_back(std::make_unique<text_shape>(data, functions_));
828  } else if(type == "pre_commit") {
829 
830  /* note this should get split if more preprocessing is used. */
831  for(const auto [func_key, func_cfg] : data.all_children_view())
832  {
833  if(func_key == "blur") {
834  blur_depth_ = func_cfg["depth"].to_unsigned();
835  } else {
836  ERR_GUI_P << "Canvas: found a pre commit function"
837  << " of an invalid type " << type << ".";
838  }
839  }
840 
841  } else {
842  ERR_GUI_P << "Canvas: found a shape of an invalid type " << type
843  << ".";
844  }
845  }
846 }
847 
849 {
851  variables_.add("width", wfl::variant(w_));
852  variables_.add("height", wfl::variant(h_));
853 }
854 
856 {
857  w_ = size.x;
858  h_ = size.y;
860 }
861 
862 void canvas::clear_shapes(const bool force)
863 {
864  if(force) {
865  shapes_.clear();
866  } else {
867  utils::erase_if(shapes_, [](const std::unique_ptr<shape>& s) { return !s->immutable(); });
868  }
869 }
870 
871 /***** ***** ***** ***** ***** SHAPE ***** ***** ***** ***** *****/
872 
873 } // namespace gui2
std::size_t w_
#define debug(x)
This file contains the canvas object which is the part where the widgets draw (temporally) images on.
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:157
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:795
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:281
Helper class to encapsulate the management of a PangoAttrList.
Definition: attributes.hpp:28
void splice_into(PangoAttrList *target) const
Definition: attributes.hpp:71
Text class.
Definition: text.hpp:78
pango_text & set_font_style(const FONT_STYLE font_style)
Definition: text.cpp:381
void clear_attributes()
Definition: text.cpp:319
point get_size()
Returns the size of the text, in drawing coordinates.
Definition: text.cpp:154
pango_text & set_characters_per_line(const unsigned characters_per_line)
Definition: text.cpp:416
pango_text & set_foreground_color(const color_t &color)
Definition: text.cpp:391
pango_text & set_line_spacing(float line_spacing)
Definition: text.hpp:352
pango_text & set_family_class(font::family_class fclass)
Definition: text.cpp:359
void apply_attributes(const font::attribute_list &attrs)
Definition: text.cpp:324
pango_text & set_add_outline(bool do_add)
Definition: text.cpp:514
pango_text & set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
Definition: text.cpp:452
pango_text & set_alignment(const PangoAlignment alignment)
Definition: text.cpp:472
pango_text & set_font_size(unsigned font_size)
Definition: text.cpp:369
pango_text & set_link_aware(bool b)
Definition: text.cpp:495
bool set_text(const std::string &text, const bool markedup)
Sets the text to render.
Definition: text.cpp:333
pango_text & set_maximum_height(int height, bool multiline)
Definition: text.cpp:427
pango_text & set_maximum_width(int width)
Definition: text.cpp:400
texture render_and_get_texture()
Returns the cached texture, or creates a new one otherwise.
Definition: text.cpp:122
pango_text & set_link_color(const color_t &color)
Definition: text.cpp:504
std::ostringstream wrapper.
Definition: formatter.hpp:40
Abstract base class for all other shapes.
Definition: canvas.hpp:54
virtual void draw(wfl::map_formula_callable &variables)=0
Draws the canvas.
texture blur_texture_
Blurred background texture.
Definition: canvas.hpp:190
bool deferred_
Whether we have deferred rendering so we can capture for blur.
Definition: canvas.hpp:196
wfl::action_function_symbol_table functions_
Action function definitions for the canvas.
Definition: canvas.hpp:208
void clear_shapes(const bool force)
Definition: canvas.cpp:862
unsigned blur_depth_
The depth of the blur to use in the pre committing.
Definition: canvas.hpp:187
wfl::map_formula_callable variables_
The variables of the canvas.
Definition: canvas.hpp:205
bool update_blur(const rect &screen_region, const bool force=false)
Update the background blur texture, if relevant and necessary.
Definition: canvas.cpp:719
rect blur_region_
The region of the screen we have blurred (if any).
Definition: canvas.hpp:193
void parse_cfg(const config &cfg)
Parses a config object.
Definition: canvas.cpp:808
void queue_reblur()
Clear the cached blur texture, forcing it to regenerate.
Definition: canvas.cpp:774
canvas(const config &cfg)
Definition: canvas.cpp:704
std::vector< std::unique_ptr< shape > > shapes_
Vector with the shapes to draw.
Definition: canvas.hpp:177
unsigned w_
The full width of the canvas.
Definition: canvas.hpp:199
void update_size_variables()
Update WFL size variables.
Definition: canvas.cpp:848
unsigned h_
The full height of the canvas.
Definition: canvas.hpp:202
void draw()
Draw the canvas' shapes onto the screen.
Definition: canvas.cpp:779
void set_size(const point &size)
Definition: canvas.cpp:855
typed_formula< color_t > border_color_
The border color of the circle.
typed_formula< unsigned > x_
The center x coordinate of the circle.
typed_formula< unsigned > radius_
The radius of the circle.
unsigned int border_thickness_
The border thickness of the circle.
circle_shape(const config &cfg)
Constructor.
Definition: canvas.cpp:214
typed_formula< color_t > fill_color_
The fill color of the circle.
typed_formula< unsigned > y_
The center y coordinate of the circle.
void draw(wfl::map_formula_callable &variables) override
Draws the canvas.
Definition: canvas.cpp:229
typed_formula< std::string > image_name_
Image path from which the image will be loaded.
resize_mode
Determines the way an image will be resized.
bool failure_logged_
Prevents duplicate error logs when an image can't be loaded.
typed_formula< unsigned > w_
The width of the image.
resize_mode get_resize_mode(const std::string &resize_mode)
Converts a string to a resize mode.
Definition: canvas.cpp:396
typed_formula< unsigned > x_
The x coordinate of the image.
static void dimension_validation(unsigned value, const std::string &name, const std::string &key)
Definition: canvas.cpp:285
typed_formula< unsigned > y_
The y coordinate of the image.
typed_formula< unsigned > h_
The height of the image.
resize_mode resize_mode_
The resize mode for an image.
wfl::formula actions_formula_
typed_formula< bool > mirror_
Mirror the image over the vertical axis.
void draw(wfl::map_formula_callable &variables) override
Draws the canvas.
Definition: canvas.cpp:294
image_shape(const config &cfg, wfl::action_function_symbol_table &functions)
Constructor.
Definition: canvas.cpp:252
typed_formula< color_t > color_
The color of the line.
typed_formula< unsigned > x1_
The start x coordinate of the line.
typed_formula< unsigned > y1_
The start y coordinate of the line.
typed_formula< unsigned > x2_
The end x coordinate of the line.
line_shape(const config &cfg)
Constructor.
Definition: canvas.cpp:53
typed_formula< unsigned > y2_
The end y coordinate of the line.
void draw(wfl::map_formula_callable &variables) override
Draws the canvas.
Definition: canvas.cpp:68
typed_formula< int > x_
The x coordinate of the rectangle.
typed_formula< int > w_
The width of the rectangle.
typed_formula< int > y_
The y coordinate of the rectangle.
typed_formula< int > h_
The height of the rectangle.
rectangle_shape(const config &cfg)
Constructor.
Definition: canvas.cpp:90
int border_thickness_
Border thickness.
typed_formula< color_t > fill_color_
The border color of the rectangle.
void draw(wfl::map_formula_callable &variables) override
Draws the canvas.
Definition: canvas.cpp:119
typed_formula< color_t > border_color_
The border color of the rectangle.
typed_formula< color_t > border_color_
The border color of the rounded rectangle.
void draw(wfl::map_formula_callable &variables) override
Draws the canvas.
Definition: canvas.cpp:167
typed_formula< int > r_
The radius of the corners.
round_rectangle_shape(const config &cfg)
Constructor.
Definition: canvas.cpp:149
int border_thickness_
Border thickness.
typed_formula< color_t > fill_color_
The border color of the rounded rectangle.
font::pango_text::FONT_STYLE font_style_
The style of the text.
typed_formula< bool > outline_
Whether to apply a text outline.
typed_formula< color_t > color_
The color of the text.
typed_formula< int > maximum_height_
The maximum height for the text.
bool parse_text_as_formula_
Whether to parse text_ as WFL formula.
t_string get_text() const
Definition: canvas.cpp:545
typed_formula< bool > link_aware_
The link aware switch of the text.
typed_formula< PangoAlignment > text_alignment_
The alignment of the text.
void set_text(const t_string &text)
Definition: canvas.cpp:549
void add_attributes_from(text_shape &tshape2, const unsigned attr_start)
Definition: canvas.cpp:611
typed_formula< color_t > highlight_color_
The color to be used for highlighting.
font::family_class font_family_
The text font family.
typed_formula< color_t > link_color_
The link color of the text.
typed_formula< int > maximum_width_
The maximum width for the text.
void draw(wfl::map_formula_callable &variables) override
Draws the canvas.
Definition: canvas.cpp:616
text_shape(const config &cfg, wfl::action_function_symbol_table &functions)
Constructor.
Definition: canvas.cpp:486
std::pair< std::size_t, std::size_t > add_text(const t_string &text)
Definition: canvas.cpp:553
config::attribute_value text_
The text to draw.
typed_formula< int > highlight_end_
End offset for highlight.
void set_wrap_width(const unsigned wrap_width)
Definition: canvas.cpp:562
float line_spacing_
Spacing between lines.
void add_attribute(const std::string &attr_name, const std::string &extra_data="", std::size_t start=PANGO_ATTR_INDEX_FROM_TEXT_BEGINNING, std::size_t end=PANGO_ATTR_INDEX_TO_TEXT_END)
Definition: canvas.cpp:566
typed_formula< int > highlight_start_
Start offset for highlight.
typed_formula< unsigned > font_size_
The font size of the text.
unsigned characters_per_line_
The number of characters per line.
wfl::formula actions_formula_
Any extra WFL actions to execute.
font::attribute_list text_attributes_
Any custom Pango text attributes.
typed_formula< bool > text_markup_
The text markup switch of the text.
Template class can hold a value or a formula to calculate the value.
bool has_formula() const
Determine whether the class contains a formula.
void set_value(T value)
Set the value for this object.
Generic locator abstracting the location of an image.
Definition: picture.hpp:59
std::string::size_type size() const
Definition: tstring.hpp:201
Wrapper class to encapsulate creation and management of an SDL_Texture.
Definition: texture.hpp:33
int w() const
The draw-space width of the texture, in pixels.
Definition: texture.hpp:103
void reset()
Releases ownership of the managed texture and resets the ptr to null.
Definition: texture.cpp:184
point draw_size() const
The size of the texture in draw-space.
Definition: texture.hpp:120
int h() const
The draw-space height of the texture, in pixels.
Definition: texture.hpp:112
formula_callable_ptr fake_ptr()
Definition: callable.hpp:42
variant query_value(const std::string &key) const
Definition: callable.hpp:50
bool has_key(const std::string &key) const
Definition: callable.hpp:82
static variant evaluate(const const_formula_ptr &f, const formula_callable &variables, formula_debugger *fdb=nullptr, variant default_res=variant(0))
Definition: formula.hpp:48
map_formula_callable & add(const std::string &key, const variant &value)
Definition: callable.hpp:249
int as_int(int fallback=0) const
Returns the variant's value as an integer.
Definition: variant.cpp:336
bool as_bool() const
Returns a boolean state of the variant value.
Definition: variant.cpp:357
Drawing functions, for drawing things on the screen.
const config * cfg
std::size_t i
Definition: function.cpp:1031
static std::string _(const char *str)
Definition: gettext.hpp:97
Define the common log macros for the gui toolkit.
#define ERR_GUI_P
Definition: log.hpp:69
#define DBG_GUI_P
Definition: log.hpp:66
#define ERR_GUI_D
Definition: log.hpp:32
#define ERR_GUI_E
Definition: log.hpp:38
#define DBG_GUI_D
Definition: log.hpp:29
#define WRN_GUI_D
Definition: log.hpp:31
#define log_scope2(domain, description)
Definition: log.hpp:276
void request_extra_render_pass()
Request an extra render pass.
void tiled_highres(const texture &tex, const ::rect &dst, bool centered=false, bool mirrored=false)
Tile a texture to fill a region.
Definition: draw.cpp:481
render_target_setter set_render_target(const texture &t)
Set the given texture as the active render target.
Definition: draw.cpp:748
void circle(int x, int y, int r, const color_t &c, uint8_t octants=0xff)
Draw a circle of the given colour.
Definition: draw.cpp:220
void tiled(const texture &tex, const ::rect &dst, bool centered=false, bool mirrored=false)
Tile a texture to fill a region.
Definition: draw.cpp:453
void rect(const ::rect &rect)
Draw a rectangle.
Definition: draw.cpp:159
void cairo_disc(int cx, int cy, int r, const color_t &c)
Draw filled circle using Cairo.
Definition: draw.cpp:358
void flipped(const texture &tex, const ::rect &dst, bool flip_h=true, bool flip_v=false)
Draws a texture, or part of a texture, at the given location, also mirroring/flipping the texture hor...
Definition: draw.cpp:424
void cairo_circle(int cx, int cy, int r, const color_t &c, int thickness)
Draw outline of circle using Cairo.
Definition: draw.cpp:320
void disc(int x, int y, int r, const color_t &c, uint8_t octants=0xff)
Draw a solid disc of the given colour.
Definition: draw.cpp:262
void set_color(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
Set the drawing colour.
Definition: draw.cpp:109
void fill(const ::rect &rect, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
Fill an area with the given colour.
Definition: draw.cpp:52
void blit(const texture &tex, const ::rect &dst)
Draws a texture, or part of a texture, at the given location.
Definition: draw.cpp:394
void line(int from_x, int from_y, int to_x, int to_y)
Draw a line.
Definition: draw.cpp:189
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.
Graphical text output.
const color_t YELLOW_COLOR
pango_text & get_text_renderer()
Returns a reference to a static pango_text object.
Definition: text.cpp:960
void add_attribute_image_shape(attribute_list &list, unsigned offset_start, unsigned offset_end, const std::string &image_path)
Add Pango shape attribute to a specific portion of text.
Definition: attributes.cpp:180
constexpr float get_line_spacing_factor()
Definition: text.hpp:570
const color_t GOOD_COLOR
void add_attribute_size(attribute_list &list, unsigned offset_start, unsigned offset_end, int size)
Add Pango font size attribute to a specific portion of text.
Definition: attributes.cpp:64
void add_attribute_bg_color(attribute_list &list, unsigned offset_start, unsigned offset_end, const color_t &color)
Mark a specific portion of text for highlighting.
Definition: attributes.cpp:135
void add_attribute_weight(attribute_list &list, unsigned offset_start, unsigned offset_end, PangoWeight weight)
Add Pango font weight attribute to a specific portion of text.
Definition: attributes.cpp:80
void add_attribute_line_height(attribute_list &list, unsigned offset_start, unsigned offset_end, const double factor)
Add Pango line height attribute to a specific portion of text.
Definition: attributes.cpp:166
void add_attribute_underline(attribute_list &list, unsigned offset_start, unsigned offset_end, PangoUnderline underline)
Add Pango underline attribute to a specific portion of text.
Definition: attributes.cpp:106
family_class decode_family_class(const std::string &str)
void add_attribute_font_family(attribute_list &list, unsigned offset_start, unsigned offset_end, font::family_class family)
Add Pango font family attribute to a specific portion of text.
Definition: attributes.cpp:150
void add_attribute_style(attribute_list &list, unsigned offset_start, unsigned offset_end, PangoStyle style)
Add Pango font style attribute to a specific portion of text, used to set italic/oblique text.
Definition: attributes.cpp:93
const int SIZE_NORMAL
Definition: constants.cpp:20
const color_t NORMAL_COLOR
color_t string_to_color(const std::string &color_str)
Return the color the string represents.
void add_attribute_fg_color(attribute_list &list, unsigned offset_start, unsigned offset_end, const color_t &color)
Add Pango fg color attribute to a specific portion of text.
Definition: attributes.cpp:119
Generic file dialog.
void get_screen_size_variables(wfl::map_formula_callable &variable)
Gets a formula object with the screen size.
Definition: helper.cpp:150
lg::log_domain log_gui_draw("gui/draw")
Definition: log.hpp:28
PangoWeight decode_text_weight(const std::string &weight)
Converts a text weight string to a PangoWeight.
Definition: helper.cpp:48
font::pango_text::FONT_STYLE decode_font_style(const std::string &style)
Converts a font style string to a font style.
Definition: helper.cpp:31
lg::log_domain log_gui_parse("gui/parse")
Definition: log.hpp:65
PangoStyle decode_text_style(const std::string &style)
Converts a text style string to a PangoStyle.
Definition: helper.cpp:69
texture get_texture(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image texture suitable for hardware-accelerated rendering.
Definition: picture.cpp:953
scale_quality
Definition: picture.hpp:181
std::size_t size(std::string_view str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:81
int stoi(std::string_view str)
Same interface as std::stoi and meant as a drop in replacement, except:
Definition: charconv.hpp:156
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition: general.hpp:107
double stod(std::string_view str)
Same interface as std::stod and meant as a drop in replacement, except:
Definition: charconv.hpp:142
surface read_pixels_low_res(rect *r)
The same as read_pixels, but returns a low-resolution surface suitable for use with the old drawing s...
Definition: video.cpp:663
variant execute_actions(const variant &execute, const variant &context)
Executes all action_callables in execute using the provided context.
Definition: variant.cpp:696
int w
Definition: pathfind.cpp:188
std::string_view data
Definition: picture.cpp:188
int x2_
Definition: pump.cpp:132
int y1_
Definition: pump.cpp:132
int x1_
Definition: pump.cpp:132
int y2_
Definition: pump.cpp:132
Contains the SDL_Rect helper code.
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:51
constexpr bool null() const
Definition: color.hpp:180
Holds a 2D point.
Definition: point.hpp:25
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:49
static map_location::direction s
void blur_surface(surface &surf, rect rect, int depth)
Cross-fades a surface in place.
Definition: utils.cpp:842
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define VALIDATE_WITH_DEV_MESSAGE(cond, message, dev_message)
#define h