/*
   Copyright (C) 2007 - 2018 by Mark de Wever <koraq@xs4all.nl>
   Part of the Battle for Wesnoth Project https://www.wesnoth.org/

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY.

   See the COPYING file for more details.
*/

/**
 * @file
 * Implementation of canvas.hpp.
 */

#define GETTEXT_DOMAIN "wesnoth-lib"

#include "gui/core/canvas.hpp"
#include "gui/core/canvas_private.hpp"

#include "font/text.hpp"
#include "formatter.hpp"
#include "gettext.hpp"
#include "picture.hpp"

#include "gui/auxiliary/typed_formula.hpp"
#include "gui/core/log.hpp"
#include "gui/widgets/helper.hpp"
#include "sdl/rect.hpp"
#include "video.hpp"
#include "wml_exception.hpp"

#include <iterator>

namespace gui2
{

namespace
{

/*WIKI
 * @page = GUICanvasWML
 *
 * {{Autogenerated}}
 *
 * = Canvas =
 *
 * A canvas is a blank drawing area on which the user can draw several shapes.
 * The drawing is done by adding WML structures to the canvas.
 */

/*WIKI
 * @page = GUICanvasWML
 * @begin{parent}{name="generic/state/draw/"}
 *
 * == Pre commit ==
 * @begin{tag}{name="pre_commit"}{min="0"}{max="1"}
 *
 * This section contains the pre commit functions. These functions will be
 * executed before the drawn canvas is applied on top of the normal
 * background. There should only be one pre commit section and its order
 * regarding the other shapes doesn't matter. The function has effect on the
 * entire canvas, it's not possible to affect only a small part of the canvas.
 *
 * The section can have one of the following subsections.
 *
 * === Blur ===
 * @begin{tag}{name="blur"}{min="0"}{max="1"}
 *
 * Blurs the background before applying the canvas. This doesn't make sense
 * if the widget isn't semi-transparent.
 *
 * Keys:
 * @begin{table}{config}
 *     depth & unsigned & 0 &            The depth to blur. $
 * @end{table}
 * @end{tag}{name="blur"}
 * @end{tag}{name="pre_commit"}
 */

/***** ***** ***** ***** ***** DRAWING PRIMITIVES ***** ***** ***** ***** *****/

static void set_renderer_color(SDL_Renderer* renderer, color_t color)
{
	SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
}

/**
 * Draws a line on a surface.
 *
 * @pre                   The caller needs to make sure the entire line fits on
 *                        the @p surface.
 * @pre                   @p x2 >= @p x1
 * @pre                   The @p surface is locked.
 *
 * @param canvas          The canvas to draw upon, the caller should lock the
 *                        surface before calling.
 * @param color           The color of the line to draw.
 * @param x1              The start x coordinate of the line to draw.
 * @param y1              The start y coordinate of the line to draw.
 * @param x2              The end x coordinate of the line to draw.
 * @param y2              The end y coordinate of the line to draw.
 */
static void draw_line(surface& canvas,
					  SDL_Renderer* renderer,
					  color_t color,
					  unsigned x1,
					  unsigned y1,
					  const unsigned x2,
					  unsigned y2)
{
	unsigned w = canvas->w;

	DBG_GUI_D << "Shape: draw line from " << x1 << ',' << y1 << " to " << x2
			  << ',' << y2 << " canvas width " << w << " canvas height "
			  << canvas->h << ".\n";

	assert(static_cast<int>(x1) < canvas->w);
	assert(static_cast<int>(x2) < canvas->w);
	assert(static_cast<int>(y1) < canvas->h);
	assert(static_cast<int>(y2) < canvas->h);

	set_renderer_color(renderer, color);

	if(x1 == x2 && y1 == y2) {
		// Handle single-pixel lines properly
		SDL_RenderDrawPoint(renderer, x1, y1);
	} else {
		SDL_RenderDrawLine(renderer, x1, y1, x2, y2);
	}
}

/**
 * Draws a circle on a surface.
 *
 * @pre                   The circle must fit on the canvas.
 * @pre                   The @p surface is locked.
 *
 * @param canvas          The canvas to draw upon, the caller should lock the
 *                        surface before calling.
 * @param color           The border color of the circle to draw.
 * @param x_center        The x coordinate of the center of the circle to draw.
 * @param y_center        The y coordinate of the center of the circle to draw.
 * @param radius          The radius of the circle to draw.
 * @tparam octants        A bitfield indicating which octants to draw, starting at twelve o'clock and moving clockwise.
 */
template<unsigned int octants = 0xff>
static void draw_circle(surface& canvas,
						SDL_Renderer* renderer,
						color_t color,
						const int x_center,
						const int y_center,
						const int radius)
{
	unsigned w = canvas->w;

	DBG_GUI_D << "Shape: draw circle at " << x_center << ',' << y_center
			  << " with radius " << radius << " canvas width " << w
			  << " canvas height " << canvas->h << ".\n";

	if(octants & 0x0f) assert((x_center + radius) < canvas->w);
	if(octants & 0xf0) assert((x_center - radius) >= 0);
	if(octants & 0x3c) assert((y_center + radius) < canvas->h);
	if(octants & 0xc3) assert((y_center - radius) >= 0);

	set_renderer_color(renderer, color);

	// Algorithm based on
	// http://de.wikipedia.org/wiki/Rasterung_von_Kreisen#Methode_von_Horn
	// version of 2011.02.07.
	int d = -static_cast<int>(radius);
	int x = radius;
	int y = 0;

	std::vector<SDL_Point> points;

	while(!(y > x)) {
		if(octants & 0x04) points.push_back({x_center + x, y_center + y});
		if(octants & 0x02) points.push_back({x_center + x, y_center - y});
		if(octants & 0x20) points.push_back({x_center - x, y_center + y});
		if(octants & 0x40) points.push_back({x_center - x, y_center - y});

		if(octants & 0x08) points.push_back({x_center + y, y_center + x});
		if(octants & 0x01) points.push_back({x_center + y, y_center - x});
		if(octants & 0x10) points.push_back({x_center - y, y_center + x});
		if(octants & 0x80) points.push_back({x_center - y, y_center - x});

		d += 2 * y + 1;
		++y;
		if(d > 0) {
			d += -2 * x + 2;
			--x;
		}
	}

	SDL_RenderDrawPoints(renderer, points.data(), points.size());
}

/**
 * Draws a filled circle on a surface.
 *
 * @pre                   The circle must fit on the canvas.
 * @pre                   The @p surface is locked.
 *
 * @param canvas          The canvas to draw upon, the caller should lock the
 *                        surface before calling.
 * @param color           The fill color of the circle to draw.
 * @param x_center        The x coordinate of the center of the circle to draw.
 * @param y_center        The y coordinate of the center of the circle to draw.
 * @param radius          The radius of the circle to draw.
 * @tparam octants        A bitfield indicating which octants to draw, starting at twelve o'clock and moving clockwise.
 */
template<unsigned int octants = 0xff>
static void fill_circle(surface& canvas,
						SDL_Renderer* renderer,
						color_t color,
						const int x_center,
						const int y_center,
						const int radius)
{
	unsigned w = canvas->w;

	DBG_GUI_D << "Shape: draw filled circle at " << x_center << ',' << y_center
			  << " with radius " << radius << " canvas width " << w
			  << " canvas height " << canvas->h << ".\n";

	if(octants & 0x0f) assert((x_center + radius) < canvas->w);
	if(octants & 0xf0) assert((x_center - radius) >= 0);
	if(octants & 0x3c) assert((y_center + radius) < canvas->h);
	if(octants & 0xc3) assert((y_center - radius) >= 0);

	set_renderer_color(renderer, color);

	int d = -static_cast<int>(radius);
	int x = radius;
	int y = 0;

	while(!(y > x)) {
		// I use the formula of Bresenham's line algorithm to determine the boundaries of a segment.
		// The slope of the line is always 1 or -1 in this case.
		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
		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
		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)
		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)

		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)
		if(octants & 0x01) SDL_RenderDrawLine(renderer, x_center + y,     y_center - x,     x_center + y,     y_center - y);     // y2 = x_center - x2 + y_center
		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)
		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

		d += 2 * y + 1;
		++y;
		if(d > 0) {
			d += -2 * x + 2;
			--x;
		}
	}
}

} // namespace

