The Battle for Wesnoth  1.15.0-dev
canvas.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2007 - 2018 by Mark de Wever <koraq@xs4all.nl>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 /**
16  * @file
17  * Implementation of canvas.hpp.
18  */
19 
20 #define GETTEXT_DOMAIN "wesnoth-lib"
21 
22 #include "gui/core/canvas.hpp"
24 
25 #include "font/text.hpp"
26 #include "formatter.hpp"
27 #include "gettext.hpp"
28 #include "picture.hpp"
29 
31 #include "gui/core/log.hpp"
32 #include "gui/widgets/helper.hpp"
33 #include "sdl/rect.hpp"
34 #include "sdl/render_utils.hpp"
35 #include "video.hpp"
36 #include "wml_exception.hpp"
37 
38 namespace gui2
39 {
40 
41 namespace
42 {
43 
44 /*WIKI
45  * @page = GUICanvasWML
46  *
47  * {{Autogenerated}}
48  *
49  * = Canvas =
50  *
51  * A canvas is a blank drawing area on which the user can draw several shapes.
52  * The drawing is done by adding WML structures to the canvas.
53  */
54 
55 /*WIKI
56  * @page = GUICanvasWML
57  * @begin{parent}{name="generic/state/draw/"}
58  *
59  * == Pre commit ==
60  * @begin{tag}{name="pre_commit"}{min="0"}{max="1"}
61  *
62  * This section contains the pre commit functions. These functions will be
63  * executed before the drawn canvas is applied on top of the normal
64  * background. There should only be one pre commit section and its order
65  * regarding the other shapes doesn't matter. The function has effect on the
66  * entire canvas, it's not possible to affect only a small part of the canvas.
67  *
68  * The section can have one of the following subsections.
69  *
70  * === Blur ===
71  * @begin{tag}{name="blur"}{min="0"}{max="1"}
72  *
73  * Blurs the background before applying the canvas. This doesn't make sense
74  * if the widget isn't semi-transparent.
75  *
76  * Keys:
77  * @begin{table}{config}
78  * depth & unsigned & 0 & The depth to blur. $
79  * @end{table}
80  * @end{tag}{name="blur"}
81  * @end{tag}{name="pre_commit"}
82  */
83 
84 /***** ***** ***** ***** ***** DRAWING PRIMITIVES ***** ***** ***** ***** *****/
85 
86 /**
87  * Draws a line on a surface.
88  *
89  * @pre The caller needs to make sure the entire line fits on
90  * the @p surface.
91  * @pre @p x2 >= @p x1
92  * @pre The @p surface is locked.
93  *
94  * @param color The color of the line to draw.
95  * @param x1 The start x coordinate of the line to draw.
96  * @param y1 The start y coordinate of the line to draw.
97  * @param x2 The end x coordinate of the line to draw.
98  * @param y2 The end y coordinate of the line to draw.
99  */
100 static void draw_line(const int canvas_w,
101  const int canvas_h,
102  SDL_Renderer* renderer,
103  color_t color,
104  unsigned x1,
105  unsigned y1,
106  const unsigned x2,
107  unsigned y2)
108 {
109  DBG_GUI_D << "Shape: draw line from " << x1 << ',' << y1 << " to " << x2
110  << ',' << y2 << " canvas width " << canvas_w << " canvas height "
111  << canvas_h << ".\n";
112 
113  assert(static_cast<int>(x1) < canvas_w);
114  assert(static_cast<int>(x2) < canvas_w);
115  assert(static_cast<int>(y1) < canvas_h);
116  assert(static_cast<int>(y2) < canvas_h);
117 
118  set_draw_color(renderer, color);
119 
120  if(x1 == x2 && y1 == y2) {
121  // Handle single-pixel lines properly
122  SDL_RenderDrawPoint(renderer, x1, y1);
123  } else {
124  SDL_RenderDrawLine(renderer, x1, y1, x2, y2);
125  }
126 }
127 
128 /**
129  * Draws a circle on a surface.
130  *
131  * @pre The circle must fit on the canvas.
132  * @pre The @p surface is locked.
133  *
134  * @param color The border color of the circle to draw.
135  * @param x_center The x coordinate of the center of the circle to draw.
136  * @param y_center The y coordinate of the center of the circle to draw.
137  * @param radius The radius of the circle to draw.
138  * @tparam octants A bitfield indicating which octants to draw, starting at twelve o'clock and moving clockwise.
139  */
140 template<unsigned int octants = 0xff>
141 static void draw_circle(const int canvas_w,
142  const int canvas_h,
143  SDL_Renderer* renderer,
144  color_t color,
145  const int x_center,
146  const int y_center,
147  const int radius)
148 {
149  unsigned w = canvas_w;
150 
151  DBG_GUI_D << "Shape: draw circle at " << x_center << ',' << y_center
152  << " with radius " << radius << " canvas width " << w
153  << " canvas height " << canvas_h << ".\n";
154 
155  if(octants & 0x0f) assert((x_center + radius) < canvas_w);
156  if(octants & 0xf0) assert((x_center - radius) >= 0);
157  if(octants & 0x3c) assert((y_center + radius) < canvas_h);
158  if(octants & 0xc3) assert((y_center - radius) >= 0);
159 
160  set_draw_color(renderer, color);
161 
162  // Algorithm based on
163  // http://de.wikipedia.org/wiki/Rasterung_von_Kreisen#Methode_von_Horn
164  // version of 2011.02.07.
165  int d = -static_cast<int>(radius);
166  int x = radius;
167  int y = 0;
168 
169  std::vector<SDL_Point> points;
170 
171  while(!(y > x)) {
172  if(octants & 0x04) points.push_back({x_center + x, y_center + y});
173  if(octants & 0x02) points.push_back({x_center + x, y_center - y});
174  if(octants & 0x20) points.push_back({x_center - x, y_center + y});
175  if(octants & 0x40) points.push_back({x_center - x, y_center - y});
176 
177  if(octants & 0x08) points.push_back({x_center + y, y_center + x});
178  if(octants & 0x01) points.push_back({x_center + y, y_center - x});
179  if(octants & 0x10) points.push_back({x_center - y, y_center + x});
180  if(octants & 0x80) points.push_back({x_center - y, y_center - x});
181 
182  d += 2 * y + 1;
183  ++y;
184  if(d > 0) {
185  d += -2 * x + 2;
186  --x;
187  }
188  }
189 
190  SDL_RenderDrawPoints(renderer, points.data(), points.size());
191 }
192 
193 /**
194  * Draws a filled circle on a surface.
195  *
196  * @pre The circle must fit on the canvas.
197  * @pre The @p surface is locked.
198  *
199  * @param color The fill color of the circle to draw.
200  * @param x_center The x coordinate of the center of the circle to draw.
201  * @param y_center The y coordinate of the center of the circle to draw.
202  * @param radius The radius of the circle to draw.
203  * @tparam octants A bitfield indicating which octants to draw, starting at twelve o'clock and moving clockwise.
204  */
205 template<unsigned int octants = 0xff>
206 static void fill_circle(const int canvas_w,
207  const int canvas_h,
208  SDL_Renderer* renderer,
209  color_t color,
210  const int x_center,
211  const int y_center,
212  const int radius)
213 {
214  unsigned w = canvas_w;
215 
216  DBG_GUI_D << "Shape: draw filled circle at " << x_center << ',' << y_center
217  << " with radius " << radius << " canvas width " << w
218  << " canvas height " << canvas_h << ".\n";
219 
220  if(octants & 0x0f) assert((x_center + radius) < canvas_w);
221  if(octants & 0xf0) assert((x_center - radius) >= 0);
222  if(octants & 0x3c) assert((y_center + radius) < canvas_h);
223  if(octants & 0xc3) assert((y_center - radius) >= 0);
224 
225  set_draw_color(renderer, color);
226 
227  int d = -static_cast<int>(radius);
228  int x = radius;
229  int y = 0;
230 
231  while(!(y > x)) {
232  // I use the formula of Bresenham's line algorithm to determine the boundaries of a segment.
233  // The slope of the line is always 1 or -1 in this case.
234  if(octants & 0x04) SDL_RenderDrawLine(renderer, x_center + x, y_center + y + 1, x_center + y + 1, y_center + y + 1); // x2 - 1 = y2 - (y_center + 1) + x_center
235  if(octants & 0x02) SDL_RenderDrawLine(renderer, x_center + x, y_center - y, x_center + y + 1, y_center - y); // x2 - 1 = y_center - y2 + x_center
236  if(octants & 0x20) SDL_RenderDrawLine(renderer, x_center - x - 1, y_center + y + 1, x_center - y - 2, y_center + y + 1); // x2 + 1 = (y_center + 1) - y2 + (x_center - 1)
237  if(octants & 0x40) SDL_RenderDrawLine(renderer, x_center - x - 1, y_center - y, x_center - y - 2, y_center - y); // x2 + 1 = y2 - y_center + (x_center - 1)
238 
239  if(octants & 0x08) SDL_RenderDrawLine(renderer, x_center + y, y_center + x + 1, x_center + y, y_center + y + 1); // y2 = x2 - x_center + (y_center + 1)
240  if(octants & 0x01) SDL_RenderDrawLine(renderer, x_center + y, y_center - x, x_center + y, y_center - y); // y2 = x_center - x2 + y_center
241  if(octants & 0x10) SDL_RenderDrawLine(renderer, x_center - y - 1, y_center + x + 1, x_center - y - 1, y_center + y + 1); // y2 = (x_center - 1) - x2 + (y_center + 1)
242  if(octants & 0x80) SDL_RenderDrawLine(renderer, x_center - y - 1, y_center - x, x_center - y - 1, y_center - y); // y2 = x2 - (x_center - 1) + y_center
243 
244  d += 2 * y + 1;
245  ++y;
246  if(d > 0) {
247  d += -2 * x + 2;
248  --x;
249  }
250  }
251 }
252 
253 } // namespace
254 
255 /*WIKI - unclassified
256  * This code can be used by a parser to generate the wiki page
257  * structure
258  * [tag name]
259  * param type_info description
260  *
261  * param Name of the parameter.
262  *
263  * type_info = ( type = default_value) The info about a optional parameter.
264  * type_info = ( type ) The info about a mandatory parameter
265  * type_info = [ type_info ] The info about a conditional parameter
266  * description should explain the reason.
267  *
268  * description Description of the parameter.
269  *
270  *
271  *
272  *
273  * Formulas are a function between brackets, that way the engine can see whether
274  * there is standing a plain number or a formula eg:
275  * 0 A value of zero
276  * (0) A formula returning zero
277  *
278  * When formulas are available the text should state the available variables
279  * which are available in that function.
280  */
281 
282 /*WIKI
283  * @page = GUIVariable
284  *
285  * {{Autogenerated}}
286  *
287  * == Variables ==
288  *
289  * In various parts of the GUI there are several variables types in use. This
290  * page describes them.
291  *
292  * === Simple types ===
293  *
294  * The simple types are types which have one value or a short list of options.
295  *
296  * @begin{table}{variable_types}
297  * unsigned & Unsigned number (positive whole numbers
298  * and zero). $
299  * f_unsigned & Unsigned number or formula returning an
300  * unsigned number. $
301  * int & Signed number (whole numbers). $
302  * f_int & Signed number or formula returning an
303  * signed number. $
304  * bool & A boolean value accepts the normal
305  * values as the rest of the game. $
306  * f_bool & Boolean value or a formula returning a
307  * boolean value. $
308  * string & A text. $
309  * tstring & A translatable string. $
310  * f_tstring & Formula returning a translatable string.
311  * $
312  * function & A string containing a set of function
313  * definition for the formula language. $
314  *
315  * color & A string which contains the color, this
316  * a group of 4 numbers between 0 and 255
317  * separated by a comma. The numbers are red
318  * component, green component, blue
319  * component and alpha. A color of 0 is not
320  * available. An alpha of 255 is fully
321  * transparent. Omitted values are set to
322  * 0. $
323  * f_color & A string which contains a color, or a formula
324  * which evaluates to a list containing the color's
325  * components. $
326  *
327  * font_style & A string which contains the style of the
328  * font:
329  * @* normal normal font
330  * @* bold bold font
331  * @* italic italic font
332  * @* underline underlined font
333  * @-Since SDL has problems combining these
334  * styles only one can be picked. Once SDL
335  * will allow multiple options, this type
336  * will be transformed to a comma separated
337  * list. If empty we default to the normal
338  * style. Since the render engine is
339  * replaced by Pango markup this field will
340  * change later on. Note widgets that allow
341  * marked up text can use markup to change
342  * the font style. $
343  *
344  * v_align & Vertical alignment; how an item is
345  * aligned vertically in the available
346  * space. Possible values:
347  * @* top aligned at the top
348  * @* bottom aligned at the bottom
349  * @* center centered
350  * @-When nothing is set or an another
351  * value as in the list the item is
352  * centered. $
353  *
354  * h_align & Horizontal alignment; how an item is
355  * aligned horizontal in the available
356  * space. Possible values:
357  * @* left aligned at the left side
358  * @* right aligned at the right side
359  * @* center centered $
360  *
361  * f_h_align & A horizontal alignment or a formula
362  * returning a horizontal alignment. $
363  *
364  * border & Comma separated list of borders to use.
365  * Possible values:
366  * @* left border at the left side
367  * @* right border at the right side
368  * @* top border at the top
369  * @* bottom border at the bottom
370  * @* all alias for "left, right, top,
371  * bottom" $
372  *
373  * scrollbar_mode & How to show the scrollbar of a widget.
374  * Possible values:
375  * @* always The scrollbar is always
376  * shown, regardless whether it's required
377  * or not.
378  * @* never The scrollbar is never
379  * shown, even not when needed. (Note when
380  * setting this mode dialogs might
381  * not properly fit anymore).
382  * @* auto Shows the scrollbar when
383  * needed. The widget will reserve space for
384  * the scrollbar, but only show when needed.
385  * @* initial_auto Like auto, but when the
386  * scrollbar is not needed the space is not
387  * reserved.
388  * @-Use auto when the list can be changed
389  * dynamically eg the game list in the
390  * lobby. For optimization you can also
391  * use auto when you really expect a
392  * scrollbar, but don't want it to be shown
393  * when not needed eg the language list
394  * will need a scrollbar on most screens. $
395  *
396  * resize_mode & Determines how an image is resized.
397  * Possible values:
398  * @* scale The image is scaled.
399  * @* stretch The first row or column
400  * of pixels is copied over the entire
401  * image. (Can only be used to scale resize
402  * in one direction, else falls
403  * back to scale.)
404  * @* tile The image is placed
405  * several times until the entire surface
406  * is filled. The last images are
407  * truncated.
408  * @* tile_center Like tile, but the
409  * image is first centered on the screen. $
410  *
411  * grow_direction & Determines whether the image may grow in
412  * the horizontal or vertical direction. $
413  * @end{table}
414  * @allow{type}{name="unsigned"}{value="^\d+$"}
415  * @allow{type}{name="f_unsigned"}{value="^.+$"}
416  * @allow{type}{name="int"}{value="^-?\d+$"}
417  * @allow{type}{name="f_int"}{value="^.*$"}
418  * @allow{type}{name="bool"}{value="^true|false|yes|no$"}
419  * @allow{type}{name="f_bool"}{value="^.*$"}
420  * @allow{type}{name="string"}{value="^.*$"}
421  * @allow{type}{name="t_string"}{value="^_?.*$"}
422  * @allow{type}{name="f_string"}{value="^.*$"}
423  * @allow{type}{name="f_tstring"}{value="^_?.*$"}
424  * @allow{type}{name="function"}{value="^_?.*$"}
425  *
426  * @allow{type}{name="color"}{value="^(?:2[0-5][0-5]|[01]?\d?\d)[.,]\s*(?:2[0-5][0-5]|[01]?\d?\d)[.,]\s*(?:2[0-5][0-5]|[01]?\d?\d)[.,]\s*(?:2[0-5][0-5]|[01]?\d?\d)$"}
427  *
428  * @allow{type}{name="font_style"}{value="^(normal|bold|italic|underline)?$"}
429  * @allow{type}{name="v_align"}{value="^top|bottom|center$"}
430  * @allow{type}{name="h_align"}{value="^left|right|center$"}
431  * @allow{type}{name="f_h_align"}{value="^.*$"}
432  * @allow{type}{name="border"}{value="^(top|bottom|left|right|all)?(,\s*(top|bottom|left|right|all))*$"}
433  * @allow{type}{name="scrollbar_mode"}{value="^always|never|auto|initial_auto$"}
434  * @allow{type}{name="resize_mode"}{value="^scale|stretch|tile$"}
435  * @allow{type}{name="grow_direction"}{value="^horizontal|vertical$"}
436  *
437  * @remove{type}{name="section"}
438  * @remove{type}{name="config"}
439  * @remove{type}{name="grid"}
440  * == Section types ==
441  *
442  * For more complex parts, there are sections. Sections contain of several
443  * lines of WML and can have sub sections. For example a grid has sub sections
444  * which contain various widgets. Here's the list of sections.
445  *
446  * @begin{table}{variable_types}
447  * section & A generic section. The documentation
448  * about the section should describe the
449  * section in further detail. $
450  *
451  * grid & A grid contains several widgets. (TODO
452  * add link to generic grid page.) $
453  * @end{table}
454  */
455 
456 /***** ***** ***** ***** ***** LINE ***** ***** ***** ***** *****/
457 
458 /*WIKI
459  * @page = GUICanvasWML
460  *
461  * == Line ==
462  * @begin{tag}{name="line"}{min="0"}{max="-1"}
463  * Definition of a line.
464  *
465  * Keys:
466  * @begin{table}{config}
467  * x1 & f_unsigned & 0 & The x coordinate of the startpoint. $
468  * y1 & f_unsigned & 0 & The y coordinate of the startpoint. $
469  * x2 & f_unsigned & 0 & The x coordinate of the endpoint. $
470  * y2 & f_unsigned & 0 & The y coordinate of the endpoint. $
471  * color & f_color & "" & The color of the line. $
472  * thickness & unsigned & 0 & The thickness of the line if 0 nothing
473  * is drawn. $
474  * debug & string & "" & Debug message to show upon creation
475  * this message is not stored. $
476  * @end{table}
477  * @end{tag}{name="line"}
478  *
479  * <span id="general_variables">Variables:</span>.
480  * @begin{table}{formula}
481  * width & unsigned & The width of the canvas. $
482  * height & unsigned & The height of the canvas. $
483  * text & tstring & The text to render on the widget. $
484  * text_maximum_width & unsigned & The maximum width available for the
485  * text on the widget. $
486  * text_maximum_height & unsigned & The maximum height available for the
487  * text on the widget. $
488  * text_wrap_mode & int & When the text doesn't fit in the
489  * available width there are several ways
490  * to fix that. This variable holds the
491  * best method. (NOTE this is a 'hidden'
492  * variable meant to copy state from a
493  * widget to its canvas so there's no
494  * reason to use this variable and thus
495  * its values are not listed and might
496  * change without further notice.) $
497  * text_alignment & h_align & The way the text is aligned inside the
498  * canvas. $
499  *@end{table}
500  *
501  * The size variables are copied to the window and will be determined at
502  * runtime. This is needed since the main window can be resized and the dialog
503  * needs to resize accordingly. The following variables are available:
504  * @begin{table}{formula}
505  * screen_width & unsigned & The usable width of the Wesnoth main
506  * window. $
507  * screen_height & unsigned & The usable height of the Wesnoth main
508  * window. $
509  * gamemapx_offset & unsigned & The distance between left edge of the
510  * screen and the game map. $
511  * gamemap_width & unsigned & The usable width of the Wesnoth gamemap,
512  * if no gamemap shown it's the same value
513  * as screen_width. $
514  * gamemap_height & unsigned & The usable height of the Wesnoth
515  * gamemap, if no gamemap shown it's the
516  * same value as screen_height. $
517  *
518  * mouse_x & unsigned & The x coordinate of the mouse pointer. $
519  * mouse_y & unsigned & The y coordinate of the mouse pointer. $
520  *
521  * window_width & unsigned & The window width. This value has two
522  * meanings during the layout phase. This
523  * only applies if automatic placement is
524  * not enabled.
525  * - When set to 0 it should return the
526  * wanted maximum width. If no maximum
527  * is wanted it should be set to the
528  * '"(screen_width)"'.
529  * - When not equal to 0 its value is the
530  * best width for the window. When the
531  * size should remain unchanged it
532  * should be set to '"(window_width)"'.
533  * $
534  *
535  * window_height & unsigned & The window height. This value has two
536  * meanings during the layout phase. This
537  * only applies if automatic placement is
538  * not enabled.
539  * - When set to 0 it should return the
540  * wanted maximum height. If no maximum
541  * is wanted it should be set to the
542  * '"(screen_height)"'.
543  * - When not equal to 0 its value is the
544  * best height for the window. When the
545  * size should remain unchanged it
546  * should be set to '"(window_height)"'.
547  * $
548  *
549  * size_request_mode & string & A field foo:
550  * - maximum
551  * - size
552  *
553  * @end{table}
554  *
555  * Note when drawing the valid coordinates are:<br>
556  * 0 -> width - 1 <br>
557  * 0 -> height -1
558  *
559  * Drawing outside this area will result in unpredictable results including
560  * crashing. (That should be fixed, when encountered.)
561  */
562 
564  : shape(cfg)
565  , x1_(cfg["x1"])
566  , y1_(cfg["y1"])
567  , x2_(cfg["x2"])
568  , y2_(cfg["y2"])
569  , color_(cfg["color"])
570  , thickness_(cfg["thickness"])
571 {
572  const std::string& debug = (cfg["debug"]);
573  if(!debug.empty()) {
574  DBG_GUI_P << "Line: found debug message '" << debug << "'.\n";
575  }
576 }
577 
579  const int canvas_w,
580  const int canvas_h,
581  SDL_Renderer* renderer,
582  wfl::map_formula_callable& variables)
583 {
584  /**
585  * @todo formulas are now recalculated every draw cycle which is a bit silly
586  * unless there has been a resize. So to optimize we should use an extra
587  * flag or do the calculation in a separate routine.
588  */
589 
590  const unsigned x1 = x1_(variables);
591  const unsigned y1 = y1_(variables);
592  const unsigned x2 = x2_(variables);
593  const unsigned y2 = y2_(variables);
594 
595  DBG_GUI_D << "Line: draw from " << x1 << ',' << y1 << " to " << x2 << ','
596  << y2 << " canvas size " << canvas_w << ',' << canvas_h
597  << ".\n";
598 
599  VALIDATE(static_cast<int>(x1) < canvas_w
600  && static_cast<int>(x2) < canvas_w
601  && static_cast<int>(y1) < canvas_h
602  && static_cast<int>(y2) < canvas_h,
603  _("Line doesn't fit on canvas."));
604 
605  // @todo FIXME respect the thickness.
606 
607  draw_line(canvas_w, canvas_h, renderer, color_(variables), x1, y1, x2, y2);
608 }
609 
610 /***** ***** ***** ***** ***** Rectangle ***** ***** ***** ***** *****/
611 
612 /*WIKI
613  * @page = GUICanvasWML
614  *
615  * == Rectangle ==
616  * @begin{tag}{name="rectangle"}{min="0"}{max="-1"}
617  *
618  * Definition of a rectangle. When drawing a rectangle it doesn't get blended on
619  * the surface but replaces the pixels instead. A blitting flag might be added
620  * later if needed.
621  *
622  * Keys:
623  * @begin{table}{config}
624  * x & f_unsigned & 0 & The x coordinate of the top left corner.
625  * $
626  * y & f_unsigned & 0 & The y coordinate of the top left corner.
627  * $
628  * w & f_unsigned & 0 & The width of the rectangle. $
629  * h & f_unsigned & 0 & The height of the rectangle. $
630  * border_thickness & unsigned & 0 &
631  * The thickness of the border; if the
632  * thickness is zero it's not drawn. $
633  * border_color & f_color & "" & The color of the border; if empty it's
634  * not drawn. $
635  * fill_color & f_color & "" & The color of the interior; if omitted
636  * it's not drawn. $
637  * debug & string & "" & Debug message to show upon creation;
638  * this message is not stored. $
639  * @end{table}
640  * @end{tag}{name="rectangle"}
641  * Variables:
642  * See [[#general_variables|Line]].
643  *
644  */
646  : shape(cfg)
647  , x_(cfg["x"])
648  , y_(cfg["y"])
649  , w_(cfg["w"])
650  , h_(cfg["h"])
651  , border_thickness_(cfg["border_thickness"])
652  , border_color_(cfg["border_color"], color_t::null_color())
653  , fill_color_(cfg["fill_color"], color_t::null_color())
654 {
655  // Check if a raw color string evaluates to a null color.
656  if(!border_color_.has_formula() && border_color_().null()) {
657  border_thickness_ = 0;
658  }
659 
660  const std::string& debug = (cfg["debug"]);
661  if(!debug.empty()) {
662  DBG_GUI_P << "Rectangle: found debug message '" << debug << "'.\n";
663  }
664 }
665 
667  const int canvas_w,
668  const int canvas_h,
669  SDL_Renderer* renderer,
670  wfl::map_formula_callable& variables)
671 {
672  /**
673  * @todo formulas are now recalculated every draw cycle which is a bit
674  * silly unless there has been a resize. So to optimize we should use an
675  * extra flag or do the calculation in a separate routine.
676  */
677  const int x = x_(variables);
678  const int y = y_(variables);
679  const int w = w_(variables);
680  const int h = h_(variables);
681 
682  DBG_GUI_D << "Rectangle: draw from " << x << ',' << y << " width " << w
683  << " height " << h << " canvas size " << canvas_w << ','
684  << canvas_h << ".\n";
685 
686  VALIDATE(x < canvas_w
687  && x + w <= canvas_w
688  && y < canvas_h
689  && y + h <= canvas_h, _("Rectangle doesn't fit on canvas."));
690 
691  const color_t fill_color = fill_color_(variables);
692 
693  // Fill the background, if applicable
694  if(!fill_color.null() && w && h) {
695  set_draw_color(renderer, fill_color);
696 
697  SDL_Rect area {
698  x + border_thickness_,
699  y + border_thickness_,
700  w - (border_thickness_ * 2),
701  h - (border_thickness_ * 2)
702  };
703 
704  SDL_RenderFillRect(renderer, &area);
705  }
706 
707  // Draw the border
708  for(int i = 0; i < border_thickness_; ++i) {
709  SDL_Rect dimensions {
710  x + i,
711  y + i,
712  w - (i * 2),
713  h - (i * 2)
714  };
715 
716  set_draw_color(renderer, border_color_(variables));
717 
718  SDL_RenderDrawRect(renderer, &dimensions);
719  }
720 }
721 
722 /***** ***** ***** ***** ***** Rounded Rectangle ***** ***** ***** ***** *****/
723 
724 /*WIKI
725  * @page = GUICanvasWML
726  *
727  * == Rounded Rectangle ==
728  * @begin{tag}{name="round_rectangle"}{min="0"}{max="-1"}
729  *
730  * Definition of a rounded rectangle. When drawing a rounded rectangle it doesn't get blended on
731  * the surface but replaces the pixels instead. A blitting flag might be added
732  * later if needed.
733  *
734  * Keys:
735  * @begin{table}{config}
736  * x & f_unsigned & 0 & The x coordinate of the top left corner.
737  * $
738  * y & f_unsigned & 0 & The y coordinate of the top left corner.
739  * $
740  * w & f_unsigned & 0 & The width of the rounded rectangle. $
741  * h & f_unsigned & 0 & The height of the rounded rectangle. $
742  * corner_radius & f_unsigned & 0 &The radius of the rectangle's corners. $
743  * border_thickness & unsigned & 0 &
744  * The thickness of the border; if the
745  * thickness is zero it's not drawn. $
746  * border_color & f_color & "" & The color of the border; if empty it's
747  * not drawn. $
748  * fill_color & f_color & "" & The color of the interior; if omitted
749  * it's not drawn. $
750  * debug & string & "" & Debug message to show upon creation;
751  * this message is not stored. $
752  * @end{table}
753  * @end{tag}{name="round_rectangle"}
754  * Variables:
755  * See [[#general_variables|Line]].
756  *
757  */
759  : shape(cfg)
760  , x_(cfg["x"])
761  , y_(cfg["y"])
762  , w_(cfg["w"])
763  , h_(cfg["h"])
764  , r_(cfg["corner_radius"])
765  , border_thickness_(cfg["border_thickness"])
766  , border_color_(cfg["border_color"], color_t::null_color())
767  , fill_color_(cfg["fill_color"], color_t::null_color())
768 {
769  // Check if a raw color string evaluates to a null color.
770  if(!border_color_.has_formula() && border_color_().null()) {
771  border_thickness_ = 0;
772  }
773 
774  const std::string& debug = (cfg["debug"]);
775  if(!debug.empty()) {
776  DBG_GUI_P << "Rounded Rectangle: found debug message '" << debug << "'.\n";
777  }
778 }
779 
781  const int canvas_w,
782  const int canvas_h,
783  SDL_Renderer* renderer,
784  wfl::map_formula_callable& variables)
785 {
786  /**
787  * @todo formulas are now recalculated every draw cycle which is a bit
788  * silly unless there has been a resize. So to optimize we should use an
789  * extra flag or do the calculation in a separate routine.
790  */
791  const int x = x_(variables);
792  const int y = y_(variables);
793  const int w = w_(variables);
794  const int h = h_(variables);
795  const int r = r_(variables);
796 
797  DBG_GUI_D << "Rounded Rectangle: draw from " << x << ',' << y << " width " << w
798  << " height " << h << " canvas size " << canvas_w << ','
799  << canvas_h << ".\n";
800 
801  VALIDATE(x < canvas_w
802  && x + w <= canvas_w
803  && y < canvas_h
804  && y + h <= canvas_h, _("Rounded Rectangle doesn't fit on canvas."));
805 
806  const color_t fill_color = fill_color_(variables);
807 
808  // Fill the background, if applicable
809  if(!fill_color.null() && w && h) {
810  set_draw_color(renderer, fill_color);
811  static const int count = 3;
812  SDL_Rect area[count] {
813  {x + r, y + border_thickness_, w - r * 2, r - border_thickness_ + 1},
814  {x + border_thickness_, y + r + 1, w - border_thickness_ * 2, h - r * 2},
815  {x + r, y - r + h + 1, w - r * 2, r - border_thickness_},
816  };
817 
818  SDL_RenderFillRects(renderer, area, count);
819 
820  fill_circle<0xc0>(canvas_w, canvas_h, renderer, fill_color, x + r, y + r, r);
821  fill_circle<0x03>(canvas_w, canvas_h, renderer, fill_color, x + w - r, y + r, r);
822  fill_circle<0x30>(canvas_w, canvas_h, renderer, fill_color, x + r, y + h - r, r);
823  fill_circle<0x0c>(canvas_w, canvas_h, renderer, fill_color, x + w - r, y + h - r, r);
824  }
825 
826  const color_t border_color = border_color_(variables);
827 
828  // Draw the border
829  for(int i = 0; i < border_thickness_; ++i) {
830  set_draw_color(renderer, border_color);
831 
832  SDL_RenderDrawLine(renderer, x + r, y + i, x + w - r, y + i);
833  SDL_RenderDrawLine(renderer, x + r, y + h - i, x + w - r, y + h - i);
834 
835  SDL_RenderDrawLine(renderer, x + i, y + r, x + i, y + h - r);
836  SDL_RenderDrawLine(renderer, x + w - i, y + r, x + w - i, y + h - r);
837 
838  draw_circle<0xc0>(canvas_w, canvas_h, renderer, border_color, x + r, y + r, r - i);
839  draw_circle<0x03>(canvas_w, canvas_h, renderer, border_color, x + w - r, y + r, r - i);
840  draw_circle<0x30>(canvas_w, canvas_h, renderer, border_color, x + r, y + h - r, r - i);
841  draw_circle<0x0c>(canvas_w, canvas_h, renderer, border_color, x + w - r, y + h - r, r - i);
842  }
843 }
844 
845 /***** ***** ***** ***** ***** CIRCLE ***** ***** ***** ***** *****/
846 
847 /*WIKI
848  * @page = GUICanvasWML
849  *
850  * == Circle ==
851  * @begin{tag}{name="circle"}{min="0"}{max="-1"}
852  *
853  * Definition of a circle. When drawing a circle it doesn't get blended on
854  * the surface but replaces the pixels instead. A blitting flag might be
855  * added later if needed.
856  *
857  * Keys:
858  * @begin{table}{config}
859  * x & f_unsigned & 0 & The x coordinate of the center. $
860  * y & f_unsigned & 0 & The y coordinate of the center. $
861  * radius & f_unsigned & 0 & The radius of the circle; if 0 nothing is
862  * drawn. $
863  * border_thickness & unsigned & "" &
864  * The border thickness of the circle. $
865  * border_color & f_color & "" & The color of the circle. $
866  * fill_color & f_color & "" & The color of the circle. $
867  * debug & string & "" & Debug message to show upon creation; this
868  * message is not stored. $
869  * @end{table}
870  * @end{tag}{name="circle"}
871  * Variables:
872  * See [[#general_variables|Line]].
873  *
874  * Drawing outside the area will result in unpredictable results including
875  * crashing. (That should be fixed, when encountered.)
876  */
878  : shape(cfg)
879  , x_(cfg["x"])
880  , y_(cfg["y"])
881  , radius_(cfg["radius"])
882  , border_color_(cfg["border_color"])
883  , fill_color_(cfg["fill_color"])
884  , border_thickness_(cfg["border_thickness"].to_int(1))
885 {
886  const std::string& debug = (cfg["debug"]);
887  if(!debug.empty()) {
888  DBG_GUI_P << "Circle: found debug message '" << debug << "'.\n";
889  }
890 }
891 
893  const int canvas_w,
894  const int canvas_h,
895  SDL_Renderer* renderer,
896  wfl::map_formula_callable& variables)
897 {
898  /**
899  * @todo formulas are now recalculated every draw cycle which is a bit
900  * silly unless there has been a resize. So to optimize we should use an
901  * extra flag or do the calculation in a separate routine.
902  */
903 
904  const unsigned x = x_(variables);
905  const unsigned y = y_(variables);
906  const unsigned radius = radius_(variables);
907 
908  DBG_GUI_D << "Circle: drawn at " << x << ',' << y << " radius " << radius
909  << " canvas size " << canvas_w << ',' << canvas_h << ".\n";
910 
912  static_cast<int>(x - radius) >= 0,
913  _("Circle doesn't fit on canvas."),
914  formatter() << "x = " << x << ", radius = " << radius);
915 
917  static_cast<int>(y - radius) >= 0,
918  _("Circle doesn't fit on canvas."),
919  formatter() << "y = " << y << ", radius = " << radius);
920 
922  static_cast<int>(x + radius) < canvas_w,
923  _("Circle doesn't fit on canvas."),
924  formatter() << "x = " << x << ", radius = " << radius
925  << "', canvas width = " << canvas_w << ".");
926 
928  static_cast<int>(y + radius) < canvas_h,
929  _("Circle doesn't fit on canvas."),
930  formatter() << "y = " << y << ", radius = " << radius
931  << "', canvas height = " << canvas_h << ".");
932 
933  const color_t fill_color = fill_color_(variables);
934  if(!fill_color.null() && radius) {
935  fill_circle(canvas_w, canvas_h, renderer, fill_color, x, y, radius);
936  }
937 
938  const color_t border_color = border_color_(variables);
939  for(unsigned int i = 0; i < border_thickness_; i++) {
940  draw_circle(canvas_w, canvas_h, renderer, border_color, x, y, radius - i);
941  }
942 }
943 
944 /***** ***** ***** ***** ***** IMAGE ***** ***** ***** ***** *****/
945 
946 /*WIKI
947  * @page = GUICanvasWML
948  *
949  * == Image ==
950  * @begin{tag}{name="image"}{min="0"}{max="-1"}
951  * Definition of an image.
952  *
953  * Keys:
954  * @begin{table}{config}
955  * x & f_unsigned & 0 & The x coordinate of the top left corner.
956  * $
957  * y & f_unsigned & 0 & The y coordinate of the top left corner.
958  * $
959  * w & f_unsigned & 0 & The width of the image, if not zero the
960  * image will be scaled to the desired
961  * width. $
962  * h & f_unsigned & 0 & The height of the image, if not zero the
963  * image will be scaled to the desired
964  * height. $
965  * resize_mode & resize_mode & scale &
966  * Determines how an image is scaled to fit
967  * the wanted size. $
968  * vertical_mirror & f_bool & false &
969  * Mirror the image over the vertical axis.
970  * $
971  * name & f_string & "" & The name of the image. $
972  * debug & string & "" & Debug message to show upon creation
973  * this message is not stored. $
974  *
975  * @end{table}
976  * @end{tag}{name="image"}
977  * Variables:
978  * @begin{table}{formula}
979  * image_width & unsigned & The width of the image, either the
980  * requested width or the natural width of
981  * the image. This value can be used to set
982  * the x (or y) value of the image. (This
983  * means x and y are evaluated after the
984  * width and height.) $
985  * image_height & unsigned & The height of the image, either the
986  * requested height or the natural height
987  * of the image. This value can be used to
988  * set the y (or x) value of the image.
989  * (This means x and y are evaluated after
990  * the width and height.) $
991  * image_original_width & unsigned &
992  * The width of the image as stored on
993  * disk, can be used to set x or w
994  * (also y and h can be set). $
995  * image_original_height & unsigned &
996  * The height of the image as stored on
997  * disk, can be used to set y or h
998  * (also x and y can be set). $
999  * @end{table}
1000  * Also the general variables are available, see [[#general_variables|Line]].
1001  */
1002 
1004  : shape(cfg)
1005  , x_(cfg["x"])
1006  , y_(cfg["y"])
1007  , w_(cfg["w"])
1008  , h_(cfg["h"])
1009  , image_()
1010  , image_name_(cfg["name"])
1011  , resize_mode_(get_resize_mode(cfg["resize_mode"]))
1012  , vertical_mirror_(cfg["vertical_mirror"])
1013  , actions_formula_(cfg["actions"], &functions)
1014 {
1015  const std::string& debug = (cfg["debug"]);
1016  if(!debug.empty()) {
1017  DBG_GUI_P << "Image: found debug message '" << debug << "'.\n";
1018  }
1019 }
1020 
1021 void image_shape::dimension_validation(unsigned value, const std::string& name, const std::string& key)
1022 {
1023  const int as_int = static_cast<int>(value);
1024 
1025  VALIDATE_WITH_DEV_MESSAGE(as_int >= 0, _("Image doesn't fit on canvas."),
1026  formatter() << "Image '" << name << "', " << key << " = " << as_int << "."
1027  );
1028 }
1029 
1031  const int /*canvas_w*/,
1032  const int /*canvas_h*/,
1033  SDL_Renderer* /*renderer*/,
1034  wfl::map_formula_callable& variables)
1035 {
1036  DBG_GUI_D << "Image: draw.\n";
1037 
1038  /**
1039  * @todo formulas are now recalculated every draw cycle which is a bit
1040  * silly unless there has been a resize. So to optimize we should use an
1041  * extra flag or do the calculation in a separate routine.
1042  */
1043  const std::string& name = image_name_(variables);
1044 
1045  if(name.empty()) {
1046  DBG_GUI_D << "Image: formula returned no value, will not be drawn.\n";
1047  return;
1048  }
1049 
1050  // NOTE: if we need a key to specify NN scaling it can be added later.
1052 
1053  if(!image_) {
1054  ERR_GUI_D << "Image: '" << name << "' not found and won't be drawn." << std::endl;
1055  return;
1056  }
1057 
1058  //
1059  // Calculate dimensions and set WFL variables.
1060  //
1061  const texture::info info = image_.get_info();
1062 
1063  wfl::map_formula_callable local_variables(variables);
1064  local_variables.add("image_original_width", wfl::variant(info.w));
1065  local_variables.add("image_original_height", wfl::variant(info.h));
1066 
1067  unsigned w = w_(local_variables);
1068  dimension_validation(w, name, "w");
1069 
1070  unsigned h = h_(local_variables);
1071  dimension_validation(h, name, "h");
1072 
1073  local_variables.add("image_width", wfl::variant(w ? w : info.w));
1074  local_variables.add("image_height", wfl::variant(h ? h : info.h));
1075 
1076  const unsigned clip_x = x_(local_variables);
1077  dimension_validation(clip_x, name, "x");
1078 
1079  const unsigned clip_y = y_(local_variables);
1080  dimension_validation(clip_y, name, "y");
1081 
1082  local_variables.add("clip_x", wfl::variant(clip_x));
1083  local_variables.add("clip_y", wfl::variant(clip_y));
1084 
1085  // Execute the provided actions for this context.
1086  wfl::variant(variables.fake_ptr()).execute_variant(actions_formula_.evaluate(local_variables));
1087 
1088  //
1089  // Copy image texture to canvas texture (which should be the current rendering target)/
1090  //
1091  CVideo& video = CVideo::get_singleton();
1092 
1093  // Flip on the vertical axis - ie, a horizontal flip.
1094  const bool mirror = vertical_mirror_(local_variables);
1095 
1096  SDL_Rect dst_clip = sdl::create_rect(clip_x, clip_y, 0, 0);
1097 
1098  const unsigned int dst_w = w ? w : info.w;
1099  const unsigned int dst_h = h ? h : info.h;
1100 
1101  dst_clip.w = dst_w;
1102  dst_clip.h = dst_h;
1103 
1104  // TODO: remove stretch mode
1105  switch(resize_mode_) {
1106  case scale:
1107  case stretch: {
1108  DBG_GUI_D << "Image: scaling from " << info.w << ',' << info.h << " to " << w << ',' << h << std::endl;
1109 
1110  //
1111  // Textures are automatically sized to the dst rect.
1112  //
1113 
1114  video.render_copy(image_, nullptr, &dst_clip, mirror, false);
1115  break;
1116  }
1117 
1118  // TODO: move this to a more general place.
1119  case tile: {
1120  DBG_GUI_D << "Image: tiling from " << info.w << ',' << info.h << " to " << w << ',' << h << std::endl;
1121 
1122  render_clip_rect_setter tile_clip_setter(&dst_clip);
1123 
1124  const unsigned int w_count = static_cast<int>(std::ceil(static_cast<double>(dst_w) / static_cast<double>(info.w)));
1125  const unsigned int h_count = static_cast<int>(std::ceil(static_cast<double>(dst_h) / static_cast<double>(info.h)));
1126 
1127  for(unsigned int xi = 0, current_x = dst_clip.x; xi < w_count; ++xi, current_x += info.w) {
1128  for(unsigned int iy = 0, current_y = dst_clip.y; iy < h_count; ++iy, current_y += info.h) {
1129  SDL_Rect area = sdl::create_rect(current_x, current_y, info.w, info.h);
1130  video.render_copy(image_, nullptr, &area, mirror, false);
1131  }
1132  }
1133 
1134  break;
1135  }
1136 
1137  case tile_center: {
1138  DBG_GUI_D << "Image: tiling centrally from " << info.w << ',' << info.h << " to " << w << ',' << h << std::endl;
1139  break;
1140  }
1141 
1142  default:
1143  // TODO: text description
1144  ERR_GUI_D << "Unknown resize mode option: " << resize_mode_ << std::endl;
1145  }
1146 }
1147 
1149 {
1150  if(resize_mode == "tile") {
1151  return image_shape::tile;
1152  } else if(resize_mode == "tile_center") {
1153  return image_shape::tile_center;
1154  } else if(resize_mode == "stretch") {
1155  return image_shape::stretch;
1156  } else {
1157  if(!resize_mode.empty() && resize_mode != "scale") {
1158  ERR_GUI_E << "Invalid resize mode '" << resize_mode
1159  << "' falling back to 'scale'.\n";
1160  }
1161  return image_shape::scale;
1162  }
1163 }
1164 
1165 /***** ***** ***** ***** ***** TEXT ***** ***** ***** ***** *****/
1166 
1167 /*WIKI
1168  * @page = GUICanvasWML
1169  *
1170  * == Text ==
1171  * @begin{tag}{name="text"}{min="0"}{max="-1"}
1172  * Definition of text.
1173  *
1174  * Keys:
1175  * @begin{table}{config}
1176  * x & f_unsigned & 0 & The x coordinate of the top left corner.
1177  * $
1178  * y & f_unsigned & 0 & The y coordinate of the top left corner.
1179  * $
1180  * w & f_unsigned & 0 & The width of the text's bounding
1181  * rectangle. $
1182  * h & f_unsigned & 0 & The height of the text's bounding
1183  * rectangle. $
1184  * font_family & font_family & "sans" &
1185  * The font family used for the text. $
1186  * font_size & unsigned & & The size of the text font. $
1187  * font_style & font_style & "" & The style of the text. $
1188  * text_alignment & f_h_align & "left" &
1189  * The alignment of the text. $
1190  * color & f_color & "" & The color of the text. $
1191  * text & f_tstring & "" & The text to draw (translatable). $
1192  * text_markup & f_bool & false & Can the text have mark-up? $
1193  * text_link_aware & f_bool & false &
1194  * Is the text link aware? $
1195  * text_link_color & f_string & "#ffff00" &
1196  * The color of links in the text $
1197  * maximum_width & f_int & -1 & The maximum width the text is allowed to
1198  * be. $
1199  * maximum_height & f_int & -1 & The maximum height the text is allowed
1200  * to be. $
1201  * debug & string & "" & Debug message to show upon creation
1202  * this message is not stored. $
1203  * @end{table}
1204  * @end{tag}{name="text"}
1205  * NOTE alignment could only be done with the formulas, but now with the
1206  * text_alignment flag as well, older widgets might still use the formulas and
1207  * not all widgets may expose the text alignment yet and when exposed not use
1208  * it yet.
1209  *
1210  * Variables:
1211  * @begin{table}{formula}
1212  * text_width & unsigned & The width of the rendered text. $
1213  * text_height & unsigned & The height of the rendered text. $
1214  * @end{table}
1215  * Also the general variables are available, see [[#general_variables|Line]].
1216  * @end{parent}{name="generic/state/draw/"}
1217  */
1218 
1220  : shape(cfg)
1221  , x_(cfg["x"])
1222  , y_(cfg["y"])
1223  , w_(cfg["w"])
1224  , h_(cfg["h"])
1225  , font_family_(font::str_to_family_class(cfg["font_family"]))
1226  , font_size_(cfg["font_size"])
1227  , font_style_(decode_font_style(cfg["font_style"]))
1228  , text_alignment_(cfg["text_alignment"])
1229  , color_(cfg["color"])
1230  , text_(cfg["text"])
1231  , text_markup_(cfg["text_markup"], false)
1232  , link_aware_(cfg["text_link_aware"], false)
1233  , link_color_(cfg["text_link_color"], color_t::from_hex_string("ffff00"))
1234  , maximum_width_(cfg["maximum_width"], -1)
1235  , characters_per_line_(cfg["text_characters_per_line"])
1236  , maximum_height_(cfg["maximum_height"], -1)
1237 {
1238  if(!font_size_.has_formula()) {
1239  VALIDATE(font_size_(), _("Text has a font size of 0."));
1240  }
1241 
1242  const std::string& debug = (cfg["debug"]);
1243  if(!debug.empty()) {
1244  DBG_GUI_P << "Text: found debug message '" << debug << "'.\n";
1245  }
1246 }
1247 
1249  const int canvas_w,
1250  const int canvas_h,
1251  SDL_Renderer* /*renderer*/,
1252  wfl::map_formula_callable& variables)
1253 {
1254  assert(variables.has_key("text"));
1255 
1256  // We first need to determine the size of the text which need the rendered
1257  // text. So resolve and render the text first and then start to resolve
1258  // the other formulas.
1259  const t_string text = text_(variables);
1260 
1261  if(text.empty()) {
1262  DBG_GUI_D << "Text: no text to render, leave.\n";
1263  return;
1264  }
1265 
1266  // Note about color handling. It's possible to render the text with alpha set directly
1267  // but it results in unnecessary extra renders and cache storage. Instead, we save the
1268  // provided alpha value and override the one passed to the rext renderer. Then, if the
1269  // alpha isn't opaque, we set a texture alpha mod once the text is rendered.
1270  color_t fg_color = color_(variables);
1271 
1272  const uint8_t saved_alpha = fg_color.a;
1273  fg_color.a = ALPHA_OPAQUE;
1274 
1275  font::pango_text& text_renderer = font::get_text_renderer();
1276 
1277  text_renderer
1278  .set_link_aware(link_aware_(variables))
1279  .set_link_color(link_color_(variables))
1280  .set_text(text, text_markup_(variables));
1281 
1282  text_renderer.set_family_class(font_family_)
1283  .set_font_size(font_size_(variables))
1285  .set_alignment(text_alignment_(variables))
1286  .set_foreground_color(fg_color)
1287  .set_maximum_width(maximum_width_(variables))
1288  .set_maximum_height(maximum_height_(variables), true)
1289  .set_ellipse_mode(variables.has_key("text_wrap_mode")
1290  ? static_cast<PangoEllipsizeMode>(variables.query_value("text_wrap_mode").as_int())
1291  : PANGO_ELLIPSIZE_END)
1293 
1294  // Get the resulting texture.
1295  texture& txt = text_renderer.render_and_get_texture();
1296 
1297  // Set texture alpha.
1298  set_texture_alpha(txt, saved_alpha);
1299 
1300  // TODO: should use pango_text::get_size but the dimensions are inaccurate. Investigate.
1301  texture::info info = txt.get_info();
1302 
1303  if(info.w == 0) {
1304  DBG_GUI_D << "Text: Rendering '" << text
1305  << "' resulted in an empty canvas, leave.\n";
1306  return;
1307  }
1308 
1309  wfl::map_formula_callable local_variables(variables);
1310  local_variables.add("text_width", wfl::variant(info.w));
1311  local_variables.add("text_height", wfl::variant(info.h));
1312  /*
1313  std::cerr << "Text: drawing text '" << text
1314  << " maximum width " << maximum_width_(variables)
1315  << " maximum height " << maximum_height_(variables)
1316  << " text width " << info.w
1317  << " text height " << info.h;
1318  */
1319  ///@todo formulas are now recalculated every draw cycle which is a
1320  // bit silly unless there has been a resize. So to optimize we should
1321  // use an extra flag or do the calculation in a separate routine.
1322 
1323  const unsigned x = x_(local_variables);
1324  const unsigned y = y_(local_variables);
1325  const unsigned w = w_(local_variables);
1326  const unsigned h = h_(local_variables);
1327 
1328  DBG_GUI_D << "Text: drawing text '" << text << "' drawn from " << x << ','
1329  << y << " width " << w << " height " << h << " canvas size "
1330  << canvas_w << ',' << canvas_h << ".\n";
1331 
1332  VALIDATE(static_cast<int>(x) < canvas_w && static_cast<int>(y) < canvas_h,
1333  _("Text doesn't start on canvas."));
1334 
1335  // A text might be to long and will be clipped.
1336  if(info.w > static_cast<int>(w)) {
1337  WRN_GUI_D << "Text: text is too wide for the "
1338  "canvas and will be clipped.\n";
1339  }
1340 
1341  if(info.h > static_cast<int>(h)) {
1342  WRN_GUI_D << "Text: text is too high for the "
1343  "canvas and will be clipped.\n";
1344  }
1345 
1346  SDL_Rect dst = sdl::create_rect(x, y, info.w, info.h);
1347 
1348  CVideo::get_singleton().render_copy(txt, nullptr, &dst);
1349 }
1350 
1351 /***** ***** ***** ***** ***** CANVAS ***** ***** ***** ***** *****/
1352 
1354  : shapes_()
1355  , blur_depth_(0)
1356  , w_(0)
1357  , h_(0)
1358  , renderer_(CVideo::get_singleton().get_renderer())
1359  , variables_()
1360  , functions_()
1361  , size_changed_(true)
1362 {
1363 }
1364 
1366  : shapes_(std::move(c.shapes_))
1368  , w_(c.w_)
1369  , h_(c.h_)
1370  , renderer_(std::exchange(c.renderer_, nullptr))
1374 {
1375 }
1376 
1378 {
1379 }
1380 
1382 {
1383  /**
1384  * @note Both the clip rect and viewport should be set before this function is called.
1385  * The clip rect ensures the canvas texture is cropped appropriately, and the viewport sets the
1386  * origin for all the drawing operations, as well as specifying the area of the screen to which
1387  * this canvas applies.
1388  */
1389 
1390  log_scope2(log_gui_draw, "Canvas: drawing.");
1391 
1392  if(size_changed_) {
1394  variables_.add("width", wfl::variant(w_));
1395  variables_.add("height", wfl::variant(h_));
1396 
1397  size_changed_ = false;
1398  }
1399 
1400  // Draw shapes.
1401  // TODO: reenable blur handling. Need a shader.
1402  for(auto& shape : shapes_) {
1403  lg::scope_logger inner_scope_logging_object__(log_gui_draw, "Canvas: draw shape.");
1404 
1406  }
1407 }
1408 
1409 void canvas::parse_cfg(const config& cfg)
1410 {
1411  log_scope2(log_gui_parse, "Canvas: parsing config.");
1412 
1413  for(const auto & shape : cfg.all_children_range())
1414  {
1415  const std::string& type = shape.key;
1416  const config& data = shape.cfg;
1417 
1418  DBG_GUI_P << "Canvas: found shape of the type " << type << ".\n";
1419 
1420  if(type == "line") {
1421  shapes_.emplace_back(std::make_shared<line_shape>(data));
1422  } else if(type == "rectangle") {
1423  shapes_.emplace_back(std::make_shared<rectangle_shape>(data));
1424  } else if(type == "round_rectangle") {
1425  shapes_.emplace_back(std::make_shared<round_rectangle_shape>(data));
1426  } else if(type == "circle") {
1427  shapes_.emplace_back(std::make_shared<circle_shape>(data));
1428  } else if(type == "image") {
1429  shapes_.emplace_back(std::make_shared<image_shape>(data, functions_));
1430  } else if(type == "text") {
1431  shapes_.emplace_back(std::make_shared<text_shape>(data));
1432  } else if(type == "pre_commit") {
1433 
1434  /* note this should get split if more preprocessing is used. */
1435  for(const auto & function : data.all_children_range())
1436  {
1437 
1438  if(function.key == "blur") {
1439  blur_depth_ = function.cfg["depth"];
1440  } else {
1441  ERR_GUI_P << "Canvas: found a pre commit function"
1442  << " of an invalid type " << type << ".\n";
1443  }
1444  }
1445 
1446  } else {
1447  ERR_GUI_P << "Canvas: found a shape of an invalid type " << type
1448  << ".\n";
1449 
1450  assert(false);
1451  }
1452  }
1453 }
1454 
1455 void canvas::clear_shapes(const bool force)
1456 {
1457  if(force) {
1458  shapes_.clear();
1459  } else {
1460  const auto conditional = [](const shape_ptr s)->bool { return !s->immutable(); };
1461 
1462  auto iter = std::remove_if(shapes_.begin(), shapes_.end(), conditional);
1463  shapes_.erase(iter, shapes_.end());
1464  }
1465 }
1466 
1467 void canvas::set_size(unsigned w, unsigned h)
1468 {
1469  w_ = w;
1470  h_ = h;
1471  size_changed_ = true;
1472 }
1473 
1474 /***** ***** ***** ***** ***** SHAPE ***** ***** ***** ***** *****/
1475 
1476 } // namespace gui2
1477 
1478 /*WIKI
1479  * @page = GUICanvasWML
1480  * @order = ZZZZZZ_footer
1481  *
1482  * [[Category: WML Reference]]
1483  * [[Category: GUI WML Reference]]
1484  *
1485  */
1486 
1487 /*WIKI
1488  * @page = GUIVariable
1489  * @order = ZZZZZZ_footer
1490  *
1491  * [[Category: WML Reference]]
1492  * [[Category: GUI WML Reference]]
1493  */
Define the common log macros for the gui toolkit.
#define ERR_GUI_E
Definition: log.hpp:37
typed_formula< color_t > color_
The color of the line.
typed_formula< unsigned > y_
The center y coordinate of the circle.
#define DBG_GUI_P
Definition: log.hpp:68
int x2_
Definition: pump.cpp:135
void set_draw_color(SDL_Renderer *renderer, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
Set renderer drawing color.
resize_mode get_resize_mode(const std::string &resize_mode)
Converts a string to a resize mode.
Definition: canvas.cpp:1148
void set_size(unsigned w, unsigned h)
Definition: canvas.cpp:1467
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:874
int border_thickness_
Border thickness.
void draw(const int canvas_w, const int canvas_h, SDL_Renderer *renderer, wfl::map_formula_callable &variables) override
Implement shape::draw().
Definition: canvas.cpp:892
typed_formula< int > w_
The width of the rectangle.
static variant evaluate(const const_formula_ptr &f, const formula_callable &variables, formula_debugger *fdb=nullptr, variant default_res=variant(0))
Definition: formula.hpp:39
int border_thickness_
Border thickness.
bool set_text(const std::string &text, const bool markedup)
Sets the text to render.
Definition: text.cpp:291
Collection of helper functions relating to Pango formatting.
round_rectangle_shape(const config &cfg)
Constructor.
Definition: canvas.cpp:758
SDL_Renderer * renderer_
Definition: widget.cpp:393
texture image_
The image texture.
Add a special kind of assert to validate whether the input from WML doesn&#39;t contain any problems that...
rectangle_shape(const config &cfg)
Constructor.
Definition: canvas.cpp:645
int as_int() const
Definition: variant.cpp:298
logger & info()
Definition: log.cpp:90
typed_formula< color_t > border_color_
The border color of the rounded rectangle.
Definition: video.hpp:36
circle_shape(const config &cfg)
Constructor.
Definition: canvas.cpp:877
typed_formula< unsigned > x2_
The end x coordinate of the line.
unsigned h_
Height of the canvas.
Definition: canvas.hpp:163
pango_text & set_link_aware(bool b)
Definition: text.cpp:457
typed_formula< int > maximum_height_
The maximum height for the text.
static CVideo & get_singleton()
Definition: video.hpp:48
STL namespace.
#define h
typed_formula< unsigned > y2_
The end y coordinate of the line.
typed_formula< color_t > fill_color_
The border color of the rounded rectangle.
#define d
#define VALIDATE_WITH_DEV_MESSAGE(cond, message, dev_message)
pango_text & set_font_style(const FONT_STYLE font_style)
Definition: text.cpp:352
Base class for renderer RAII helpers that operate on SDL_Rects.
pango_text & get_text_renderer()
Returns a reference to a static pango_text object.
Definition: text.cpp:893
pango_text & set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
Definition: text.cpp:419
typed_formula< int > w_
The width of the rectangle.
-file util.hpp
void draw(const int canvas_w, const int canvas_h, SDL_Renderer *renderer, wfl::map_formula_callable &variables) override
Implement shape::draw().
Definition: canvas.cpp:666
Wrapper class to encapsulate creation and management of an SDL_Texture.
Definition: texture.hpp:26
unsigned int border_thickness_
The border thickness of the circle.
#define ERR_GUI_P
Definition: log.hpp:71
void get_screen_size_variables(wfl::map_formula_callable &variable)
Gets a formula object with the screen size.
Definition: helper.cpp:99
wfl::map_formula_callable variables_
The variables of the canvas.
Definition: canvas.hpp:169
pango_text & set_alignment(const PangoAlignment alignment)
Definition: text.cpp:433
Generic file dialog.
Definition: field-fwd.hpp:22
SDL_Renderer * renderer_
A pointer to the window renderer.
Definition: canvas.hpp:166
typed_formula< bool > vertical_mirror_
Mirror the image over the vertical axis.
typed_formula< unsigned > h_
The height of the text.
int x1_
Definition: pump.cpp:135
int y2_
Definition: pump.cpp:135
#define DBG_GUI_D
Definition: log.hpp:28
virtual void draw(const int canvas_w, const int canvas_h, SDL_Renderer *renderer, wfl::map_formula_callable &variables)=0
Draws the canvas.
typed_formula< int > h_
The height of the rectangle.
Abstract base class for all other shapes.
Definition: canvas.hpp:50
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
void draw(const int canvas_w, const int canvas_h, SDL_Renderer *renderer, wfl::map_formula_callable &variables) override
Implement shape::draw().
Definition: canvas.cpp:780
static void dimension_validation(unsigned value, const std::string &name, const std::string &key)
Definition: canvas.cpp:1021
lg::log_domain log_gui_parse("gui/parse")
Definition: log.hpp:67
#define VALIDATE(cond, message)
The macro to use for the validation of WML.
const t_string name
shape(const config &cfg)
Definition: canvas.hpp:53
texture & render_and_get_texture()
Returns the rendered text texture from the cache.
Definition: text.cpp:93
map_formula_callable & add(const std::string &key, const variant &value)
Definition: callable.hpp:252
std::ostringstream wrapper.
Definition: formatter.hpp:38
text_shape(const config &cfg)
Constructor.
Definition: canvas.cpp:1219
pango_text & set_font_size(const unsigned font_size)
Definition: text.cpp:340
This file contains the canvas object which is the part where the widgets draw (temporally) images on...
typed_formula< unsigned > w_
The width of the text.
typed_formula< int > y_
The y coordinate of the rectangle.
typed_formula< unsigned > x_
The center x coordinate of the circle.
pango_text & set_characters_per_line(const unsigned characters_per_line)
Definition: text.cpp:388
wfl::formula actions_formula_
uint8_t a
Alpha value.
Definition: color.hpp:186
void set_texture_alpha(texture &t, uint8_t amount)
resize_mode resize_mode_
The resize mode for an image.
wfl::action_function_symbol_table functions_
Action function definitions for the canvas.
Definition: canvas.hpp:172
#define log_scope2(domain, description)
Definition: log.hpp:187
typed_formula< bool > link_aware_
The link aware switch of the text.
typed_formula< bool > text_markup_
The text markup switch of the text.
font::family_class font_family_
The text font family.
typed_formula< color_t > fill_color_
The border color of the rectangle.
typed_formula< std::string > image_name_
Name of the image.
#define ERR_GUI_D
Definition: log.hpp:31
void draw(const int canvas_w, const int canvas_h, SDL_Renderer *renderer, wfl::map_formula_callable &variables) override
Implement shape::draw().
Definition: canvas.cpp:578
typed_formula< color_t > border_color_
The border color of the circle.
typed_formula< int > y_
The y coordinate of the rectangle.
typed_formula< unsigned > x_
The x coordinate of the text.
typed_formula< int > maximum_width_
The maximum width for the text.
pango_text & set_family_class(font::family_class fclass)
Definition: text.cpp:329
A simple canvas which can be drawn upon.
Definition: canvas.hpp:41
int y1_
Definition: pump.cpp:135
typed_formula< color_t > border_color_
The border color of the rectangle.
typed_formula< int > h_
The height of the rectangle.
typed_formula< unsigned > y_
The y coordinate of the text.
std::size_t i
Definition: function.cpp:933
typed_formula< unsigned > x_
The x coordinate of the image.
formula_callable_ptr fake_ptr()
Definition: callable.hpp:41
unsigned blur_depth_
The depth of the blur to use in the pre committing.
Definition: canvas.hpp:157
font::pango_text::FONT_STYLE font_style_
The style of the text.
#define WRN_GUI_D
Definition: log.hpp:30
void draw(const int canvas_w, const int canvas_h, SDL_Renderer *renderer, wfl::map_formula_callable &variables) override
Implement shape::draw().
Definition: canvas.cpp:1248
void clear_shapes(const bool force)
Definition: canvas.cpp:1455
static map_location::DIRECTION s
bool size_changed_
Whether canvas dimensions changed.
Definition: canvas.hpp:175
image_shape(const config &cfg, wfl::action_function_symbol_table &functions)
Constructor.
Definition: canvas.cpp:1003
pango_text & set_link_color(const color_t &color)
Definition: text.cpp:467
line_shape(const config &cfg)
Constructor.
Definition: canvas.cpp:563
int w
#define debug(x)
lg::log_domain log_gui_draw("gui/draw")
Definition: log.hpp:27
typed_formula< unsigned > h_
The height of the image.
typed_formula< color_t > fill_color_
The fill color of the circle.
const info get_info() const
Queries metadata about the texture, such as its dimensions.
Definition: texture.hpp:55
typed_formula< unsigned > y_
The y coordinate of the image.
bool null() const
Definition: color.hpp:188
Text class.
Definition: text.hpp:76
SDL_Rect create_rect(const int x, const int y, const int w, const int h)
Creates an SDL_Rect with the given dimensions.
Definition: rect.hpp:39
pango_text & set_maximum_width(int width)
Definition: text.cpp:373
resize_mode
Determines the way an image will be resized.
std::vector< shape_ptr > shapes_
Vector with the shapes to draw.
Definition: canvas.hpp:147
typed_formula< t_string > text_
The text to draw.
Contains the SDL_Rect helper code.
const uint8_t ALPHA_OPAQUE
Definition: color.hpp:48
variant query_value(const std::string &key) const
Definition: callable.hpp:49
typed_formula< PangoAlignment > text_alignment_
The alignment of the text.
typed_formula< color_t > link_color_
The link color of the text.
void draw(const int canvas_w, const int canvas_h, SDL_Renderer *renderer, wfl::map_formula_callable &variables) override
Implement shape::draw().
Definition: canvas.cpp:1030
typed_formula< unsigned > font_size_
The font size of the text.
std::shared_ptr< shape > shape_ptr
Definition: canvas.hpp:87
typed_formula< int > r_
The radius of the corners.
typed_formula< unsigned > x1_
The start x coordinate of the line.
family_class str_to_family_class(const std::string &str)
unsigned characters_per_line_
The number of characters per line.
pango_text & set_maximum_height(int height, bool multiline)
Definition: text.cpp:400
bool has_formula() const
Determine whether the class contains a formula.
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:68
typed_formula< color_t > color_
The color of the text.
mock_char c
void parse_cfg(const config &cfg)
Parses a config object.
Definition: canvas.cpp:1409
pango_text & set_foreground_color(const color_t &color)
Definition: text.cpp:363
font::pango_text::FONT_STYLE decode_font_style(const std::string &style)
Converts a font style string to a font style.
Definition: helper.cpp:37
std::size_t w_
bool has_key(const std::string &key) const
Definition: callable.hpp:81
typed_formula< unsigned > y1_
The start y coordinate of the line.
typed_formula< int > x_
The x coordinate of the rectangle.
Small wrapper that queries metadata about the provided texture.
Definition: texture.hpp:44
typed_formula< unsigned > radius_
The radius of the circle.
typed_formula< int > x_
The x coordinate of the rectangle.
unsigned w_
Width of the canvas.
Definition: canvas.hpp:160
texture get_texture(const image::locator &i_locator, TYPE type)
Definition: picture.cpp:1424
void render()
Renders the canvas contents to screen.
Definition: canvas.cpp:1381
void render_copy(const texture &txt, SDL_Rect *src_rect=nullptr, SDL_Rect *dst_rect=nullptr, const bool flip_h=false, const bool flip_v=false)
Definition: video.cpp:253
typed_formula< unsigned > w_
The width of the image.