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