/*WIKI - unclassified
 * This code can be used by a parser to generate the wiki page
 * structure
 * [tag name]
 * param type_info description
 *
 * param                               Name of the parameter.
 *
 * type_info = ( type = default_value) The info about a optional parameter.
 * type_info = ( type )                The info about a mandatory parameter
 * type_info = [ type_info ]           The info about a conditional parameter
 *                                     description should explain the reason.
 *
 * description                         Description of the parameter.
 *
 *
 *
 *
 * Formulas are a function between brackets, that way the engine can see whether
 * there is standing a plain number or a formula eg:
 * 0     A value of zero
 * (0)   A formula returning zero
 *
 * When formulas are available the text should state the available variables
 * which are available in that function.
 */

/*WIKI
 * @page = GUIVariable
 *
 * {{Autogenerated}}
 *
 * == Variables ==
 *
 * In various parts of the GUI there are several variables types in use. This
 * page describes them.
 *
 * === Simple types ===
 *
 * The simple types are types which have one value or a short list of options.
 *
 * @begin{table}{variable_types}
 *     unsigned &                      Unsigned number (positive whole numbers
 *                                     and zero). $
 *     f_unsigned &                    Unsigned number or formula returning an
 *                                     unsigned number. $
 *     int &                           Signed number (whole numbers). $
 *     f_int &                         Signed number or formula returning an
 *                                     signed number. $
 *     bool &                          A boolean value accepts the normal
 *                                     values as the rest of the game. $
 *     f_bool &                        Boolean value or a formula returning a
 *                                     boolean value. $
 *     string &                        A text. $
 *     tstring &                       A translatable string. $
 *     f_tstring &                     Formula returning a translatable string.
 *                                     $
 *     function &                      A string containing a set of function
 *                                     definition for the formula language. $
 *
 *     color &                         A string which contains the color, this
 *                                     a group of 4 numbers between 0 and 255
 *                                     separated by a comma. The numbers are red
 *                                     component, green component, blue
 *                                     component and alpha. A color of 0 is not
 *                                     available. An alpha of 255 is fully
 *                                     transparent. Omitted values are set to
 *                                     0. $
 *     f_color &                       A string which contains a color, or a formula
 *                                     which evaluates to a list containing the color's
 *                                     components. $
 *
 *     font_style &                    A string which contains the style of the
 *                                     font:
 *                                     @* normal    normal font
 *                                     @* bold      bold font
 *                                     @* italic    italic font
 *                                     @* underline underlined font
 *                                     @-Since SDL has problems combining these
 *                                     styles only one can be picked. Once SDL
 *                                     will allow multiple options, this type
 *                                     will be transformed to a comma separated
 *                                     list. If empty we default to the normal
 *                                     style. Since the render engine is
 *                                     replaced by Pango markup this field will
 *                                     change later on. Note widgets that allow
 *                                     marked up text can use markup to change
 *                                     the font style. $
 *
 *     v_align &                       Vertical alignment; how an item is
 *                                     aligned vertically in the available
 *                                     space. Possible values:
 *                                     @* top    aligned at the top
 *                                     @* bottom aligned at the bottom
 *                                     @* center centered
 *                                     @-When nothing is set or an another
 *                                     value as in the list the item is
 *                                     centered. $
 *
 *     h_align &                       Horizontal alignment; how an item is
 *                                     aligned horizontal in the available
 *                                     space. Possible values:
 *                                     @* left   aligned at the left side
 *                                     @* right  aligned at the right side
 *                                     @* center centered $
 *
 *     f_h_align &                     A horizontal alignment or a formula
 *                                     returning a horizontal alignment. $
 *
 *     border &                        Comma separated list of borders to use.
 *                                     Possible values:
 *                                     @* left   border at the left side
 *                                     @* right  border at the right side
 *                                     @* top    border at the top
 *                                     @* bottom border at the bottom
 *                                     @* all    alias for "left, right, top,
 *                                     bottom" $
 *
 *     scrollbar_mode &                How to show the scrollbar of a widget.
 *                                     Possible values:
 *                                     @* always       The scrollbar is always
 *                                     shown, regardless whether it's required
 *                                     or not.
 *                                     @* never        The scrollbar is never
 *                                     shown, even not when needed. (Note when
 *                                     setting this mode dialogs might
 *                                     not properly fit anymore).
 *                                     @* auto         Shows the scrollbar when
 *                                     needed. The widget will reserve space for
 *                                     the scrollbar, but only show when needed.
 *                                     @* initial_auto Like auto, but when the
 *                                     scrollbar is not needed the space is not
 *                                     reserved.
 *                                     @-Use auto when the list can be changed
 *                                     dynamically eg the game list in the
 *                                     lobby. For optimization you can also
 *                                     use auto when you really expect a
 *                                     scrollbar, but don't want it to be shown
 *                                     when not needed eg the language list
 *                                     will need a scrollbar on most screens. $
 *
 *     resize_mode &                   Determines how an image is resized.
 *                                     Possible values:
 *                                     @* scale        The image is scaled.
 *                                     @* stretch      The first row or column
 *                                     of pixels is copied over the entire
 *                                     image. (Can only be used to scale resize
 *                                     in one direction, else falls
 *                                     back to scale.)
 *                                     @* tile         The image is placed
 *                                     several times until the entire surface
 *                                     is filled. The last images are
 *                                     truncated.
 *                                     @* tile_center  Like tile, but the
 *                                     image is first centered on the screen. $
 *
 *     grow_direction &                Determines whether the image may grow in
 * 									   the horizontal or vertical direction. $
 * @end{table}
 * @allow{type}{name="unsigned"}{value="^\d+$"}
 * @allow{type}{name="f_unsigned"}{value="^.+$"}
 * @allow{type}{name="int"}{value="^-?\d+$"}
 * @allow{type}{name="f_int"}{value="^.*$"}
 * @allow{type}{name="bool"}{value="^true|false|yes|no$"}
 * @allow{type}{name="f_bool"}{value="^.*$"}
 * @allow{type}{name="string"}{value="^.*$"}
 * @allow{type}{name="t_string"}{value="^_?.*$"}
 * @allow{type}{name="f_string"}{value="^.*$"}
 * @allow{type}{name="f_tstring"}{value="^_?.*$"}
 * @allow{type}{name="function"}{value="^_?.*$"}
 *
 * @allow{type}{name="color"}{value="^(?:2[0-5][0-5]|[01]?\d?\d)[.,]\s*(?:2[0-5][0-5]|[01]?\d?\d)[.,]\s*(?:2[0-5][0-5]|[01]?\d?\d)[.,]\s*(?:2[0-5][0-5]|[01]?\d?\d)$"}
 *
 * @allow{type}{name="font_style"}{value="^(normal|bold|italic|underline)?$"}
 * @allow{type}{name="v_align"}{value="^top|bottom|center$"}
 * @allow{type}{name="h_align"}{value="^left|right|center$"}
 * @allow{type}{name="f_h_align"}{value="^.*$"}
 * @allow{type}{name="border"}{value="^(top|bottom|left|right|all)?(,\s*(top|bottom|left|right|all))*$"}
 * @allow{type}{name="scrollbar_mode"}{value="^always|never|auto|initial_auto$"}
 * @allow{type}{name="resize_mode"}{value="^scale|stretch|tile$"}
 * @allow{type}{name="grow_direction"}{value="^horizontal|vertical$"}
 *
 * @remove{type}{name="section"}
 * @remove{type}{name="config"}
 * @remove{type}{name="grid"}
 * == Section types ==
 *
 * For more complex parts, there are sections. Sections contain of several
 * lines of WML and can have sub sections. For example a grid has sub sections
 * which contain various widgets. Here's the list of sections.
 *
 * @begin{table}{variable_types}
 *     section &                       A generic section. The documentation
 *                                     about the section should describe the
 *                                     section in further detail. $
 *
 *     grid &                          A grid contains several widgets. (TODO
 *                                     add link to generic grid page.) $
 * @end{table}
 */

