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