The Battle for Wesnoth  1.13.10+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 - 2017 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 how an image is resized.
423  * Possible values:
424  * @* scale The image is scaled.
425  * @* stretch The first row or column
426  * of pixels is copied over the entire
427  * image. (Can only be used to scale resize
428  * in one direction, else falls
429  * back to scale.)
430  * @* tile The image is placed
431  * several times until the entire surface
432  * is filled. The last images are
433  * truncated. $
434  * @end{table}
435  * @allow{type}{name="unsigned"}{value="^\d+$"}
436  * @allow{type}{name="f_unsigned"}{value="^.+$"}
437  * @allow{type}{name="int"}{value="^-?\d+$"}
438  * @allow{type}{name="f_int"}{value="^.*$"}
439  * @allow{type}{name="bool"}{value="^true|false|yes|no$"}
440  * @allow{type}{name="f_bool"}{value="^.*$"}
441  * @allow{type}{name="string"}{value="^.*$"}
442  * @allow{type}{name="t_string"}{value="^_?.*$"}
443  * @allow{type}{name="f_string"}{value="^.*$"}
444  * @allow{type}{name="f_tstring"}{value="^_?.*$"}
445  * @allow{type}{name="function"}{value="^_?.*$"}
446  *
447  * @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)$"}
448  *
449  * @allow{type}{name="font_style"}{value="^(normal|bold|italic|underline)?$"}
450  * @allow{type}{name="v_align"}{value="^top|bottom|center$"}
451  * @allow{type}{name="h_align"}{value="^left|right|center$"}
452  * @allow{type}{name="f_h_align"}{value="^.*$"}
453  * @allow{type}{name="border"}{value="^(top|bottom|left|right|all)?(,\s*(top|bottom|left|right|all))*$"}
454  * @allow{type}{name="scrollbar_mode"}{value="^always|never|auto|initial_auto$"}
455  * @allow{type}{name="resize_mode"}{value="^scale|stretch|tile$"}
456  * @allow{type}{name="grow_direction"}{value="^horizontal|vertical$"}
457  *
458  * @remove{type}{name="section"}
459  * @remove{type}{name="config"}
460  * @remove{type}{name="grid"}
461  * == Section types ==
462  *
463  * For more complex parts, there are sections. Sections contain of several
464  * lines of WML and can have sub sections. For example a grid has sub sections
465  * which contain various widgets. Here's the list of sections.
466  *
467  * @begin{table}{variable_types}
468  * section & A generic section. The documentation
469  * about the section should describe the
470  * section in further detail. $
471  *
472  * grid & A grid contains several widgets. (TODO
473  * add link to generic grid page.) $
474  * @end{table}
475  */
476 
477 /***** ***** ***** ***** ***** LINE ***** ***** ***** ***** *****/
478 
479 /*WIKI
480  * @page = GUICanvasWML
481  *
482  * == Line ==
483  * @begin{tag}{name="line"}{min="0"}{max="-1"}
484  * Definition of a line.
485  *
486  * Keys:
487  * @begin{table}{config}
488  * x1 & f_unsigned & 0 & The x coordinate of the startpoint. $
489  * y1 & f_unsigned & 0 & The y coordinate of the startpoint. $
490  * x2 & f_unsigned & 0 & The x coordinate of the endpoint. $
491  * y2 & f_unsigned & 0 & The y coordinate of the endpoint. $
492  * color & f_color & "" & The color of the line. $
493  * thickness & unsigned & 0 & The thickness of the line if 0 nothing
494  * is drawn. $
495  * debug & string & "" & Debug message to show upon creation
496  * this message is not stored. $
497  * @end{table}
498  * @end{tag}{name="line"}
499  *
500  * <span id="general_variables">Variables:</span>.
501  * @begin{table}{formula}
502  * width & unsigned & The width of the canvas. $
503  * height & unsigned & The height of the canvas. $
504  * text & tstring & The text to render on the widget. $
505  * text_maximum_width & unsigned & The maximum width available for the
506  * text on the widget. $
507  * text_maximum_height & unsigned & The maximum height available for the
508  * text on the widget. $
509  * text_wrap_mode & int & When the text doesn't fit in the
510  * available width there are several ways
511  * to fix that. This variable holds the
512  * best method. (NOTE this is a 'hidden'
513  * variable meant to copy state from a
514  * widget to its canvas so there's no
515  * reason to use this variable and thus
516  * its values are not listed and might
517  * change without further notice.) $
518  * text_alignment & h_align & The way the text is aligned inside the
519  * canvas. $
520  *@end{table}
521  *
522  * The size variables are copied to the window and will be determined at
523  * runtime. This is needed since the main window can be resized and the dialog
524  * needs to resize accordingly. The following variables are available:
525  * @begin{table}{formula}
526  * screen_width & unsigned & The usable width of the Wesnoth main
527  * window. $
528  * screen_height & unsigned & The usable height of the Wesnoth main
529  * window. $
530  * gamemapx_offset & unsigned & The distance between left edge of the
531  * screen and the game map. $
532  * gamemap_width & unsigned & The usable width of the Wesnoth gamemap,
533  * if no gamemap shown it's the same value
534  * as screen_width. $
535  * gamemap_height & unsigned & The usable height of the Wesnoth
536  * gamemap, if no gamemap shown it's the
537  * same value as screen_height. $
538  *
539  * mouse_x & unsigned & The x coordinate of the mouse pointer. $
540  * mouse_y & unsigned & The y coordinate of the mouse pointer. $
541  *
542  * window_width & unsigned & The window width. This value has two
543  * meanings during the layout phase. This
544  * only applies if automatic placement is
545  * not enabled.
546  * - When set to 0 it should return the
547  * wanted maximum width. If no maximum
548  * is wanted it should be set to the
549  * '"(screen_width)"'.
550  * - When not equal to 0 its value is the
551  * best width for the window. When the
552  * size should remain unchanged it
553  * should be set to '"(window_width)"'.
554  * $
555  *
556  * window_height & unsigned & The window height. This value has two
557  * meanings during the layout phase. This
558  * only applies if automatic placement is
559  * not enabled.
560  * - When set to 0 it should return the
561  * wanted maximum height. If no maximum
562  * is wanted it should be set to the
563  * '"(screen_height)"'.
564  * - When not equal to 0 its value is the
565  * best height for the window. When the
566  * size should remain unchanged it
567  * should be set to '"(window_height)"'.
568  * $
569  *
570  * size_request_mode & string & A field foo:
571  * - maximum
572  * - size
573  *
574  * @end{table}
575  *
576  * Note when drawing the valid coordinates are:<br>
577  * 0 -> width - 1 <br>
578  * 0 -> height -1
579  *
580  * Drawing outside this area will result in unpredictable results including
581  * crashing. (That should be fixed, when encountered.)
582  */
583 
585  : shape(cfg)
586  , x1_(cfg["x1"])
587  , y1_(cfg["y1"])
588  , x2_(cfg["x2"])
589  , y2_(cfg["y2"])
590  , color_(cfg["color"])
591  , thickness_(cfg["thickness"])
592 {
593  const std::string& debug = (cfg["debug"]);
594  if(!debug.empty()) {
595  DBG_GUI_P << "Line: found debug message '" << debug << "'.\n";
596  }
597 }
598 
600  SDL_Renderer* renderer,
601  wfl::map_formula_callable& variables)
602 {
603  /**
604  * @todo formulas are now recalculated every draw cycle which is a bit silly
605  * unless there has been a resize. So to optimize we should use an extra
606  * flag or do the calculation in a separate routine.
607  */
608 
609  const unsigned x1 = x1_(variables);
610  const unsigned y1 = y1_(variables);
611  const unsigned x2 = x2_(variables);
612  const unsigned y2 = y2_(variables);
613 
614  DBG_GUI_D << "Line: draw from " << x1 << ',' << y1 << " to " << x2 << ','
615  << y2 << " canvas size " << canvas->w << ',' << canvas->h
616  << ".\n";
617 
618  VALIDATE(static_cast<int>(x1) < canvas->w
619  && static_cast<int>(x2) < canvas->w
620  && static_cast<int>(y1) < canvas->h
621  && static_cast<int>(y2) < canvas->h,
622  _("Line doesn't fit on canvas."));
623 
624  // @todo FIXME respect the thickness.
625 
626  // lock the surface
627  surface_lock locker(canvas);
628 
629  draw_line(canvas, renderer, color_(variables), x1, y1, x2, y2);
630 }
631 
632 /***** ***** ***** ***** ***** Rectangle ***** ***** ***** ***** *****/
633 
634 /*WIKI
635  * @page = GUICanvasWML
636  *
637  * == Rectangle ==
638  * @begin{tag}{name="rectangle"}{min="0"}{max="-1"}
639  *
640  * Definition of a rectangle. When drawing a rectangle it doesn't get blended on
641  * the surface but replaces the pixels instead. A blitting flag might be added
642  * later if needed.
643  *
644  * Keys:
645  * @begin{table}{config}
646  * x & f_unsigned & 0 & The x coordinate of the top left corner.
647  * $
648  * y & f_unsigned & 0 & The y coordinate of the top left corner.
649  * $
650  * w & f_unsigned & 0 & The width of the rectangle. $
651  * h & f_unsigned & 0 & The height of the rectangle. $
652  * border_thickness & unsigned & 0 &
653  * The thickness of the border; if the
654  * thickness is zero it's not drawn. $
655  * border_color & f_color & "" & The color of the border; if empty it's
656  * not drawn. $
657  * fill_color & f_color & "" & The color of the interior; if omitted
658  * it's not drawn. $
659  * debug & string & "" & Debug message to show upon creation;
660  * this message is not stored. $
661  * @end{table}
662  * @end{tag}{name="rectangle"}
663  * Variables:
664  * See [[#general_variables|Line]].
665  *
666  */
668  : shape(cfg)
669  , x_(cfg["x"])
670  , y_(cfg["y"])
671  , w_(cfg["w"])
672  , h_(cfg["h"])
673  , border_thickness_(cfg["border_thickness"])
674  , border_color_(cfg["border_color"], color_t::null_color())
675  , fill_color_(cfg["fill_color"], color_t::null_color())
676 {
677  // Check if a raw color string evaluates to a null color.
678  if(!border_color_.has_formula() && border_color_().null()) {
679  border_thickness_ = 0;
680  }
681 
682  const std::string& debug = (cfg["debug"]);
683  if(!debug.empty()) {
684  DBG_GUI_P << "Rectangle: found debug message '" << debug << "'.\n";
685  }
686 }
687 
689  SDL_Renderer* renderer,
690  wfl::map_formula_callable& variables)
691 {
692  /**
693  * @todo formulas are now recalculated every draw cycle which is a bit
694  * silly unless there has been a resize. So to optimize we should use an
695  * extra flag or do the calculation in a separate routine.
696  */
697  const int x = x_(variables);
698  const int y = y_(variables);
699  const int w = w_(variables);
700  const int h = h_(variables);
701 
702  DBG_GUI_D << "Rectangle: draw from " << x << ',' << y << " width " << w
703  << " height " << h << " canvas size " << canvas->w << ','
704  << canvas->h << ".\n";
705 
706  VALIDATE(x < canvas->w
707  && x + w <= canvas->w
708  && y < canvas->h
709  && y + h <= canvas->h, _("Rectangle doesn't fit on canvas."));
710 
711  surface_lock locker(canvas);
712 
713  const color_t fill_color = fill_color_(variables);
714 
715  // Fill the background, if applicable
716  if(!fill_color.null() && w && h) {
717  set_renderer_color(renderer, fill_color);
718 
719  SDL_Rect area {
720  x + border_thickness_,
721  y + border_thickness_,
722  w - (border_thickness_ * 2),
723  h - (border_thickness_ * 2)
724  };
725 
726  SDL_RenderFillRect(renderer, &area);
727  }
728 
729  // Draw the border
730  for(int i = 0; i < border_thickness_; ++i) {
731  SDL_Rect dimensions {
732  x + i,
733  y + i,
734  w - (i * 2),
735  h - (i * 2)
736  };
737 
738  set_renderer_color(renderer, border_color_(variables));
739 
740  SDL_RenderDrawRect(renderer, &dimensions);
741  }
742 }
743 
744 /***** ***** ***** ***** ***** Rounded Rectangle ***** ***** ***** ***** *****/
745 
746 /*WIKI
747  * @page = GUICanvasWML
748  *
749  * == Rounded Rectangle ==
750  * @begin{tag}{name="round_rectangle"}{min="0"}{max="-1"}
751  *
752  * Definition of a rounded rectangle. When drawing a rounded rectangle it doesn't get blended on
753  * the surface but replaces the pixels instead. A blitting flag might be added
754  * later if needed.
755  *
756  * Keys:
757  * @begin{table}{config}
758  * x & f_unsigned & 0 & The x coordinate of the top left corner.
759  * $
760  * y & f_unsigned & 0 & The y coordinate of the top left corner.
761  * $
762  * w & f_unsigned & 0 & The width of the rounded rectangle. $
763  * h & f_unsigned & 0 & The height of the rounded rectangle. $
764  * corner_radius & f_unsigned & 0 &The radius of the rectangle's corners. $
765  * border_thickness & unsigned & 0 &
766  * The thickness of the border; if the
767  * thickness is zero it's not drawn. $
768  * border_color & f_color & "" & The color of the border; if empty it's
769  * not drawn. $
770  * fill_color & f_color & "" & The color of the interior; if omitted
771  * it's not drawn. $
772  * debug & string & "" & Debug message to show upon creation;
773  * this message is not stored. $
774  * @end{table}
775  * @end{tag}{name="round_rectangle"}
776  * Variables:
777  * See [[#general_variables|Line]].
778  *
779  */
781  : shape(cfg)
782  , x_(cfg["x"])
783  , y_(cfg["y"])
784  , w_(cfg["w"])
785  , h_(cfg["h"])
786  , r_(cfg["corner_radius"])
787  , border_thickness_(cfg["border_thickness"])
788  , border_color_(cfg["border_color"], color_t::null_color())
789  , fill_color_(cfg["fill_color"], color_t::null_color())
790 {
791  // Check if a raw color string evaluates to a null color.
792  if(!border_color_.has_formula() && border_color_().null()) {
793  border_thickness_ = 0;
794  }
795 
796  const std::string& debug = (cfg["debug"]);
797  if(!debug.empty()) {
798  DBG_GUI_P << "Rounded Rectangle: found debug message '" << debug << "'.\n";
799  }
800 }
801 
803  SDL_Renderer* renderer,
804  wfl::map_formula_callable& variables)
805 {
806  /**
807  * @todo formulas are now recalculated every draw cycle which is a bit
808  * silly unless there has been a resize. So to optimize we should use an
809  * extra flag or do the calculation in a separate routine.
810  */
811  const int x = x_(variables);
812  const int y = y_(variables);
813  const int w = w_(variables);
814  const int h = h_(variables);
815  const int r = r_(variables);
816 
817  DBG_GUI_D << "Rounded Rectangle: draw from " << x << ',' << y << " width " << w
818  << " height " << h << " canvas size " << canvas->w << ','
819  << canvas->h << ".\n";
820 
821  VALIDATE(x < canvas->w
822  && x + w <= canvas->w
823  && y < canvas->h
824  && y + h <= canvas->h, _("Rounded Rectangle doesn't fit on canvas."));
825 
826  surface_lock locker(canvas);
827 
828  const color_t fill_color = fill_color_(variables);
829 
830  // Fill the background, if applicable
831  if(!fill_color.null() && w && h) {
832  set_renderer_color(renderer, fill_color);
833  static const int count = 3;
834  SDL_Rect area[count] {
835  {x + r, y + border_thickness_, w - r * 2, r - border_thickness_ + 1},
836  {x + border_thickness_, y + r + 1, w - border_thickness_ * 2, h - r * 2},
837  {x + r, y - r + h + 1, w - r * 2, r - border_thickness_},
838  };
839 
840  SDL_RenderFillRects(renderer, area, count);
841 
842  fill_circle<0xc0>(canvas, renderer, fill_color, x + r, y + r, r);
843  fill_circle<0x03>(canvas, renderer, fill_color, x + w - r, y + r, r);
844  fill_circle<0x30>(canvas, renderer, fill_color, x + r, y + h - r, r);
845  fill_circle<0x0c>(canvas, renderer, fill_color, x + w - r, y + h - r, r);
846  }
847 
848  const color_t border_color = border_color_(variables);
849 
850  // Draw the border
851  for(int i = 0; i < border_thickness_; ++i) {
852  set_renderer_color(renderer, border_color);
853 
854  SDL_RenderDrawLine(renderer, x + r, y + i, x + w - r, y + i);
855  SDL_RenderDrawLine(renderer, x + r, y + h - i, x + w - r, y + h - i);
856 
857  SDL_RenderDrawLine(renderer, x + i, y + r, x + i, y + h - r);
858  SDL_RenderDrawLine(renderer, x + w - i, y + r, x + w - i, y + h - r);
859 
860  draw_circle<0xc0>(canvas, renderer, border_color, x + r, y + r, r - i);
861  draw_circle<0x03>(canvas, renderer, border_color, x + w - r, y + r, r - i);
862  draw_circle<0x30>(canvas, renderer, border_color, x + r, y + h - r, r - i);
863  draw_circle<0x0c>(canvas, renderer, border_color, x + w - r, y + h - r, r - i);
864  }
865 }
866 
867 /***** ***** ***** ***** ***** CIRCLE ***** ***** ***** ***** *****/
868 
869 /*WIKI
870  * @page = GUICanvasWML
871  *
872  * == Circle ==
873  * @begin{tag}{name="circle"}{min="0"}{max="-1"}
874  *
875  * Definition of a circle. When drawing a circle it doesn't get blended on
876  * the surface but replaces the pixels instead. A blitting flag might be
877  * added later if needed.
878  *
879  * Keys:
880  * @begin{table}{config}
881  * x & f_unsigned & 0 & The x coordinate of the center. $
882  * y & f_unsigned & 0 & The y coordinate of the center. $
883  * radius & f_unsigned & 0 & The radius of the circle; if 0 nothing is
884  * drawn. $
885  * border_thickness & unsigned & "" &
886  * The border thickness of the circle. $
887  * border_color & f_color & "" & The color of the circle. $
888  * fill_color & f_color & "" & The color of the circle. $
889  * debug & string & "" & Debug message to show upon creation; this
890  * message is not stored. $
891  * @end{table}
892  * @end{tag}{name="circle"}
893  * Variables:
894  * See [[#general_variables|Line]].
895  *
896  * Drawing outside the area will result in unpredictable results including
897  * crashing. (That should be fixed, when encountered.)
898  */
900  : shape(cfg)
901  , x_(cfg["x"])
902  , y_(cfg["y"])
903  , radius_(cfg["radius"])
904  , border_color_(cfg["border_color"])
905  , fill_color_(cfg["fill_color"])
906  , border_thickness_(cfg["border_thickness"].to_int(1))
907 {
908  const std::string& debug = (cfg["debug"]);
909  if(!debug.empty()) {
910  DBG_GUI_P << "Circle: found debug message '" << debug << "'.\n";
911  }
912 }
913 
915  SDL_Renderer* renderer,
916  wfl::map_formula_callable& variables)
917 {
918  /**
919  * @todo formulas are now recalculated every draw cycle which is a bit
920  * silly unless there has been a resize. So to optimize we should use an
921  * extra flag or do the calculation in a separate routine.
922  */
923 
924  const unsigned x = x_(variables);
925  const unsigned y = y_(variables);
926  const unsigned radius = radius_(variables);
927 
928  DBG_GUI_D << "Circle: drawn at " << x << ',' << y << " radius " << radius
929  << " canvas size " << canvas->w << ',' << canvas->h << ".\n";
930 
932  static_cast<int>(x - radius) >= 0,
933  _("Circle doesn't fit on canvas."),
934  formatter() << "x = " << x << ", radius = " << radius);
935 
937  static_cast<int>(y - radius) >= 0,
938  _("Circle doesn't fit on canvas."),
939  formatter() << "y = " << y << ", radius = " << radius);
940 
942  static_cast<int>(x + radius) < canvas->w,
943  _("Circle doesn't fit on canvas."),
944  formatter() << "x = " << x << ", radius = " << radius
945  << "', canvas width = " << canvas->w << ".");
946 
948  static_cast<int>(y + radius) < canvas->h,
949  _("Circle doesn't fit on canvas."),
950  formatter() << "y = " << y << ", radius = " << radius
951  << "', canvas height = " << canvas->h << ".");
952 
953  // lock the surface
954  surface_lock locker(canvas);
955 
956  const color_t fill_color = fill_color_(variables);
957  if(!fill_color.null() && radius) {
958  fill_circle(canvas, renderer, fill_color, x, y, radius);
959  }
960 
961  const color_t border_color = border_color_(variables);
962  for(unsigned int i = 0; i < border_thickness_; i++) {
963  draw_circle(canvas, renderer, border_color, x, y, radius - i);
964  }
965 }
966 
967 /***** ***** ***** ***** ***** IMAGE ***** ***** ***** ***** *****/
968 
969 /*WIKI
970  * @page = GUICanvasWML
971  *
972  * == Image ==
973  * @begin{tag}{name="image"}{min="0"}{max="-1"}
974  * Definition of an image.
975  *
976  * Keys:
977  * @begin{table}{config}
978  * x & f_unsigned & 0 & The x coordinate of the top left corner.
979  * $
980  * y & f_unsigned & 0 & The y coordinate of the top left corner.
981  * $
982  * w & f_unsigned & 0 & The width of the image, if not zero the
983  * image will be scaled to the desired
984  * width. $
985  * h & f_unsigned & 0 & The height of the image, if not zero the
986  * image will be scaled to the desired
987  * height. $
988  * resize_mode & resize_mode & scale &
989  * Determines how an image is scaled to fit
990  * the wanted size. $
991  * vertical_mirror & f_bool & false &
992  * Mirror the image over the vertical axis.
993  * $
994  * name & f_string & "" & The name of the image. $
995  * debug & string & "" & Debug message to show upon creation
996  * this message is not stored. $
997  *
998  * @end{table}
999  * @end{tag}{name="image"}
1000  * Variables:
1001  * @begin{table}{formula}
1002  * image_width & unsigned & The width of the image, either the
1003  * requested width or the natural width of
1004  * the image. This value can be used to set
1005  * the x (or y) value of the image. (This
1006  * means x and y are evaluated after the
1007  * width and height.) $
1008  * image_height & unsigned & The height of the image, either the
1009  * requested height or the natural height
1010  * of the image. This value can be used to
1011  * set the y (or x) value of the image.
1012  * (This means x and y are evaluated after
1013  * the width and height.) $
1014  * image_original_width & unsigned &
1015  * The width of the image as stored on
1016  * disk, can be used to set x or w
1017  * (also y and h can be set). $
1018  * image_original_height & unsigned &
1019  * The height of the image as stored on
1020  * disk, can be used to set y or h
1021  * (also x and y can be set). $
1022  * @end{table}
1023  * Also the general variables are available, see [[#general_variables|Line]].
1024  */
1025 
1027  : shape(cfg)
1028  , x_(cfg["x"])
1029  , y_(cfg["y"])
1030  , w_(cfg["w"])
1031  , h_(cfg["h"])
1032  , src_clip_()
1033  , image_()
1034  , image_name_(cfg["name"])
1035  , resize_mode_(get_resize_mode(cfg["resize_mode"]))
1036  , vertical_mirror_(cfg["vertical_mirror"])
1037  , actions_formula_(cfg["actions"], &functions)
1038 {
1039  const std::string& debug = (cfg["debug"]);
1040  if(!debug.empty()) {
1041  DBG_GUI_P << "Image: found debug message '" << debug << "'.\n";
1042  }
1043 }
1044 
1045 void image_shape::dimension_validation(unsigned value, const std::string& name, const std::string& key)
1046 {
1047  const int as_int = static_cast<int>(value);
1048 
1049  VALIDATE_WITH_DEV_MESSAGE(as_int >= 0, _("Image doesn't fit on canvas."),
1050  formatter() << "Image '" << name << "', " << key << " = " << as_int << "."
1051  );
1052 }
1053 
1055  SDL_Renderer* /*renderer*/,
1056  wfl::map_formula_callable& variables)
1057 {
1058  DBG_GUI_D << "Image: draw.\n";
1059 
1060  /**
1061  * @todo formulas are now recalculated every draw cycle which is a bit
1062  * silly unless there has been a resize. So to optimize we should use an
1063  * extra flag or do the calculation in a separate routine.
1064  */
1065  const std::string& name = image_name_(variables);
1066 
1067  if(name.empty()) {
1068  DBG_GUI_D << "Image: formula returned no value, will not be drawn.\n";
1069  return;
1070  }
1071 
1072  /*
1073  * The locator might return a different surface for every call so we can't
1074  * cache the output, also not if no formula is used.
1075  */
1077 
1078  if(!tmp) {
1079  ERR_GUI_D << "Image: '" << name << "' not found and won't be drawn." << std::endl;
1080  return;
1081  }
1082 
1084  assert(image_);
1085  src_clip_ = {0, 0, image_->w, image_->h};
1086 
1087  wfl::map_formula_callable local_variables(variables);
1088  local_variables.add("image_original_width", wfl::variant(image_->w));
1089  local_variables.add("image_original_height", wfl::variant(image_->h));
1090 
1091  unsigned w = w_(local_variables);
1092  dimension_validation(w, name, "w");
1093 
1094  unsigned h = h_(local_variables);
1095  dimension_validation(h, name, "h");
1096 
1097  local_variables.add("image_width", wfl::variant(w ? w : image_->w));
1098  local_variables.add("image_height", wfl::variant(h ? h : image_->h));
1099 
1100  const unsigned clip_x = x_(local_variables);
1101  dimension_validation(clip_x, name, "x");
1102 
1103  const unsigned clip_y = y_(local_variables);
1104  dimension_validation(clip_y, name, "y");
1105 
1106  local_variables.add("clip_x", wfl::variant(clip_x));
1107  local_variables.add("clip_y", wfl::variant(clip_y));
1108 
1109  // Execute the provided actions for this context.
1110  wfl::variant(variables.fake_ptr()).execute_variant(actions_formula_.evaluate(local_variables));
1111 
1112  // Copy the data to local variables to avoid overwriting the originals.
1113  SDL_Rect src_clip = src_clip_;
1114  SDL_Rect dst_clip = sdl::create_rect(clip_x, clip_y, 0, 0);
1115  surface surf;
1116 
1117  // Test whether we need to scale and do the scaling if needed.
1118  if(w || h) {
1119  bool done = false;
1120  bool stretch_image = (resize_mode_ == stretch) && (!!w ^ !!h);
1121  if(!w) {
1122  if(stretch_image) {
1123  DBG_GUI_D << "Image: vertical stretch from " << image_->w << ','
1124  << image_->h << " to a height of " << h << ".\n";
1125 
1126  surf = stretch_surface_vertical(image_, h);
1127  done = true;
1128  }
1129  w = image_->w;
1130  }
1131 
1132  if(!h) {
1133  if(stretch_image) {
1134  DBG_GUI_D << "Image: horizontal stretch from " << image_->w
1135  << ',' << image_->h << " to a width of " << w
1136  << ".\n";
1137 
1139  done = true;
1140  }
1141  h = image_->h;
1142  }
1143 
1144  if(!done) {
1145 
1146  if(resize_mode_ == tile) {
1147  DBG_GUI_D << "Image: tiling from " << image_->w << ','
1148  << image_->h << " to " << w << ',' << h << ".\n";
1149 
1150  surf = tile_surface(image_, w, h, false);
1151  } else if(resize_mode_ == tile_center) {
1152  DBG_GUI_D << "Image: tiling centrally from " << image_->w << ','
1153  << image_->h << " to " << w << ',' << h << ".\n";
1154 
1155  surf = tile_surface(image_, w, h, true);
1156  } else {
1157  if(resize_mode_ == stretch) {
1158  ERR_GUI_D << "Image: failed to stretch image, "
1159  "fall back to scaling.\n";
1160  }
1161 
1162  DBG_GUI_D << "Image: scaling from " << image_->w << ','
1163  << image_->h << " to " << w << ',' << h << ".\n";
1164 
1165  surf = scale_surface_legacy(image_, w, h);
1166  }
1167  }
1168  src_clip.w = w;
1169  src_clip.h = h;
1170  } else {
1171  surf = image_;
1172  }
1173 
1174  if(vertical_mirror_(local_variables)) {
1175  surf = flip_surface(surf);
1176  }
1177 
1178  blit_surface(surf, &src_clip, canvas, &dst_clip);
1179 }
1180 
1182 {
1183  if(resize_mode == "tile") {
1184  return image_shape::tile;
1185  } else if(resize_mode == "tile_center") {
1186  return image_shape::tile_center;
1187  } else if(resize_mode == "stretch") {
1188  return image_shape::stretch;
1189  } else {
1190  if(!resize_mode.empty() && resize_mode != "scale") {
1191  ERR_GUI_E << "Invalid resize mode '" << resize_mode
1192  << "' falling back to 'scale'.\n";
1193  }
1194  return image_shape::scale;
1195  }
1196 }
1197 
1198 /***** ***** ***** ***** ***** TEXT ***** ***** ***** ***** *****/
1199 
1200 /*WIKI
1201  * @page = GUICanvasWML
1202  *
1203  * == Text ==
1204  * @begin{tag}{name="text"}{min="0"}{max="-1"}
1205  * Definition of text.
1206  *
1207  * Keys:
1208  * @begin{table}{config}
1209  * x & f_unsigned & 0 & The x coordinate of the top left corner.
1210  * $
1211  * y & f_unsigned & 0 & The y coordinate of the top left corner.
1212  * $
1213  * w & f_unsigned & 0 & The width of the text's bounding
1214  * rectangle. $
1215  * h & f_unsigned & 0 & The height of the text's bounding
1216  * rectangle. $
1217  * font_family & font_family & "sans" &
1218  * The font family used for the text. $
1219  * font_size & unsigned & & The size of the text font. $
1220  * font_style & font_style & "" & The style of the text. $
1221  * text_alignment & f_h_align & "left" &
1222  * The alignment of the text. $
1223  * color & f_color & "" & The color of the text. $
1224  * text & f_tstring & "" & The text to draw (translatable). $
1225  * text_markup & f_bool & false & Can the text have mark-up? $
1226  * text_link_aware & f_bool & false &
1227  * Is the text link aware? $
1228  * text_link_color & f_string & "#ffff00" &
1229  * The color of links in the text $
1230  * maximum_width & f_int & -1 & The maximum width the text is allowed to
1231  * be. $
1232  * maximum_height & f_int & -1 & The maximum height the text is allowed
1233  * to be. $
1234  * debug & string & "" & Debug message to show upon creation
1235  * this message is not stored. $
1236  * @end{table}
1237  * @end{tag}{name="text"}
1238  * NOTE alignment could only be done with the formulas, but now with the
1239  * text_alignment flag as well, older widgets might still use the formulas and
1240  * not all widgets may expose the text alignment yet and when exposed not use
1241  * it yet.
1242  *
1243  * Variables:
1244  * @begin{table}{formula}
1245  * text_width & unsigned & The width of the rendered text. $
1246  * text_height & unsigned & The height of the rendered text. $
1247  * @end{table}
1248  * Also the general variables are available, see [[#general_variables|Line]].
1249  * @end{parent}{name="generic/state/draw/"}
1250  */
1251 
1253  : shape(cfg)
1254  , x_(cfg["x"])
1255  , y_(cfg["y"])
1256  , w_(cfg["w"])
1257  , h_(cfg["h"])
1258  , font_family_(font::str_to_family_class(cfg["font_family"]))
1259  , font_size_(cfg["font_size"])
1260  , font_style_(decode_font_style(cfg["font_style"]))
1261  , text_alignment_(cfg["text_alignment"])
1262  , color_(cfg["color"])
1263  , text_(cfg["text"])
1264  , text_markup_(cfg["text_markup"], false)
1265  , link_aware_(cfg["text_link_aware"], false)
1266  , link_color_(cfg["text_link_color"], color_t::from_hex_string("ffff00"))
1267  , maximum_width_(cfg["maximum_width"], -1)
1268  , characters_per_line_(cfg["text_characters_per_line"])
1269  , maximum_height_(cfg["maximum_height"], -1)
1270 {
1271  VALIDATE(font_size_, _("Text has a font size of 0."));
1272 
1273  const std::string& debug = (cfg["debug"]);
1274  if(!debug.empty()) {
1275  DBG_GUI_P << "Text: found debug message '" << debug << "'.\n";
1276  }
1277 }
1278 
1280  SDL_Renderer* /*renderer*/,
1281  wfl::map_formula_callable& variables)
1282 {
1283  assert(variables.has_key("text"));
1284 
1285  // We first need to determine the size of the text which need the rendered
1286  // text. So resolve and render the text first and then start to resolve
1287  // the other formulas.
1288  const t_string text = text_(variables);
1289 
1290  if(text.empty()) {
1291  DBG_GUI_D << "Text: no text to render, leave.\n";
1292  return;
1293  }
1294 
1295  static font::pango_text text_renderer;
1296 
1297  text_renderer
1298  .set_link_aware(link_aware_(variables))
1299  .set_link_color(link_color_(variables))
1300  .set_text(text, text_markup_(variables));
1301 
1302  text_renderer.set_family_class(font_family_)
1305  .set_alignment(text_alignment_(variables))
1306  .set_foreground_color(color_(variables))
1307  .set_maximum_width(maximum_width_(variables))
1308  .set_maximum_height(maximum_height_(variables), true)
1309  .set_ellipse_mode(variables.has_key("text_wrap_mode")
1310  ? static_cast<PangoEllipsizeMode>(variables.query_value("text_wrap_mode").as_int())
1311  : PANGO_ELLIPSIZE_END)
1313 
1314  surface& surf = text_renderer.render();
1315  if(surf->w == 0) {
1316  DBG_GUI_D << "Text: Rendering '" << text
1317  << "' resulted in an empty canvas, leave.\n";
1318  return;
1319  }
1320 
1321  wfl::map_formula_callable local_variables(variables);
1322  local_variables.add("text_width", wfl::variant(surf->w));
1323  local_variables.add("text_height", wfl::variant(surf->h));
1324  /*
1325  std::cerr << "Text: drawing text '" << text
1326  << " maximum width " << maximum_width_(variables)
1327  << " maximum height " << maximum_height_(variables)
1328  << " text width " << surf->w
1329  << " text height " << surf->h;
1330  */
1331  ///@todo formulas are now recalculated every draw cycle which is a
1332  // bit silly unless there has been a resize. So to optimize we should
1333  // use an extra flag or do the calculation in a separate routine.
1334 
1335  const unsigned x = x_(local_variables);
1336  const unsigned y = y_(local_variables);
1337  const unsigned w = w_(local_variables);
1338  const unsigned h = h_(local_variables);
1339 
1340  DBG_GUI_D << "Text: drawing text '" << text << "' drawn from " << x << ','
1341  << y << " width " << w << " height " << h << " canvas size "
1342  << canvas->w << ',' << canvas->h << ".\n";
1343 
1344  VALIDATE(static_cast<int>(x) < canvas->w && static_cast<int>(y) < canvas->h,
1345  _("Text doesn't start on canvas."));
1346 
1347  // A text might be to long and will be clipped.
1348  if(surf->w > static_cast<int>(w)) {
1349  WRN_GUI_D << "Text: text is too wide for the "
1350  "canvas and will be clipped.\n";
1351  }
1352 
1353  if(surf->h > static_cast<int>(h)) {
1354  WRN_GUI_D << "Text: text is too high for the "
1355  "canvas and will be clipped.\n";
1356  }
1357 
1358  SDL_Rect dst = sdl::create_rect(x, y, canvas->w, canvas->h);
1359  blit_surface(surf, nullptr, canvas, &dst);
1360 }
1361 
1362 /***** ***** ***** ***** ***** CANVAS ***** ***** ***** ***** *****/
1363 
1365  : shapes_()
1366  , blur_depth_(0)
1367  , w_(0)
1368  , h_(0)
1369  , canvas_()
1370  , renderer_(nullptr)
1371  , variables_()
1372  , functions_()
1373  , is_dirty_(true)
1374 {
1375 }
1376 
1378 {
1379  SDL_DestroyRenderer(renderer_);
1380 }
1381 
1382 void canvas::draw(const bool force)
1383 {
1384  log_scope2(log_gui_draw, "Canvas: drawing.");
1385  if(!is_dirty_ && !force) {
1386  DBG_GUI_D << "Canvas: nothing to draw.\n";
1387  return;
1388  }
1389 
1390  if(is_dirty_) {
1392  variables_.add("width", wfl::variant(w_));
1393  variables_.add("height", wfl::variant(h_));
1394  }
1395 
1396  if(!canvas_.null()) {
1397  DBG_GUI_D << "Canvas: use cached canvas.\n";
1398  } else {
1399  // create surface
1400  DBG_GUI_D << "Canvas: create new empty canvas.\n";
1402  }
1403 
1404  SDL_DestroyRenderer(renderer_);
1405 
1406  renderer_ = SDL_CreateSoftwareRenderer(canvas_);
1407  SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
1408 
1409  // draw items
1410  for(auto& shape : shapes_) {
1411  lg::scope_logger inner_scope_logging_object__(log_gui_draw, "Canvas: draw shape.");
1412 
1414  }
1415 
1416  // The shapes have been drawn and the draw result has been cached. Clear the list.
1417  std::copy(shapes_.begin(), shapes_.end(), std::back_inserter(drawn_shapes_));
1418  shapes_.clear();
1419 
1420  SDL_RenderPresent(renderer_);
1421 
1422  is_dirty_ = false;
1423 }
1424 
1425 void canvas::blit(surface& surf, SDL_Rect rect)
1426 {
1427  draw();
1428 
1429  if(blur_depth_) {
1430  /*
1431  * If the surf is the video surface the blurring seems to stack, this
1432  * can be seen in the title screen. So also use the not 32 bpp method
1433  * for this situation.
1434  */
1435  if(surf != CVideo::get_singleton().getSurface() && is_neutral(surf)) {
1436  blur_surface(surf, rect, blur_depth_);
1437  } else {
1438  // Can't directly blur the surface if not 32 bpp.
1439  SDL_Rect r = rect;
1440  surface s = get_surface_portion(surf, r);
1441  s = blur_surface(s, blur_depth_);
1442  sdl_blit(s, nullptr, surf, &r);
1443  }
1444  }
1445 
1446  sdl_blit(canvas_, nullptr, surf, &rect);
1447 }
1448 
1449 void canvas::parse_cfg(const config& cfg)
1450 {
1451  log_scope2(log_gui_parse, "Canvas: parsing config.");
1452 
1453  for(const auto & shape : cfg.all_children_range())
1454  {
1455  const std::string& type = shape.key;
1456  const config& data = shape.cfg;
1457 
1458  DBG_GUI_P << "Canvas: found shape of the type " << type << ".\n";
1459 
1460  if(type == "line") {
1461  shapes_.emplace_back(std::make_shared<line_shape>(data));
1462  } else if(type == "rectangle") {
1463  shapes_.emplace_back(std::make_shared<rectangle_shape>(data));
1464  } else if(type == "round_rectangle") {
1465  shapes_.emplace_back(std::make_shared<round_rectangle_shape>(data));
1466  } else if(type == "circle") {
1467  shapes_.emplace_back(std::make_shared<circle_shape>(data));
1468  } else if(type == "image") {
1469  shapes_.emplace_back(std::make_shared<image_shape>(data, functions_));
1470  } else if(type == "text") {
1471  shapes_.emplace_back(std::make_shared<text_shape>(data));
1472  } else if(type == "pre_commit") {
1473 
1474  /* note this should get split if more preprocessing is used. */
1475  for(const auto & function : data.all_children_range())
1476  {
1477 
1478  if(function.key == "blur") {
1479  blur_depth_ = function.cfg["depth"];
1480  } else {
1481  ERR_GUI_P << "Canvas: found a pre commit function"
1482  << " of an invalid type " << type << ".\n";
1483  }
1484  }
1485 
1486  } else {
1487  ERR_GUI_P << "Canvas: found a shape of an invalid type " << type
1488  << ".\n";
1489 
1490  assert(false);
1491  }
1492  }
1493 }
1494 
1495 void canvas::clear_shapes(const bool force)
1496 {
1497  auto conditional = [force](const shape_ptr s)->bool { return !s->immutable() && !force; };
1498 
1499  auto iter = std::remove_if(shapes_.begin(), shapes_.end(), conditional);
1500  shapes_.erase(iter, shapes_.end());
1501 
1502  iter = std::remove_if(drawn_shapes_.begin(), drawn_shapes_.end(), conditional);
1503  drawn_shapes_.erase(iter, drawn_shapes_.end());
1504 }
1505 
1507 {
1508  canvas_.assign(nullptr);
1509 
1510  if(shapes_.empty()) {
1511  shapes_.swap(drawn_shapes_);
1512  } else {
1513  std::copy(drawn_shapes_.begin(), drawn_shapes_.end(), std::inserter(shapes_, shapes_.begin()));
1514  drawn_shapes_.clear();
1515  }
1516 }
1517 
1518 /***** ***** ***** ***** ***** SHAPE ***** ***** ***** ***** *****/
1519 
1520 } // namespace gui2
1521 
1522 /*WIKI
1523  * @page = GUICanvasWML
1524  * @order = ZZZZZZ_footer
1525  *
1526  * [[Category: WML Reference]]
1527  * [[Category: GUI WML Reference]]
1528  *
1529  */
1530 
1531 /*WIKI
1532  * @page = GUIVariable
1533  * @order = ZZZZZZ_footer
1534  *
1535  * [[Category: WML Reference]]
1536  * [[Category: GUI WML Reference]]
1537  */
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:394
surface create_neutral_surface(int w, int h)
Definition: utils.cpp:77
int x2_
Definition: pump.cpp:141
void invalidate_cache()
Definition: canvas.cpp:1506
resize_mode get_resize_mode(const std::string &resize_mode)
Converts a string to a resize mode.
Definition: canvas.cpp:1181
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:216
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:802
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:281
Note: Specific to sdl_ttf.
round_rectangle_shape(const config &cfg)
Constructor.
Definition: canvas.cpp:780
surface image_
The image is cached in this surface.
bool null() const
Definition: surface.hpp:66
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:186
void draw(surface &canvas, SDL_Renderer *renderer, wfl::map_formula_callable &variables) override
Implement shape::draw().
Definition: canvas.cpp:688
rectangle_shape(const config &cfg)
Constructor.
Definition: canvas.cpp:667
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:899
typed_formula< unsigned > x2_
The end x coordinate of the line.
unsigned h_
Height of the canvas.
Definition: canvas.hpp:202
pango_text & set_link_aware(bool b)
Definition: text.cpp:465
typed_formula< int > maximum_height_
The maximum height for the text.
static CVideo & get_singleton()
Definition: video.hpp:50
#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:2232
#define VALIDATE_WITH_DEV_MESSAGE(cond, message, dev_message)
pango_text & set_font_style(const FONT_STYLE font_style)
Definition: text.cpp:342
pango_text & set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
Definition: text.cpp:427
surface flip_surface(const surface &surf)
Definition: utils.cpp:2005
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:114
wfl::map_formula_callable variables_
The variables of the canvas.
Definition: canvas.hpp:210
pango_text & set_alignment(const PangoAlignment alignment)
Definition: text.cpp:441
Generic file dialog.
Definition: text.hpp:37
SDL_Renderer * renderer_
Definition: canvas.hpp:207
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:2082
typed_formula< unsigned > h_
The height of the text.
int x1_
Definition: pump.cpp:141
bool null() const
Definition: color.hpp:188
int y2_
Definition: pump.cpp:141
#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:1425
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:1045
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:1252
pango_text & set_font_size(const unsigned font_size)
Definition: text.cpp:330
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:396
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:1452
wfl::action_function_symbol_table functions_
Action function definitions for the canvas.
Definition: canvas.hpp:213
#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:319
A simple canvas which can be drawn upon.
Definition: canvas.hpp:41
int y1_
Definition: pump.cpp:141
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:196
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:95
void clear_shapes(const bool force)
Definition: canvas.cpp:1495
void draw(surface &canvas, SDL_Renderer *renderer, wfl::map_formula_callable &variables) override
Implement shape::draw().
Definition: canvas.cpp:599
static map_location::DIRECTION s
image_shape(const config &cfg, wfl::action_function_symbol_table &functions)
Constructor.
Definition: canvas.cpp:1026
Helper class for pinning SDL surfaces into memory.
Definition: surface.hpp:115
#define i
pango_text & set_link_color(const color_t &color)
Definition: text.cpp:475
line_shape(const config &cfg)
Constructor.
Definition: canvas.cpp:584
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:42
typed_formula< unsigned > y_
The y coordinate of the image.
Text class.
Definition: text.hpp:78
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:42
pango_text & set_maximum_width(int width)
Definition: text.cpp:363
resize_mode
Determines the way an image will be resized.
void assign(SDL_Surface *surf)
Definition: surface.hpp:41
std::vector< shape_ptr > shapes_
Vector with the shapes to draw.
Definition: canvas.hpp:182
void draw(const bool force=false)
Draws the canvas.
Definition: canvas.cpp:1382
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:627
typed_formula< color_t > link_color_
The link color of the text.
surface make_neutral_surface(const surface &surf)
Definition: utils.cpp:65
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:140
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:914
surface & render()
Returns the rendered text.
Definition: text.cpp:93
pango_text & set_maximum_height(int height, bool multiline)
Definition: text.cpp:408
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:1279
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:1054
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.
void parse_cfg(const config &cfg)
Parses a config object.
Definition: canvas.cpp:1449
pango_text & set_foreground_color(const color_t &color)
Definition: text.cpp:353
surface canvas_
The surface we draw all items on.
Definition: canvas.hpp:205
font::pango_text::FONT_STYLE decode_font_style(const std::string &style)
Converts a font style string to a font style.
Definition: helper.cpp:51
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:787
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:199
typed_formula< unsigned > w_
The width of the image.