/***** ***** ***** ***** ***** LINE ***** ***** ***** ***** *****/

/*WIKI
 * @page = GUICanvasWML
 *
 * == Line ==
 * @begin{tag}{name="line"}{min="0"}{max="-1"}
 * Definition of a line.
 *
 * Keys:
 * @begin{table}{config}
 *     x1 & f_unsigned & 0 &           The x coordinate of the startpoint. $
 *     y1 & f_unsigned & 0 &           The y coordinate of the startpoint. $
 *     x2 & f_unsigned & 0 &           The x coordinate of the endpoint. $
 *     y2 & f_unsigned & 0 &           The y coordinate of the endpoint. $
 *     color & f_color & "" &          The color of the line. $
 *     thickness & unsigned & 0 &      The thickness of the line if 0 nothing
 *                                     is drawn. $
 *     debug & string & "" &           Debug message to show upon creation
 *                                     this message is not stored. $
 * @end{table}
 * @end{tag}{name="line"}
 *
 * <span id="general_variables">Variables:</span>.
 * @begin{table}{formula}
 *     width & unsigned &                 The width of the canvas. $
 *     height & unsigned &                The height of the canvas. $
 *     text & tstring &                   The text to render on the widget. $
 *     text_maximum_width & unsigned &    The maximum width available for the
 *                                        text on the widget. $
 *     text_maximum_height & unsigned &   The maximum height available for the
 *                                        text on the widget. $
 *     text_wrap_mode & int  &            When the text doesn't fit in the
 *                                        available width there are several ways
 *                                        to fix that. This variable holds the
 *                                        best method. (NOTE this is a 'hidden'
 *                                        variable meant to copy state from a
 *                                        widget to its canvas so there's no
 *                                        reason to use this variable and thus
 *                                        its values are not listed and might
 *                                        change without further notice.) $
 *     text_alignment & h_align &         The way the text is aligned inside the
 *                                        canvas. $
 *@end{table}
 *
 * The size variables are copied to the window and will be determined at
 * runtime. This is needed since the main window can be resized and the dialog
 * needs to resize accordingly. The following variables are available:
 * @begin{table}{formula}
 *     screen_width & unsigned &        The usable width of the Wesnoth main
 *                                      window. $
 *     screen_height & unsigned &       The usable height of the Wesnoth main
 *                                      window. $
 *     gamemapx_offset & unsigned &     The distance between left edge of the
 *                                      screen and the game map. $
 *     gamemap_width & unsigned &       The usable width of the Wesnoth gamemap,
 *                                      if no gamemap shown it's the same value
 *                                      as screen_width. $
 *     gamemap_height & unsigned &      The usable height of the Wesnoth
 *                                      gamemap, if no gamemap shown it's the
 *                                      same value as screen_height. $
 *
 *     mouse_x & unsigned &             The x coordinate of the mouse pointer. $
 *     mouse_y & unsigned &             The y coordinate of the mouse pointer. $
 *
 *     window_width & unsigned &        The window width. This value has two
 *                                      meanings during the layout phase. This
 *                                      only applies if automatic placement is
 *                                      not enabled.
 *                                      - When set to 0 it should return the
 *                                        wanted maximum width. If no maximum
 *                                        is wanted it should be set to the
 *                                        '"(screen_width)"'.
 *                                      - When not equal to 0 its value is the
 *                                        best width for the window. When the
 *                                        size should remain unchanged it
 *                                        should be set to '"(window_width)"'.
 *                                        $
 *
 *     window_height & unsigned &       The window height. This value has two
 *                                      meanings during the layout phase. This
 *                                      only applies if automatic placement is
 *                                      not enabled.
 *                                      - When set to 0 it should return the
 *                                        wanted maximum height. If no maximum
 *                                        is wanted it should be set to the
 *                                        '"(screen_height)"'.
 *                                      - When not equal to 0 its value is the
 *                                        best height for the window. When the
 *                                        size should remain unchanged it
 *                                        should be set to '"(window_height)"'.
 *                                        $
 *
 *    size_request_mode & string &      A field foo:
 *                                      - maximum
 *                                      - size
 *
 * @end{table}
 *
 * Note when drawing the valid coordinates are:<br>
 * 0 -> width - 1 <br>
 * 0 -> height -1
 *
 * Drawing outside this area will result in unpredictable results including
 * crashing. (That should be fixed, when encountered.)
 */

