The Battle for Wesnoth  1.15.11+dev
canvas.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2007 - 2018 by Mark de Wever <koraq@xs4all.nl>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 /**
16  * @file
17  * Implementation of canvas.hpp.
18  */
19 
20 #define GETTEXT_DOMAIN "wesnoth-lib"
21 
22 #include "gui/core/canvas.hpp"
24 
25 #include "font/text.hpp"
26 #include "formatter.hpp"
27 #include "gettext.hpp"
28 #include "picture.hpp"
29 
31 #include "gui/core/log.hpp"
32 #include "gui/widgets/helper.hpp"
33 #include "sdl/rect.hpp"
34 #include "video.hpp"
35 #include "wml_exception.hpp"
36 
37 namespace gui2
38 {
39 
40 namespace
41 {
42 
43 /***** ***** ***** ***** ***** DRAWING PRIMITIVES ***** ***** ***** ***** *****/
44 
45 static void set_renderer_color(SDL_Renderer* renderer, color_t color)
46 {
47  SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
48 }
49 
50 /**
51  * Draws a line on a surface.
52  *
53  * @pre The caller needs to make sure the entire line fits on
54  * the @p surface.
55  * @pre @p x2 >= @p x1
56  * @pre The @p surface is locked.
57  *
58  * @param canvas The canvas to draw upon, the caller should lock the
59  * surface before calling.
60  * @param color The color of the line to draw.
61  * @param x1 The start x coordinate of the line to draw.
62  * @param y1 The start y coordinate of the line to draw.
63  * @param x2 The end x coordinate of the line to draw.
64  * @param y2 The end y coordinate of the line to draw.
65  */
66 static void draw_line(surface& canvas,
67  SDL_Renderer* renderer,
68  color_t color,
69  unsigned x1,
70  unsigned y1,
71  const unsigned x2,
72  unsigned y2)
73 {
74  unsigned w = canvas->w;
75 
76  DBG_GUI_D << "Shape: draw line from " << x1 << ',' << y1 << " to " << x2
77  << ',' << y2 << " canvas width " << w << " canvas height "
78  << canvas->h << ".\n";
79 
80  assert(static_cast<int>(x1) < canvas->w);
81  assert(static_cast<int>(x2) < canvas->w);
82  assert(static_cast<int>(y1) < canvas->h);
83  assert(static_cast<int>(y2) < canvas->h);
84 
85  set_renderer_color(renderer, color);
86 
87  if(x1 == x2 && y1 == y2) {
88  // Handle single-pixel lines properly
89  SDL_RenderDrawPoint(renderer, x1, y1);
90  } else {
91  SDL_RenderDrawLine(renderer, x1, y1, x2, y2);
92  }
93 }
94 
95 /**
96  * Draws a circle on a surface.
97  *
98  * @pre The circle must fit on the canvas.
99  * @pre The @p surface is locked.
100  *
101  * @param canvas The canvas to draw upon, the caller should lock the
102  * surface before calling.
103  * @param color The border color of the circle to draw.
104  * @param x_center The x coordinate of the center of the circle to draw.
105  * @param y_center The y coordinate of the center of the circle to draw.
106  * @param radius The radius of the circle to draw.
107  * @tparam octants A bitfield indicating which octants to draw, starting at twelve o'clock and moving clockwise.
108  */
109 template<unsigned int octants = 0xff>
110 static void draw_circle(surface& canvas,
111  SDL_Renderer* renderer,
112  color_t color,
113  const int x_center,
114  const int y_center,
115  const int radius)
116 {
117  unsigned w = canvas->w;
118 
119  DBG_GUI_D << "Shape: draw circle at " << x_center << ',' << y_center
120  << " with radius " << radius << " canvas width " << w
121  << " canvas height " << canvas->h << ".\n";
122 
123  if(octants & 0x0f) assert((x_center + radius) < canvas->w);
124  if(octants & 0xf0) assert((x_center - radius) >= 0);
125  if(octants & 0x3c) assert((y_center + radius) < canvas->h);
126  if(octants & 0xc3) assert((y_center - radius) >= 0);
127 
128  set_renderer_color(renderer, color);
129 
130  // Algorithm based on
131  // http://de.wikipedia.org/wiki/Rasterung_von_Kreisen#Methode_von_Horn
132  // version of 2011.02.07.
133  int d = -static_cast<int>(radius);
134  int x = radius;
135  int y = 0;
136 
137  std::vector<SDL_Point> points;
138 
139  while(!(y > x)) {
140  if(octants & 0x04) points.push_back({x_center + x, y_center + y});
141  if(octants & 0x02) points.push_back({x_center + x, y_center - y});
142  if(octants & 0x20) points.push_back({x_center - x, y_center + y});
143  if(octants & 0x40) points.push_back({x_center - x, y_center - y});
144 
145  if(octants & 0x08) points.push_back({x_center + y, y_center + x});
146  if(octants & 0x01) points.push_back({x_center + y, y_center - x});
147  if(octants & 0x10) points.push_back({x_center - y, y_center + x});
148  if(octants & 0x80) points.push_back({x_center - y, y_center - x});
149 
150  d += 2 * y + 1;
151  ++y;
152  if(d > 0) {
153  d += -2 * x + 2;
154  --x;
155  }
156  }
157 
158  SDL_RenderDrawPoints(renderer, points.data(), points.size());
159 }
160 
161 /**
162  * Draws a filled circle on a surface.
163  *
164  * @pre The circle must fit on the canvas.
165  * @pre The @p surface is locked.
166  *
167  * @param canvas The canvas to draw upon, the caller should lock the
168  * surface before calling.
169  * @param color The fill color of the circle to draw.
170  * @param x_center The x coordinate of the center of the circle to draw.
171  * @param y_center The y coordinate of the center of the circle to draw.
172  * @param radius The radius of the circle to draw.
173  * @tparam octants A bitfield indicating which octants to draw, starting at twelve o'clock and moving clockwise.
174  */
175 template<unsigned int octants = 0xff>
176 static void fill_circle(surface& canvas,
177  SDL_Renderer* renderer,
178  color_t color,
179  const int x_center,
180  const int y_center,
181  const int radius)
182 {
183  unsigned w = canvas->w;
184 
185  DBG_GUI_D << "Shape: draw filled circle at " << x_center << ',' << y_center
186  << " with radius " << radius << " canvas width " << w
187  << " canvas height " << canvas->h << ".\n";
188 
189  if(octants & 0x0f) assert((x_center + radius) < canvas->w);
190  if(octants & 0xf0) assert((x_center - radius) >= 0);
191  if(octants & 0x3c) assert((y_center + radius) < canvas->h);
192  if(octants & 0xc3) assert((y_center - radius) >= 0);
193 
194  set_renderer_color(renderer, color);
195 
196  int d = -static_cast<int>(radius);
197  int x = radius;
198  int y = 0;
199 
200  while(!(y > x)) {
201  // I use the formula of Bresenham's line algorithm to determine the boundaries of a segment.
202  // The slope of the line is always 1 or -1 in this case.
203  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
204  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
205  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)
206  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)
207 
208  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)
209  if(octants & 0x01) SDL_RenderDrawLine(renderer, x_center + y, y_center - x, x_center + y, y_center - y); // y2 = x_center - x2 + y_center
210  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)
211  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
212 
213  d += 2 * y + 1;
214  ++y;
215  if(d > 0) {
216  d += -2 * x + 2;
217  --x;
218  }
219  }
220 }
221 
222 } // namespace
223 
224 
225 /***** ***** ***** ***** ***** LINE ***** ***** ***** ***** *****/
226 
228  : shape(cfg)
229  , x1_(cfg["x1"])
230  , y1_(cfg["y1"])
231  , x2_(cfg["x2"])
232  , y2_(cfg["y2"])
233  , color_(cfg["color"])
234  , thickness_(cfg["thickness"])
235 {
236  const std::string& debug = (cfg["debug"]);
237  if(!debug.empty()) {
238  DBG_GUI_P << "Line: found debug message '" << debug << "'.\n";
239  }
240 }
241 
243  SDL_Renderer* renderer,
244  wfl::map_formula_callable& variables)
245 {
246  /**
247  * @todo formulas are now recalculated every draw cycle which is a bit silly
248  * unless there has been a resize. So to optimize we should use an extra
249  * flag or do the calculation in a separate routine.
250  */
251 
252  const unsigned x1 = x1_(variables);
253  const unsigned y1 = y1_(variables);
254  const unsigned x2 = x2_(variables);
255  const unsigned y2 = y2_(variables);
256 
257  DBG_GUI_D << "Line: draw from " << x1 << ',' << y1 << " to " << x2 << ','
258  << y2 << " canvas size " << canvas->w << ',' << canvas->h
259  << ".\n";
260 
261  VALIDATE(static_cast<int>(x1) < canvas->w
262  && static_cast<int>(x2) < canvas->w
263  && static_cast<int>(y1) < canvas->h
264  && static_cast<int>(y2) < canvas->h,
265  _("Line doesn't fit on canvas."));
266 
267  // @todo FIXME respect the thickness.
268 
269  // lock the surface
270  surface_lock locker(canvas);
271 
272  draw_line(canvas, renderer, color_(variables), x1, y1, x2, y2);
273 }
274 
275 /***** ***** ***** ***** ***** Rectangle ***** ***** ***** ***** *****/
276 
278  : shape(cfg)
279  , x_(cfg["x"])
280  , y_(cfg["y"])
281  , w_(cfg["w"])
282  , h_(cfg["h"])
283  , border_thickness_(cfg["border_thickness"])
284  , border_color_(cfg["border_color"], color_t::null_color())
285  , fill_color_(cfg["fill_color"], color_t::null_color())
286 {
287  // Check if a raw color string evaluates to a null color.
288  if(!border_color_.has_formula() && border_color_().null()) {
289  border_thickness_ = 0;
290  }
291 
292  const std::string& debug = (cfg["debug"]);
293  if(!debug.empty()) {
294  DBG_GUI_P << "Rectangle: found debug message '" << debug << "'.\n";
295  }
296 }
297 
299  SDL_Renderer* renderer,
300  wfl::map_formula_callable& variables)
301 {
302  /**
303  * @todo formulas are now recalculated every draw cycle which is a bit
304  * silly unless there has been a resize. So to optimize we should use an
305  * extra flag or do the calculation in a separate routine.
306  */
307  const int x = x_(variables);
308  const int y = y_(variables);
309  const int w = w_(variables);
310  const int h = h_(variables);
311 
312  DBG_GUI_D << "Rectangle: draw from " << x << ',' << y << " width " << w
313  << " height " << h << " canvas size " << canvas->w << ','
314  << canvas->h << ".\n";
315 
316  VALIDATE(x < canvas->w
317  && x + w <= canvas->w
318  && y < canvas->h
319  && y + h <= canvas->h, _("Rectangle doesn't fit on canvas."));
320 
321  surface_lock locker(canvas);
322 
323  const color_t fill_color = fill_color_(variables);
324 
325  // Fill the background, if applicable
326  if(!fill_color.null() && w && h) {
327  set_renderer_color(renderer, fill_color);
328 
329  SDL_Rect area {
330  x + border_thickness_,
331  y + border_thickness_,
332  w - (border_thickness_ * 2),
333  h - (border_thickness_ * 2)
334  };
335 
336  SDL_RenderFillRect(renderer, &area);
337  }
338 
339  // Draw the border
340  for(int i = 0; i < border_thickness_; ++i) {
341  SDL_Rect dimensions {
342  x + i,
343  y + i,
344  w - (i * 2),
345  h - (i * 2)
346  };
347 
348  set_renderer_color(renderer, border_color_(variables));
349 
350  SDL_RenderDrawRect(renderer, &dimensions);
351  }
352 }
353 
354 /***** ***** ***** ***** ***** Rounded Rectangle ***** ***** ***** ***** *****/
355 
357  : shape(cfg)
358  , x_(cfg["x"])
359  , y_(cfg["y"])
360  , w_(cfg["w"])
361  , h_(cfg["h"])
362  , r_(cfg["corner_radius"])
363  , border_thickness_(cfg["border_thickness"])
364  , border_color_(cfg["border_color"], color_t::null_color())
365  , fill_color_(cfg["fill_color"], color_t::null_color())
366 {
367  // Check if a raw color string evaluates to a null color.
368  if(!border_color_.has_formula() && border_color_().null()) {
369  border_thickness_ = 0;
370  }
371 
372  const std::string& debug = (cfg["debug"]);
373  if(!debug.empty()) {
374  DBG_GUI_P << "Rounded Rectangle: found debug message '" << debug << "'.\n";
375  }
376 }
377 
379  SDL_Renderer* renderer,
380  wfl::map_formula_callable& variables)
381 {
382  /**
383  * @todo formulas are now recalculated every draw cycle which is a bit
384  * silly unless there has been a resize. So to optimize we should use an
385  * extra flag or do the calculation in a separate routine.
386  */
387  const int x = x_(variables);
388  const int y = y_(variables);
389  const int w = w_(variables);
390  const int h = h_(variables);
391  const int r = r_(variables);
392 
393  DBG_GUI_D << "Rounded Rectangle: draw from " << x << ',' << y << " width " << w
394  << " height " << h << " canvas size " << canvas->w << ','
395  << canvas->h << ".\n";
396 
397  VALIDATE(x < canvas->w
398  && x + w <= canvas->w
399  && y < canvas->h
400  && y + h <= canvas->h, _("Rounded Rectangle doesn't fit on canvas."));
401 
402  surface_lock locker(canvas);
403 
404  const color_t fill_color = fill_color_(variables);
405 
406  // Fill the background, if applicable
407  if(!fill_color.null() && w && h) {
408  set_renderer_color(renderer, fill_color);
409  static const int count = 3;
410  SDL_Rect area[count] {
411  {x + r, y + border_thickness_, w - r * 2, r - border_thickness_ + 1},
412  {x + border_thickness_, y + r + 1, w - border_thickness_ * 2, h - r * 2},
413  {x + r, y - r + h + 1, w - r * 2, r - border_thickness_},
414  };
415 
416  SDL_RenderFillRects(renderer, area, count);
417 
418  fill_circle<0xc0>(canvas, renderer, fill_color, x + r, y + r, r);
419  fill_circle<0x03>(canvas, renderer, fill_color, x + w - r, y + r, r);
420  fill_circle<0x30>(canvas, renderer, fill_color, x + r, y + h - r, r);
421  fill_circle<0x0c>(canvas, renderer, fill_color, x + w - r, y + h - r, r);
422  }
423 
424  const color_t border_color = border_color_(variables);
425 
426  // Draw the border
427  for(int i = 0; i < border_thickness_; ++i) {
428  set_renderer_color(renderer, border_color);
429 
430  SDL_RenderDrawLine(renderer, x + r, y + i, x + w - r, y + i);
431  SDL_RenderDrawLine(renderer, x + r, y + h - i, x + w - r, y + h - i);
432 
433  SDL_RenderDrawLine(renderer, x + i, y + r, x + i, y + h - r);
434  SDL_RenderDrawLine(renderer, x + w - i, y + r, x + w - i, y + h - r);
435 
436  draw_circle<0xc0>(canvas, renderer, border_color, x + r, y + r, r - i);
437  draw_circle<0x03>(canvas, renderer, border_color, x + w - r, y + r, r - i);
438  draw_circle<0x30>(canvas, renderer, border_color, x + r, y + h - r, r - i);
439  draw_circle<0x0c>(canvas, renderer, border_color, x + w - r, y + h - r, r - i);
440  }
441 }
442 
443 /***** ***** ***** ***** ***** CIRCLE ***** ***** ***** ***** *****/
444 
446  : shape(cfg)
447  , x_(cfg["x"])
448  , y_(cfg["y"])
449  , radius_(cfg["radius"])
450  , border_color_(cfg["border_color"])
451  , fill_color_(cfg["fill_color"])
452  , border_thickness_(cfg["border_thickness"].to_int(1))
453 {
454  const std::string& debug = (cfg["debug"]);
455  if(!debug.empty()) {
456  DBG_GUI_P << "Circle: found debug message '" << debug << "'.\n";
457  }
458 }
459 
461  SDL_Renderer* renderer,
462  wfl::map_formula_callable& variables)
463 {
464  /**
465  * @todo formulas are now recalculated every draw cycle which is a bit
466  * silly unless there has been a resize. So to optimize we should use an
467  * extra flag or do the calculation in a separate routine.
468  */
469 
470  const unsigned x = x_(variables);
471  const unsigned y = y_(variables);
472  const unsigned radius = radius_(variables);
473 
474  DBG_GUI_D << "Circle: drawn at " << x << ',' << y << " radius " << radius
475  << " canvas size " << canvas->w << ',' << canvas->h << ".\n";
476 
478  static_cast<int>(x - radius) >= 0,
479  _("Circle doesn't fit on canvas."),
480  formatter() << "x = " << x << ", radius = " << radius);
481 
483  static_cast<int>(y - radius) >= 0,
484  _("Circle doesn't fit on canvas."),
485  formatter() << "y = " << y << ", radius = " << radius);
486 
488  static_cast<int>(x + radius) < canvas->w,
489  _("Circle doesn't fit on canvas."),
490  formatter() << "x = " << x << ", radius = " << radius
491  << "', canvas width = " << canvas->w << ".");
492 
494  static_cast<int>(y + radius) < canvas->h,
495  _("Circle doesn't fit on canvas."),
496  formatter() << "y = " << y << ", radius = " << radius
497  << "', canvas height = " << canvas->h << ".");
498 
499  // lock the surface
500  surface_lock locker(canvas);
501 
502  const color_t fill_color = fill_color_(variables);
503  if(!fill_color.null() && radius) {
504  fill_circle(canvas, renderer, fill_color, x, y, radius);
505  }
506 
507  const color_t border_color = border_color_(variables);
508  for(unsigned int i = 0; i < border_thickness_; i++) {
509  draw_circle(canvas, renderer, border_color, x, y, radius - i);
510  }
511 }
512 
513 /***** ***** ***** ***** ***** IMAGE ***** ***** ***** ***** *****/
514 
516  : shape(cfg)
517  , x_(cfg["x"])
518  , y_(cfg["y"])
519  , w_(cfg["w"])
520  , h_(cfg["h"])
521  , src_clip_()
522  , image_()
523  , image_name_(cfg["name"])
524  , resize_mode_(get_resize_mode(cfg["resize_mode"]))
525  , vertical_mirror_(cfg["vertical_mirror"])
526  , actions_formula_(cfg["actions"], &functions)
527 {
528  const std::string& debug = (cfg["debug"]);
529  if(!debug.empty()) {
530  DBG_GUI_P << "Image: found debug message '" << debug << "'.\n";
531  }
532 }
533 
534 void image_shape::dimension_validation(unsigned value, const std::string& name, const std::string& key)
535 {
536  const int as_int = static_cast<int>(value);
537 
538  VALIDATE_WITH_DEV_MESSAGE(as_int >= 0, _("Image doesn't fit on canvas."),
539  formatter() << "Image '" << name << "', " << key << " = " << as_int << "."
540  );
541 }
542 
544  SDL_Renderer* /*renderer*/,
545  wfl::map_formula_callable& variables)
546 {
547  DBG_GUI_D << "Image: draw.\n";
548 
549  /**
550  * @todo formulas are now recalculated every draw cycle which is a bit
551  * silly unless there has been a resize. So to optimize we should use an
552  * extra flag or do the calculation in a separate routine.
553  */
554  const std::string& name = image_name_(variables);
555 
556  if(name.empty()) {
557  DBG_GUI_D << "Image: formula returned no value, will not be drawn.\n";
558  return;
559  }
560 
561  /*
562  * The locator might return a different surface for every call so we can't
563  * cache the output, also not if no formula is used.
564  */
566 
567  if(!tmp) {
568  ERR_GUI_D << "Image: '" << name << "' not found and won't be drawn." << std::endl;
569  return;
570  }
571 
572  image_ = tmp;
573  assert(image_);
574  src_clip_ = {0, 0, image_->w, image_->h};
575 
576  wfl::map_formula_callable local_variables(variables);
577  local_variables.add("image_original_width", wfl::variant(image_->w));
578  local_variables.add("image_original_height", wfl::variant(image_->h));
579 
580  unsigned w = w_(local_variables);
581  dimension_validation(w, name, "w");
582 
583  unsigned h = h_(local_variables);
584  dimension_validation(h, name, "h");
585 
586  local_variables.add("image_width", wfl::variant(w ? w : image_->w));
587  local_variables.add("image_height", wfl::variant(h ? h : image_->h));
588 
589  const unsigned clip_x = x_(local_variables);
590  dimension_validation(clip_x, name, "x");
591 
592  const unsigned clip_y = y_(local_variables);
593  dimension_validation(clip_y, name, "y");
594 
595  local_variables.add("clip_x", wfl::variant(clip_x));
596  local_variables.add("clip_y", wfl::variant(clip_y));
597 
598  // Execute the provided actions for this context.
599  wfl::variant(variables.fake_ptr()).execute_variant(actions_formula_.evaluate(local_variables));
600 
601  // Copy the data to local variables to avoid overwriting the originals.
602  SDL_Rect src_clip = src_clip_;
603  SDL_Rect dst_clip = sdl::create_rect(clip_x, clip_y, 0, 0);
604  surface surf;
605 
606  // Test whether we need to scale and do the scaling if needed.
607  if ((w == 0) && (h == 0)) {
608  surf = image_;
609  }
610  else { // assert((w != 0) || (h != 0))
611  if(w == 0 && resize_mode_ == stretch) {
612  DBG_GUI_D << "Image: vertical stretch from " << image_->w << ','
613  << image_->h << " to a height of " << h << ".\n";
614 
615  surf = stretch_surface_vertical(image_, h);
616  w = image_->w;
617  }
618  else if(h == 0 && resize_mode_ == stretch) {
619  DBG_GUI_D << "Image: horizontal stretch from " << image_->w
620  << ',' << image_->h << " to a width of " << w
621  << ".\n";
622 
624  h = image_->h;
625  }
626  else {
627  if(w == 0) {
628  w = image_->w;
629  }
630  if(h == 0) {
631  h = image_->h;
632  }
633  if(resize_mode_ == tile) {
634  DBG_GUI_D << "Image: tiling from " << image_->w << ','
635  << image_->h << " to " << w << ',' << h << ".\n";
636 
637  surf = tile_surface(image_, w, h, false);
638  } else if(resize_mode_ == tile_center) {
639  DBG_GUI_D << "Image: tiling centrally from " << image_->w << ','
640  << image_->h << " to " << w << ',' << h << ".\n";
641 
642  surf = tile_surface(image_, w, h, true);
643  } else {
644  if(resize_mode_ == stretch) {
645  ERR_GUI_D << "Image: failed to stretch image, "
646  "fall back to scaling.\n";
647  }
648 
649  DBG_GUI_D << "Image: scaling from " << image_->w << ','
650  << image_->h << " to " << w << ',' << h << ".\n";
651 
652  surf = scale_surface_legacy(image_, w, h);
653  }
654  }
655  src_clip.w = w;
656  src_clip.h = h;
657  }
658 
659  if(vertical_mirror_(local_variables)) {
660  surf = flip_surface(surf);
661  }
662 
663  blit_surface(surf, &src_clip, canvas, &dst_clip);
664 }
665 
667 {
668  if(resize_mode == "tile") {
669  return image_shape::tile;
670  } else if(resize_mode == "tile_center") {
672  } else if(resize_mode == "stretch") {
673  return image_shape::stretch;
674  } else {
675  if(!resize_mode.empty() && resize_mode != "scale") {
676  ERR_GUI_E << "Invalid resize mode '" << resize_mode
677  << "' falling back to 'scale'.\n";
678  }
679  return image_shape::scale;
680  }
681 }
682 
683 /***** ***** ***** ***** ***** TEXT ***** ***** ***** ***** *****/
684 
686  : shape(cfg)
687  , x_(cfg["x"])
688  , y_(cfg["y"])
689  , w_(cfg["w"])
690  , h_(cfg["h"])
691  , font_family_(font::str_to_family_class(cfg["font_family"]))
692  , font_size_(cfg["font_size"])
693  , font_style_(decode_font_style(cfg["font_style"]))
694  , text_alignment_(cfg["text_alignment"])
695  , color_(cfg["color"])
696  , text_(cfg["text"])
697  , text_markup_(cfg["text_markup"], false)
698  , link_aware_(cfg["text_link_aware"], false)
699  , link_color_(cfg["text_link_color"], color_t::from_hex_string("ffff00"))
700  , maximum_width_(cfg["maximum_width"], -1)
701  , characters_per_line_(cfg["text_characters_per_line"])
702  , maximum_height_(cfg["maximum_height"], -1)
703 {
704  if(!font_size_.has_formula()) {
705  VALIDATE(font_size_(), _("Text has a font size of 0."));
706  }
707 
708  const std::string& debug = (cfg["debug"]);
709  if(!debug.empty()) {
710  DBG_GUI_P << "Text: found debug message '" << debug << "'.\n";
711  }
712 }
713 
715  SDL_Renderer* /*renderer*/,
716  wfl::map_formula_callable& variables)
717 {
718  assert(variables.has_key("text"));
719 
720  // We first need to determine the size of the text which need the rendered
721  // text. So resolve and render the text first and then start to resolve
722  // the other formulas.
723  const t_string text = text_(variables);
724 
725  if(text.empty()) {
726  DBG_GUI_D << "Text: no text to render, leave.\n";
727  return;
728  }
729 
730  font::pango_text& text_renderer = font::get_text_renderer();
731 
732  text_renderer
733  .set_link_aware(link_aware_(variables))
734  .set_link_color(link_color_(variables))
735  .set_text(text, text_markup_(variables));
736 
737  text_renderer.set_family_class(font_family_)
738  .set_font_size(font_size_(variables))
740  .set_alignment(text_alignment_(variables))
741  .set_foreground_color(color_(variables))
742  .set_maximum_width(maximum_width_(variables))
743  .set_maximum_height(maximum_height_(variables), true)
744  .set_ellipse_mode(variables.has_key("text_wrap_mode")
745  ? static_cast<PangoEllipsizeMode>(variables.query_value("text_wrap_mode").as_int())
746  : PANGO_ELLIPSIZE_END)
748 
749  surface& surf = text_renderer.render();
750  if(surf->w == 0) {
751  DBG_GUI_D << "Text: Rendering '" << text
752  << "' resulted in an empty canvas, leave.\n";
753  return;
754  }
755 
756  wfl::map_formula_callable local_variables(variables);
757  local_variables.add("text_width", wfl::variant(surf->w));
758  local_variables.add("text_height", wfl::variant(surf->h));
759  /*
760  std::cerr << "Text: drawing text '" << text
761  << " maximum width " << maximum_width_(variables)
762  << " maximum height " << maximum_height_(variables)
763  << " text width " << surf->w
764  << " text height " << surf->h;
765  */
766  // TODO: formulas are now recalculated every draw cycle which is a
767  // bit silly unless there has been a resize. So to optimize we should
768  // use an extra flag or do the calculation in a separate routine.
769 
770  const unsigned x = x_(local_variables);
771  const unsigned y = y_(local_variables);
772  const unsigned w = w_(local_variables);
773  const unsigned h = h_(local_variables);
774 
775  DBG_GUI_D << "Text: drawing text '" << text << "' drawn from " << x << ','
776  << y << " width " << w << " height " << h << " canvas size "
777  << canvas->w << ',' << canvas->h << ".\n";
778 
779  VALIDATE(static_cast<int>(x) < canvas->w && static_cast<int>(y) < canvas->h,
780  _("Text doesn't start on canvas."));
781 
782  // A text might be to long and will be clipped.
783  if(surf->w > static_cast<int>(w)) {
784  WRN_GUI_D << "Text: text is too wide for the "
785  "canvas and will be clipped.\n";
786  }
787 
788  if(surf->h > static_cast<int>(h)) {
789  WRN_GUI_D << "Text: text is too high for the "
790  "canvas and will be clipped.\n";
791  }
792 
793  SDL_Rect dst = sdl::create_rect(x, y, canvas->w, canvas->h);
794  blit_surface(surf, nullptr, canvas, &dst);
795 }
796 
797 /***** ***** ***** ***** ***** CANVAS ***** ***** ***** ***** *****/
798 
800  : shapes_()
801  , drawn_shapes_()
802  , blur_depth_(0)
803  , w_(0)
804  , h_(0)
805  , canvas_()
806  , renderer_(nullptr)
807  , variables_()
808  , functions_()
809  , is_dirty_(true)
810 {
811 }
812 
814  : shapes_(std::move(c.shapes_))
815  , drawn_shapes_(std::move(c.drawn_shapes_))
816  , blur_depth_(c.blur_depth_)
817  , w_(c.w_)
818  , h_(c.h_)
819  , canvas_(std::move(c.canvas_))
820  , renderer_(std::exchange(c.renderer_, nullptr))
821  , variables_(c.variables_)
822  , functions_(c.functions_)
823  , is_dirty_(c.is_dirty_)
824 {
825 }
826 
828 {
829  if(renderer_)
830  SDL_DestroyRenderer(renderer_);
831 }
832 
833 void canvas::draw(const bool force)
834 {
835  log_scope2(log_gui_draw, "Canvas: drawing.");
836  if(!is_dirty_ && !force) {
837  DBG_GUI_D << "Canvas: nothing to draw.\n";
838  return;
839  }
840 
841  if(is_dirty_) {
843  variables_.add("width", wfl::variant(w_));
844  variables_.add("height", wfl::variant(h_));
845  }
846 
847  if(canvas_) {
848  DBG_GUI_D << "Canvas: use cached canvas.\n";
849  } else {
850  // create surface
851  DBG_GUI_D << "Canvas: create new empty canvas.\n";
852  canvas_ = surface(w_, h_);
853  }
854 
855  if(renderer_) {
856  SDL_DestroyRenderer(renderer_);
857  }
858 
859  renderer_ = SDL_CreateSoftwareRenderer(canvas_);
860  SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
861 
862  // draw items
863  for(auto& shape : shapes_) {
864  lg::scope_logger inner_scope_logging_object__(log_gui_draw, "Canvas: draw shape.");
865 
867  }
868 
869  // The shapes have been drawn and the draw result has been cached. Clear the list.
870  std::copy(shapes_.begin(), shapes_.end(), std::back_inserter(drawn_shapes_));
871  shapes_.clear();
872 
873  SDL_RenderPresent(renderer_);
874 
875  is_dirty_ = false;
876 }
877 
878 void canvas::blit(surface& surf, SDL_Rect rect)
879 {
880  draw();
881 
882  if(blur_depth_) {
883  /*
884  * If the surf is the video surface the blurring seems to stack, this
885  * can be seen in the title screen. So also use the not 32 bpp method
886  * for this situation.
887  */
888  if(surf != CVideo::get_singleton().getSurface() && surf.is_neutral()) {
889  blur_surface(surf, rect, blur_depth_);
890  } else {
891  // Can't directly blur the surface if not 32 bpp.
892  SDL_Rect r = rect;
893  surface s = get_surface_portion(surf, r);
894  s = blur_surface(s, blur_depth_);
895  sdl_blit(s, nullptr, surf, &r);
896  }
897  }
898 
899  sdl_blit(canvas_, nullptr, surf, &rect);
900 }
901 
902 void canvas::parse_cfg(const config& cfg)
903 {
904  log_scope2(log_gui_parse, "Canvas: parsing config.");
905 
906  for(const auto & shape : cfg.all_children_range())
907  {
908  const std::string& type = shape.key;
909  const config& data = shape.cfg;
910 
911  DBG_GUI_P << "Canvas: found shape of the type " << type << ".\n";
912 
913  if(type == "line") {
914  shapes_.emplace_back(std::make_shared<line_shape>(data));
915  } else if(type == "rectangle") {
916  shapes_.emplace_back(std::make_shared<rectangle_shape>(data));
917  } else if(type == "round_rectangle") {
918  shapes_.emplace_back(std::make_shared<round_rectangle_shape>(data));
919  } else if(type == "circle") {
920  shapes_.emplace_back(std::make_shared<circle_shape>(data));
921  } else if(type == "image") {
922  shapes_.emplace_back(std::make_shared<image_shape>(data, functions_));
923  } else if(type == "text") {
924  shapes_.emplace_back(std::make_shared<text_shape>(data));
925  } else if(type == "pre_commit") {
926 
927  /* note this should get split if more preprocessing is used. */
928  for(const auto & function : data.all_children_range())
929  {
930 
931  if(function.key == "blur") {
932  blur_depth_ = function.cfg["depth"];
933  } else {
934  ERR_GUI_P << "Canvas: found a pre commit function"
935  << " of an invalid type " << type << ".\n";
936  }
937  }
938 
939  } else {
940  ERR_GUI_P << "Canvas: found a shape of an invalid type " << type
941  << ".\n";
942 
943  assert(false);
944  }
945  }
946 }
947 
948 void canvas::clear_shapes(const bool force)
949 {
950  if(force) {
951  shapes_.clear();
952  drawn_shapes_.clear();
953  } else {
954  auto conditional = [](const shape_ptr s)->bool { return !s->immutable(); };
955 
956  auto iter = std::remove_if(shapes_.begin(), shapes_.end(), conditional);
957  shapes_.erase(iter, shapes_.end());
958 
959  iter = std::remove_if(drawn_shapes_.begin(), drawn_shapes_.end(), conditional);
960  drawn_shapes_.erase(iter, drawn_shapes_.end());
961  }
962 }
963 
965 {
966  canvas_ = nullptr;
967 
968  if(shapes_.empty()) {
969  shapes_.swap(drawn_shapes_);
970  } else {
971  std::copy(drawn_shapes_.begin(), drawn_shapes_.end(), std::inserter(shapes_, shapes_.begin()));
972  drawn_shapes_.clear();
973  }
974 }
975 
976 /***** ***** ***** ***** ***** SHAPE ***** ***** ***** ***** *****/
977 
978 } // namespace gui2
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:65
surface scale_surface_legacy(const surface &surf, int w, int h)
Scale a surface using simple bilinear filtering (discarding rgb from source pixels with 0 alpha) ...
Definition: utils.cpp:325
int x2_
Definition: pump.cpp:135
void invalidate_cache()
Definition: canvas.cpp:964
resize_mode get_resize_mode(const std::string &resize_mode)
Converts a string to a resize mode.
Definition: canvas.cpp:666
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:953
int border_thickness_
Border thickness.
bool is_dirty_
The dirty state of the canvas.
Definition: canvas.hpp:217
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:378
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:274
Collection of helper functions relating to Pango formatting.
round_rectangle_shape(const config &cfg)
Constructor.
Definition: canvas.cpp:356
surface image_
The image is cached in this surface.
Add a special kind of assert to validate whether the input from WML doesn&#39;t contain any problems that...
std::vector< shape_ptr > drawn_shapes_
All shapes which have been already drawn.
Definition: canvas.hpp:187
void draw(surface &canvas, SDL_Renderer *renderer, wfl::map_formula_callable &variables) override
Implement shape::draw().
Definition: canvas.cpp:298
rectangle_shape(const config &cfg)
Constructor.
Definition: canvas.cpp:277
int as_int() const
Definition: variant.cpp:294
typed_formula< color_t > border_color_
The border color of the rounded rectangle.
circle_shape(const config &cfg)
Constructor.
Definition: canvas.cpp:445
typed_formula< unsigned > x2_
The end x coordinate of the line.
unsigned h_
Height of the canvas.
Definition: canvas.hpp:203
t_string get_image() const
Wrapper for label.
Definition: image.hpp:65
pango_text & set_link_aware(bool b)
Definition: text.cpp:439
typed_formula< int > maximum_height_
The maximum height for the text.
static CVideo & get_singleton()
Definition: video.hpp:48
#define h
typed_formula< unsigned > y2_
The end y coordinate of the line.
typed_formula< color_t > fill_color_
The border color of the rounded rectangle.
#define d
surface get_surface_portion(const surface &src, SDL_Rect &area)
Get a portion of the screen.
Definition: utils.cpp:2157
#define VALIDATE_WITH_DEV_MESSAGE(cond, message, dev_message)
pango_text & set_font_style(const FONT_STYLE font_style)
Definition: text.cpp:334
static std::string _(const char *str)
Definition: gettext.hpp:92
pango_text & get_text_renderer()
Returns a reference to a static pango_text object.
Definition: text.cpp:923
pango_text & set_ellipse_mode(const PangoEllipsizeMode ellipse_mode)
Definition: text.cpp:401
surface flip_surface(const surface &surf)
Definition: utils.cpp:1951
typed_formula< int > w_
The width of the rectangle.
unsigned int border_thickness_
The border thickness of the circle.
#define ERR_GUI_P
Definition: log.hpp:68
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:211
pango_text & set_alignment(const PangoAlignment alignment)
Definition: text.cpp:415
Generic file dialog.
Definition: field-fwd.hpp:22
SDL_Renderer * renderer_
Definition: canvas.hpp:208
typed_formula< bool > vertical_mirror_
Mirror the image over the vertical axis.
void blit_surface(const surface &surf, const SDL_Rect *srcrect, surface &dst, const SDL_Rect *dstrect)
Replacement for sdl_blit.
Definition: utils.cpp:2009
typed_formula< unsigned > h_
The height of the text.
int x1_
Definition: pump.cpp:135
int y2_
Definition: pump.cpp:135
#define DBG_GUI_D
Definition: log.hpp:28
typed_formula< int > h_
The height of the rectangle.
Abstract base class for all other shapes.
Definition: canvas.hpp:50
void blit(surface &surf, SDL_Rect rect)
Blits the canvas unto another surface.
Definition: canvas.cpp:878
static void dimension_validation(unsigned value, const std::string &name, const std::string &key)
Definition: canvas.cpp:534
lg::log_domain log_gui_parse("gui/parse")
Definition: log.hpp:64
#define VALIDATE(cond, message)
The macro to use for the validation of WML.
shape(const config &cfg)
Definition: canvas.hpp:53
map_formula_callable & add(const std::string &key, const variant &value)
Definition: callable.hpp:252
std::ostringstream wrapper.
Definition: formatter.hpp:38
text_shape(const config &cfg)
Constructor.
Definition: canvas.cpp:685
pango_text & set_font_size(const unsigned font_size)
Definition: text.cpp:322
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:370
wfl::formula actions_formula_
uint8_t r
Red value.
Definition: color.hpp:177
uint8_t a
Alpha value.
Definition: color.hpp:186
resize_mode resize_mode_
The resize mode for an image.
surface blur_surface(const surface &surf, int depth)
Cross-fades a surface.
Definition: utils.cpp:1376
wfl::action_function_symbol_table functions_
Action function definitions for the canvas.
Definition: canvas.hpp:214
#define log_scope2(domain, description)
Definition: log.hpp:207
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:311
Generic locator abstracting the location of an image.
Definition: picture.hpp:59
A simple canvas which can be drawn upon.
Definition: canvas.hpp:41
int y1_
Definition: pump.cpp:135
typed_formula< color_t > border_color_
The border color of the rectangle.
typed_formula< int > h_
The height of the rectangle.
typed_formula< unsigned > y_
The y coordinate of the text.
std::size_t i
Definition: function.cpp:940
typed_formula< unsigned > x_
The x coordinate of the image.
formula_callable_ptr fake_ptr()
Definition: callable.hpp:41
unsigned blur_depth_
The depth of the blur to use in the pre committing.
Definition: canvas.hpp:197
font::pango_text::FONT_STYLE font_style_
The style of the text.
surface & surf()
Definition: canvas.hpp:164
#define WRN_GUI_D
Definition: log.hpp:30
surface stretch_surface_horizontal(const surface &surf, const unsigned w)
Stretches a surface in the horizontal direction.
Definition: utils.cpp:40
void clear_shapes(const bool force)
Definition: canvas.cpp:948
void draw(surface &canvas, SDL_Renderer *renderer, wfl::map_formula_callable &variables) override
Implement shape::draw().
Definition: canvas.cpp:242
static map_location::DIRECTION s
image_shape(const config &cfg, wfl::action_function_symbol_table &functions)
Constructor.
Definition: canvas.cpp:515
Helper class for pinning SDL surfaces into memory.
Definition: surface.hpp:144
pango_text & set_link_color(const color_t &color)
Definition: text.cpp:449
line_shape(const config &cfg)
Constructor.
Definition: canvas.cpp:227
int w
#define debug(x)
lg::log_domain log_gui_draw("gui/draw")
Definition: log.hpp:27
typed_formula< unsigned > h_
The height of the image.
typed_formula< color_t > fill_color_
The fill color of the circle.
bool is_neutral() const
Check that the surface is neutral bpp 32.
Definition: surface.cpp:52
typed_formula< unsigned > y_
The y coordinate of the image.
bool null() const
Definition: color.hpp:188
Text class.
Definition: text.hpp:74
virtual void draw(surface &canvas, SDL_Renderer *renderer, wfl::map_formula_callable &variables)=0
Draws the canvas.
SDL_Rect create_rect(const int x, const int y, const int w, const int h)
Creates an SDL_Rect with the given dimensions.
Definition: rect.hpp:39
pango_text & set_maximum_width(int width)
Definition: text.cpp:355
resize_mode
Determines the way an image will be resized.
std::vector< shape_ptr > shapes_
Vector with the shapes to draw.
Definition: canvas.hpp:183
void draw(const bool force=false)
Draws the canvas.
Definition: canvas.cpp:833
SDL_Rect src_clip_
Contains the size of the image.
typed_formula< t_string > text_
The text to draw.
Contains the SDL_Rect helper code.
variant query_value(const std::string &key) const
Definition: callable.hpp:49
typed_formula< PangoAlignment > text_alignment_
The alignment of the text.
surface tile_surface(const surface &surf, int w, int h, bool centered)
Tile a surface.
Definition: utils.cpp:552
typed_formula< color_t > link_color_
The link color of the text.
typed_formula< unsigned > font_size_
The font size of the text.
std::shared_ptr< shape > shape_ptr
Definition: canvas.hpp:87
typed_formula< int > r_
The radius of the corners.
typed_formula< unsigned > x1_
The start x coordinate of the line.
family_class str_to_family_class(const std::string &str)
surface stretch_surface_vertical(const surface &surf, const unsigned h)
Stretches a surface in the vertical direction.
Definition: utils.cpp:82
unsigned characters_per_line_
The number of characters per line.
void draw(surface &canvas, SDL_Renderer *renderer, wfl::map_formula_callable &variables) override
Implement shape::draw().
Definition: canvas.cpp:460
surface & render()
Returns the rendered text.
Definition: text.cpp:92
pango_text & set_maximum_height(int height, bool multiline)
Definition: text.cpp:382
void sdl_blit(const surface &src, SDL_Rect *src_rect, surface &dst, SDL_Rect *dst_rect)
Definition: utils.hpp:31
void draw(surface &canvas, SDL_Renderer *renderer, wfl::map_formula_callable &variables) override
Implement shape::draw().
Definition: canvas.cpp:714
bool has_formula() const
Determine whether the class contains a formula.
uint8_t g
Green value.
Definition: color.hpp:180
void draw(surface &canvas, SDL_Renderer *renderer, wfl::map_formula_callable &variables) override
Implement shape::draw().
Definition: canvas.cpp:543
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:59
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:902
pango_text & set_foreground_color(const color_t &color)
Definition: text.cpp:345
surface canvas_
The surface we draw all items on.
Definition: canvas.hpp:206
font::pango_text::FONT_STYLE decode_font_style(const std::string &style)
Converts a font style string to a font style.
Definition: helper.cpp:37
std::size_t w_
bool has_key(const std::string &key) const
Definition: callable.hpp:81
typed_formula< unsigned > y1_
The start y coordinate of the line.
typed_formula< int > x_
The x coordinate of the rectangle.
typed_formula< unsigned > radius_
The radius of the circle.
typed_formula< int > x_
The x coordinate of the rectangle.
unsigned w_
Width of the canvas.
Definition: canvas.hpp:200
typed_formula< unsigned > w_
The width of the image.