mirror of
https://github.com/wesnoth/wesnoth
synced 2025-04-20 16:20:32 +00:00

This was a small proposed change as part of PR #844. While that PR is rather outdated at this point, this seemed like a simple, useful change.
1543 lines
54 KiB
C++
1543 lines
54 KiB
C++
/*
|
|
Copyright (C) 2007 - 2016 by Mark de Wever <koraq@xs4all.nl>
|
|
Part of the Battle for Wesnoth Project http://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 "config.hpp"
|
|
#include "font/text.hpp"
|
|
#include "formatter.hpp"
|
|
#include "gettext.hpp"
|
|
#include "image.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"
|
|
|
|
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 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.
|
|
*/
|
|
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";
|
|
|
|
assert((x_center + radius) < canvas->w);
|
|
assert((x_center - radius) >= 0);
|
|
assert((y_center + radius) < canvas->h);
|
|
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)) {
|
|
points.push_back({x_center + x, y_center + y});
|
|
points.push_back({x_center + x, y_center - y});
|
|
points.push_back({x_center - x, y_center + y});
|
|
points.push_back({x_center - x, y_center - y});
|
|
|
|
points.push_back({x_center + y, y_center + x});
|
|
points.push_back({x_center + y, y_center - x});
|
|
points.push_back({x_center - y, y_center + x});
|
|
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());
|
|
}
|
|
|
|
/***** ***** ***** ***** ***** LINE ***** ***** ***** ***** *****/
|
|
|
|
/** Definition of a line shape. */
|
|
class line_shape : public canvas::shape
|
|
{
|
|
public:
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param cfg The config object to define the line see
|
|
* http://www.wesnoth.org/wiki/GUICanvasWML#Line
|
|
* for more information.
|
|
*/
|
|
explicit line_shape(const config& cfg);
|
|
|
|
/** Implement shape::draw(). */
|
|
void draw(surface& canvas,
|
|
SDL_Renderer* renderer,
|
|
const game_logic::map_formula_callable& variables);
|
|
|
|
private:
|
|
typed_formula<unsigned> x1_, /**< The start x coordinate of the line. */
|
|
y1_, /**< The start y coordinate of the line. */
|
|
x2_, /**< The end x coordinate of the line. */
|
|
y2_, /**< The end y coordinate of the line. */
|
|
alpha_; /**< Alpha value override computed as a formula. */
|
|
|
|
/** The color of the line. */
|
|
color_t color_;
|
|
|
|
/**
|
|
* The thickness of the line.
|
|
*
|
|
* if the value is odd the x and y are the middle of the line.
|
|
* if the value is even the x and y are the middle of a line
|
|
* with width - 1. (0 is special case, does nothing.)
|
|
*/
|
|
unsigned thickness_;
|
|
};
|
|
|
|
/*WIKI
|
|
* @page = GUICanvasWML
|
|
*
|
|
* == Line ==
|
|
* @begin{tag}{name="line"}{min="0"}{max="-1"}
|
|
* Definition of a line. When drawing a line 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}
|
|
* 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 & 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.)
|
|
*/
|
|
|
|
/*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. $
|
|
*
|
|
* 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. $
|
|
*
|
|
* grow_direction & 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. $
|
|
* @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_shape::line_shape(const config& cfg)
|
|
: x1_(cfg["x1"])
|
|
, y1_(cfg["y1"])
|
|
, x2_(cfg["x2"])
|
|
, y2_(cfg["y2"])
|
|
, alpha_(cfg["alpha"])
|
|
, color_(decode_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,
|
|
const game_logic::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);
|
|
const unsigned alpha = alpha_(variables);
|
|
|
|
// Override alpha from color with formula.
|
|
if(alpha_.has_formula()) {
|
|
color_.a = alpha;
|
|
}
|
|
|
|
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_, x1, y1, x2, y2);
|
|
}
|
|
|
|
/***** ***** ***** ***** ***** Rectangle ***** ***** ***** ***** *****/
|
|
|
|
/** Definition of a rectangle shape. */
|
|
class rectangle_shape : public canvas::shape
|
|
{
|
|
public:
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param cfg The config object to define the rectangle see
|
|
* http://www.wesnoth.org/wiki/GUICanvasWML#Rectangle
|
|
* for more information.
|
|
*/
|
|
explicit rectangle_shape(const config& cfg);
|
|
|
|
/** Implement shape::draw(). */
|
|
void draw(surface& canvas,
|
|
SDL_Renderer* renderer,
|
|
const game_logic::map_formula_callable& variables);
|
|
|
|
private:
|
|
typed_formula<int> x_, /**< The x coordinate of the rectangle. */
|
|
y_, /**< The y coordinate of the rectangle. */
|
|
w_, /**< The width of the rectangle. */
|
|
h_; /**< The height of the rectangle. */
|
|
|
|
/**
|
|
* Border thickness.
|
|
*
|
|
* If 0 the fill color is used for the entire widget.
|
|
*/
|
|
int border_thickness_;
|
|
|
|
/**
|
|
* The border color of the rectangle.
|
|
*
|
|
* If the color is fully transparent the border isn't drawn.
|
|
*/
|
|
color_t border_color_;
|
|
|
|
/**
|
|
* The border color of the rectangle.
|
|
*
|
|
* If the color is fully transparent the rectangle won't be filled.
|
|
*/
|
|
color_t fill_color_;
|
|
};
|
|
|
|
/*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 & color & "" & The color of the border if empty it's
|
|
* not drawn. $
|
|
* fill_color & 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)
|
|
: x_(cfg["x"])
|
|
, y_(cfg["y"])
|
|
, w_(cfg["w"])
|
|
, h_(cfg["h"])
|
|
, border_thickness_(cfg["border_thickness"])
|
|
, border_color_(decode_color(cfg["border_color"]))
|
|
, fill_color_(decode_color(cfg["fill_color"]))
|
|
{
|
|
if(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,
|
|
const game_logic::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);
|
|
|
|
// 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_);
|
|
|
|
SDL_RenderDrawRect(renderer, &dimensions);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
/***** ***** ***** ***** ***** CIRCLE ***** ***** ***** ***** *****/
|
|
|
|
/** Definition of a circle shape. */
|
|
class circle_shape : public canvas::shape
|
|
{
|
|
public:
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param cfg The config object to define the circle see
|
|
* http://www.wesnoth.org/wiki/GUICanvasWML#Circle
|
|
* for more information.
|
|
*/
|
|
explicit circle_shape(const config& cfg);
|
|
|
|
/** Implement shape::draw(). */
|
|
void draw(surface& canvas,
|
|
SDL_Renderer* renderer,
|
|
const game_logic::map_formula_callable& variables);
|
|
|
|
private:
|
|
typed_formula<unsigned> x_, /**< The center x coordinate of the circle. */
|
|
y_, /**< The center y coordinate of the circle. */
|
|
radius_; /**< The radius of the circle. */
|
|
|
|
/** The color of the circle. */
|
|
color_t color_;
|
|
};
|
|
|
|
/*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. $
|
|
* color & 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)
|
|
: x_(cfg["x"])
|
|
, y_(cfg["y"])
|
|
, radius_(cfg["radius"])
|
|
, color_(decode_color(cfg["color"]))
|
|
{
|
|
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,
|
|
const game_logic::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);
|
|
|
|
draw_circle(canvas, renderer, color_, x, y, radius);
|
|
}
|
|
|
|
/***** ***** ***** ***** ***** IMAGE ***** ***** ***** ***** *****/
|
|
|
|
/** Definition of an image shape. */
|
|
class image_shape : public canvas::shape
|
|
{
|
|
public:
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param cfg The config object to define the image see
|
|
* http://www.wesnoth.org/wiki/GUICanvasWML#Image
|
|
* for more information.
|
|
*/
|
|
explicit image_shape(const config& cfg);
|
|
|
|
/** Implement shape::draw(). */
|
|
void draw(surface& canvas,
|
|
SDL_Renderer* renderer,
|
|
const game_logic::map_formula_callable& variables);
|
|
|
|
private:
|
|
typed_formula<unsigned> x_, /**< The x coordinate of the image. */
|
|
y_, /**< The y coordinate of the image. */
|
|
w_, /**< The width of the image. */
|
|
h_; /**< The height of the image. */
|
|
|
|
/** Contains the size of the image. */
|
|
SDL_Rect src_clip_;
|
|
|
|
/** The image is cached in this surface. */
|
|
surface image_;
|
|
|
|
|
|
|
|
/**
|
|
* Name of the image.
|
|
*
|
|
* This value is only used when the image name is a formula. If it isn't a
|
|
* formula the image will be loaded in the constructor. If it's a formula it
|
|
* will be loaded every draw cycles. This allows 'changing' images.
|
|
*/
|
|
typed_formula<std::string> image_name_;
|
|
|
|
/**
|
|
* Determines the way an image will be resized.
|
|
*
|
|
* If the image is smaller is needed it needs to resized, how is determined
|
|
* by the value of this enum.
|
|
*/
|
|
enum resize_mode {
|
|
scale,
|
|
stretch,
|
|
tile
|
|
};
|
|
|
|
/** Converts a string to a resize mode. */
|
|
resize_mode get_resize_mode(const std::string& resize_mode);
|
|
|
|
/** The resize mode for an image. */
|
|
resize_mode resize_mode_;
|
|
|
|
/** Mirror the image over the vertical axis. */
|
|
typed_formula<bool> vertical_mirror_;
|
|
};
|
|
|
|
/*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)
|
|
: 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"])
|
|
{
|
|
const std::string& debug = (cfg["debug"]);
|
|
if(!debug.empty()) {
|
|
DBG_GUI_P << "Image: found debug message '" << debug << "'.\n";
|
|
}
|
|
}
|
|
|
|
void image_shape::draw(surface& canvas,
|
|
SDL_Renderer* /*renderer*/,
|
|
const game_logic::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_.assign(make_neutral_surface(tmp));
|
|
assert(image_);
|
|
src_clip_ = sdl::create_rect(0, 0, image_->w, image_->h);
|
|
|
|
game_logic::map_formula_callable local_variables(variables);
|
|
local_variables.add("image_original_width", variant(image_->w));
|
|
local_variables.add("image_original_height", variant(image_->h));
|
|
|
|
unsigned w = w_(local_variables);
|
|
VALIDATE_WITH_DEV_MESSAGE(static_cast<int>(w) >= 0,
|
|
_("Image doesn't fit on canvas."),
|
|
formatter() << "Image '" << name
|
|
<< "', w = " << static_cast<int>(w)
|
|
<< ".");
|
|
|
|
unsigned h = h_(local_variables);
|
|
VALIDATE_WITH_DEV_MESSAGE(static_cast<int>(h) >= 0,
|
|
_("Image doesn't fit on canvas."),
|
|
formatter() << "Image '" << name
|
|
<< "', h = " << static_cast<int>(h)
|
|
<< ".");
|
|
|
|
local_variables.add("image_width", variant(w ? w : image_->w));
|
|
local_variables.add("image_height", variant(h ? h : image_->h));
|
|
|
|
const unsigned clip_x = x_(local_variables);
|
|
VALIDATE_WITH_DEV_MESSAGE(static_cast<int>(clip_x) >= 0,
|
|
_("Image doesn't fit on canvas."),
|
|
formatter() << "Image '" << name
|
|
<< "', x = " << static_cast<int>(clip_x)
|
|
<< ".");
|
|
|
|
const unsigned clip_y = y_(local_variables);
|
|
VALIDATE_WITH_DEV_MESSAGE(static_cast<int>(clip_y) >= 0,
|
|
_("Image doesn't fit on canvas."),
|
|
formatter() << "Image '" << name
|
|
<< "', y = " << static_cast<int>(clip_y)
|
|
<< ".");
|
|
|
|
// 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 || h) {
|
|
bool done = false;
|
|
bool stretch_image = (resize_mode_ == stretch) && (!!w ^ !!h);
|
|
if(!w) {
|
|
if(stretch_image) {
|
|
DBG_GUI_D << "Image: vertical stretch from " << image_->w << ','
|
|
<< image_->h << " to a height of " << h << ".\n";
|
|
|
|
surf = stretch_surface_vertical(image_, h, false);
|
|
done = true;
|
|
}
|
|
w = image_->w;
|
|
}
|
|
|
|
if(!h) {
|
|
if(stretch_image) {
|
|
DBG_GUI_D << "Image: horizontal stretch from " << image_->w
|
|
<< ',' << image_->h << " to a width of " << w
|
|
<< ".\n";
|
|
|
|
surf = stretch_surface_horizontal(image_, w, false);
|
|
done = true;
|
|
}
|
|
h = image_->h;
|
|
}
|
|
|
|
if(!done) {
|
|
|
|
if(resize_mode_ == tile) {
|
|
DBG_GUI_D << "Image: tiling from " << image_->w << ','
|
|
<< image_->h << " to " << w << ',' << h << ".\n";
|
|
|
|
const int columns = (w + image_->w - 1) / image_->w;
|
|
const int rows = (h + image_->h - 1) / image_->h;
|
|
surf = create_neutral_surface(w, h);
|
|
|
|
for(int x = 0; x < columns; ++x) {
|
|
for(int y = 0; y < rows; ++y) {
|
|
const SDL_Rect dest = sdl::create_rect(
|
|
x * image_->w, y * image_->h, 0, 0);
|
|
blit_surface(image_, nullptr, surf, &dest);
|
|
}
|
|
}
|
|
|
|
} 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(image_, w, h, false);
|
|
}
|
|
}
|
|
src_clip.w = w;
|
|
src_clip.h = h;
|
|
} else {
|
|
surf = image_;
|
|
}
|
|
|
|
if(vertical_mirror_(local_variables)) {
|
|
surf = flip_surface(surf, false);
|
|
}
|
|
|
|
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 == "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 ***** ***** ***** ***** *****/
|
|
|
|
/** Definition of a text shape. */
|
|
class text_shape : public canvas::shape
|
|
{
|
|
public:
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param cfg The config object to define the text see
|
|
* http://www.wesnoth.org/wiki/GUICanvasWML#Text
|
|
* for more information.
|
|
*/
|
|
explicit text_shape(const config& cfg);
|
|
|
|
/** Implement shape::draw(). */
|
|
void draw(surface& canvas,
|
|
SDL_Renderer* renderer,
|
|
const game_logic::map_formula_callable& variables);
|
|
|
|
private:
|
|
typed_formula<unsigned> x_, /**< The x coordinate of the text. */
|
|
y_, /**< The y coordinate of the text. */
|
|
w_, /**< The width of the text. */
|
|
h_; /**< The height of the text. */
|
|
|
|
/** The text font family. */
|
|
font::family_class font_family_;
|
|
|
|
/** The font size of the text. */
|
|
unsigned font_size_;
|
|
|
|
/** The style of the text. */
|
|
font::pango_text::FONT_STYLE font_style_;
|
|
|
|
/** The alignment of the text. */
|
|
typed_formula<PangoAlignment> text_alignment_;
|
|
|
|
/** The color of the text. */
|
|
color_t color_;
|
|
|
|
/** The text to draw. */
|
|
typed_formula<t_string> text_;
|
|
|
|
/** The text markup switch of the text. */
|
|
typed_formula<bool> text_markup_;
|
|
|
|
/** The link aware switch of the text. */
|
|
typed_formula<bool> link_aware_;
|
|
|
|
/** The link color of the text. */
|
|
typed_formula<color_t> link_color_;
|
|
|
|
/** The maximum width for the text. */
|
|
typed_formula<int> maximum_width_;
|
|
|
|
/** The number of characters per line. */
|
|
unsigned characters_per_line_;
|
|
|
|
/** The maximum height for the text. */
|
|
typed_formula<int> maximum_height_;
|
|
};
|
|
|
|
/*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 & 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)
|
|
: 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_(decode_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)
|
|
{
|
|
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*/,
|
|
const game_logic::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));
|
|
text_renderer.set_text(text, text_markup_(variables));
|
|
|
|
text_renderer
|
|
.set_family_class(font_family_)
|
|
.set_font_size(font_size_)
|
|
.set_font_style(font_style_)
|
|
.set_alignment(text_alignment_(variables))
|
|
.set_foreground_color(color_)
|
|
.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;
|
|
}
|
|
|
|
game_logic::map_formula_callable local_variables(variables);
|
|
local_variables.add("text_width", variant(surf->w));
|
|
local_variables.add("text_height", 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, 0, canvas, &dst);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
/***** ***** ***** ***** ***** CANVAS ***** ***** ***** ***** *****/
|
|
|
|
canvas::canvas()
|
|
: shapes_()
|
|
, blur_depth_(0)
|
|
, w_(0)
|
|
, h_(0)
|
|
, canvas_()
|
|
, renderer_(nullptr)
|
|
, variables_()
|
|
, is_dirty_(true)
|
|
{
|
|
}
|
|
|
|
canvas::~canvas()
|
|
{
|
|
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", variant(w_));
|
|
variables_.add("height", variant(h_));
|
|
}
|
|
|
|
// create surface
|
|
DBG_GUI_D << "Canvas: create new empty canvas.\n";
|
|
canvas_.assign(create_neutral_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_);
|
|
}
|
|
|
|
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() && is_neutral(surf)) {
|
|
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_, false);
|
|
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.");
|
|
shapes_.clear();
|
|
|
|
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_.push_back(std::make_shared<line_shape>(data));
|
|
} else if(type == "rectangle") {
|
|
shapes_.push_back(std::make_shared<rectangle_shape>(data));
|
|
} else if(type == "circle") {
|
|
shapes_.push_back(std::make_shared<circle_shape>(data));
|
|
} else if(type == "image") {
|
|
shapes_.push_back(std::make_shared<image_shape>(data));
|
|
} else if(type == "text") {
|
|
shapes_.push_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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/***** ***** ***** ***** ***** 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]]
|
|
*/
|