line_shape::line_shape(const config& cfg)
	: shape(cfg)
	, x1_(cfg["x1"])
	, y1_(cfg["y1"])
	, x2_(cfg["x2"])
	, y2_(cfg["y2"])
	, color_(cfg["color"])
	, thickness_(cfg["thickness"])
{
	const std::string& debug = (cfg["debug"]);
	if(!debug.empty()) {
		DBG_GUI_P << "Line: found debug message '" << debug << "'.\n";
	}
}

void line_shape::draw(surface& canvas,
				 SDL_Renderer* renderer,
				 wfl::map_formula_callable& variables)
{
	/**
	 * @todo formulas are now recalculated every draw cycle which is a bit silly
	 * unless there has been a resize. So to optimize we should use an extra
	 * flag or do the calculation in a separate routine.
	 */

	const unsigned x1 = x1_(variables);
	const unsigned y1 = y1_(variables);
	const unsigned x2 = x2_(variables);
	const unsigned y2 = y2_(variables);

	DBG_GUI_D << "Line: draw from " << x1 << ',' << y1 << " to " << x2 << ','
			  << y2 << " canvas size " << canvas->w << ',' << canvas->h
			  << ".\n";

	VALIDATE(static_cast<int>(x1) < canvas->w
			 && static_cast<int>(x2) < canvas->w
			 && static_cast<int>(y1) < canvas->h
			 && static_cast<int>(y2) < canvas->h,
			 _("Line doesn't fit on canvas."));

	// @todo FIXME respect the thickness.

	// lock the surface
	surface_lock locker(canvas);

	draw_line(canvas, renderer, color_(variables), x1, y1, x2, y2);
}

/***** ***** ***** ***** ***** Rectangle ***** ***** ***** ***** *****/

/*WIKI
 * @page = GUICanvasWML
 *
 * == Rectangle ==
 * @begin{tag}{name="rectangle"}{min="0"}{max="-1"}
 *
 * Definition of a rectangle. When drawing a rectangle it doesn't get blended on
 * the surface but replaces the pixels instead. A blitting flag might be added
 * later if needed.
 *
 * Keys:
 * @begin{table}{config}
 *     x & f_unsigned & 0 &            The x coordinate of the top left corner.
 *                                     $
 *     y & f_unsigned & 0 &            The y coordinate of the top left corner.
 *                                     $
 *     w & f_unsigned & 0 &            The width of the rectangle. $
 *     h & f_unsigned & 0 &            The height of the rectangle. $
 *     border_thickness & unsigned & 0 &
 *                                     The thickness of the border; if the
 *                                     thickness is zero it's not drawn. $
 *     border_color & f_color & "" &   The color of the border; if empty it's
 *                                     not drawn. $
 *     fill_color & f_color & "" &     The color of the interior; if omitted
 *                                     it's not drawn. $
 *     debug & string & "" &           Debug message to show upon creation;
 *                                     this message is not stored. $
 * @end{table}
 * @end{tag}{name="rectangle"}
 * Variables:
 * See [[#general_variables|Line]].
 *
 */
rectangle_shape::rectangle_shape(const config& cfg)
	: shape(cfg)
	, x_(cfg["x"])
	, y_(cfg["y"])
	, w_(cfg["w"])
	, h_(cfg["h"])
	, border_thickness_(cfg["border_thickness"])
	, border_color_(cfg["border_color"], color_t::null_color())
	, fill_color_(cfg["fill_color"], color_t::null_color())
{
	// Check if a raw color string evaluates to a null color.
	if(!border_color_.has_formula() && border_color_().null()) {
		border_thickness_ = 0;
	}

	const std::string& debug = (cfg["debug"]);
	if(!debug.empty()) {
		DBG_GUI_P << "Rectangle: found debug message '" << debug << "'.\n";
	}
}

void rectangle_shape::draw(surface& canvas,
					  SDL_Renderer* renderer,
					  wfl::map_formula_callable& variables)
{
	/**
	 * @todo formulas are now recalculated every draw cycle which is a  bit
	 * silly unless there has been a resize. So to optimize we should use an
	 * extra flag or do the calculation in a separate routine.
	 */
	const int x = x_(variables);
	const int y = y_(variables);
	const int w = w_(variables);
	const int h = h_(variables);

	DBG_GUI_D << "Rectangle: draw from " << x << ',' << y << " width " << w
			  << " height " << h << " canvas size " << canvas->w << ','
			  << canvas->h << ".\n";

	VALIDATE(x     <  canvas->w
	      && x + w <= canvas->w
	      && y     <  canvas->h
	      && y + h <= canvas->h, _("Rectangle doesn't fit on canvas."));

	surface_lock locker(canvas);

	const color_t fill_color = fill_color_(variables);

	// Fill the background, if applicable
	if(!fill_color.null() && w && h) {
		set_renderer_color(renderer, fill_color);

		SDL_Rect area {
			x +  border_thickness_,
			y +  border_thickness_,
			w - (border_thickness_ * 2),
			h - (border_thickness_ * 2)
		};

		SDL_RenderFillRect(renderer, &area);
	}

	// Draw the border
	for(int i = 0; i < border_thickness_; ++i) {
		SDL_Rect dimensions {
			x + i,
			y + i,
			w - (i * 2),
			h - (i * 2)
		};

		set_renderer_color(renderer, border_color_(variables));

		SDL_RenderDrawRect(renderer, &dimensions);
	}
}

/***** ***** ***** ***** ***** Rounded Rectangle ***** ***** ***** ***** *****/

/*WIKI
 * @page = GUICanvasWML
 *
 * == Rounded Rectangle ==
 * @begin{tag}{name="round_rectangle"}{min="0"}{max="-1"}
 *
 * Definition of a rounded rectangle. When drawing a rounded rectangle it doesn't get blended on
 * the surface but replaces the pixels instead. A blitting flag might be added
 * later if needed.
 *
 * Keys:
 * @begin{table}{config}
 *     x & f_unsigned & 0 &            The x coordinate of the top left corner.
 *                                     $
 *     y & f_unsigned & 0 &            The y coordinate of the top left corner.
 *                                     $
 *     w & f_unsigned & 0 &            The width of the rounded rectangle. $
 *     h & f_unsigned & 0 &            The height of the rounded rectangle. $
 *     corner_radius & f_unsigned & 0 &The radius of the rectangle's corners. $
 *     border_thickness & unsigned & 0 &
 *                                     The thickness of the border; if the
 *                                     thickness is zero it's not drawn. $
 *     border_color & f_color & "" &   The color of the border; if empty it's
 *                                     not drawn. $
 *     fill_color & f_color & "" &     The color of the interior; if omitted
 *                                     it's not drawn. $
 *     debug & string & "" &           Debug message to show upon creation;
 *                                     this message is not stored. $
 * @end{table}
 * @end{tag}{name="round_rectangle"}
 * Variables:
 * See [[#general_variables|Line]].
 *
 */
