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