The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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"
36#include "font/font_config.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
48namespace 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()) {
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
285void 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 {
317 scale_quality = image::scale_quality::linear;
318 }
319 texture tex = image::get_texture(image::locator(name), scale_quality);
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.
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_) {
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.
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") {
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
426namespace
427{
428/** Populates the attribute list from the given config child range. */
429auto 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
550 text_ = text;
551}
552
553std::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
562void 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") {
587 add_attribute_size(text_attributes_, start, end, std::stoi(extra_data));
588 } else if (name == "font_family" || name == "face") {
589 add_attribute_font_family(text_attributes_, start, end, font::decode_family_class(extra_data));
590 } else if (name == "weight") {
591 add_attribute_weight(text_attributes_, start, end, decode_text_weight(extra_data));
592 } else if (name == "style") {
593 add_attribute_style(text_attributes_, start, end, decode_text_style(extra_data));
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") {
601 add_attribute_line_height(text_attributes_, start, end, std::stod(extra_data));
602 } else if (name == "image") { // An inline image that behave as a custom text glyph
603 add_attribute_image_shape(text_attributes_, start, end, extra_data);
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
611void 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))
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)
661 .set_characters_per_line(characters_per_line_)
662 .set_add_outline(outline_(variables))
663 .set_line_spacing(line_spacing_);
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.
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.
719bool 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;
767 surface s = video::read_pixels_low_res(&read_region);
768 if (!s) {
769 // Reading pixels can fail for reasons outside our control. The
770 // failure may not be transient, so skip the blur for this frame.
771 ERR_GUI_D << "Failed to read pixels to blur " << screen_region << ", skipping.";
772 deferred_ = false;
773 return true;
774 }
775 blur_surface(s, {0, 0, s->w, s->h}, blur_depth_);
777 deferred_ = false;
778 return true;
779}
780
785
787{
788 // This early-return has to come before the `validate(rect.w <= w_)` check, as during the boost_unit_tests execution
789 // the debug_clock widget will have no shapes, 0x0 size, yet be given a larger rect to draw.
790 if(shapes_.empty()) {
791 DBG_GUI_D << "Canvas: empty (no shapes to draw).";
792 return;
793 }
794
795 if(deferred_) {
796 // We will draw next frame.
797 return;
798 }
799
800 // Draw blurred background.
801 // TODO: hwaccel - this should be able to be removed at some point with shaders
803 DBG_GUI_D << "blitting blur size " << blur_texture_.draw_size();
805 }
806
807 // Draw items
808 for(auto& shape : shapes_) {
809 assert(shape);
810 const lg::scope_logger inner_scope_logging_object__{log_gui_draw, "Canvas: draw shape."};
812 }
813}
814
816{
817 log_scope2(log_gui_parse, "Canvas: parsing config.");
818
819 for(const auto [type, data] : cfg.all_children_view())
820 {
821 DBG_GUI_P << "Canvas: found shape of the type " << type << ".";
822
823 if(type == "line") {
824 shapes_.emplace_back(std::make_unique<line_shape>(data));
825 } else if(type == "rectangle") {
826 shapes_.emplace_back(std::make_unique<rectangle_shape>(data));
827 } else if(type == "round_rectangle") {
828 shapes_.emplace_back(std::make_unique<round_rectangle_shape>(data));
829 } else if(type == "circle") {
830 shapes_.emplace_back(std::make_unique<circle_shape>(data));
831 } else if(type == "image") {
832 shapes_.emplace_back(std::make_unique<image_shape>(data, functions_));
833 } else if(type == "text") {
834 shapes_.emplace_back(std::make_unique<text_shape>(data, functions_));
835 } else if(type == "pre_commit") {
836
837 /* note this should get split if more preprocessing is used. */
838 for(const auto [func_key, func_cfg] : data.all_children_view())
839 {
840 if(func_key == "blur") {
841 blur_depth_ = func_cfg["depth"].to_unsigned();
842 } else {
843 ERR_GUI_P << "Canvas: found a pre commit function"
844 << " of an invalid type " << type << ".";
845 }
846 }
847
848 } else {
849 ERR_GUI_P << "Canvas: found a shape of an invalid type " << type
850 << ".";
851 }
852 }
853}
854
861
862void canvas::set_size(const point& size)
863{
864 w_ = size.x;
865 h_ = size.y;
867}
868
869void canvas::clear_shapes(const bool force)
870{
871 if(force) {
872 shapes_.clear();
873 } else {
874 utils::erase_if(shapes_, [](const std::unique_ptr<shape>& s) { return !s->immutable(); });
875 }
876}
877
878/***** ***** ***** ***** ***** SHAPE ***** ***** ***** ***** *****/
879
880} // 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.
void splice_into(PangoAttrList *target) const
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_foreground_color(const color_t &color)
Definition text.cpp:391
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:869
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:815
void queue_reblur()
Clear the cached blur texture, forcing it to regenerate.
Definition canvas.cpp:781
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:855
unsigned h_
The full height of the canvas.
Definition canvas.hpp:202
void draw()
Draw the canvas' shapes onto the screen.
Definition canvas.cpp:786
void set_size(const point &size)
Definition canvas.cpp:862
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:202
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
static std::string _(const char *str)
Definition gettext.hpp:100
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:522
render_target_setter set_render_target(const texture &t)
Set the given texture as the active render target.
Definition draw.cpp:757
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:234
void tiled(const texture &tex, const ::rect &dst, bool centered=false, bool mirrored=false)
Tile a texture to fill a region.
Definition draw.cpp:494
void rect(const ::rect &rect)
Draw a rectangle.
Definition draw.cpp:171
void cairo_disc(int cx, int cy, int r, const color_t &c)
Draw filled circle using Cairo.
Definition draw.cpp:373
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:452
void cairo_circle(int cx, int cy, int r, const color_t &c, int thickness)
Draw outline of circle using Cairo.
Definition draw.cpp:335
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:277
void set_color(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
Set the drawing colour.
Definition draw.cpp:121
void fill()
Definition draw.cpp:115
void blit(const texture &tex, const ::rect &dst)
Draws a texture, or part of a texture, at the given location.
Definition draw.cpp:409
void line(int from_x, int from_y, int to_x, int to_y)
Draw a line.
Definition draw.cpp:203
Graphical text output.
pango_text & get_text_renderer()
Returns a reference to a static pango_text object.
Definition text.cpp:952
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.
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.
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.
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.
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.
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.
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.
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.
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.
Generic file dialog.
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
wfl::map_formula_callable get_screen_size_variables()
Gets a formula object with the screen size.
Definition helper.cpp:159
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:955
scale_quality
Definition picture.hpp:181
void erase_if(Container &container, const Predicate &predicate)
Convenience wrapper for using std::remove_if on a container.
Definition general.hpp:108
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:677
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.
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
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:838
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 i
#define h