round_rectangle_shape::round_rectangle_shape(const config& cfg)
	: shape(cfg)
	, x_(cfg["x"])
	, y_(cfg["y"])
	, w_(cfg["w"])
	, h_(cfg["h"])
	, r_(cfg["corner_radius"])
	, border_thickness_(cfg["border_thickness"])
	, border_color_(cfg["border_color"], color_t::null_color())
	, fill_color_(cfg["fill_color"], color_t::null_color())
{
	// Check if a raw color string evaluates to a null color.
	if(!border_color_.has_formula() && border_color_().null()) {
		border_thickness_ = 0;
	}

	const std::string& debug = (cfg["debug"]);
	if(!debug.empty()) {
		DBG_GUI_P << "Rounded Rectangle: found debug message '" << debug << "'.\n";
	}
}

void round_rectangle_shape::draw(surface& canvas,
	SDL_Renderer* renderer,
	wfl::map_formula_callable& variables)
{
	/**
	 * @todo formulas are now recalculated every draw cycle which is a  bit
	 * silly unless there has been a resize. So to optimize we should use an
	 * extra flag or do the calculation in a separate routine.
	 */
	const int x = x_(variables);
	const int y = y_(variables);
	const int w = w_(variables);
	const int h = h_(variables);
	const int r = r_(variables);

	DBG_GUI_D << "Rounded Rectangle: draw from " << x << ',' << y << " width " << w
		<< " height " << h << " canvas size " << canvas->w << ','
		<< canvas->h << ".\n";

	VALIDATE(x     <  canvas->w
		&& x + w <= canvas->w
		&& y     <  canvas->h
		&& y + h <= canvas->h, _("Rounded Rectangle doesn't fit on canvas."));

	surface_lock locker(canvas);

	const color_t fill_color = fill_color_(variables);

	// Fill the background, if applicable
	if(!fill_color.null() && w && h) {
		set_renderer_color(renderer, fill_color);
		static const int count = 3;
		SDL_Rect area[count] {
			{x + r,                 y + border_thickness_, w - r                 * 2, r - border_thickness_ + 1},
			{x + border_thickness_, y + r + 1,             w - border_thickness_ * 2, h - r * 2},
			{x + r,                 y - r + h + 1,         w - r                 * 2, r - border_thickness_},
		};

		SDL_RenderFillRects(renderer, area, count);

		fill_circle<0xc0>(canvas, renderer, fill_color, x + r,     y + r,     r);
		fill_circle<0x03>(canvas, renderer, fill_color, x + w - r, y + r,     r);
		fill_circle<0x30>(canvas, renderer, fill_color, x + r,     y + h - r, r);
		fill_circle<0x0c>(canvas, renderer, fill_color, x + w - r, y + h - r, r);
	}

	const color_t border_color = border_color_(variables);

	// Draw the border
	for(int i = 0; i < border_thickness_; ++i) {
		set_renderer_color(renderer, border_color);

		SDL_RenderDrawLine(renderer, x + r, y + i,     x + w - r, y + i);
		SDL_RenderDrawLine(renderer, x + r, y + h - i, x + w - r, y + h - i);

		SDL_RenderDrawLine(renderer, x + i,     y + r, x + i,     y + h - r);
		SDL_RenderDrawLine(renderer, x + w - i, y + r, x + w - i, y + h - r);

		draw_circle<0xc0>(canvas, renderer, border_color, x + r,     y + r,     r - i);
		draw_circle<0x03>(canvas, renderer, border_color, x + w - r, y + r,     r - i);
		draw_circle<0x30>(canvas, renderer, border_color, x + r,     y + h - r, r - i);
		draw_circle<0x0c>(canvas, renderer, border_color, x + w - r, y + h - r, r - i);
	}
}

/***** ***** ***** ***** ***** CIRCLE ***** ***** ***** ***** *****/

/*WIKI
 * @page = GUICanvasWML
 *
 * == Circle ==
 * @begin{tag}{name="circle"}{min="0"}{max="-1"}
 *
 * Definition of a circle. When drawing a circle it doesn't get blended on
 * the surface but replaces the pixels instead. A blitting flag might be
 * added later if needed.
 *
 * Keys:
 * @begin{table}{config}
 * x      & f_unsigned & 0 &       The x coordinate of the center. $
 * y      & f_unsigned & 0 &       The y coordinate of the center. $
 * radius & f_unsigned & 0 &       The radius of the circle; if 0 nothing is
 *                                 drawn. $
 * border_thickness & unsigned & "" &
 *                                 The border thickness of the circle. $
 * border_color & f_color & "" &   The color of the circle. $
 * fill_color & f_color & "" &     The color of the circle. $
 * debug & string & "" &           Debug message to show upon creation; this
 *                                 message is not stored. $
 * @end{table}
 * @end{tag}{name="circle"}
 * Variables:
 * See [[#general_variables|Line]].
 *
 * Drawing outside the area will result in unpredictable results including
 * crashing. (That should be fixed, when encountered.)
 */
circle_shape::circle_shape(const config& cfg)
	: shape(cfg)
	, x_(cfg["x"])
	, y_(cfg["y"])
	, radius_(cfg["radius"])
	, border_color_(cfg["border_color"])
	, fill_color_(cfg["fill_color"])
	, border_thickness_(cfg["border_thickness"].to_int(1))
{
	const std::string& debug = (cfg["debug"]);
	if(!debug.empty()) {
		DBG_GUI_P << "Circle: found debug message '" << debug << "'.\n";
	}
}

void circle_shape::draw(surface& canvas,
				   SDL_Renderer* renderer,
				   wfl::map_formula_callable& variables)
{
	/**
	 * @todo formulas are now recalculated every draw cycle which is a bit
	 * silly unless there has been a resize. So to optimize we should use an
	 * extra flag or do the calculation in a separate routine.
	 */

	const unsigned x = x_(variables);
	const unsigned y = y_(variables);
	const unsigned radius = radius_(variables);

	DBG_GUI_D << "Circle: drawn at " << x << ',' << y << " radius " << radius
			  << " canvas size " << canvas->w << ',' << canvas->h << ".\n";

	VALIDATE_WITH_DEV_MESSAGE(
			static_cast<int>(x - radius) >= 0,
			_("Circle doesn't fit on canvas."),
			formatter() << "x = " << x << ", radius = " << radius);

	VALIDATE_WITH_DEV_MESSAGE(
			static_cast<int>(y - radius) >= 0,
			_("Circle doesn't fit on canvas."),
			formatter() << "y = " << y << ", radius = " << radius);

	VALIDATE_WITH_DEV_MESSAGE(
			static_cast<int>(x + radius) < canvas->w,
			_("Circle doesn't fit on canvas."),
			formatter() << "x = " << x << ", radius = " << radius
						 << "', canvas width = " << canvas->w << ".");

	VALIDATE_WITH_DEV_MESSAGE(
			static_cast<int>(y + radius) < canvas->h,
			_("Circle doesn't fit on canvas."),
			formatter() << "y = " << y << ", radius = " << radius
						 << "', canvas height = " << canvas->h << ".");

	// lock the surface
	surface_lock locker(canvas);

	const color_t fill_color = fill_color_(variables);
	if(!fill_color.null() && radius) {
		fill_circle(canvas, renderer, fill_color, x, y, radius);
	}

	const color_t border_color = border_color_(variables);
	for(unsigned int i = 0; i < border_thickness_; i++) {
		draw_circle(canvas, renderer, border_color, x, y, radius - i);
	}
}

