The Battle for Wesnoth  1.19.15+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 "formatter.hpp"
30 #include "gettext.hpp"
32 #include "gui/core/log.hpp"
33 #include "gui/widgets/helper.hpp"
34 #include "font/font_config.hpp"
35 #include "font/standard_colors.hpp"
36 #include "picture.hpp"
37 #include "sdl/point.hpp"
38 #include "sdl/rect.hpp"
39 #include "sdl/surface.hpp"
40 #include "sdl/texture.hpp"
41 #include "sdl/utils.hpp" // blur_surface
42 #include "video.hpp" // read_pixels_low_res, only used for blurring
43 #include "wml_exception.hpp"
44 
45 namespace gui2
46 {
47 
48 /***** ***** ***** ***** ***** LINE ***** ***** ***** ***** *****/
49 
51  : shape(cfg)
52  , x1_(cfg["x1"])
53  , y1_(cfg["y1"])
54  , x2_(cfg["x2"])
55  , y2_(cfg["y2"])
56  , color_(cfg["color"])
57  , thickness_(cfg["thickness"].to_unsigned())
58 {
59  const std::string& debug = (cfg["debug"]);
60  if(!debug.empty()) {
61  DBG_GUI_P << "Line: found debug message '" << debug << "'.";
62  }
63 }
64 
66 {
67  /**
68  * @todo formulas are now recalculated every draw cycle which is a bit silly
69  * unless there has been a resize. So to optimize we should use an extra
70  * flag or do the calculation in a separate routine.
71  */
72 
73  const unsigned x1 = x1_(variables);
74  const unsigned y1 = y1_(variables);
75  const unsigned x2 = x2_(variables);
76  const unsigned y2 = y2_(variables);
77 
78  DBG_GUI_D << "Line: draw from " << x1 << ',' << y1 << " to " << x2 << ',' << y2 << ".";
79 
80  // @todo FIXME respect the thickness.
81 
82  draw::line(x1, y1, x2, y2, color_(variables));
83 }
84 
85 /***** ***** ***** ***** ***** Rectangle ***** ***** ***** ***** *****/
86 
89  , border_thickness_(cfg["border_thickness"].to_int())
90  , border_color_(cfg["border_color"], color_t::null_color())
91  , fill_color_(cfg["fill_color"], color_t::null_color())
92 {
93  // Check if a raw color string evaluates to a null color.
94  if(!border_color_.has_formula() && border_color_().null()) {
96  }
97 
98  const std::string& debug = (cfg["debug"]);
99  if(!debug.empty()) {
100  DBG_GUI_P << "Rectangle: found debug message '" << debug << "'.";
101  }
102 }
103 
105 {
106  const rect area {
107  x_(variables),
108  y_(variables),
109  w_(variables),
110  h_(variables)
111  };
112 
113  const color_t fill_color = fill_color_(variables);
114 
115  // Fill the background, if applicable
116  if(!fill_color.null()) {
117  DBG_GUI_D << "fill " << fill_color;
118  draw::set_color(fill_color);
119  draw::fill(area.padded_by(-border_thickness_));
120  }
121 
122  const color_t border_color = border_color_(variables);
123 
124  // Draw the border
125  draw::set_color(border_color);
126  DBG_GUI_D << "border thickness " << border_thickness_ << ", colour " << border_color;
127  for(int i = 0; i < border_thickness_; ++i) {
128  draw::rect(area.padded_by(-i));
129  }
130 }
131 
132 /***** ***** ***** ***** ***** Rounded Rectangle ***** ***** ***** ***** *****/
133 
136  , r_(cfg["corner_radius"])
137  , border_thickness_(cfg["border_thickness"].to_int())
138  , border_color_(cfg["border_color"], color_t::null_color())
139  , fill_color_(cfg["fill_color"], color_t::null_color())
140 {
141  // Check if a raw color string evaluates to a null color.
142  if(!border_color_.has_formula() && border_color_().null()) {
143  border_thickness_ = 0;
144  }
145 
146  const std::string& debug = (cfg["debug"]);
147  if(!debug.empty()) {
148  DBG_GUI_P << "Rounded Rectangle: found debug message '" << debug << "'.";
149  }
150 }
151 
153 {
154  const int x = x_(variables);
155  const int y = y_(variables);
156  const int w = w_(variables);
157  const int h = h_(variables);
158  const int r = r_(variables);
159 
160  DBG_GUI_D << "Rounded Rectangle: draw from " << x << ',' << y << " width " << w << " height " << h << ".";
161 
162  const color_t fill_color = fill_color_(variables);
163 
164  // Fill the background, if applicable
165  if(!fill_color.null() && w && h) {
166  draw::set_color(fill_color);
167 
168  draw::fill(rect{x + r, y + border_thickness_, w - r * 2, r - border_thickness_ + 1});
169  draw::fill(rect{x + border_thickness_, y + r + 1, w - border_thickness_ * 2, h - r * 2});
170  draw::fill(rect{x + r, y - r + h + 1, w - r * 2, r - border_thickness_});
171 
172  draw::disc(x + r, y + r, r, 0xc0);
173  draw::disc(x + w - r, y + r, r, 0x03);
174  draw::disc(x + r, y + h - r, r, 0x30);
175  draw::disc(x + w - r, y + h - r, r, 0x0c);
176  }
177 
178  const color_t border_color = border_color_(variables);
179 
180  // Draw the border
181  draw::set_color(border_color);
182 
183  for(int i = 0; i < border_thickness_; ++i) {
184  draw::line(x + r, y + i, x + w - r, y + i);
185  draw::line(x + r, y + h - i, x + w - r, y + h - i);
186 
187  draw::line(x + i, y + r, x + i, y + h - r);
188  draw::line(x + w - i, y + r, x + w - i, y + h - r);
189 
190  draw::circle(x + r, y + r, r - i, 0xc0);
191  draw::circle(x + w - r, y + r, r - i, 0x03);
192  draw::circle(x + r, y + h - r, r - i, 0x30);
193  draw::circle(x + w - r, y + h - r, r - i, 0x0c);
194  }
195 }
196 
197 /***** ***** ***** ***** ***** CIRCLE ***** ***** ***** ***** *****/
198 
200  : shape(cfg)
201  , x_(cfg["x"])
202  , y_(cfg["y"])
203  , radius_(cfg["radius"])
204  , border_color_(cfg["border_color"])
205  , fill_color_(cfg["fill_color"])
206  , border_thickness_(cfg["border_thickness"].to_int(1))
207 {
208  const std::string& debug = (cfg["debug"]);
209  if(!debug.empty()) {
210  DBG_GUI_P << "Circle: found debug message '" << debug << "'.";
211  }
212 }
213 
215 {
216  /**
217  * @todo formulas are now recalculated every draw cycle which is a bit
218  * silly unless there has been a resize. So to optimize we should use an
219  * extra flag or do the calculation in a separate routine.
220  */
221  const int x = x_(variables);
222  const int y = y_(variables);
223  const unsigned radius = radius_(variables);
224  const color_t fill_color = fill_color_(variables);
225  if (!fill_color.null()) {
226  draw::cairo_disc(x, y, radius, fill_color);
227  }
228 
229  const color_t border_color = border_color_(variables);
230  draw::cairo_circle(x, y, radius, border_color, border_thickness_);
231 
232  DBG_GUI_D << "Circle: drawn at " << x << ',' << y << " radius " << radius << ".";
233 }
234 
235 /***** ***** ***** ***** ***** IMAGE ***** ***** ***** ***** *****/
236 
238  : shape(cfg)
239  , x_(cfg["x"])
240  , y_(cfg["y"])
241  , w_(cfg["w"])
242  , h_(cfg["h"])
243  , image_name_(cfg["name"])
244  , resize_mode_(get_resize_mode(cfg["resize_mode"]))
245  , mirror_(cfg.get_old_attribute("mirror", "vertical_mirror", "image"))
246  , actions_formula_(cfg["actions"], &functions)
247  , failure_logged_(false)
248 {
249  const std::string& debug = (cfg["debug"]);
250  if(!debug.empty()) {
251  DBG_GUI_P << "Image: found debug message '" << debug << "'.";
252  }
253 }
254 
255 void image_shape::dimension_validation(unsigned value, const std::string& name, const std::string& key)
256 {
257  const int as_int = static_cast<int>(value);
258 
259  VALIDATE_WITH_DEV_MESSAGE(as_int >= 0, _("Image doesn’t fit on canvas."),
260  formatter() << "Image '" << name << "', " << key << " = " << as_int << "."
261  );
262 }
263 
265 {
266  DBG_GUI_D << "Image: draw.";
267 
268  // The name will be passed to the texture loading code on each draw, any caching is left to the
269  // image loading code itself. A name can point to a different image file due to i18n support,
270  // even when the image_name_ isn't a formula.
271  const std::string& name = image_name_(variables);
272 
273  if(name.empty()) {
274  if(!failure_logged_) {
275  DBG_GUI_D << "Image: name is empty or contains invalid formula, will not be drawn.";
276  failure_logged_ = true;
277  }
278  return;
279  }
280 
281  // Texture filtering mode must be set on texture creation,
282  // so check whether we need smooth scaling or not here.
286  {
288  }
290 
291  if(!tex) {
292  if(!failure_logged_) {
293  ERR_GUI_D << "Image: '" << name << "' not found and won't be drawn.";
294  failure_logged_ = true;
295  }
296  return;
297  }
298 
299  wfl::map_formula_callable local_variables(variables);
300  local_variables.add("image_original_width", wfl::variant(tex.w()));
301  local_variables.add("image_original_height", wfl::variant(tex.h()));
302 
303  int w = w_(local_variables);
304  dimension_validation(w, name, "w");
305 
306  int h = h_(local_variables);
307  dimension_validation(h, name, "h");
308 
309  local_variables.add("image_width", wfl::variant(w ? w : tex.w()));
310  local_variables.add("image_height", wfl::variant(h ? h : tex.h()));
311 
312  const int x = x_(local_variables);
313  const int y = y_(local_variables);
314 
315  // used in gui/dialogs/story_viewer.cpp
316  local_variables.add("clip_x", wfl::variant(x));
317  local_variables.add("clip_y", wfl::variant(y));
318 
319  if (variables.has_key("fake_draw") && variables.query_value("fake_draw").as_bool()) {
320  variables.add("image_original_width", wfl::variant(tex.w()));
321  variables.add("image_original_height", wfl::variant(tex.h()));
322  variables.add("image_width", wfl::variant(w ? w : tex.w()));
323  variables.add("image_height", wfl::variant(h ? h : tex.h()));
324  return;
325  }
326 
327  // Execute the provided actions for this context.
328  wfl::variant(variables.fake_ptr()).execute_variant(actions_formula_.evaluate(local_variables));
329 
330  // If w or h is 0, assume it means the whole image.
331  if (!w) { w = tex.w(); }
332  if (!h) { h = tex.h(); }
333 
334  const rect dst_rect { x, y, w, h };
335 
336  // What to do with the image depends on whether we need to tile it or not.
337  switch(resize_mode_) {
338  case resize_mode::tile:
339  draw::tiled(tex, dst_rect, false, mirror_(variables));
340  break;
342  draw::tiled(tex, dst_rect, true, mirror_(variables));
343  break;
345  draw::tiled_highres(tex, dst_rect, false, mirror_(variables));
346  break;
348  // Stretching is identical to scaling in terms of handling.
349  // Is this intended? That's what previous code was doing.
350  case resize_mode::scale:
351  // Filtering mode is set on texture creation above.
352  // Handling is otherwise identical to sharp scaling.
354  if(mirror_(variables)) {
355  draw::flipped(tex, dst_rect);
356  } else {
357  draw::blit(tex, dst_rect);
358  }
359  break;
360  default:
361  ERR_GUI_D << "Image: unrecognized resize mode.";
362  break;
363  }
364 }
365 
367 {
368  if(resize_mode == "tile") {
369  return resize_mode::tile;
370  } else if(resize_mode == "tile_center") {
372  } else if(resize_mode == "tile_highres") {
374  } else if(resize_mode == "stretch") {
375  return resize_mode::stretch;
376  } else if(resize_mode == "scale_sharp") {
378  } else if(resize_mode == "scale") {
379  return resize_mode::scale;
380  } else {
381  if(!resize_mode.empty()) {
382  ERR_GUI_E << "Invalid resize mode '" << resize_mode << "' falling back to 'scale'.";
383  }
384 
385  // Linear scaling just looks horrible as a default, especially on HDPI screens, and even
386  // for some non-pixel art (the logo, for example). Nearest-neighbor isn't perfect for those
387  // usecases, but it's definitely better, in my opinion.
388  //
389  // -- vultraz, 2022-08-20
391  }
392 }
393 
394 /***** ***** ***** ***** ***** TEXT ***** ***** ***** ***** *****/
395 
396 namespace
397 {
398 /** Populates the attribute list from the given config child range. */
399 auto parse_attributes(const config::const_child_itors& range)
400 {
401  // TODO: most of the time this will be empty, unless you're using rich_label.
402  // It's a lot of memory allocations to always have a valid object here...
403  // Do we need store it as an optional?
404  font::attribute_list text_attributes;
405 
406  for(const config& attr : range) {
407  const std::string name = attr["name"];
408 
409  if(name.empty()) {
410  continue;
411  }
412 
413  const unsigned start = attr["start"].to_int(PANGO_ATTR_INDEX_FROM_TEXT_BEGINNING);
414  const unsigned end = attr["end"].to_int(PANGO_ATTR_INDEX_TO_TEXT_END);
415 
416  // Attributes with start == end set won't do anything, so skip
417  if (start == end) {
418  WRN_GUI_D << "attribute " << name << " has equal start and end indices, will not be added.";
419  continue;
420  }
421 
422  if (name == "color" || name == "fgcolor" || name == "foreground") {
423  add_attribute_fg_color(text_attributes, start, end, attr["value"].empty() ? font::NORMAL_COLOR : font::string_to_color(attr["value"]));
424  } else if (name == "bgcolor" || name == "background") {
425  add_attribute_bg_color(text_attributes, start, end, attr["value"].empty() ? font::GOOD_COLOR : font::string_to_color(attr["value"]));
426  } else if (name == "font_size" || name == "size") {
427  add_attribute_size(text_attributes, start, end, attr["value"].to_int(font::SIZE_NORMAL));
428  } else if (name == "font_family" || name == "face") {
429  add_attribute_font_family(text_attributes, start, end, font::decode_family_class(attr["value"]));
430  } else if (name == "weight") {
431  add_attribute_weight(text_attributes, start, end, decode_text_weight(attr["value"]));
432  } else if (name == "style") {
433  add_attribute_style(text_attributes, start, end, decode_text_style(attr["value"]));
434  } else if (name == "bold" || name == "b") {
435  add_attribute_weight(text_attributes, start, end, PANGO_WEIGHT_BOLD);
436  } else if (name == "italic" || name == "i") {
437  add_attribute_style(text_attributes, start, end, PANGO_STYLE_ITALIC);
438  } else if (name == "underline" || name == "u") {
439  add_attribute_underline(text_attributes, start, end, PANGO_UNDERLINE_SINGLE);
440  } else if (name == "line_height") {
441  add_attribute_line_height(text_attributes, start, end, attr["value"].to_double());
442  } else if (name == "image") { // An inline image that behave as a custom text glyph
443  add_attribute_image_shape(text_attributes, start, end, attr["value"]);
444  } else {
445  // Unsupported formatting or normal text
446  add_attribute_weight(text_attributes, start, end, PANGO_WEIGHT_NORMAL);
447  add_attribute_style(text_attributes, start, end, PANGO_STYLE_NORMAL);
448  }
449  }
450 
451  return text_attributes;
452 }
453 
454 } // anon namespace
455 
458  , font_family_(font::decode_family_class(cfg["font_family"]))
459  , font_size_(cfg["font_size"], font::SIZE_NORMAL)
460  , font_style_(decode_font_style(cfg["font_style"]))
461  , text_alignment_(cfg["text_alignment"])
462  , color_(cfg["color"], font::NORMAL_COLOR)
463  , text_(cfg["text"])
464  , parse_text_as_formula_(cfg["parse_text_as_formula"].to_bool(true))
465  , text_markup_(cfg["text_markup"], false)
466  , link_aware_(cfg["text_link_aware"], false)
467  , link_color_(cfg["text_link_color"], font::YELLOW_COLOR)
468  , maximum_width_(cfg["maximum_width"], -1)
469  , characters_per_line_(cfg["text_characters_per_line"].to_unsigned())
470  , maximum_height_(cfg["maximum_height"], -1)
471  , highlight_start_(cfg["highlight_start"])
472  , highlight_end_(cfg["highlight_end"])
473  , highlight_color_(cfg["highlight_color"], color_t::from_hex_string("215380"))
474  , line_spacing_(cfg["line_spacing"].to_double(font::get_line_spacing_factor()))
475  , outline_(cfg["outline"], false)
476  , actions_formula_(cfg["actions"], &functions)
477  , text_attributes_(parse_attributes(cfg.child_range("attribute")))
478 {
479  const std::string& debug = (cfg["debug"]);
480  if(!debug.empty()) {
481  DBG_GUI_P << "Text: found debug message '" << debug << "'.";
482  }
483 }
484 
486 {
487  assert(variables.has_key("text"));
488 
489  // We first need to determine the size of the text which need the rendered
490  // text. So resolve and render the text first and then start to resolve
491  // the other formulas.
492  const auto text = parse_text_as_formula_
493  ? typed_formula<t_string>{text_}(variables)
494  : text_.t_str();
495 
496  if(text.empty()) {
497  DBG_GUI_D << "Text: no text to render, leave.";
498  return;
499  }
500 
501  //
502  // Highlight
503  //
504  const int highlight_start = highlight_start_(variables);
505  const int highlight_end = highlight_end_(variables);
506 
507  if(highlight_start != highlight_end) {
508  add_attribute_bg_color(text_attributes_, highlight_start, highlight_end, highlight_color_(variables));
509  }
510 
511  font::pango_text& text_renderer = font::get_text_renderer();
512  text_renderer.clear_attributes();
513 
514  text_renderer
515  .set_link_aware(link_aware_(variables))
516  .set_link_color(link_color_(variables))
517  .set_text(text, text_markup_(variables));
518 
519  text_renderer
521  .set_font_size(font_size_(variables))
523  .set_alignment(text_alignment_(variables))
524  .set_foreground_color(color_(variables))
525  .set_maximum_width(maximum_width_(variables))
526  .set_maximum_height(maximum_height_(variables), true)
527  .set_ellipse_mode(variables.has_key("text_wrap_mode")
528  ? static_cast<PangoEllipsizeMode>(variables.query_value("text_wrap_mode").as_int())
529  : PANGO_ELLIPSIZE_END)
531  .set_add_outline(outline_(variables))
533 
534  // Do this last so it can merge with attributes from markup
535  text_renderer.apply_attributes(text_attributes_);
536 
537  wfl::map_formula_callable local_variables(variables);
538  const auto [tw, th] = text_renderer.get_size();
539 
540  // Translate text width and height back to draw-space, rounding up.
541  local_variables.add("text_width", wfl::variant(tw));
542  local_variables.add("text_height", wfl::variant(th));
543 
544  if (variables.has_key("fake_draw") && variables.query_value("fake_draw").as_bool()) {
545  variables.add("text_width", wfl::variant(tw));
546  variables.add("text_height", wfl::variant(th));
547  return;
548  }
549 
550  const int x = x_(local_variables);
551  const int y = y_(local_variables);
552  const int w = w_(local_variables);
553  const int h = h_(local_variables);
554  rect dst_rect{x, y, w, h};
555 
556  // Execute the provided actions for this context.
557  wfl::variant(variables.fake_ptr()).execute_variant(actions_formula_.evaluate(local_variables));
558 
559  texture tex = text_renderer.render_and_get_texture();
560  if(!tex) {
561  DBG_GUI_D << "Text: Rendering '" << text << "' resulted in an empty canvas, leave.";
562  return;
563  }
564 
565  dst_rect.w = std::min(dst_rect.w, tex.w());
566  dst_rect.h = std::min(dst_rect.h, tex.h());
567 
568  draw::blit(tex, dst_rect);
569 }
570 
571 /***** ***** ***** ***** ***** CANVAS ***** ***** ***** ***** *****/
572 
574  : shapes_()
575  , blur_depth_(0)
576  , blur_region_()
577  , deferred_(false)
578  , w_(0)
579  , h_(0)
580  , variables_()
581  , functions_()
582 {
583  parse_cfg(cfg);
584 }
585 
586 // It would be better if the blur effect was managed at a higher level.
587 // But for now this works and should be both general and robust.
588 bool canvas::update_blur(const rect& screen_region, bool force)
589 {
590  if(!blur_depth_) {
591  // No blurring needed.
592  return true;
593  }
594 
595  if(screen_region != blur_region_) {
596  DBG_GUI_D << "blur region changed from " << blur_region_
597  << " to " << screen_region;
598  // something has changed. regenerate the texture.
600  blur_region_ = screen_region;
601  }
602 
603  if(blur_texture_ && !force) {
604  // We already made the blur. It's expensive, so don't do it again.
605  return true;
606  }
607 
608  // To blur what is underneath us, it must already be rendered somewhere.
609  // This is okay for sub-elements of an opaque window (panels on the main
610  // title screen for example) as the window will already have rendered
611  // its background to the render buffer before we get here.
612  // If however we are blurring elements behind the window, such as if
613  // the window itself is translucent (objectives popup), or it is
614  // transparent with a translucent element (character dialogue),
615  // then we need to render what will be behind it before capturing that
616  // and rendering a blur.
617  // We could use the previous render frame, but there could well have been
618  // another element there last frame such as a popup window which we
619  // don't want to be part of the blur.
620  // The stable solution is to render in multiple passes,
621  // so that is what we shall do.
622 
623  // For the first pass, this element and its children are not rendered.
624  if(!deferred_) {
625  DBG_GUI_D << "Deferring blur at " << screen_region;
626  deferred_ = true;
628  return false;
629  }
630 
631  // For the second pass we read the result of the first pass at
632  // this widget's location, and blur it.
633  DBG_GUI_D << "Blurring " << screen_region << " depth " << blur_depth_;
634  rect read_region = screen_region;
635  auto setter = draw::set_render_target({});
636  surface s = video::read_pixels_low_res(&read_region);
637  blur_surface(s, {0, 0, s->w, s->h}, blur_depth_);
639  deferred_ = false;
640  return true;
641 }
642 
644 {
646 }
647 
649 {
650  // This early-return has to come before the `validate(rect.w <= w_)` check, as during the boost_unit_tests execution
651  // the debug_clock widget will have no shapes, 0x0 size, yet be given a larger rect to draw.
652  if(shapes_.empty()) {
653  DBG_GUI_D << "Canvas: empty (no shapes to draw).";
654  return;
655  }
656 
657  if(deferred_) {
658  // We will draw next frame.
659  return;
660  }
661 
662  // Draw blurred background.
663  // TODO: hwaccel - this should be able to be removed at some point with shaders
664  if(blur_depth_ && blur_texture_) {
665  DBG_GUI_D << "blitting blur size " << blur_texture_.draw_size();
667  }
668 
669  // Draw items
670  for(auto& shape : shapes_) {
671  const lg::scope_logger inner_scope_logging_object__{log_gui_draw, "Canvas: draw shape."};
673  }
674 }
675 
677 {
678  log_scope2(log_gui_parse, "Canvas: parsing config.");
679 
680  for(const auto [type, data] : cfg.all_children_view())
681  {
682  DBG_GUI_P << "Canvas: found shape of the type " << type << ".";
683 
684  if(type == "line") {
685  shapes_.emplace_back(std::make_unique<line_shape>(data));
686  } else if(type == "rectangle") {
687  shapes_.emplace_back(std::make_unique<rectangle_shape>(data));
688  } else if(type == "round_rectangle") {
689  shapes_.emplace_back(std::make_unique<round_rectangle_shape>(data));
690  } else if(type == "circle") {
691  shapes_.emplace_back(std::make_unique<circle_shape>(data));
692  } else if(type == "image") {
693  shapes_.emplace_back(std::make_unique<image_shape>(data, functions_));
694  } else if(type == "text") {
695  shapes_.emplace_back(std::make_unique<text_shape>(data, functions_));
696  } else if(type == "pre_commit") {
697 
698  /* note this should get split if more preprocessing is used. */
699  for(const auto [func_key, func_cfg] : data.all_children_view())
700  {
701  if(func_key == "blur") {
702  blur_depth_ = func_cfg["depth"].to_unsigned();
703  } else {
704  ERR_GUI_P << "Canvas: found a pre commit function"
705  << " of an invalid type " << type << ".";
706  }
707  }
708 
709  } else {
710  ERR_GUI_P << "Canvas: found a shape of an invalid type " << type
711  << ".";
712  }
713  }
714 }
715 
717 {
719  variables_.add("width", wfl::variant(w_));
720  variables_.add("height", wfl::variant(h_));
721 }
722 
724 {
725  w_ = size.x;
726  h_ = size.y;
728 }
729 
730 void canvas::clear_shapes(const bool force)
731 {
732  if(force) {
733  shapes_.clear();
734  } else {
735  utils::erase_if(shapes_, [](const std::unique_ptr<shape>& s) { return !s->immutable(); });
736  }
737 }
738 
739 /***** ***** ***** ***** ***** SHAPE ***** ***** ***** ***** *****/
740 
741 } // 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:158
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:796
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:282
Helper class to encapsulate the management of a PangoAttrList.
Definition: attributes.hpp:28
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:182
bool deferred_
Whether we have deferred rendering so we can capture for blur.
Definition: canvas.hpp:188
wfl::action_function_symbol_table functions_
Action function definitions for the canvas.
Definition: canvas.hpp:200
void clear_shapes(const bool force)
Definition: canvas.cpp:730
unsigned blur_depth_
The depth of the blur to use in the pre committing.
Definition: canvas.hpp:179
wfl::map_formula_callable variables_
The variables of the canvas.
Definition: canvas.hpp:197
bool update_blur(const rect &screen_region, const bool force=false)
Update the background blur texture, if relevant and necessary.
Definition: canvas.cpp:588
rect blur_region_
The region of the screen we have blurred (if any).
Definition: canvas.hpp:185
void parse_cfg(const config &cfg)
Parses a config object.
Definition: canvas.cpp:676
void queue_reblur()
Clear the cached blur texture, forcing it to regenerate.
Definition: canvas.cpp:643
canvas(const config &cfg)
Definition: canvas.cpp:573
std::vector< std::unique_ptr< shape > > shapes_
Vector with the shapes to draw.
Definition: canvas.hpp:169
unsigned w_
The full width of the canvas.
Definition: canvas.hpp:191
void update_size_variables()
Update WFL size variables.
Definition: canvas.cpp:716
unsigned h_
The full height of the canvas.
Definition: canvas.hpp:194
void draw()
Draw the canvas' shapes onto the screen.
Definition: canvas.cpp:648
void set_size(const point &size)
Definition: canvas.cpp:723
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:199
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:214
typed_formula< std::string > image_name_
String to pass to the image loader.
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:366
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:255
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:264
image_shape(const config &cfg, wfl::action_function_symbol_table &functions)
Constructor.
Definition: canvas.cpp:237
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:50
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:65
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:87
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:104
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:152
typed_formula< int > r_
The radius of the corners.
round_rectangle_shape(const config &cfg)
Constructor.
Definition: canvas.cpp:134
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.
typed_formula< bool > link_aware_
The link aware switch of the text.
typed_formula< PangoAlignment > text_alignment_
The alignment of the text.
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:485
text_shape(const config &cfg, wfl::action_function_symbol_table &functions)
Constructor.
Definition: canvas.cpp:456
config::attribute_value text_
The text to draw.
typed_formula< int > highlight_end_
End offset for highlight.
float line_spacing_
Spacing between lines.
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.
Generic locator abstracting the location of an image.
Definition: picture.hpp:59
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:253
variant execute_variant(const variant &to_exec)
Definition: variant.cpp:653
int as_int(int fallback=0) const
Returns the variant's value as an integer.
Definition: variant.cpp:291
bool as_bool() const
Returns a boolean state of the variant value.
Definition: variant.cpp:313
Drawing functions, for drawing things on the screen.
const config * cfg
std::size_t i
Definition: function.cpp:1032
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:151
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:942
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:85
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition: general.hpp:107
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:657
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:61
constexpr bool null() const
Definition: color.hpp:220
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:846
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