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