/***** ***** ***** ***** ***** IMAGE ***** ***** ***** ***** *****/

/*WIKI
 * @page = GUICanvasWML
 *
 * == Image ==
 * @begin{tag}{name="image"}{min="0"}{max="-1"}
 * Definition of an image.
 *
 * Keys:
 * @begin{table}{config}
 *     x & f_unsigned & 0 &            The x coordinate of the top left corner.
 *                                     $
 *     y & f_unsigned & 0 &            The y coordinate of the top left corner.
 *                                     $
 *     w & f_unsigned & 0 &            The width of the image, if not zero the
 *                                     image will be scaled to the desired
 *                                     width. $
 *     h & f_unsigned & 0 &            The height of the image, if not zero the
 *                                     image will be scaled to the desired
 *                                     height. $
 *     resize_mode & resize_mode & scale &
 *                                     Determines how an image is scaled to fit
 *                                     the wanted size. $
 *     vertical_mirror & f_bool & false &
 *                                     Mirror the image over the vertical axis.
 *                                     $
 *     name & f_string & "" &          The name of the image. $
 *     debug & string & "" &           Debug message to show upon creation
 *                                     this message is not stored. $
 *
 * @end{table}
 * @end{tag}{name="image"}
 * Variables:
 * @begin{table}{formula}
 *     image_width & unsigned &         The width of the image, either the
 *                                      requested width or the natural width of
 *                                      the image. This value can be used to set
 *                                      the x (or y) value of the image. (This
 *                                      means x and y are evaluated after the
 *                                      width and height.) $
 *     image_height & unsigned &        The height of the image, either the
 *                                      requested height or the natural height
 *                                      of the image. This value can be used to
 *                                      set the y (or x) value of the image.
 *                                      (This means x and y are evaluated after
 *                                      the width and height.) $
 *     image_original_width & unsigned &
 *                                      The width of the image as stored on
 *                                      disk, can be used to set x or w
 *                                      (also y and h can be set). $
 *     image_original_height & unsigned &
 *                                      The height of the image as stored on
 *                                      disk, can be used to set y or h
 *                                      (also x and y can be set). $
 * @end{table}
 * Also the general variables are available, see [[#general_variables|Line]].
 */

image_shape::image_shape(const config& cfg, wfl::action_function_symbol_table& functions)
	: shape(cfg)
	, x_(cfg["x"])
	, y_(cfg["y"])
	, w_(cfg["w"])
	, h_(cfg["h"])
	, src_clip_()
	, image_()
	, image_name_(cfg["name"])
	, resize_mode_(get_resize_mode(cfg["resize_mode"]))
	, vertical_mirror_(cfg["vertical_mirror"])
	, actions_formula_(cfg["actions"], &functions)
{
	const std::string& debug = (cfg["debug"]);
	if(!debug.empty()) {
		DBG_GUI_P << "Image: found debug message '" << debug << "'.\n";
	}
}

void image_shape::dimension_validation(unsigned value, const std::string& name, const std::string& key)
{
	const int as_int = static_cast<int>(value);

	VALIDATE_WITH_DEV_MESSAGE(as_int >= 0, _("Image doesn't fit on canvas."),
		formatter() << "Image '" << name << "', " << key << " = " << as_int << "."
	);
}

void image_shape::draw(surface& canvas,
				  SDL_Renderer* /*renderer*/,
				  wfl::map_formula_callable& variables)
{
	DBG_GUI_D << "Image: draw.\n";

	/**
	 * @todo formulas are now recalculated every draw cycle which is a  bit
	 * silly unless there has been a resize. So to optimize we should use an
	 * extra flag or do the calculation in a separate routine.
	 */
	const std::string& name = image_name_(variables);

	if(name.empty()) {
		DBG_GUI_D << "Image: formula returned no value, will not be drawn.\n";
		return;
	}

	/*
	 * The locator might return a different surface for every call so we can't
	 * cache the output, also not if no formula is used.
	 */
	surface tmp(image::get_image(image::locator(name)));

	if(!tmp) {
		ERR_GUI_D << "Image: '" << name << "' not found and won't be drawn." << std::endl;
		return;
	}

	image_ = tmp;
	assert(image_);
	src_clip_ = {0, 0, image_->w, image_->h};

	wfl::map_formula_callable local_variables(variables);
	local_variables.add("image_original_width", wfl::variant(image_->w));
	local_variables.add("image_original_height", wfl::variant(image_->h));

	unsigned w = w_(local_variables);
	dimension_validation(w, name, "w");

	unsigned h = h_(local_variables);
	dimension_validation(h, name, "h");

	local_variables.add("image_width", wfl::variant(w ? w : image_->w));
	local_variables.add("image_height", wfl::variant(h ? h : image_->h));

	const unsigned clip_x = x_(local_variables);
	dimension_validation(clip_x, name, "x");

	const unsigned clip_y = y_(local_variables);
	dimension_validation(clip_y, name, "y");

	local_variables.add("clip_x", wfl::variant(clip_x));
	local_variables.add("clip_y", wfl::variant(clip_y));

	// Execute the provided actions for this context.
	wfl::variant(variables.fake_ptr()).execute_variant(actions_formula_.evaluate(local_variables));

	// Copy the data to local variables to avoid overwriting the originals.
	SDL_Rect src_clip = src_clip_;
	SDL_Rect dst_clip = sdl::create_rect(clip_x, clip_y, 0, 0);
	surface surf;

	// Test whether we need to scale and do the scaling if needed.
	if ((w == 0) && (h == 0)) {
		surf = image_;
	}
	else { // assert((w != 0) || (h != 0))
		if(w == 0 && resize_mode_ == stretch) {
			DBG_GUI_D << "Image: vertical stretch from " << image_->w << ','
					  << image_->h << " to a height of " << h << ".\n";

			surf = stretch_surface_vertical(image_, h);
			w = image_->w;
		}
		else if(h == 0 && resize_mode_ == stretch) {
			DBG_GUI_D << "Image: horizontal stretch from " << image_->w
					  << ',' << image_->h << " to a width of " << w
					  << ".\n";

			surf = stretch_surface_horizontal(image_, w);
			h = image_->h;
		}
		else {
			if(w == 0) {
				w = image_->w;
			}
			if(h == 0) {
				h = image_->h;
			}
			if(resize_mode_ == tile) {
				DBG_GUI_D << "Image: tiling from " << image_->w << ','
						  << image_->h << " to " << w << ',' << h << ".\n";

				surf = tile_surface(image_, w, h, false);
			} else if(resize_mode_ == tile_center) {
				DBG_GUI_D << "Image: tiling centrally from " << image_->w << ','
						  << image_->h << " to " << w << ',' << h << ".\n";

				surf = tile_surface(image_, w, h, true);
			} else {
				if(resize_mode_ == stretch) {
					ERR_GUI_D << "Image: failed to stretch image, "
								 "fall back to scaling.\n";
				}

				DBG_GUI_D << "Image: scaling from " << image_->w << ','
						  << image_->h << " to " << w << ',' << h << ".\n";

				surf = scale_surface_legacy(image_, w, h);
			}
		}
		src_clip.w = w;
		src_clip.h = h;
	}

	if(vertical_mirror_(local_variables)) {
		surf = flip_surface(surf);
	}

	blit_surface(surf, &src_clip, canvas, &dst_clip);
}

