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