image_shape::resize_mode image_shape::get_resize_mode(const std::string& resize_mode)
{
	if(resize_mode == "tile") {
		return image_shape::tile;
	} else if(resize_mode == "tile_center") {
		return image_shape::tile_center;
	} else if(resize_mode == "stretch") {
		return image_shape::stretch;
	} else {
		if(!resize_mode.empty() && resize_mode != "scale") {
			ERR_GUI_E << "Invalid resize mode '" << resize_mode
					  << "' falling back to 'scale'.\n";
		}
		return image_shape::scale;
	}
}

/***** ***** ***** ***** ***** TEXT ***** ***** ***** ***** *****/

/*WIKI
 * @page = GUICanvasWML
 *
 * == Text ==
 * @begin{tag}{name="text"}{min="0"}{max="-1"}
 * Definition of text.
 *
 * Keys:
 * @begin{table}{config}
 *     x & f_unsigned & 0 &            The x coordinate of the top left corner.
 *                                     $
 *     y & f_unsigned & 0 &            The y coordinate of the top left corner.
 *                                     $
 *     w & f_unsigned & 0 &            The width of the text's bounding
 *                                     rectangle. $
 *     h & f_unsigned & 0 &            The height of the text's bounding
 *                                     rectangle. $
 *     font_family & font_family & "sans" &
 *                                     The font family used for the text. $
 *     font_size & unsigned & &        The size of the text font. $
 *     font_style & font_style & "" &  The style of the text. $
 *     text_alignment & f_h_align & "left" &
 *                                     The alignment of the text. $
 *     color & f_color & "" &          The color of the text. $
 *     text & f_tstring & "" &         The text to draw (translatable). $
 *     text_markup & f_bool & false &  Can the text have mark-up? $
 *     text_link_aware & f_bool & false &
 *                                     Is the text link aware? $
 *     text_link_color & f_string & "#ffff00" &
 *                                     The color of links in the text $
 *     maximum_width & f_int & -1 &    The maximum width the text is allowed to
 *                                     be. $
 *     maximum_height & f_int & -1 &   The maximum height the text is allowed
 *                                     to be. $
 *     debug & string & "" &           Debug message to show upon creation
 *                                     this message is not stored. $
 * @end{table}
 * @end{tag}{name="text"}
 * NOTE alignment could only be done with the formulas, but now with the
 * text_alignment flag as well, older widgets might still use the formulas and
 * not all widgets may expose the text alignment yet and when exposed not use
 * it yet.
 *
 * Variables:
 * @begin{table}{formula}
 *     text_width & unsigned &            The width of the rendered text. $
 *     text_height & unsigned &           The height of the rendered text. $
 * @end{table}
 * Also the general variables are available, see [[#general_variables|Line]].
 * @end{parent}{name="generic/state/draw/"}
 */

text_shape::text_shape(const config& cfg)
	: shape(cfg)
	, x_(cfg["x"])
	, y_(cfg["y"])
	, w_(cfg["w"])
	, h_(cfg["h"])
	, font_family_(font::str_to_family_class(cfg["font_family"]))
	, font_size_(cfg["font_size"])
	, font_style_(decode_font_style(cfg["font_style"]))
	, text_alignment_(cfg["text_alignment"])
	, color_(cfg["color"])
	, text_(cfg["text"])
	, text_markup_(cfg["text_markup"], false)
	, link_aware_(cfg["text_link_aware"], false)
	, link_color_(cfg["text_link_color"], color_t::from_hex_string("ffff00"))
	, maximum_width_(cfg["maximum_width"], -1)
	, characters_per_line_(cfg["text_characters_per_line"])
	, maximum_height_(cfg["maximum_height"], -1)
{
	if(!font_size_.has_formula()) {
		VALIDATE(font_size_(), _("Text has a font size of 0."));
	}

	const std::string& debug = (cfg["debug"]);
	if(!debug.empty()) {
		DBG_GUI_P << "Text: found debug message '" << debug << "'.\n";
	}
}

void text_shape::draw(surface& canvas,
				 SDL_Renderer* /*renderer*/,
				 wfl::map_formula_callable& variables)
{
	assert(variables.has_key("text"));

	// We first need to determine the size of the text which need the rendered
	// text. So resolve and render the text first and then start to resolve
	// the other formulas.
	const t_string text = text_(variables);

	if(text.empty()) {
		DBG_GUI_D << "Text: no text to render, leave.\n";
		return;
	}

	static font::pango_text text_renderer;

	text_renderer
		.set_link_aware(link_aware_(variables))
		.set_link_color(link_color_(variables))
		.set_text(text, text_markup_(variables));

	text_renderer.set_family_class(font_family_)
		.set_font_size(font_size_(variables))
		.set_font_style(font_style_)
		.set_alignment(text_alignment_(variables))
		.set_foreground_color(color_(variables))
		.set_maximum_width(maximum_width_(variables))
		.set_maximum_height(maximum_height_(variables), true)
		.set_ellipse_mode(variables.has_key("text_wrap_mode")
				? static_cast<PangoEllipsizeMode>(variables.query_value("text_wrap_mode").as_int())
				: PANGO_ELLIPSIZE_END)
		.set_characters_per_line(characters_per_line_);

	surface& surf = text_renderer.render();
	if(surf->w == 0) {
		DBG_GUI_D << "Text: Rendering '" << text
				  << "' resulted in an empty canvas, leave.\n";
		return;
	}

	wfl::map_formula_callable local_variables(variables);
	local_variables.add("text_width", wfl::variant(surf->w));
	local_variables.add("text_height", wfl::variant(surf->h));
	/*
		std::cerr << "Text: drawing text '" << text
			<< " maximum width " << maximum_width_(variables)
			<< " maximum height " << maximum_height_(variables)
			<< " text width " << surf->w
			<< " text height " << surf->h;
	*/
	///@todo formulas are now recalculated every draw cycle which is a
	// bit silly unless there has been a resize. So to optimize we should
	// use an extra flag or do the calculation in a separate routine.

	const unsigned x = x_(local_variables);
	const unsigned y = y_(local_variables);
	const unsigned w = w_(local_variables);
	const unsigned h = h_(local_variables);

	DBG_GUI_D << "Text: drawing text '" << text << "' drawn from " << x << ','
			  << y << " width " << w << " height " << h << " canvas size "
			  << canvas->w << ',' << canvas->h << ".\n";

	VALIDATE(static_cast<int>(x) < canvas->w && static_cast<int>(y) < canvas->h,
			 _("Text doesn't start on canvas."));

	// A text might be to long and will be clipped.
	if(surf->w > static_cast<int>(w)) {
		WRN_GUI_D << "Text: text is too wide for the "
					 "canvas and will be clipped.\n";
	}

	if(surf->h > static_cast<int>(h)) {
		WRN_GUI_D << "Text: text is too high for the "
					 "canvas and will be clipped.\n";
	}

	SDL_Rect dst = sdl::create_rect(x, y, canvas->w, canvas->h);
	blit_surface(surf, nullptr, canvas, &dst);
}

/***** ***** ***** ***** ***** CANVAS ***** ***** ***** ***** *****/

canvas::canvas()
	: shapes_()
	, drawn_shapes_()
	, blur_depth_(0)
	, w_(0)
	, h_(0)
	, canvas_()
	, renderer_(nullptr)
	, variables_()
	, functions_()
	, is_dirty_(true)
{
}

canvas::canvas(canvas&& c) NOEXCEPT
	: shapes_(std::move(c.shapes_))
	, drawn_shapes_(std::move(c.drawn_shapes_))
	, blur_depth_(c.blur_depth_)
	, w_(c.w_)
	, h_(c.h_)
	, canvas_(std::move(c.canvas_))
	, renderer_(c.renderer_)
	, variables_(c.variables_)
	, functions_(c.functions_)
	, is_dirty_(c.is_dirty_)
{
	// Needed to ensure the other object doesn't destroy our software renderer prematurely.
	c.renderer_ = nullptr;
}

canvas::~canvas()
{
	if(renderer_)
		SDL_DestroyRenderer(renderer_);
}

void canvas::draw(const bool force)
{
	log_scope2(log_gui_draw, "Canvas: drawing.");
	if(!is_dirty_ && !force) {
		DBG_GUI_D << "Canvas: nothing to draw.\n";
		return;
	}

	if(is_dirty_) {
		get_screen_size_variables(variables_);
		variables_.add("width", wfl::variant(w_));
		variables_.add("height", wfl::variant(h_));
	}

	if(canvas_) {
		DBG_GUI_D << "Canvas: use cached canvas.\n";
	} else {
		// create surface
		DBG_GUI_D << "Canvas: create new empty canvas.\n";
		canvas_ = surface(w_, h_);
	}

	SDL_DestroyRenderer(renderer_);

	renderer_ = SDL_CreateSoftwareRenderer(canvas_);
	SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);

	// draw items
	for(auto& shape : shapes_) {
		lg::scope_logger inner_scope_logging_object__(log_gui_draw, "Canvas: draw shape.");

		shape->draw(canvas_, renderer_, variables_);
	}

	// The shapes have been drawn and the draw result has been cached. Clear the list.
	std::copy(shapes_.begin(), shapes_.end(), std::back_inserter(drawn_shapes_));
	shapes_.clear();

	SDL_RenderPresent(renderer_);

	is_dirty_ = false;
}

void canvas::blit(surface& surf, SDL_Rect rect)
{
	draw();

	if(blur_depth_) {
		/*
		 * If the surf is the video surface the blurring seems to stack, this
		 * can be seen in the title screen. So also use the not 32 bpp method
		 * for this situation.
		 */
		if(surf != CVideo::get_singleton().getSurface() && surf.is_neutral()) {
			blur_surface(surf, rect, blur_depth_);
		} else {
			// Can't directly blur the surface if not 32 bpp.
			SDL_Rect r = rect;
			surface s = get_surface_portion(surf, r);
			s = blur_surface(s, blur_depth_);
			sdl_blit(s, nullptr, surf, &r);
		}
	}

	sdl_blit(canvas_, nullptr, surf, &rect);
}

void canvas::parse_cfg(const config& cfg)
{
	log_scope2(log_gui_parse, "Canvas: parsing config.");

	for(const auto & shape : cfg.all_children_range())
	{
		const std::string& type = shape.key;
		const config& data = shape.cfg;

		DBG_GUI_P << "Canvas: found shape of the type " << type << ".\n";

		if(type == "line") {
			shapes_.emplace_back(std::make_shared<line_shape>(data));
		} else if(type == "rectangle") {
			shapes_.emplace_back(std::make_shared<rectangle_shape>(data));
		} else if(type == "round_rectangle") {
			shapes_.emplace_back(std::make_shared<round_rectangle_shape>(data));
		} else if(type == "circle") {
			shapes_.emplace_back(std::make_shared<circle_shape>(data));
		} else if(type == "image") {
			shapes_.emplace_back(std::make_shared<image_shape>(data, functions_));
		} else if(type == "text") {
			shapes_.emplace_back(std::make_shared<text_shape>(data));
		} else if(type == "pre_commit") {

			/* note this should get split if more preprocessing is used. */
			for(const auto & function : data.all_children_range())
			{

				if(function.key == "blur") {
					blur_depth_ = function.cfg["depth"];
				} else {
					ERR_GUI_P << "Canvas: found a pre commit function"
							  << " of an invalid type " << type << ".\n";
				}
			}

		} else {
			ERR_GUI_P << "Canvas: found a shape of an invalid type " << type
					  << ".\n";

			assert(false);
		}
	}
}

void canvas::clear_shapes(const bool force)
{
	auto conditional = [force](const shape_ptr s)->bool { return !s->immutable() && !force; };

	auto iter = std::remove_if(shapes_.begin(), shapes_.end(), conditional);
	shapes_.erase(iter, shapes_.end());

	iter = std::remove_if(drawn_shapes_.begin(), drawn_shapes_.end(), conditional);
	drawn_shapes_.erase(iter, drawn_shapes_.end());
}

void canvas::invalidate_cache()
{
	canvas_ = nullptr;

	if(shapes_.empty()) {
		shapes_.swap(drawn_shapes_);
	} else {
		std::copy(drawn_shapes_.begin(), drawn_shapes_.end(), std::inserter(shapes_, shapes_.begin()));
		drawn_shapes_.clear();
	}
}

/***** ***** ***** ***** ***** SHAPE ***** ***** ***** ***** *****/

} // namespace gui2

/*WIKI
 * @page = GUICanvasWML
 * @order = ZZZZZZ_footer
 *
 * [[Category: WML Reference]]
 * [[Category: GUI WML Reference]]
 *
 */

/*WIKI
 * @page = GUIVariable
 * @order = ZZZZZZ_footer
 *
 * [[Category: WML Reference]]
 * [[Category: GUI WML Reference]]
 */
