2009-08-21 22:21:05 +02:00
/*
* This file is part of OpenTTD .
* OpenTTD 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 , version 2.
* OpenTTD is distributed in the hope that it will be useful , but WITHOUT ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE .
* See the GNU General Public License for more details . You should have received a copy of the GNU General Public License along with OpenTTD . If not , see < http : //www.gnu.org/licenses/>.
*/
2008-05-06 17:11:33 +02:00
/** @file gfx.cpp Handling of drawing text and other gfx related stuff. */
2007-03-01 02:24:44 +01:00
2004-08-09 19:04:08 +02:00
# include "stdafx.h"
2013-06-25 22:38:12 +02:00
# include "gfx_layout.h"
2011-08-21 14:48:46 +02:00
# include "progress.h"
2007-12-23 11:56:02 +01:00
# include "zoom_func.h"
2007-06-17 22:30:28 +02:00
# include "blitter/factory.hpp"
2007-07-05 14:23:54 +02:00
# include "video/video_driver.hpp"
2007-12-21 20:49:27 +01:00
# include "strings_func.h"
2008-01-13 15:37:30 +01:00
# include "settings_type.h"
2010-01-12 01:10:38 +01:00
# include "network/network.h"
2008-08-12 00:45:11 +02:00
# include "network/network_func.h"
2022-09-07 22:17:10 +02:00
# include "window_gui.h"
2009-07-12 14:19:41 +02:00
# include "window_func.h"
2010-04-26 22:35:27 +02:00
# include "newgrf_debug.h"
2021-12-31 00:36:20 +01:00
# include "core/backup_type.hpp"
2023-05-18 11:20:35 +02:00
# include "core/container_func.hpp"
2022-09-07 22:17:10 +02:00
# include "viewport_func.h"
2004-08-09 19:04:08 +02:00
2015-07-26 11:47:17 +02:00
# include "table/string_colours.h"
2008-01-13 02:21:35 +01:00
# include "table/sprites.h"
# include "table/control_codes.h"
2014-04-23 22:13:33 +02:00
# include "safeguards.h"
2024-03-16 23:59:32 +01:00
uint8_t _dirkeys ; ///< 1 = left, 2 = up, 4 = right, 8 = down
2007-01-13 15:43:46 +01:00
bool _fullscreen ;
2024-03-16 23:59:32 +01:00
uint8_t _support8bpp ;
2007-01-13 15:43:46 +01:00
CursorVars _cursor ;
2007-03-01 02:24:44 +01:00
bool _ctrl_pressed ; ///< Is Ctrl pressed?
bool _shift_pressed ; ///< Is Shift pressed?
2023-05-08 19:01:06 +02:00
uint16_t _game_speed = 100 ; ///< Current game-speed; 100 is 1x, 0 is infinite.
2008-04-07 22:28:58 +02:00
bool _left_button_down ; ///< Is left mouse button pressed?
bool _left_button_clicked ; ///< Is left mouse button clicked?
bool _right_button_down ; ///< Is right mouse button pressed?
bool _right_button_clicked ; ///< Is right mouse button clicked?
2007-01-13 15:43:46 +01:00
DrawPixelInfo _screen ;
2008-01-11 18:12:41 +01:00
bool _screen_disable_anim = false ; ///< Disable palette animation (important for 32bpp-anim blitter during giant screenshot)
2021-06-17 18:58:59 +02:00
std : : atomic < bool > _exit_game ;
2009-02-25 01:14:46 +01:00
GameMode _game_mode ;
2009-02-25 01:45:52 +01:00
SwitchMode _switch_mode ; ///< The next mainloop command.
2019-04-22 14:14:46 +02:00
PauseMode _pause_mode ;
2024-03-16 08:58:56 +01:00
GameSessionStats _game_session_stats ; ///< Statistics about the current session.
2009-11-22 17:31:59 +01:00
2024-03-16 23:59:32 +01:00
static uint8_t _stringwidth_table [ FS_END ] [ 224 ] ; ///< Cache containing width of often used characters. @see GetCharacterWidth()
2007-12-23 11:56:02 +01:00
DrawPixelInfo * _cur_dpi ;
2021-06-17 10:38:04 +02:00
2019-04-10 23:07:06 +02:00
static void GfxMainBlitterViewport ( const Sprite * sprite , int x , int y , BlitterMode mode , const SubSprite * sub = nullptr , SpriteID sprite_id = SPR_CURSOR_MOUSE ) ;
2024-04-04 19:49:37 +02:00
static void GfxMainBlitter ( const Sprite * sprite , int x , int y , BlitterMode mode , const SubSprite * sub = nullptr , SpriteID sprite_id = SPR_CURSOR_MOUSE , ZoomLevel zoom = ZOOM_LVL_MIN ) ;
2004-08-09 19:04:08 +02:00
2023-05-08 19:01:06 +02:00
static ReusableBuffer < uint8_t > _cursor_backup ;
2007-09-09 12:13:17 +02:00
2024-04-04 19:51:46 +02:00
ZoomLevel _gui_zoom = ZOOM_LVL_NORMAL ; ///< GUI Zoom level
2023-04-17 00:14:03 +02:00
ZoomLevel _font_zoom = _gui_zoom ; ///< Sprite font Zoom level (not clamped)
int _gui_scale = MIN_INTERFACE_SCALE ; ///< GUI scale, 100 is 100%.
int _gui_scale_cfg ; ///< GUI scale in config.
2021-01-08 22:15:06 +01:00
2007-09-09 12:13:17 +02:00
/**
* The rect for repaint .
*
* This rectangle defines the area which should be repaint by the video driver .
*
* @ ingroup dirty
*/
2004-08-09 19:04:08 +02:00
static Rect _invalid_rect ;
2024-03-16 23:59:32 +01:00
static const uint8_t * _colour_remap_ptr ;
static uint8_t _string_colourremap [ 3 ] ; ///< Recoloursprite for stringdrawing. The grf loader ensures that #SpriteType::Font sprites only use colours 0 to 2.
2004-08-09 19:04:08 +02:00
2010-05-13 12:14:29 +02:00
static const uint DIRTY_BLOCK_HEIGHT = 8 ;
static const uint DIRTY_BLOCK_WIDTH = 64 ;
2008-04-18 23:49:38 +02:00
static uint _dirty_bytes_per_line = 0 ;
2024-03-16 23:59:32 +01:00
static uint8_t * _dirty_blocks = nullptr ;
2012-03-25 21:06:59 +02:00
extern uint _dirty_block_colour ;
2004-08-09 19:04:08 +02:00
2005-07-05 13:09:18 +02:00
void GfxScroll ( int left , int top , int width , int height , int xo , int yo )
{
2014-01-02 23:41:58 +01:00
Blitter * blitter = BlitterFactory : : GetCurrentBlitter ( ) ;
2004-08-09 19:04:08 +02:00
2005-07-05 13:09:18 +02:00
if ( xo = = 0 & & yo = = 0 ) return ;
2004-08-09 19:04:08 +02:00
2005-07-05 13:09:18 +02:00
if ( _cursor . visible ) UndrawMouseCursor ( ) ;
2008-08-12 00:45:11 +02:00
2010-01-12 01:10:38 +01:00
if ( _networking ) NetworkUndrawChatMessage ( ) ;
2004-08-09 19:04:08 +02:00
2007-06-18 22:08:21 +02:00
blitter - > ScrollBuffer ( _screen . dst_ptr , left , top , width , height , xo , yo ) ;
2007-03-01 02:24:44 +01:00
/* This part of the screen is now dirty. */
2014-04-28 23:06:51 +02:00
VideoDriver : : GetInstance ( ) - > MakeDirty ( left , top , width , height ) ;
2004-08-09 19:04:08 +02:00
}
2008-06-28 17:44:24 +02:00
/**
* Applies a certain FillRectMode - operation to a rectangle [ left , right ] x [ top , bottom ] on the screen .
*
2024-04-04 19:49:37 +02:00
* @ pre dpi - > zoom = = ZOOM_LVL_MIN , right > = left , bottom > = top
2008-06-28 17:44:24 +02:00
* @ param left Minimum X ( inclusive )
* @ param top Minimum Y ( inclusive )
* @ param right Maximum X ( inclusive )
* @ param bottom Maximum Y ( inclusive )
2009-02-09 03:57:15 +01:00
* @ param colour A 8 bit palette index ( FILLRECT_OPAQUE and FILLRECT_CHECKER ) or a recolour spritenumber ( FILLRECT_RECOLOUR )
2008-06-28 17:44:24 +02:00
* @ param mode
2009-02-09 03:09:47 +01:00
* FILLRECT_OPAQUE : Fill the rectangle with the specified colour
2008-06-28 17:44:24 +02:00
* FILLRECT_CHECKER : Like FILLRECT_OPAQUE , but only draw every second pixel ( used to grey out things )
2009-02-09 03:57:15 +01:00
* FILLRECT_RECOLOUR : Apply a recolour sprite to every pixel in the rectangle currently on screen
2008-06-28 17:44:24 +02:00
*/
2009-02-09 03:09:47 +01:00
void GfxFillRect ( int left , int top , int right , int bottom , int colour , FillRectMode mode )
2005-07-05 13:09:18 +02:00
{
2014-01-02 23:41:58 +01:00
Blitter * blitter = BlitterFactory : : GetCurrentBlitter ( ) ;
2007-06-12 22:24:12 +02:00
const DrawPixelInfo * dpi = _cur_dpi ;
void * dst ;
2004-11-01 16:08:40 +01:00
const int otop = top ;
const int oleft = left ;
2004-08-09 19:04:08 +02:00
2024-04-04 19:49:37 +02:00
if ( dpi - > zoom ! = ZOOM_LVL_MIN ) return ;
2005-07-05 13:09:18 +02:00
if ( left > right | | top > bottom ) return ;
if ( right < dpi - > left | | left > = dpi - > left + dpi - > width ) return ;
if ( bottom < dpi - > top | | top > = dpi - > top + dpi - > height ) return ;
2004-08-09 19:04:08 +02:00
if ( ( left - = dpi - > left ) < 0 ) left = 0 ;
right = right - dpi - > left + 1 ;
2005-07-05 13:09:18 +02:00
if ( right > dpi - > width ) right = dpi - > width ;
2004-08-09 19:04:08 +02:00
right - = left ;
assert ( right > 0 ) ;
if ( ( top - = dpi - > top ) < 0 ) top = 0 ;
bottom = bottom - dpi - > top + 1 ;
2005-07-05 13:09:18 +02:00
if ( bottom > dpi - > height ) bottom = dpi - > height ;
2004-08-09 19:04:08 +02:00
bottom - = top ;
assert ( bottom > 0 ) ;
2007-06-17 22:30:28 +02:00
dst = blitter - > MoveTo ( dpi - > dst_ptr , left , top ) ;
2004-08-09 19:04:08 +02:00
2008-06-28 17:44:24 +02:00
switch ( mode ) {
default : // FILLRECT_OPAQUE
2023-05-08 19:01:06 +02:00
blitter - > DrawRect ( dst , right , bottom , ( uint8_t ) colour ) ;
2008-06-28 17:44:24 +02:00
break ;
2009-02-09 03:57:15 +01:00
case FILLRECT_RECOLOUR :
blitter - > DrawColourMappingRect ( dst , right , bottom , GB ( colour , 0 , PALETTE_WIDTH ) ) ;
2008-06-28 17:44:24 +02:00
break ;
case FILLRECT_CHECKER : {
2024-03-16 23:59:32 +01:00
uint8_t bo = ( oleft - left + dpi - > left + otop - top + dpi - > top ) & 1 ;
2008-06-28 17:44:24 +02:00
do {
2023-05-08 19:01:06 +02:00
for ( int i = ( bo ^ = 1 ) ; i < right ; i + = 2 ) blitter - > SetPixel ( dst , i , 0 , ( uint8_t ) colour ) ;
2008-06-28 17:44:24 +02:00
dst = blitter - > MoveTo ( dst , 0 , 1 ) ;
} while ( - - bottom > 0 ) ;
break ;
2004-08-09 19:04:08 +02:00
}
}
}
2020-01-05 15:19:32 +01:00
typedef std : : pair < Point , Point > LineSegment ;
/**
* Make line segments from a polygon defined by points , translated by an offset .
* Entirely horizontal lines ( start and end at same Y coordinate ) are skipped , as they are irrelevant to scanline conversion algorithms .
* Generated line segments always have the lowest Y coordinate point first , i . e . original direction is lost .
* @ param shape The polygon to convert .
* @ param offset Offset vector subtracted from all coordinates in the shape .
* @ return Vector of undirected line segments .
*/
static std : : vector < LineSegment > MakePolygonSegments ( const std : : vector < Point > & shape , Point offset )
{
std : : vector < LineSegment > segments ;
if ( shape . size ( ) < 3 ) return segments ; // fewer than 3 will always result in an empty polygon
segments . reserve ( shape . size ( ) ) ;
/* Connect first and last point by having initial previous point be the last */
Point prev = shape . back ( ) ;
prev . x - = offset . x ;
prev . y - = offset . y ;
for ( Point pt : shape ) {
pt . x - = offset . x ;
pt . y - = offset . y ;
/* Create segments for all non-horizontal lines in the polygon.
* The segments always have lowest Y coordinate first . */
if ( prev . y > pt . y ) {
segments . emplace_back ( pt , prev ) ;
} else if ( prev . y < pt . y ) {
segments . emplace_back ( prev , pt ) ;
}
prev = pt ;
}
return segments ;
}
/**
* Fill a polygon with colour .
* The odd - even winding rule is used , i . e . self - intersecting polygons will have holes in them .
* Left and top edges are inclusive , right and bottom edges are exclusive .
* @ note For rectangles the GfxFillRect function will be faster .
2024-04-04 19:49:37 +02:00
* @ pre dpi - > zoom = = ZOOM_LVL_MIN
2020-01-05 15:19:32 +01:00
* @ param shape List of points on the polygon .
* @ param colour An 8 bit palette index ( FILLRECT_OPAQUE and FILLRECT_CHECKER ) or a recolour spritenumber ( FILLRECT_RECOLOUR ) .
* @ param mode
* FILLRECT_OPAQUE : Fill the polygon with the specified colour .
* FILLRECT_CHECKER : Fill every other pixel with the specified colour , in a checkerboard pattern .
* FILLRECT_RECOLOUR : Apply a recolour sprite to every pixel in the polygon .
*/
void GfxFillPolygon ( const std : : vector < Point > & shape , int colour , FillRectMode mode )
{
Blitter * blitter = BlitterFactory : : GetCurrentBlitter ( ) ;
const DrawPixelInfo * dpi = _cur_dpi ;
2024-04-04 19:49:37 +02:00
if ( dpi - > zoom ! = ZOOM_LVL_MIN ) return ;
2020-01-05 15:19:32 +01:00
std : : vector < LineSegment > segments = MakePolygonSegments ( shape , Point { dpi - > left , dpi - > top } ) ;
/* Remove segments appearing entirely above or below the clipping area. */
segments . erase ( std : : remove_if ( segments . begin ( ) , segments . end ( ) , [ dpi ] ( const LineSegment & s ) { return s . second . y < = 0 | | s . first . y > = dpi - > height ; } ) , segments . end ( ) ) ;
/* Check that this wasn't an empty shape (all points on a horizontal line or outside clipping.) */
if ( segments . empty ( ) ) return ;
/* Sort the segments by first point Y coordinate. */
std : : sort ( segments . begin ( ) , segments . end ( ) , [ ] ( const LineSegment & a , const LineSegment & b ) { return a . first . y < b . first . y ; } ) ;
/* Segments intersecting current scanline. */
std : : vector < LineSegment > active ;
/* Intersection points with a scanline.
* Kept outside loop to avoid repeated re - allocations . */
std : : vector < int > intersections ;
/* Normal, reasonable polygons don't have many intersections per scanline. */
active . reserve ( 4 ) ;
intersections . reserve ( 4 ) ;
/* Scan through the segments and paint each scanline. */
int y = segments . front ( ) . first . y ;
std : : vector < LineSegment > : : iterator nextseg = segments . begin ( ) ;
while ( ! active . empty ( ) | | nextseg ! = segments . end ( ) ) {
/* Clean up segments that have ended. */
active . erase ( std : : remove_if ( active . begin ( ) , active . end ( ) , [ y ] ( const LineSegment & s ) { return s . second . y = = y ; } ) , active . end ( ) ) ;
/* Activate all segments starting on this scanline. */
while ( nextseg ! = segments . end ( ) & & nextseg - > first . y = = y ) {
active . push_back ( * nextseg ) ;
+ + nextseg ;
}
/* Check clipping. */
if ( y < 0 ) {
+ + y ;
continue ;
}
if ( y > = dpi - > height ) return ;
/* Intersect scanline with all active segments. */
intersections . clear ( ) ;
for ( const LineSegment & s : active ) {
const int sdx = s . second . x - s . first . x ;
const int sdy = s . second . y - s . first . y ;
const int ldy = y - s . first . y ;
const int x = s . first . x + sdx * ldy / sdy ;
intersections . push_back ( x ) ;
}
/* Fill between pairs of intersections. */
std : : sort ( intersections . begin ( ) , intersections . end ( ) ) ;
for ( size_t i = 1 ; i < intersections . size ( ) ; i + = 2 ) {
/* Check clipping. */
2021-01-08 11:16:18 +01:00
const int x1 = std : : max ( 0 , intersections [ i - 1 ] ) ;
const int x2 = std : : min ( intersections [ i ] , dpi - > width ) ;
2020-01-05 15:19:32 +01:00
if ( x2 < 0 ) continue ;
if ( x1 > = dpi - > width ) continue ;
/* Fill line y from x1 to x2. */
void * dst = blitter - > MoveTo ( dpi - > dst_ptr , x1 , y ) ;
switch ( mode ) {
default : // FILLRECT_OPAQUE
2023-05-08 19:01:06 +02:00
blitter - > DrawRect ( dst , x2 - x1 , 1 , ( uint8_t ) colour ) ;
2020-01-05 15:19:32 +01:00
break ;
case FILLRECT_RECOLOUR :
blitter - > DrawColourMappingRect ( dst , x2 - x1 , 1 , GB ( colour , 0 , PALETTE_WIDTH ) ) ;
break ;
case FILLRECT_CHECKER :
/* Fill every other pixel, offset such that the sum of filled pixels' X and Y coordinates is odd.
* This creates a checkerboard effect . */
for ( int x = ( x1 + y ) & 1 ; x < x2 - x1 ; x + = 2 ) {
2023-05-08 19:01:06 +02:00
blitter - > SetPixel ( dst , x , 0 , ( uint8_t ) colour ) ;
2020-01-05 15:19:32 +01:00
}
break ;
}
}
/* Next line */
+ + y ;
}
}
2013-03-24 12:54:37 +01:00
/**
* Check line clipping by using a linear equation and draw the visible part of
* the line given by x / y and x2 / y2 .
* @ param video Destination pointer to draw into .
* @ param x X coordinate of first point .
* @ param y Y coordinate of first point .
* @ param x2 X coordinate of second point .
* @ param y2 Y coordinate of second point .
* @ param screen_width With of the screen to check clipping against .
* @ param screen_height Height of the screen to check clipping against .
* @ param colour Colour of the line .
* @ param width Width of the line .
2013-10-23 21:41:20 +02:00
* @ param dash Length of dashes for dashed lines . 0 means solid line .
2013-03-24 12:54:37 +01:00
*/
2023-05-08 19:01:06 +02:00
static inline void GfxDoDrawLine ( void * video , int x , int y , int x2 , int y2 , int screen_width , int screen_height , uint8_t colour , int width , int dash = 0 )
2013-03-24 12:54:37 +01:00
{
2014-01-02 23:41:58 +01:00
Blitter * blitter = BlitterFactory : : GetCurrentBlitter ( ) ;
2013-03-24 12:54:37 +01:00
assert ( width > 0 ) ;
2015-06-28 18:54:25 +02:00
if ( y2 = = y | | x2 = = x ) {
/* Special case: horizontal/vertical line. All checks already done in GfxPreprocessLine. */
blitter - > DrawLine ( video , x , y , x2 , y2 , screen_width , screen_height , colour , width , dash ) ;
2013-03-24 12:54:37 +01:00
return ;
}
int grade_y = y2 - y ;
int grade_x = x2 - x ;
2015-06-28 17:50:13 +02:00
/* Clipping rectangle. Slightly extended so we can ignore the width of the line. */
2015-08-10 22:24:13 +02:00
int extra = ( int ) CeilDiv ( 3 * width , 4 ) ; // not less then "width * sqrt(2) / 2"
2015-06-28 17:50:13 +02:00
Rect clip = { - extra , - extra , screen_width - 1 + extra , screen_height - 1 + extra } ;
2013-03-24 12:54:37 +01:00
/* prevent integer overflows. */
int margin = 1 ;
2021-01-08 11:16:18 +01:00
while ( INT_MAX / abs ( grade_y ) < std : : max ( abs ( clip . left - x ) , abs ( clip . right - x ) ) ) {
2013-03-24 12:54:37 +01:00
grade_y / = 2 ;
grade_x / = 2 ;
margin * = 2 ; // account for rounding errors
}
2015-06-28 17:50:13 +02:00
/* Imagine that the line is infinitely long and it intersects with
* infinitely long left and right edges of the clipping rectangle .
2015-06-28 17:54:12 +02:00
* If both intersection points are outside the clipping rectangle
* and both on the same side of it , we don ' t need to draw anything . */
2015-06-28 17:50:13 +02:00
int left_isec_y = y + ( clip . left - x ) * grade_y / grade_x ;
int right_isec_y = y + ( clip . right - x ) * grade_y / grade_x ;
if ( ( left_isec_y > clip . bottom + margin & & right_isec_y > clip . bottom + margin ) | |
( left_isec_y < clip . top - margin & & right_isec_y < clip . top - margin ) ) {
2013-03-24 12:54:37 +01:00
return ;
}
/* It is possible to use the line equation to further reduce the amount of
* work the blitter has to do by shortening the effective line segment .
* However , in order to get that right and prevent the flickering effects
* of rounding errors so much additional code has to be run here that in
2019-09-29 22:27:32 +02:00
* the general case the effect is not noticeable . */
2013-03-24 12:54:37 +01:00
2013-10-23 21:41:20 +02:00
blitter - > DrawLine ( video , x , y , x2 , y2 , screen_width , screen_height , colour , width , dash ) ;
2013-03-24 12:54:37 +01:00
}
2013-03-24 12:20:37 +01:00
/**
* Align parameters of a line to the given DPI and check simple clipping .
* @ param dpi Screen parameters to align with .
* @ param x X coordinate of first point .
* @ param y Y coordinate of first point .
* @ param x2 X coordinate of second point .
* @ param y2 Y coordinate of second point .
* @ param width Width of the line .
* @ return True if the line is likely to be visible , false if it ' s certainly
* invisible .
*/
static inline bool GfxPreprocessLine ( DrawPixelInfo * dpi , int & x , int & y , int & x2 , int & y2 , int width )
2004-08-09 19:04:08 +02:00
{
2007-06-18 21:38:23 +02:00
x - = dpi - > left ;
x2 - = dpi - > left ;
y - = dpi - > top ;
y2 - = dpi - > top ;
2004-08-09 19:04:08 +02:00
2013-03-24 12:20:37 +01:00
/* Check simple clipping */
if ( x + width / 2 < 0 & & x2 + width / 2 < 0 ) return false ;
if ( y + width / 2 < 0 & & y2 + width / 2 < 0 ) return false ;
if ( x - width / 2 > dpi - > width & & x2 - width / 2 > dpi - > width ) return false ;
if ( y - width / 2 > dpi - > height & & y2 - width / 2 > dpi - > height ) return false ;
return true ;
2004-08-09 19:04:08 +02:00
}
2013-10-23 21:41:20 +02:00
void GfxDrawLine ( int x , int y , int x2 , int y2 , int colour , int width , int dash )
2007-09-28 21:17:33 +02:00
{
DrawPixelInfo * dpi = _cur_dpi ;
2013-03-24 12:20:37 +01:00
if ( GfxPreprocessLine ( dpi , x , y , x2 , y2 , width ) ) {
2013-10-23 21:41:20 +02:00
GfxDoDrawLine ( dpi - > dst_ptr , x , y , x2 , y2 , dpi - > width , dpi - > height , colour , width , dash ) ;
2013-03-24 12:20:37 +01:00
}
}
2007-09-28 21:17:33 +02:00
2013-03-24 12:20:37 +01:00
void GfxDrawLineUnscaled ( int x , int y , int x2 , int y2 , int colour )
{
DrawPixelInfo * dpi = _cur_dpi ;
if ( GfxPreprocessLine ( dpi , x , y , x2 , y2 , 1 ) ) {
2013-03-24 12:54:37 +01:00
GfxDoDrawLine ( dpi - > dst_ptr ,
2013-03-24 12:20:37 +01:00
UnScaleByZoom ( x , dpi - > zoom ) , UnScaleByZoom ( y , dpi - > zoom ) ,
UnScaleByZoom ( x2 , dpi - > zoom ) , UnScaleByZoom ( y2 , dpi - > zoom ) ,
UnScaleByZoom ( dpi - > width , dpi - > zoom ) , UnScaleByZoom ( dpi - > height , dpi - > zoom ) , colour , 1 ) ;
}
2007-09-28 21:17:33 +02:00
}
2007-09-26 21:27:29 +02:00
/**
* Draws the projection of a parallelepiped .
* This can be used to draw boxes in world coordinates .
*
* @ param x Screen X - coordinate of top front corner .
* @ param y Screen Y - coordinate of top front corner .
* @ param dx1 Screen X - length of first edge .
* @ param dy1 Screen Y - length of first edge .
* @ param dx2 Screen X - length of second edge .
* @ param dy2 Screen Y - length of second edge .
* @ param dx3 Screen X - length of third edge .
* @ param dy3 Screen Y - length of third edge .
*/
void DrawBox ( int x , int y , int dx1 , int dy1 , int dx2 , int dy2 , int dx3 , int dy3 )
{
/* ....
* . . . . . .
* . . . . . .
* . . ^
* < - - __ ( dx1 , dy1 ) / ( dx2 , dy2 )
* : - - __ / :
* : - - __ / :
* : * ( x , y ) :
* : | :
* : | . .
* . . . . | ( dx3 , dy3 )
* . . . . | . .
* . . . . V .
*/
2024-03-16 23:59:32 +01:00
static const uint8_t colour = PC_WHITE ;
2007-09-26 21:27:29 +02:00
2009-02-09 03:09:47 +01:00
GfxDrawLineUnscaled ( x , y , x + dx1 , y + dy1 , colour ) ;
GfxDrawLineUnscaled ( x , y , x + dx2 , y + dy2 , colour ) ;
GfxDrawLineUnscaled ( x , y , x + dx3 , y + dy3 , colour ) ;
2007-09-26 21:27:29 +02:00
2009-02-09 03:09:47 +01:00
GfxDrawLineUnscaled ( x + dx1 , y + dy1 , x + dx1 + dx2 , y + dy1 + dy2 , colour ) ;
GfxDrawLineUnscaled ( x + dx1 , y + dy1 , x + dx1 + dx3 , y + dy1 + dy3 , colour ) ;
GfxDrawLineUnscaled ( x + dx2 , y + dy2 , x + dx2 + dx1 , y + dy2 + dy1 , colour ) ;
GfxDrawLineUnscaled ( x + dx2 , y + dy2 , x + dx2 + dx3 , y + dy2 + dy3 , colour ) ;
GfxDrawLineUnscaled ( x + dx3 , y + dy3 , x + dx3 + dx1 , y + dy3 + dy1 , colour ) ;
GfxDrawLineUnscaled ( x + dx3 , y + dy3 , x + dx3 + dx2 , y + dy3 + dy2 , colour ) ;
2007-09-26 21:27:29 +02:00
}
2023-11-30 19:10:07 +01:00
/**
* Draw the outline of a Rect
* @ param r Rect to draw .
* @ param colour Colour of the outline .
* @ param width Width of the outline .
* @ param dash Length of dashes for dashed lines . 0 means solid lines .
*/
void DrawRectOutline ( const Rect & r , int colour , int width , int dash )
{
GfxDrawLine ( r . left , r . top , r . right , r . top , colour , width , dash ) ;
GfxDrawLine ( r . left , r . top , r . left , r . bottom , colour , width , dash ) ;
GfxDrawLine ( r . right , r . top , r . right , r . bottom , colour , width , dash ) ;
GfxDrawLine ( r . left , r . bottom , r . right , r . bottom , colour , width , dash ) ;
}
2009-02-09 11:31:37 +01:00
/**
* Set the colour remap to be for the given colour .
* @ param colour the new colour of the remap .
*/
static void SetColourRemap ( TextColour colour )
{
if ( colour = = TC_INVALID ) return ;
2010-12-25 20:59:56 +01:00
/* Black strings have no shading ever; the shading is black, so it
* would be invisible at best , but it actually makes it illegible . */
2010-12-26 07:52:38 +01:00
bool no_shade = ( colour & TC_NO_SHADE ) ! = 0 | | colour = = TC_BLACK ;
bool raw_colour = ( colour & TC_IS_PALETTE_COLOUR ) ! = 0 ;
2019-11-26 01:28:03 +01:00
colour & = ~ ( TC_NO_SHADE | TC_IS_PALETTE_COLOUR | TC_FORCED ) ;
2010-12-25 20:59:56 +01:00
2024-03-16 23:59:32 +01:00
_string_colourremap [ 1 ] = raw_colour ? ( uint8_t ) colour : _string_colourmap [ colour ] ;
2011-05-04 19:12:37 +02:00
_string_colourremap [ 2 ] = no_shade ? 0 : 1 ;
2009-02-09 11:31:37 +01:00
_colour_remap_ptr = _string_colourremap ;
}
2006-05-09 15:23:04 +02:00
2013-06-25 22:38:12 +02:00
/**
* Drawing routine for drawing a laid out line of text .
* @ param line String to draw .
* @ param y The top most position to draw on .
* @ param left The left most position to draw on .
* @ param right The right most position to draw on .
* @ param align The alignment of the string when drawing left - to - right . In the
* case a right - to - left language is chosen this is inverted so it
* will be drawn in the right direction .
* @ param underline Whether to underline what has been drawn or not .
2013-07-01 17:26:27 +02:00
* @ param truncation Whether to perform string truncation or not .
2013-06-25 22:38:12 +02:00
*
* @ return In case of left or center alignment the right most pixel we have drawn to .
* In case of right alignment the left most pixel we have drawn to .
*/
2019-04-02 21:31:10 +02:00
static int DrawLayoutLine ( const ParagraphLayouter : : Line & line , int y , int left , int right , StringAlignment align , bool underline , bool truncation )
2013-06-25 22:38:12 +02:00
{
2019-04-02 21:31:10 +02:00
if ( line . CountRuns ( ) = = 0 ) return 0 ;
2013-06-25 22:38:12 +02:00
2019-04-02 21:31:10 +02:00
int w = line . GetWidth ( ) ;
int h = line . GetLeading ( ) ;
2013-06-25 22:38:12 +02:00
2013-06-25 22:38:29 +02:00
/*
* The following is needed for truncation .
* Depending on the text direction , we either remove bits at the rear
* or the front . For this we shift the entire area to draw so it fits
* within the left / right bounds and the side we do not truncate it on .
* Then we determine the truncation location , i . e . glyphs that fall
* outside of the range min_x - max_x will not be drawn ; they are thus
* the truncated glyphs .
*
* At a later step we insert the dots .
*/
int max_w = right - left + 1 ; // The maximum width.
int offset_x = 0 ; // The offset we need for positioning the glyphs
int min_x = left ; // The minimum x position to draw normal glyphs on.
int max_x = right ; // The maximum x position to draw normal glyphs on.
2013-07-01 17:26:27 +02:00
truncation & = max_w < w ; // Whether we need to do truncation.
2013-06-25 22:38:29 +02:00
int dot_width = 0 ; // Cache for the width of the dot.
2019-04-10 23:07:06 +02:00
const Sprite * dot_sprite = nullptr ; // Cache for the sprite of the dot.
2024-02-22 23:22:35 +01:00
bool dot_has_shadow = false ; // Whether the dot's font requires shadows.
2013-06-25 22:38:29 +02:00
if ( truncation ) {
/*
* Assumption may be made that all fonts of a run are of the same size .
* In any case , we ' ll use these dots for the abbreviation , so even if
* another size would be chosen it won ' t have truncated too little for
* the truncation dots .
*/
2023-10-25 15:08:07 +02:00
FontCache * fc = line . GetVisualRun ( 0 ) . GetFont ( ) - > fc ;
2024-02-22 23:22:35 +01:00
dot_has_shadow = fc - > GetDrawGlyphShadow ( ) ;
2013-06-25 22:38:29 +02:00
GlyphID dot_glyph = fc - > MapCharToGlyph ( ' . ' ) ;
dot_width = fc - > GetGlyphWidth ( dot_glyph ) ;
dot_sprite = fc - > GetGlyph ( dot_glyph ) ;
if ( _current_text_dir = = TD_RTL ) {
min_x + = 3 * dot_width ;
offset_x = w - 3 * dot_width - max_w ;
} else {
max_x - = 3 * dot_width ;
}
w = max_w ;
}
2013-06-25 22:38:12 +02:00
/* In case we have a RTL language we swap the alignment. */
if ( ! ( align & SA_FORCE ) & & _current_text_dir = = TD_RTL & & ( align & SA_HOR_MASK ) ! = SA_HOR_CENTER ) align ^ = SA_RIGHT ;
/* right is the right most position to draw on. In this case we want to do
* calculations with the width of the string . In comparison right can be
* seen as lastof ( todraw ) and width as lengthof ( todraw ) . They differ by 1.
* So most + 1 / - 1 additions are to move from lengthof to ' indices ' .
*/
switch ( align & SA_HOR_MASK ) {
case SA_LEFT :
/* right + 1 = left + w */
right = left + w - 1 ;
break ;
case SA_HOR_CENTER :
left = RoundDivSU ( right + 1 + left - w , 2 ) ;
/* right + 1 = left + w */
right = left + w - 1 ;
break ;
case SA_RIGHT :
left = right + 1 - w ;
break ;
default :
NOT_REACHED ( ) ;
}
2022-09-30 22:36:00 +02:00
const uint shadow_offset = ScaleGUITrad ( 1 ) ;
2024-02-18 16:30:54 +01:00
/* Draw shadow, then foreground */
for ( bool do_shadow : { true , false } ) {
2024-02-22 23:22:35 +01:00
bool colour_has_shadow = false ;
2024-02-18 16:30:54 +01:00
for ( int run_index = 0 ; run_index < line . CountRuns ( ) ; run_index + + ) {
const ParagraphLayouter : : VisualRun & run = line . GetVisualRun ( run_index ) ;
const auto & glyphs = run . GetGlyphs ( ) ;
const auto & positions = run . GetPositions ( ) ;
const Font * f = run . GetFont ( ) ;
2013-06-25 22:38:12 +02:00
2024-02-18 16:30:54 +01:00
FontCache * fc = f - > fc ;
2024-02-22 23:22:35 +01:00
TextColour colour = f - > colour ;
colour_has_shadow = ( colour & TC_NO_SHADE ) = = 0 & & colour ! = TC_BLACK ;
SetColourRemap ( do_shadow ? TC_BLACK : colour ) ; // the last run also sets the colour for the truncation dots
if ( do_shadow & & ( ! fc - > GetDrawGlyphShadow ( ) | | ! colour_has_shadow ) ) continue ;
2013-06-25 22:38:12 +02:00
2024-02-18 16:30:54 +01:00
DrawPixelInfo * dpi = _cur_dpi ;
int dpi_left = dpi - > left ;
int dpi_right = dpi - > left + dpi - > width - 1 ;
2013-06-25 22:38:12 +02:00
2024-02-18 16:30:54 +01:00
for ( int i = 0 ; i < run . GetGlyphCount ( ) ; i + + ) {
GlyphID glyph = glyphs [ i ] ;
2013-06-25 22:38:12 +02:00
2024-02-18 16:30:54 +01:00
/* Not a valid glyph (empty) */
if ( glyph = = 0xFFFF ) continue ;
2013-06-25 22:38:12 +02:00
2024-02-18 16:30:54 +01:00
int begin_x = positions [ i ] . x + left - offset_x ;
int end_x = positions [ i + 1 ] . x + left - offset_x - 1 ;
int top = positions [ i ] . y + y ;
2013-06-25 22:38:12 +02:00
2024-02-18 16:30:54 +01:00
/* Truncated away. */
if ( truncation & & ( begin_x < min_x | | end_x > max_x ) ) continue ;
2013-06-25 22:38:12 +02:00
2024-02-18 16:30:54 +01:00
const Sprite * sprite = fc - > GetGlyph ( glyph ) ;
/* Check clipping (the "+ 1" is for the shadow). */
if ( begin_x + sprite - > x_offs > dpi_right | | begin_x + sprite - > x_offs + sprite - > width /* - 1 + 1 */ < dpi_left ) continue ;
2013-06-25 22:38:12 +02:00
2024-02-18 16:30:54 +01:00
if ( do_shadow & & ( glyph & SPRITE_GLYPH ) ! = 0 ) continue ;
2013-06-26 21:35:40 +02:00
2024-02-18 16:30:54 +01:00
GfxMainBlitter ( sprite , begin_x + ( do_shadow ? shadow_offset : 0 ) , top + ( do_shadow ? shadow_offset : 0 ) , BM_COLOUR_REMAP ) ;
2013-06-25 22:38:12 +02:00
}
}
2024-02-22 23:22:35 +01:00
if ( truncation & & ( ! do_shadow | | ( dot_has_shadow & & colour_has_shadow ) ) ) {
2024-02-18 16:30:54 +01:00
int x = ( _current_text_dir = = TD_RTL ) ? left : ( right - 3 * dot_width ) ;
for ( int i = 0 ; i < 3 ; i + + , x + = dot_width ) {
GfxMainBlitter ( dot_sprite , x + ( do_shadow ? shadow_offset : 0 ) , y + ( do_shadow ? shadow_offset : 0 ) , BM_COLOUR_REMAP ) ;
2014-04-16 21:54:51 +02:00
}
2013-06-25 22:38:29 +02:00
}
}
2013-06-25 22:38:12 +02:00
if ( underline ) {
2023-11-13 22:54:26 +01:00
GfxFillRect ( left , y + h , right , y + h + WidgetDimensions : : scaled . bevel . top - 1 , _string_colourremap [ 1 ] ) ;
2013-06-25 22:38:12 +02:00
}
return ( align & SA_HOR_MASK ) = = SA_RIGHT ? left : right ;
}
2009-03-21 19:28:01 +01:00
/**
* Draw string , possibly truncated to make it fit in its allocated space
*
* @ param left The left most position to draw on .
* @ param right The right most position to draw on .
* @ param top The top most position to draw on .
* @ param str String to draw .
2019-11-25 22:59:03 +01:00
* @ param colour Colour used for drawing the string , for details see _string_colourmap in
* table / palettes . h or docs / ottd - colourtext - palette . png or the enum TextColour in gfx_type . h
2009-03-21 19:28:01 +01:00
* @ param align The alignment of the string when drawing left - to - right . In the
* case a right - to - left language is chosen this is inverted so it
* will be drawn in the right direction .
* @ param underline Whether to underline what has been drawn or not .
2011-11-20 12:50:12 +01:00
* @ param fontsize The size of the initial characters .
2013-07-06 14:18:20 +02:00
* @ return In case of left or center alignment the right most pixel we have drawn to .
* In case of right alignment the left most pixel we have drawn to .
2009-03-21 19:28:01 +01:00
*/
2023-05-06 19:53:02 +02:00
int DrawString ( int left , int right , int top , std : : string_view str , TextColour colour , StringAlignment align , bool underline , FontSize fontsize )
2009-03-21 19:28:01 +01:00
{
2013-07-06 14:18:20 +02:00
/* The string may contain control chars to change the font, just use the biggest font for clipping. */
2023-11-21 20:04:24 +01:00
int max_height = std : : max ( { GetCharacterHeight ( FS_SMALL ) , GetCharacterHeight ( FS_NORMAL ) , GetCharacterHeight ( FS_LARGE ) , GetCharacterHeight ( FS_MONO ) } ) ;
2013-07-06 14:18:20 +02:00
/* Funny glyphs may extent outside the usual bounds, so relax the clipping somewhat. */
int extra = max_height / 2 ;
if ( _cur_dpi - > top + _cur_dpi - > height + extra < top | | _cur_dpi - > top > top + max_height + extra | |
_cur_dpi - > left + _cur_dpi - > width + extra < left | | _cur_dpi - > left > right + extra ) {
return 0 ;
}
2013-06-26 21:10:22 +02:00
Layouter layout ( str , INT32_MAX , colour , fontsize ) ;
2023-10-20 20:09:58 +02:00
if ( layout . empty ( ) ) return 0 ;
2013-06-25 22:44:23 +02:00
2019-04-02 21:31:10 +02:00
return DrawLayoutLine ( * layout . front ( ) , top , left , right , align , underline , true ) ;
2009-03-21 19:28:01 +01:00
}
/**
* Draw string , possibly truncated to make it fit in its allocated space
*
* @ param left The left most position to draw on .
* @ param right The right most position to draw on .
* @ param top The top most position to draw on .
* @ param str String to draw .
2019-11-25 22:59:03 +01:00
* @ param colour Colour used for drawing the string , for details see _string_colourmap in
* table / palettes . h or docs / ottd - colourtext - palette . png or the enum TextColour in gfx_type . h
2009-03-21 19:28:01 +01:00
* @ param align The alignment of the string when drawing left - to - right . In the
* case a right - to - left language is chosen this is inverted so it
* will be drawn in the right direction .
* @ param underline Whether to underline what has been drawn or not .
2011-11-20 12:50:12 +01:00
* @ param fontsize The size of the initial characters .
2013-07-06 14:18:20 +02:00
* @ return In case of left or center alignment the right most pixel we have drawn to .
* In case of right alignment the left most pixel we have drawn to .
2009-03-21 19:28:01 +01:00
*/
2011-11-20 12:50:12 +01:00
int DrawString ( int left , int right , int top , StringID str , TextColour colour , StringAlignment align , bool underline , FontSize fontsize )
2009-03-21 19:28:01 +01:00
{
2023-05-19 14:35:53 +02:00
return DrawString ( left , right , top , GetString ( str ) , colour , align , underline , fontsize ) ;
2009-03-21 19:28:01 +01:00
}
2013-06-25 22:44:23 +02:00
/**
* Calculates height of string ( in pixels ) . The string is changed to a multiline string if needed .
* @ param str string to check
* @ param maxw maximum string width
* @ return height of pixels of string when it is drawn
*/
2023-05-06 19:53:02 +02:00
int GetStringHeight ( std : : string_view str , int maxw , FontSize fontsize )
2013-06-25 22:44:23 +02:00
{
2023-11-20 23:22:23 +01:00
assert ( maxw > 0 ) ;
2013-10-06 13:29:14 +02:00
Layouter layout ( str , maxw , TC_FROMSTRING , fontsize ) ;
2013-06-25 22:44:23 +02:00
return layout . GetBounds ( ) . height ;
}
2010-08-01 21:22:34 +02:00
/**
* Calculates height of string ( in pixels ) . The string is changed to a multiline string if needed .
2008-07-22 21:25:47 +02:00
* @ param str string to check
* @ param maxw maximum string width
* @ return height of pixels of string when it is drawn
*/
int GetStringHeight ( StringID str , int maxw )
{
2023-05-19 14:35:53 +02:00
return GetStringHeight ( GetString ( str ) , maxw ) ;
2008-07-22 21:25:47 +02:00
}
2013-06-09 14:21:44 +02:00
/**
* Calculates number of lines of string . The string is changed to a multiline string if needed .
* @ param str string to check
* @ param maxw maximum string width
* @ return number of lines of string when it is drawn
*/
int GetStringLineCount ( StringID str , int maxw )
{
2023-05-19 14:35:53 +02:00
Layouter layout ( GetString ( str ) , maxw ) ;
2019-03-28 00:09:33 +01:00
return ( uint ) layout . size ( ) ;
2013-06-09 14:21:44 +02:00
}
2010-08-01 21:22:34 +02:00
/**
* Calculate string bounding box for multi - line strings .
2009-09-02 22:40:56 +02:00
* @ param str String to check .
* @ param suggestion Suggested bounding box .
* @ return Bounding box for the multi - line string , may be bigger than \ a suggestion .
*/
Dimension GetStringMultiLineBoundingBox ( StringID str , const Dimension & suggestion )
{
2015-08-10 22:24:13 +02:00
Dimension box = { suggestion . width , ( uint ) GetStringHeight ( str , suggestion . width ) } ;
2009-09-02 22:40:56 +02:00
return box ;
}
2008-07-22 21:25:47 +02:00
2012-01-12 20:11:51 +01:00
/**
* Calculate string bounding box for multi - line strings .
* @ param str String to check .
* @ param suggestion Suggested bounding box .
* @ return Bounding box for the multi - line string , may be bigger than \ a suggestion .
*/
2023-05-06 19:53:02 +02:00
Dimension GetStringMultiLineBoundingBox ( std : : string_view str , const Dimension & suggestion )
2012-01-12 20:11:51 +01:00
{
2015-08-10 22:24:13 +02:00
Dimension box = { suggestion . width , ( uint ) GetStringHeight ( str , suggestion . width ) } ;
2012-01-12 20:11:51 +01:00
return box ;
}
2010-07-31 13:47:08 +02:00
/**
* Draw string , possibly over multiple lines .
*
* @ param left The left most position to draw on .
* @ param right The right most position to draw on .
* @ param top The top most position to draw on .
* @ param bottom The bottom most position to draw on .
* @ param str String to draw .
2019-11-25 22:59:03 +01:00
* @ param colour Colour used for drawing the string , for details see _string_colourmap in
* table / palettes . h or docs / ottd - colourtext - palette . png or the enum TextColour in gfx_type . h
2010-07-31 13:47:08 +02:00
* @ param align The horizontal and vertical alignment of the string .
* @ param underline Whether to underline all strings
2011-11-20 12:50:12 +01:00
* @ param fontsize The size of the initial characters .
2010-07-31 13:47:08 +02:00
*
* @ return If \ a align is # SA_BOTTOM , the top to where we have written , else the bottom to where we have written .
*/
2023-05-06 19:53:02 +02:00
int DrawStringMultiLine ( int left , int right , int top , int bottom , std : : string_view str , TextColour colour , StringAlignment align , bool underline , FontSize fontsize )
2010-07-31 13:47:08 +02:00
{
2013-06-25 22:44:23 +02:00
int maxw = right - left + 1 ;
int maxh = bottom - top + 1 ;
/* It makes no sense to even try if it can't be drawn anyway, or
* do we really want to support fonts of 0 or less pixels high ? */
if ( maxh < = 0 ) return top ;
Layouter layout ( str , maxw , colour , fontsize ) ;
int total_height = layout . GetBounds ( ) . height ;
int y ;
switch ( align & SA_VERT_MASK ) {
case SA_TOP :
y = top ;
break ;
case SA_VERT_CENTER :
y = RoundDivSU ( bottom + top - total_height , 2 ) ;
break ;
case SA_BOTTOM :
y = bottom - total_height ;
break ;
default : NOT_REACHED ( ) ;
}
int last_line = top ;
int first_line = bottom ;
2019-04-02 21:31:10 +02:00
for ( const auto & line : layout ) {
2013-06-25 22:44:23 +02:00
2013-11-16 22:05:26 +01:00
int line_height = line - > GetLeading ( ) ;
2022-09-10 18:48:38 +02:00
if ( y > = top & & y + line_height - 1 < = bottom ) {
2013-06-25 22:44:23 +02:00
last_line = y + line_height ;
if ( first_line > y ) first_line = y ;
2019-04-02 21:31:10 +02:00
DrawLayoutLine ( * line , y , left , right , align , underline , false ) ;
2013-06-25 22:44:23 +02:00
}
y + = line_height ;
}
return ( ( align & SA_VERT_MASK ) = = SA_BOTTOM ) ? first_line : last_line ;
2010-07-31 13:47:08 +02:00
}
/**
* Draw string , possibly over multiple lines .
*
* @ param left The left most position to draw on .
* @ param right The right most position to draw on .
* @ param top The top most position to draw on .
* @ param bottom The bottom most position to draw on .
* @ param str String to draw .
2019-11-25 22:59:03 +01:00
* @ param colour Colour used for drawing the string , for details see _string_colourmap in
* table / palettes . h or docs / ottd - colourtext - palette . png or the enum TextColour in gfx_type . h
2010-07-31 13:47:08 +02:00
* @ param align The horizontal and vertical alignment of the string .
* @ param underline Whether to underline all strings
2011-11-20 12:50:12 +01:00
* @ param fontsize The size of the initial characters .
2010-07-31 13:47:08 +02:00
*
* @ return If \ a align is # SA_BOTTOM , the top to where we have written , else the bottom to where we have written .
*/
2011-11-20 12:50:12 +01:00
int DrawStringMultiLine ( int left , int right , int top , int bottom , StringID str , TextColour colour , StringAlignment align , bool underline , FontSize fontsize )
2010-07-31 13:47:08 +02:00
{
2023-05-19 14:35:53 +02:00
return DrawStringMultiLine ( left , right , top , bottom , GetString ( str ) , colour , align , underline , fontsize ) ;
2010-07-31 13:47:08 +02:00
}
2010-08-01 21:22:34 +02:00
/**
* Return the string dimension in pixels . The height and width are returned
2007-12-23 00:30:28 +01:00
* in a single Dimension value . TINYFONT , BIGFONT modifiers are only
2006-09-16 15:20:14 +02:00
* supported as the first character of the string . The returned dimensions
* are therefore a rough estimation correct for all the current strings
* but not every possible combination
* @ param str string to calculate pixel - width
2010-05-30 17:32:37 +02:00
* @ param start_fontsize Fontsize to start the text with
2010-08-01 21:44:49 +02:00
* @ return string width and height in pixels
*/
2023-05-06 19:53:02 +02:00
Dimension GetStringBoundingBox ( std : : string_view str , FontSize start_fontsize )
2005-02-06 19:30:45 +01:00
{
2013-06-25 22:44:23 +02:00
Layouter layout ( str , INT32_MAX , TC_FROMSTRING , start_fontsize ) ;
return layout . GetBounds ( ) ;
2004-08-09 19:04:08 +02:00
}
2009-06-27 13:45:05 +02:00
/**
2018-10-28 03:17:36 +01:00
* Get bounding box of a string . Uses parameters set by # SetDParam if needed .
2023-05-06 19:53:02 +02:00
* Has the same restrictions as # GetStringBoundingBox ( std : : string_view str , FontSize start_fontsize ) .
2009-06-27 13:45:05 +02:00
* @ param strid String to examine .
* @ return Width and height of the bounding box for the string in pixels .
*/
2022-10-23 16:45:47 +02:00
Dimension GetStringBoundingBox ( StringID strid , FontSize start_fontsize )
2009-06-27 13:45:05 +02:00
{
2023-05-19 14:35:53 +02:00
return GetStringBoundingBox ( GetString ( strid ) , start_fontsize ) ;
2009-06-27 13:45:05 +02:00
}
2022-12-27 19:39:37 +01:00
/**
* Get maximum width of a list of strings .
* @ param list List of strings , terminated with INVALID_STRING_ID .
* @ param fontsize Font size to use .
* @ return Width of longest string within the list .
*/
uint GetStringListWidth ( const StringID * list , FontSize fontsize )
{
uint width = 0 ;
for ( const StringID * str = list ; * str ! = INVALID_STRING_ID ; str + + ) {
width = std : : max ( width , GetStringBoundingBox ( * str , fontsize ) . width ) ;
}
return width ;
}
2013-08-05 22:35:23 +02:00
/**
* Get the leading corner of a character in a single - line string relative
* to the start of the string .
* @ param str String containing the character .
* @ param ch Pointer to the character in the string .
* @ param start_fontsize Font size to start the text with .
* @ return Upper left corner of the glyph associated with the character .
*/
2023-05-06 19:53:02 +02:00
Point GetCharPosInString ( std : : string_view str , const char * ch , FontSize start_fontsize )
2013-08-05 22:35:23 +02:00
{
2023-05-06 19:53:02 +02:00
/* Ensure "ch" is inside "str" or at the exact end. */
assert ( ch > = str . data ( ) & & ( ch - str . data ( ) ) < = static_cast < ptrdiff_t > ( str . size ( ) ) ) ;
auto it_ch = str . begin ( ) + ( ch - str . data ( ) ) ;
2013-08-05 22:35:23 +02:00
Layouter layout ( str , INT32_MAX , TC_FROMSTRING , start_fontsize ) ;
2023-05-06 19:53:02 +02:00
return layout . GetCharPosition ( it_ch ) ;
2013-08-05 22:35:23 +02:00
}
2013-08-05 22:37:53 +02:00
/**
* Get the character from a string that is drawn at a specific position .
* @ param str String to test .
* @ param x Position relative to the start of the string .
* @ param start_fontsize Font size to start the text with .
2023-05-06 19:53:02 +02:00
* @ return Index of the character position or - 1 if there is no character at the position .
2013-08-05 22:37:53 +02:00
*/
2023-05-06 19:53:02 +02:00
ptrdiff_t GetCharAtPosition ( std : : string_view str , int x , FontSize start_fontsize )
2013-08-05 22:37:53 +02:00
{
2023-05-06 19:53:02 +02:00
if ( x < 0 ) return - 1 ;
2013-08-05 22:37:53 +02:00
Layouter layout ( str , INT32_MAX , TC_FROMSTRING , start_fontsize ) ;
2021-04-03 00:49:57 +02:00
return layout . GetCharAtPosition ( x , 0 ) ;
2013-08-05 22:37:53 +02:00
}
2008-08-01 11:34:34 +02:00
/**
* Draw single character horizontally centered around ( x , y )
* @ param c Character ( glyph ) to draw
2021-04-22 02:17:30 +02:00
* @ param r Rectangle to draw character within
2019-11-25 22:59:03 +01:00
* @ param colour Colour to use , for details see _string_colourmap in
* table / palettes . h or docs / ottd - colourtext - palette . png or the enum TextColour in gfx_type . h
2008-08-01 11:34:34 +02:00
*/
2023-05-08 19:01:06 +02:00
void DrawCharCentered ( char32_t c , const Rect & r , TextColour colour )
2008-03-26 11:08:17 +01:00
{
2009-02-09 11:31:37 +01:00
SetColourRemap ( colour ) ;
2021-04-22 02:17:30 +02:00
GfxMainBlitter ( GetGlyph ( FS_NORMAL , c ) ,
CenterBounds ( r . left , r . right , GetCharacterWidth ( FS_NORMAL , c ) ) ,
2023-11-21 20:04:24 +01:00
CenterBounds ( r . top , r . bottom , GetCharacterHeight ( FS_NORMAL ) ) ,
2021-04-22 02:17:30 +02:00
BM_COLOUR_REMAP ) ;
2008-03-26 11:08:17 +01:00
}
2009-06-27 13:45:05 +02:00
/**
* Get the size of a sprite .
* @ param sprid Sprite to examine .
2018-10-28 03:17:36 +01:00
* @ param [ out ] offset Optionally returns the sprite position offset .
2020-06-04 04:25:23 +02:00
* @ param zoom The zoom level applicable to the sprite .
2009-06-27 13:45:05 +02:00
* @ return Sprite size in pixels .
* @ note The size assumes ( 0 , 0 ) as top - left coordinate and ignores any part of the sprite drawn at the left or above that position .
*/
2011-11-24 13:38:48 +01:00
Dimension GetSpriteSize ( SpriteID sprid , Point * offset , ZoomLevel zoom )
2009-06-27 13:45:05 +02:00
{
2023-04-16 21:00:55 +02:00
const Sprite * sprite = GetSprite ( sprid , SpriteType : : Normal ) ;
2009-06-27 13:45:05 +02:00
2019-04-10 23:07:06 +02:00
if ( offset ! = nullptr ) {
2011-11-24 13:38:48 +01:00
offset - > x = UnScaleByZoom ( sprite - > x_offs , zoom ) ;
offset - > y = UnScaleByZoom ( sprite - > y_offs , zoom ) ;
2011-07-02 16:37:03 +02:00
}
2009-06-27 13:45:05 +02:00
Dimension d ;
2021-01-08 11:16:18 +01:00
d . width = std : : max < int > ( 0 , UnScaleByZoom ( sprite - > x_offs + sprite - > width , zoom ) ) ;
d . height = std : : max < int > ( 0 , UnScaleByZoom ( sprite - > y_offs + sprite - > height , zoom ) ) ;
2009-06-27 13:45:05 +02:00
return d ;
}
2014-10-06 20:45:51 +02:00
/**
* Helper function to get the blitter mode for different types of palettes .
* @ param pal The palette to get the blitter mode for .
* @ return The blitter mode associated with the palette .
*/
static BlitterMode GetBlitterMode ( PaletteID pal )
{
switch ( pal ) {
case PAL_NONE : return BM_NORMAL ;
case PALETTE_CRASH : return BM_CRASH_REMAP ;
case PALETTE_ALL_BLACK : return BM_BLACK_REMAP ;
default : return BM_COLOUR_REMAP ;
}
}
2008-08-01 11:34:34 +02:00
/**
2011-11-24 13:38:48 +01:00
* Draw a sprite in a viewport .
2008-08-01 11:34:34 +02:00
* @ param img Image number to draw
* @ param pal Palette to use .
2011-11-24 13:38:48 +01:00
* @ param x Left coordinate of image in viewport , scaled by zoom
* @ param y Top coordinate of image in viewport , scaled by zoom
2008-08-01 11:34:34 +02:00
* @ param sub If available , draw only specified part of the sprite
*/
2011-11-24 13:38:48 +01:00
void DrawSpriteViewport ( SpriteID img , PaletteID pal , int x , int y , const SubSprite * sub )
2005-07-05 13:09:18 +02:00
{
2010-04-26 22:35:27 +02:00
SpriteID real_sprite = GB ( img , 0 , SPRITE_WIDTH ) ;
2007-11-19 22:02:30 +01:00
if ( HasBit ( img , PALETTE_MODIFIER_TRANSPARENT ) ) {
2023-12-22 17:01:33 +01:00
pal = GB ( pal , 0 , PALETTE_WIDTH ) ;
_colour_remap_ptr = GetNonSprite ( pal , SpriteType : : Recolour ) + 1 ;
GfxMainBlitterViewport ( GetSprite ( real_sprite , SpriteType : : Normal ) , x , y , pal = = PALETTE_TO_TRANSPARENT ? BM_TRANSPARENT : BM_TRANSPARENT_REMAP , sub , real_sprite ) ;
2007-01-14 20:57:49 +01:00
} else if ( pal ! = PAL_NONE ) {
2014-10-05 23:18:37 +02:00
if ( HasBit ( pal , PALETTE_TEXT_RECOLOUR ) ) {
SetColourRemap ( ( TextColour ) GB ( pal , 0 , PALETTE_WIDTH ) ) ;
} else {
2023-04-16 21:00:55 +02:00
_colour_remap_ptr = GetNonSprite ( GB ( pal , 0 , PALETTE_WIDTH ) , SpriteType : : Recolour ) + 1 ;
2014-10-05 23:18:37 +02:00
}
2023-04-16 21:00:55 +02:00
GfxMainBlitterViewport ( GetSprite ( real_sprite , SpriteType : : Normal ) , x , y , GetBlitterMode ( pal ) , sub , real_sprite ) ;
2004-08-09 19:04:08 +02:00
} else {
2023-04-16 21:00:55 +02:00
GfxMainBlitterViewport ( GetSprite ( real_sprite , SpriteType : : Normal ) , x , y , BM_NORMAL , sub , real_sprite ) ;
2004-08-09 19:04:08 +02:00
}
}
2011-11-24 13:38:48 +01:00
/**
* Draw a sprite , not in a viewport
* @ param img Image number to draw
* @ param pal Palette to use .
* @ param x Left coordinate of image in pixels
* @ param y Top coordinate of image in pixels
* @ param sub If available , draw only specified part of the sprite
* @ param zoom Zoom level of sprite
*/
void DrawSprite ( SpriteID img , PaletteID pal , int x , int y , const SubSprite * sub , ZoomLevel zoom )
{
SpriteID real_sprite = GB ( img , 0 , SPRITE_WIDTH ) ;
if ( HasBit ( img , PALETTE_MODIFIER_TRANSPARENT ) ) {
2023-12-22 17:01:33 +01:00
pal = GB ( pal , 0 , PALETTE_WIDTH ) ;
_colour_remap_ptr = GetNonSprite ( pal , SpriteType : : Recolour ) + 1 ;
GfxMainBlitter ( GetSprite ( real_sprite , SpriteType : : Normal ) , x , y , pal = = PALETTE_TO_TRANSPARENT ? BM_TRANSPARENT : BM_TRANSPARENT_REMAP , sub , real_sprite , zoom ) ;
2011-11-24 13:38:48 +01:00
} else if ( pal ! = PAL_NONE ) {
2014-10-05 23:18:37 +02:00
if ( HasBit ( pal , PALETTE_TEXT_RECOLOUR ) ) {
SetColourRemap ( ( TextColour ) GB ( pal , 0 , PALETTE_WIDTH ) ) ;
} else {
2023-04-16 21:00:55 +02:00
_colour_remap_ptr = GetNonSprite ( GB ( pal , 0 , PALETTE_WIDTH ) , SpriteType : : Recolour ) + 1 ;
2014-10-05 23:18:37 +02:00
}
2023-04-16 21:00:55 +02:00
GfxMainBlitter ( GetSprite ( real_sprite , SpriteType : : Normal ) , x , y , GetBlitterMode ( pal ) , sub , real_sprite , zoom ) ;
2011-11-24 13:38:48 +01:00
} else {
2023-04-16 21:00:55 +02:00
GfxMainBlitter ( GetSprite ( real_sprite , SpriteType : : Normal ) , x , y , BM_NORMAL , sub , real_sprite , zoom ) ;
2011-11-24 13:38:48 +01:00
}
}
2014-01-02 12:47:09 +01:00
/**
* The code for setting up the blitter mode and sprite information before finally drawing the sprite .
* @ param sprite The sprite to draw .
2021-08-19 18:39:44 +02:00
* @ param x The X location to draw .
* @ param y The Y location to draw .
* @ param mode The settings for the blitter to pass .
* @ param sub Whether to only draw a sub set of the sprite .
* @ param zoom The zoom level at which to draw the sprites .
* @ param dst Optional parameter for a different blitting destination .
2014-01-02 12:47:09 +01:00
* @ tparam ZOOM_BASE The factor required to get the sub sprite information into the right size .
* @ tparam SCALED_XY Whether the X and Y are scaled or unscaled .
*/
template < int ZOOM_BASE , bool SCALED_XY >
2021-08-19 18:39:44 +02:00
static void GfxBlitter ( const Sprite * const sprite , int x , int y , BlitterMode mode , const SubSprite * const sub , SpriteID sprite_id , ZoomLevel zoom , const DrawPixelInfo * dst = nullptr )
2004-08-09 19:04:08 +02:00
{
2021-08-19 18:39:44 +02:00
const DrawPixelInfo * dpi = ( dst ! = nullptr ) ? dst : _cur_dpi ;
2007-06-11 13:50:49 +02:00
Blitter : : BlitterParams bp ;
2004-08-09 19:04:08 +02:00
2014-01-02 12:47:09 +01:00
if ( SCALED_XY ) {
/* Scale it */
x = ScaleByZoom ( x , zoom ) ;
y = ScaleByZoom ( y , zoom ) ;
2007-06-05 14:01:50 +02:00
}
2004-08-09 19:04:08 +02:00
2011-11-24 13:38:48 +01:00
/* Move to the correct offset */
x + = sprite - > x_offs ;
y + = sprite - > y_offs ;
2019-04-10 23:07:06 +02:00
if ( sub = = nullptr ) {
2014-01-02 12:58:13 +01:00
/* No clipping. */
bp . skip_left = 0 ;
bp . skip_top = 0 ;
bp . width = UnScaleByZoom ( sprite - > width , zoom ) ;
bp . height = UnScaleByZoom ( sprite - > height , zoom ) ;
} else {
/* Amount of pixels to clip from the source sprite */
2021-01-08 11:16:18 +01:00
int clip_left = std : : max ( 0 , - sprite - > x_offs + sub - > left * ZOOM_BASE ) ;
int clip_top = std : : max ( 0 , - sprite - > y_offs + sub - > top * ZOOM_BASE ) ;
int clip_right = std : : max ( 0 , sprite - > width - ( - sprite - > x_offs + ( sub - > right + 1 ) * ZOOM_BASE ) ) ;
int clip_bottom = std : : max ( 0 , sprite - > height - ( - sprite - > y_offs + ( sub - > bottom + 1 ) * ZOOM_BASE ) ) ;
2014-01-02 12:58:13 +01:00
if ( clip_left + clip_right > = sprite - > width ) return ;
if ( clip_top + clip_bottom > = sprite - > height ) return ;
bp . skip_left = UnScaleByZoomLower ( clip_left , zoom ) ;
bp . skip_top = UnScaleByZoomLower ( clip_top , zoom ) ;
bp . width = UnScaleByZoom ( sprite - > width - clip_left - clip_right , zoom ) ;
bp . height = UnScaleByZoom ( sprite - > height - clip_top - clip_bottom , zoom ) ;
x + = ScaleByZoom ( bp . skip_left , zoom ) ;
y + = ScaleByZoom ( bp . skip_top , zoom ) ;
}
2011-11-24 13:38:48 +01:00
/* Copy the main data directly from the sprite */
bp . sprite = sprite - > data ;
bp . sprite_width = sprite - > width ;
bp . sprite_height = sprite - > height ;
bp . top = 0 ;
bp . left = 0 ;
bp . dst = dpi - > dst_ptr ;
bp . pitch = dpi - > pitch ;
bp . remap = _colour_remap_ptr ;
assert ( sprite - > width > 0 ) ;
assert ( sprite - > height > 0 ) ;
if ( bp . width < = 0 ) return ;
if ( bp . height < = 0 ) return ;
2014-01-02 12:47:09 +01:00
y - = SCALED_XY ? ScaleByZoom ( dpi - > top , zoom ) : dpi - > top ;
2014-01-02 12:58:13 +01:00
int y_unscaled = UnScaleByZoom ( y , zoom ) ;
2011-11-24 13:38:48 +01:00
/* Check for top overflow */
if ( y < 0 ) {
2014-01-02 12:58:13 +01:00
bp . height - = - y_unscaled ;
2011-11-24 13:38:48 +01:00
if ( bp . height < = 0 ) return ;
2014-01-02 12:58:13 +01:00
bp . skip_top + = - y_unscaled ;
2011-11-24 13:38:48 +01:00
y = 0 ;
} else {
2014-01-02 12:58:13 +01:00
bp . top = y_unscaled ;
2011-11-24 13:38:48 +01:00
}
/* Check for bottom overflow */
2014-01-02 12:47:09 +01:00
y + = SCALED_XY ? ScaleByZoom ( bp . height - dpi - > height , zoom ) : ScaleByZoom ( bp . height , zoom ) - dpi - > height ;
2011-11-24 13:38:48 +01:00
if ( y > 0 ) {
bp . height - = UnScaleByZoom ( y , zoom ) ;
if ( bp . height < = 0 ) return ;
}
2014-01-02 12:47:09 +01:00
x - = SCALED_XY ? ScaleByZoom ( dpi - > left , zoom ) : dpi - > left ;
2014-01-02 12:58:13 +01:00
int x_unscaled = UnScaleByZoom ( x , zoom ) ;
2011-11-24 13:38:48 +01:00
/* Check for left overflow */
if ( x < 0 ) {
2014-01-02 12:58:13 +01:00
bp . width - = - x_unscaled ;
2011-11-24 13:38:48 +01:00
if ( bp . width < = 0 ) return ;
2014-01-02 12:58:13 +01:00
bp . skip_left + = - x_unscaled ;
2011-11-24 13:38:48 +01:00
x = 0 ;
} else {
2014-01-02 12:58:13 +01:00
bp . left = x_unscaled ;
2011-11-24 13:38:48 +01:00
}
/* Check for right overflow */
2014-01-02 12:47:09 +01:00
x + = SCALED_XY ? ScaleByZoom ( bp . width - dpi - > width , zoom ) : ScaleByZoom ( bp . width , zoom ) - dpi - > width ;
2011-11-24 13:38:48 +01:00
if ( x > 0 ) {
bp . width - = UnScaleByZoom ( x , zoom ) ;
if ( bp . width < = 0 ) return ;
}
assert ( bp . skip_left + bp . width < = UnScaleByZoom ( sprite - > width , zoom ) ) ;
assert ( bp . skip_top + bp . height < = UnScaleByZoom ( sprite - > height , zoom ) ) ;
/* We do not want to catch the mouse. However we also use that spritenumber for unknown (text) sprites. */
if ( _newgrf_debug_sprite_picker . mode = = SPM_REDRAW & & sprite_id ! = SPR_CURSOR_MOUSE ) {
2014-01-02 23:41:58 +01:00
Blitter * blitter = BlitterFactory : : GetCurrentBlitter ( ) ;
2011-11-24 13:38:48 +01:00
void * topleft = blitter - > MoveTo ( bp . dst , bp . left , bp . top ) ;
void * bottomright = blitter - > MoveTo ( topleft , bp . width - 1 , bp . height - 1 ) ;
void * clicked = _newgrf_debug_sprite_picker . clicked_pixel ;
if ( topleft < = clicked & & clicked < = bottomright ) {
uint offset = ( ( ( size_t ) clicked - ( size_t ) topleft ) / ( blitter - > GetScreenDepth ( ) / 8 ) ) % bp . pitch ;
if ( offset < ( uint ) bp . width ) {
2019-02-20 22:35:41 +01:00
include ( _newgrf_debug_sprite_picker . sprites , sprite_id ) ;
2011-11-24 13:38:48 +01:00
}
}
}
2014-01-02 23:41:58 +01:00
BlitterFactory : : GetCurrentBlitter ( ) - > Draw ( & bp , mode , zoom ) ;
2011-11-24 13:38:48 +01:00
}
2021-08-19 18:39:44 +02:00
/**
* Draws a sprite to a new RGBA buffer ( see Colour union ) instead of drawing to the screen .
*
* @ param spriteId The sprite to draw .
2021-12-31 00:36:20 +01:00
* @ param zoom The zoom level at which to draw the sprites .
2021-08-19 18:39:44 +02:00
* @ return Pixel buffer , or nullptr if an 8 bpp blitter is being used .
*/
2023-05-08 19:01:06 +02:00
std : : unique_ptr < uint32_t [ ] > DrawSpriteToRgbaBuffer ( SpriteID spriteId , ZoomLevel zoom )
2021-08-19 18:39:44 +02:00
{
2021-12-31 00:36:20 +01:00
/* Invalid zoom level requested? */
if ( zoom < _settings_client . gui . zoom_min | | zoom > _settings_client . gui . zoom_max ) return nullptr ;
2021-08-19 18:39:44 +02:00
Blitter * blitter = BlitterFactory : : GetCurrentBlitter ( ) ;
2021-12-31 01:22:45 +01:00
if ( blitter - > GetScreenDepth ( ) ! = 8 & & blitter - > GetScreenDepth ( ) ! = 32 ) return nullptr ;
2021-08-19 18:39:44 +02:00
/* Gather information about the sprite to write, reserve memory */
const SpriteID real_sprite = GB ( spriteId , 0 , SPRITE_WIDTH ) ;
2023-04-16 21:00:55 +02:00
const Sprite * sprite = GetSprite ( real_sprite , SpriteType : : Normal ) ;
2021-12-31 00:36:20 +01:00
Dimension dim = GetSpriteSize ( real_sprite , nullptr , zoom ) ;
2023-01-02 21:30:02 +01:00
size_t dim_size = static_cast < size_t > ( dim . width ) * dim . height ;
2024-03-11 09:09:44 +01:00
std : : unique_ptr < uint32_t [ ] > result = std : : make_unique < uint32_t [ ] > ( dim_size ) ;
2021-08-19 18:39:44 +02:00
/* Prepare new DrawPixelInfo - Normally this would be the screen but we want to draw to another buffer here.
* Normally , pitch would be scaled screen width , but in our case our " screen " is only the sprite width wide . */
DrawPixelInfo dpi ;
dpi . dst_ptr = result . get ( ) ;
2021-12-31 00:36:20 +01:00
dpi . pitch = dim . width ;
2021-08-19 18:39:44 +02:00
dpi . left = 0 ;
dpi . top = 0 ;
2021-12-31 00:36:20 +01:00
dpi . width = dim . width ;
dpi . height = dim . height ;
dpi . zoom = zoom ;
2021-08-19 18:39:44 +02:00
2023-01-02 21:30:02 +01:00
dim_size = static_cast < size_t > ( dim . width ) * dim . height ;
2021-12-31 01:22:45 +01:00
/* If the current blitter is a paletted blitter, we have to render to an extra buffer and resolve the palette later. */
2024-03-16 23:59:32 +01:00
std : : unique_ptr < uint8_t [ ] > pal_buffer { } ;
2021-12-31 01:22:45 +01:00
if ( blitter - > GetScreenDepth ( ) = = 8 ) {
2024-03-16 23:59:32 +01:00
pal_buffer = std : : make_unique < uint8_t [ ] > ( dim_size ) ;
2021-12-31 01:22:45 +01:00
dpi . dst_ptr = pal_buffer . get ( ) ;
}
2021-08-19 18:39:44 +02:00
/* Temporarily disable screen animations while blitting - This prevents 40bpp_anim from writing to the animation buffer. */
2024-01-17 03:33:23 +01:00
Backup < bool > disable_anim ( _screen_disable_anim , true ) ;
2021-12-31 00:36:20 +01:00
GfxBlitter < 1 , true > ( sprite , 0 , 0 , BM_NORMAL , nullptr , real_sprite , zoom , & dpi ) ;
disable_anim . Restore ( ) ;
2021-08-19 18:39:44 +02:00
2021-12-31 01:22:45 +01:00
if ( blitter - > GetScreenDepth ( ) = = 8 ) {
/* Resolve palette. */
2023-05-08 19:01:06 +02:00
uint32_t * dst = result . get ( ) ;
2024-03-16 23:59:32 +01:00
const uint8_t * src = pal_buffer . get ( ) ;
2023-01-02 21:30:02 +01:00
for ( size_t i = 0 ; i < dim_size ; + + i ) {
2021-12-31 01:22:45 +01:00
* dst + + = _cur_palette . palette [ * src + + ] . data ;
}
}
2021-08-19 18:39:44 +02:00
return result ;
}
2014-01-02 12:47:09 +01:00
static void GfxMainBlitterViewport ( const Sprite * sprite , int x , int y , BlitterMode mode , const SubSprite * sub , SpriteID sprite_id )
{
2024-04-04 19:27:34 +02:00
GfxBlitter < ZOOM_BASE , false > ( sprite , x , y , mode , sub , sprite_id , _cur_dpi - > zoom ) ;
2014-01-02 12:47:09 +01:00
}
static void GfxMainBlitter ( const Sprite * sprite , int x , int y , BlitterMode mode , const SubSprite * sub , SpriteID sprite_id , ZoomLevel zoom )
{
GfxBlitter < 1 , true > ( sprite , x , y , mode , sub , sprite_id , zoom ) ;
}
2011-12-10 13:56:37 +01:00
/**
* Initialize _stringwidth_table cache
* @ param monospace Whether to load the monospace cache or the normal fonts .
*/
void LoadStringWidthTable ( bool monospace )
2004-08-09 19:04:08 +02:00
{
2019-02-24 17:27:44 +01:00
ClearFontCache ( ) ;
2011-12-10 13:56:37 +01:00
for ( FontSize fs = monospace ? FS_MONO : FS_BEGIN ; fs < ( monospace ? FS_END : FS_MONO ) ; fs + + ) {
2009-11-22 17:31:59 +01:00
for ( uint i = 0 ; i ! = 224 ; i + + ) {
_stringwidth_table [ fs ] [ i ] = GetGlyphWidth ( fs , i + 32 ) ;
}
2012-03-17 17:42:34 +01:00
}
2004-08-09 19:04:08 +02:00
}
2008-08-01 11:34:34 +02:00
/**
* Return width of character glyph .
* @ param size Font of the character
* @ param key Character code glyph
* @ return Width of the character glyph
*/
2024-03-16 23:59:32 +01:00
uint8_t GetCharacterWidth ( FontSize size , char32_t key )
2006-11-16 23:05:33 +01:00
{
2008-08-01 11:34:34 +02:00
/* Use _stringwidth_table cache if possible */
2006-11-16 23:05:33 +01:00
if ( key > = 32 & & key < 256 ) return _stringwidth_table [ size ] [ key - 32 ] ;
return GetGlyphWidth ( size , key ) ;
}
2009-12-20 17:42:35 +01:00
/**
* Return the maximum width of single digit .
* @ param size Font of the digit
* @ return Width of the digit .
*/
2024-03-16 23:59:32 +01:00
uint8_t GetDigitWidth ( FontSize size )
2009-12-20 17:42:35 +01:00
{
2024-03-16 23:59:32 +01:00
uint8_t width = 0 ;
2009-12-20 17:42:35 +01:00
for ( char c = ' 0 ' ; c < = ' 9 ' ; c + + ) {
2021-01-08 11:16:18 +01:00
width = std : : max ( GetCharacterWidth ( size , c ) , width ) ;
2009-12-20 17:42:35 +01:00
}
return width ;
}
2013-06-01 16:33:48 +02:00
/**
2013-06-01 17:10:32 +02:00
* Determine the broadest digits for guessing the maximum width of a n - digit number .
2018-10-28 03:17:36 +01:00
* @ param [ out ] front Broadest digit , which is not 0. ( Use this digit as first digit for numbers with more than one digit . )
* @ param [ out ] next Broadest digit , including 0. ( Use this digit for all digits , except the first one ; or for numbers with only one digit . )
2013-06-01 16:33:48 +02:00
* @ param size Font of the digit
*/
2013-06-01 17:10:32 +02:00
void GetBroadestDigit ( uint * front , uint * next , FontSize size )
2013-06-01 16:33:48 +02:00
{
2013-06-01 17:10:32 +02:00
int width = - 1 ;
for ( char c = ' 9 ' ; c > = ' 0 ' ; c - - ) {
int w = GetCharacterWidth ( size , c ) ;
2013-06-01 16:33:48 +02:00
if ( w > width ) {
width = w ;
2013-06-01 17:10:32 +02:00
* next = c - ' 0 ' ;
if ( c ! = ' 0 ' ) * front = c - ' 0 ' ;
2013-06-01 16:33:48 +02:00
}
}
}
2006-11-16 23:05:33 +01:00
2007-03-07 12:47:46 +01:00
void ScreenSizeChanged ( )
2004-08-09 19:04:08 +02:00
{
2010-04-18 16:56:05 +02:00
_dirty_bytes_per_line = CeilDiv ( _screen . width , DIRTY_BLOCK_WIDTH ) ;
2024-03-16 23:59:32 +01:00
_dirty_blocks = ReallocT < uint8_t > ( _dirty_blocks , static_cast < size_t > ( _dirty_bytes_per_line ) * CeilDiv ( _screen . height , DIRTY_BLOCK_HEIGHT ) ) ;
2008-04-18 23:49:38 +02:00
2007-03-01 02:24:44 +01:00
/* check the dirty rect */
2004-08-09 19:04:08 +02:00
if ( _invalid_rect . right > = _screen . width ) _invalid_rect . right = _screen . width ;
if ( _invalid_rect . bottom > = _screen . height ) _invalid_rect . bottom = _screen . height ;
2007-03-01 02:24:44 +01:00
/* screen size changed and the old bitmap is invalid now, so we don't want to undraw it */
2004-08-09 19:04:08 +02:00
_cursor . visible = false ;
}
2007-03-07 12:47:46 +01:00
void UndrawMouseCursor ( )
2004-08-09 19:04:08 +02:00
{
2021-01-16 16:43:33 +01:00
/* Don't undraw mouse cursor if it is handled by the video driver. */
if ( VideoDriver : : GetInstance ( ) - > UseSystemCursor ( ) ) return ;
2009-03-17 17:28:21 +01:00
/* Don't undraw the mouse cursor if the screen is not ready */
2019-04-10 23:07:06 +02:00
if ( _screen . dst_ptr = = nullptr ) return ;
2009-03-17 17:28:21 +01:00
2004-08-09 19:04:08 +02:00
if ( _cursor . visible ) {
2014-01-02 23:41:58 +01:00
Blitter * blitter = BlitterFactory : : GetCurrentBlitter ( ) ;
2004-08-09 19:04:08 +02:00
_cursor . visible = false ;
2009-03-03 21:33:57 +01:00
blitter - > CopyFromBuffer ( blitter - > MoveTo ( _screen . dst_ptr , _cursor . draw_pos . x , _cursor . draw_pos . y ) , _cursor_backup . GetBuffer ( ) , _cursor . draw_size . x , _cursor . draw_size . y ) ;
2014-04-28 23:06:51 +02:00
VideoDriver : : GetInstance ( ) - > MakeDirty ( _cursor . draw_pos . x , _cursor . draw_pos . y , _cursor . draw_size . x , _cursor . draw_size . y ) ;
2004-08-09 19:04:08 +02:00
}
}
2007-03-07 12:47:46 +01:00
void DrawMouseCursor ( )
2004-08-09 19:04:08 +02:00
{
2021-01-16 16:43:33 +01:00
/* Don't draw mouse cursor if it is handled by the video driver. */
if ( VideoDriver : : GetInstance ( ) - > UseSystemCursor ( ) ) return ;
2009-03-17 17:28:21 +01:00
/* Don't draw the mouse cursor if the screen is not ready */
2019-04-10 23:07:06 +02:00
if ( _screen . dst_ptr = = nullptr ) return ;
2009-03-17 17:28:21 +01:00
2014-01-02 23:41:58 +01:00
Blitter * blitter = BlitterFactory : : GetCurrentBlitter ( ) ;
2004-08-09 19:04:08 +02:00
2006-03-24 01:42:35 +01:00
/* Redraw mouse cursor but only when it's inside the window */
if ( ! _cursor . in_window ) return ;
2007-03-01 02:24:44 +01:00
/* Don't draw the mouse cursor if it's already drawn */
2004-08-09 19:04:08 +02:00
if ( _cursor . visible ) {
2005-07-05 13:09:18 +02:00
if ( ! _cursor . dirty ) return ;
2004-08-09 19:04:08 +02:00
UndrawMouseCursor ( ) ;
}
2016-08-15 20:33:08 +02:00
/* Determine visible area */
int left = _cursor . pos . x + _cursor . total_offs . x ;
int width = _cursor . total_size . x ;
if ( left < 0 ) {
width + = left ;
left = 0 ;
}
if ( left + width > _screen . width ) {
width = _screen . width - left ;
2005-07-05 13:09:18 +02:00
}
2016-08-15 20:33:08 +02:00
if ( width < = 0 ) return ;
2004-08-09 19:04:08 +02:00
2016-08-15 20:33:08 +02:00
int top = _cursor . pos . y + _cursor . total_offs . y ;
int height = _cursor . total_size . y ;
if ( top < 0 ) {
height + = top ;
top = 0 ;
}
if ( top + height > _screen . height ) {
height = _screen . height - top ;
2005-07-05 13:09:18 +02:00
}
2016-08-15 20:33:08 +02:00
if ( height < = 0 ) return ;
_cursor . draw_pos . x = left ;
_cursor . draw_pos . y = top ;
_cursor . draw_size . x = width ;
_cursor . draw_size . y = height ;
2004-08-09 19:04:08 +02:00
2023-05-08 19:01:06 +02:00
uint8_t * buffer = _cursor_backup . Allocate ( blitter - > BufferSize ( _cursor . draw_size . x , _cursor . draw_size . y ) ) ;
2004-08-09 19:04:08 +02:00
2007-03-01 02:24:44 +01:00
/* Make backup of stuff below cursor */
2009-03-03 21:33:57 +01:00
blitter - > CopyToBuffer ( blitter - > MoveTo ( _screen . dst_ptr , _cursor . draw_pos . x , _cursor . draw_pos . y ) , buffer , _cursor . draw_size . x , _cursor . draw_size . y ) ;
2004-08-09 19:04:08 +02:00
2007-03-01 02:24:44 +01:00
/* Draw cursor on screen */
2004-08-09 19:04:08 +02:00
_cur_dpi = & _screen ;
2024-04-24 22:26:07 +02:00
for ( const auto & cs : _cursor . sprites ) {
DrawSprite ( cs . image . sprite , cs . image . pal , _cursor . pos . x + cs . pos . x , _cursor . pos . y + cs . pos . y ) ;
2016-08-15 20:33:08 +02:00
}
2004-08-09 19:04:08 +02:00
2014-04-28 23:06:51 +02:00
VideoDriver : : GetInstance ( ) - > MakeDirty ( _cursor . draw_pos . x , _cursor . draw_pos . y , _cursor . draw_size . x , _cursor . draw_size . y ) ;
2004-08-09 19:04:08 +02:00
_cursor . visible = true ;
_cursor . dirty = false ;
}
2020-06-08 00:38:06 +02:00
/**
* Repaints a specific rectangle of the screen .
*
* @ param left , top , right , bottom The area of the screen that needs repainting
* @ pre The rectangle should have been previously marked dirty with \ c AddDirtyBlock .
* @ see AddDirtyBlock
* @ see DrawDirtyBlocks
* @ ingroup dirty
*
*/
2004-08-09 19:04:08 +02:00
void RedrawScreenRect ( int left , int top , int right , int bottom )
{
assert ( right < = _screen . width & & bottom < = _screen . height ) ;
if ( _cursor . visible ) {
if ( right > _cursor . draw_pos . x & &
left < _cursor . draw_pos . x + _cursor . draw_size . x & &
bottom > _cursor . draw_pos . y & &
top < _cursor . draw_pos . y + _cursor . draw_size . y ) {
UndrawMouseCursor ( ) ;
}
}
2008-08-12 00:45:11 +02:00
2010-01-12 01:10:38 +01:00
if ( _networking ) NetworkUndrawChatMessage ( ) ;
2004-08-09 19:04:08 +02:00
2007-06-21 18:53:57 +02:00
DrawOverlappedWindowForAll ( left , top , right , bottom ) ;
2004-08-09 19:04:08 +02:00
2014-04-28 23:06:51 +02:00
VideoDriver : : GetInstance ( ) - > MakeDirty ( left , top , right - left , bottom - top ) ;
2004-08-09 19:04:08 +02:00
}
2011-01-18 23:31:06 +01:00
/**
2007-09-09 12:13:17 +02:00
* Repaints the rectangle blocks which are marked as ' dirty ' .
*
2020-06-08 00:38:06 +02:00
* @ see AddDirtyBlock
*
* @ ingroup dirty
2007-09-09 12:13:17 +02:00
*/
2007-03-07 12:47:46 +01:00
void DrawDirtyBlocks ( )
2004-08-09 19:04:08 +02:00
{
2024-03-16 23:59:32 +01:00
uint8_t * b = _dirty_blocks ;
2008-04-18 21:26:52 +02:00
const int w = Align ( _screen . width , DIRTY_BLOCK_WIDTH ) ;
const int h = Align ( _screen . height , DIRTY_BLOCK_HEIGHT ) ;
2005-07-08 23:24:27 +02:00
int x ;
int y ;
2004-08-09 19:04:08 +02:00
2005-07-08 23:24:27 +02:00
y = 0 ;
2004-08-09 19:04:08 +02:00
do {
2005-07-08 23:24:27 +02:00
x = 0 ;
do {
if ( * b ! = 0 ) {
int left ;
int top ;
2008-04-18 21:26:52 +02:00
int right = x + DIRTY_BLOCK_WIDTH ;
2005-07-08 23:24:27 +02:00
int bottom = y ;
2024-03-16 23:59:32 +01:00
uint8_t * p = b ;
2005-07-08 23:24:27 +02:00
int h2 ;
2007-03-01 02:24:44 +01:00
/* First try coalescing downwards */
2004-08-09 19:04:08 +02:00
do {
2005-07-08 23:24:27 +02:00
* p = 0 ;
2008-04-18 23:49:38 +02:00
p + = _dirty_bytes_per_line ;
2008-04-18 21:26:52 +02:00
bottom + = DIRTY_BLOCK_HEIGHT ;
2005-07-08 23:24:27 +02:00
} while ( bottom ! = h & & * p ! = 0 ) ;
2007-03-01 02:24:44 +01:00
/* Try coalescing to the right too. */
2008-04-18 21:26:52 +02:00
h2 = ( bottom - y ) / DIRTY_BLOCK_HEIGHT ;
2005-07-08 23:24:27 +02:00
assert ( h2 > 0 ) ;
p = b ;
while ( right ! = w ) {
2024-03-16 23:59:32 +01:00
uint8_t * p2 = + + p ;
2023-01-28 20:06:51 +01:00
int i = h2 ;
2007-03-01 02:24:44 +01:00
/* Check if a full line of dirty flags is set. */
2005-07-08 23:24:27 +02:00
do {
if ( ! * p2 ) goto no_more_coalesc ;
2008-04-18 23:49:38 +02:00
p2 + = _dirty_bytes_per_line ;
2023-01-28 20:06:51 +01:00
} while ( - - i ! = 0 ) ;
2004-08-09 19:04:08 +02:00
2007-03-01 02:24:44 +01:00
/* Wohoo, can combine it one step to the right!
* Do that , and clear the bits . */
2008-04-18 21:26:52 +02:00
right + = DIRTY_BLOCK_WIDTH ;
2004-08-09 19:04:08 +02:00
2023-01-28 20:06:51 +01:00
i = h2 ;
2005-07-08 23:24:27 +02:00
p2 = p ;
do {
* p2 = 0 ;
2008-04-18 23:49:38 +02:00
p2 + = _dirty_bytes_per_line ;
2023-01-28 20:06:51 +01:00
} while ( - - i ! = 0 ) ;
2005-07-08 23:24:27 +02:00
}
no_more_coalesc :
2004-08-09 19:04:08 +02:00
2005-07-08 23:24:27 +02:00
left = x ;
top = y ;
2004-08-09 19:04:08 +02:00
2005-07-08 23:24:27 +02:00
if ( left < _invalid_rect . left ) left = _invalid_rect . left ;
if ( top < _invalid_rect . top ) top = _invalid_rect . top ;
if ( right > _invalid_rect . right ) right = _invalid_rect . right ;
if ( bottom > _invalid_rect . bottom ) bottom = _invalid_rect . bottom ;
2004-08-09 19:04:08 +02:00
2005-07-08 23:24:27 +02:00
if ( left < right & & top < bottom ) {
RedrawScreenRect ( left , top , right , bottom ) ;
}
2004-08-09 19:04:08 +02:00
2005-07-08 23:24:27 +02:00
}
2008-04-18 21:26:52 +02:00
} while ( b + + , ( x + = DIRTY_BLOCK_WIDTH ) ! = w ) ;
2010-05-13 14:42:56 +02:00
} while ( b + = - ( int ) ( w / DIRTY_BLOCK_WIDTH ) + _dirty_bytes_per_line , ( y + = DIRTY_BLOCK_HEIGHT ) ! = h ) ;
2004-08-09 19:04:08 +02:00
2012-03-25 21:06:59 +02:00
+ + _dirty_block_colour ;
2004-08-09 19:04:08 +02:00
_invalid_rect . left = w ;
_invalid_rect . top = h ;
_invalid_rect . right = 0 ;
_invalid_rect . bottom = 0 ;
}
2011-01-18 23:31:06 +01:00
/**
2020-06-08 00:38:06 +02:00
* Extend the internal _invalid_rect rectangle to contain the rectangle
* defined by the given parameters . Note the point ( 0 , 0 ) is top left .
2007-09-09 12:13:17 +02:00
*
* @ param left The left edge of the rectangle
* @ param top The top edge of the rectangle
* @ param right The right edge of the rectangle
2013-01-08 23:46:42 +01:00
* @ param bottom The bottom edge of the rectangle
2007-09-09 12:13:17 +02:00
* @ see DrawDirtyBlocks
2020-06-08 00:38:06 +02:00
* @ ingroup dirty
2007-09-09 12:13:17 +02:00
*
*/
2020-06-08 00:38:06 +02:00
void AddDirtyBlock ( int left , int top , int right , int bottom )
2004-08-09 19:04:08 +02:00
{
2024-03-16 23:59:32 +01:00
uint8_t * b ;
2005-07-05 13:09:18 +02:00
int width ;
int height ;
2004-08-09 19:04:08 +02:00
if ( left < 0 ) left = 0 ;
if ( top < 0 ) top = 0 ;
if ( right > _screen . width ) right = _screen . width ;
if ( bottom > _screen . height ) bottom = _screen . height ;
2005-07-05 13:09:18 +02:00
if ( left > = right | | top > = bottom ) return ;
2004-08-09 19:04:08 +02:00
2005-07-05 13:09:18 +02:00
if ( left < _invalid_rect . left ) _invalid_rect . left = left ;
if ( top < _invalid_rect . top ) _invalid_rect . top = top ;
if ( right > _invalid_rect . right ) _invalid_rect . right = right ;
if ( bottom > _invalid_rect . bottom ) _invalid_rect . bottom = bottom ;
2004-08-09 19:04:08 +02:00
2008-04-18 21:26:52 +02:00
left / = DIRTY_BLOCK_WIDTH ;
top / = DIRTY_BLOCK_HEIGHT ;
2004-08-09 19:04:08 +02:00
2008-04-18 23:49:38 +02:00
b = _dirty_blocks + top * _dirty_bytes_per_line + left ;
2004-08-09 19:04:08 +02:00
2008-04-18 21:26:52 +02:00
width = ( ( right - 1 ) / DIRTY_BLOCK_WIDTH ) - left + 1 ;
height = ( ( bottom - 1 ) / DIRTY_BLOCK_HEIGHT ) - top + 1 ;
2004-08-09 19:04:08 +02:00
assert ( width > 0 & & height > 0 ) ;
do {
2005-07-05 13:09:18 +02:00
int i = width ;
2011-02-25 23:04:38 +01:00
do b [ - - i ] = 0xFF ; while ( i ! = 0 ) ;
2004-08-09 19:04:08 +02:00
2008-04-18 23:49:38 +02:00
b + = _dirty_bytes_per_line ;
2005-07-05 13:09:18 +02:00
} while ( - - height ! = 0 ) ;
2004-08-09 19:04:08 +02:00
}
2011-01-18 23:31:06 +01:00
/**
2007-09-09 12:13:17 +02:00
* This function mark the whole screen as dirty . This results in repainting
* the whole screen . Use this with care as this function will break the
* idea about marking only parts of the screen as ' dirty ' .
2011-01-18 23:31:06 +01:00
* @ ingroup dirty
2007-09-09 12:13:17 +02:00
*/
2007-03-07 12:47:46 +01:00
void MarkWholeScreenDirty ( )
2004-08-09 19:04:08 +02:00
{
2020-06-08 00:38:06 +02:00
AddDirtyBlock ( 0 , 0 , _screen . width , _screen . height ) ;
2004-08-09 19:04:08 +02:00
}
2010-08-01 21:22:34 +02:00
/**
* Set up a clipping area for only drawing into a certain area . To do this ,
2006-10-27 13:09:24 +02:00
* Fill a DrawPixelInfo object with the supplied relative rectangle , backup
* the original ( calling ) _cur_dpi and assign the just returned DrawPixelInfo
* _cur_dpi . When you are done , give restore _cur_dpi ' s original value
* @ param * n the DrawPixelInfo that will be the clipping rectangle box allowed
* for drawing
* @ param left , top , width , height the relative coordinates of the clipping
* rectangle relative to the current _cur_dpi . This will most likely be the
* offset from the calling window coordinates
* @ return return false if the requested rectangle is not possible with the
* current dpi pointer . Only continue of the return value is true , or you ' ll
2010-08-01 21:44:49 +02:00
* get some nasty results
*/
2006-08-28 09:33:51 +02:00
bool FillDrawPixelInfo ( DrawPixelInfo * n , int left , int top , int width , int height )
2004-08-09 19:04:08 +02:00
{
2014-01-02 23:41:58 +01:00
Blitter * blitter = BlitterFactory : : GetCurrentBlitter ( ) ;
2006-08-28 09:33:51 +02:00
const DrawPixelInfo * o = _cur_dpi ;
2004-08-09 19:04:08 +02:00
2024-04-04 19:49:37 +02:00
n - > zoom = ZOOM_LVL_MIN ;
2004-08-09 19:04:08 +02:00
assert ( width > 0 ) ;
assert ( height > 0 ) ;
if ( ( left - = o - > left ) < 0 ) {
2005-07-05 13:09:18 +02:00
width + = left ;
2006-09-02 23:52:06 +02:00
if ( width < = 0 ) return false ;
2004-08-09 19:04:08 +02:00
n - > left = - left ;
left = 0 ;
2006-08-28 09:33:51 +02:00
} else {
n - > left = 0 ;
2004-08-09 19:04:08 +02:00
}
2006-08-28 09:33:51 +02:00
if ( width > o - > width - left ) {
width = o - > width - left ;
2006-09-02 23:52:06 +02:00
if ( width < = 0 ) return false ;
2004-08-09 19:04:08 +02:00
}
n - > width = width ;
if ( ( top - = o - > top ) < 0 ) {
2005-07-05 13:09:18 +02:00
height + = top ;
2006-09-02 23:52:06 +02:00
if ( height < = 0 ) return false ;
2004-08-09 19:04:08 +02:00
n - > top = - top ;
top = 0 ;
2006-08-28 09:33:51 +02:00
} else {
n - > top = 0 ;
2004-08-09 19:04:08 +02:00
}
2007-06-17 22:30:28 +02:00
n - > dst_ptr = blitter - > MoveTo ( o - > dst_ptr , left , top ) ;
2007-06-12 22:24:12 +02:00
n - > pitch = o - > pitch ;
2004-08-09 19:04:08 +02:00
2006-08-28 09:33:51 +02:00
if ( height > o - > height - top ) {
height = o - > height - top ;
2006-09-02 23:52:06 +02:00
if ( height < = 0 ) return false ;
2004-08-09 19:04:08 +02:00
}
n - > height = height ;
return true ;
}
2010-08-05 21:23:19 +02:00
/**
* Update cursor dimension .
* Called when changing cursor sprite resp . reloading grfs .
*/
void UpdateCursorSize ( )
2004-08-09 19:04:08 +02:00
{
2016-08-15 20:33:08 +02:00
/* Ignore setting any cursor before the sprites are loaded. */
if ( GetMaxSpriteID ( ) = = 0 ) return ;
2024-04-24 22:26:07 +02:00
bool first = true ;
for ( const auto & cs : _cursor . sprites ) {
const Sprite * p = GetSprite ( GB ( cs . image . sprite , 0 , SPRITE_WIDTH ) , SpriteType : : Normal ) ;
2016-08-15 20:33:08 +02:00
Point offs , size ;
2024-04-24 22:26:07 +02:00
offs . x = UnScaleGUI ( p - > x_offs ) + cs . pos . x ;
offs . y = UnScaleGUI ( p - > y_offs ) + cs . pos . y ;
2016-08-15 20:33:08 +02:00
size . x = UnScaleGUI ( p - > width ) ;
size . y = UnScaleGUI ( p - > height ) ;
2024-04-24 22:26:07 +02:00
if ( first ) {
/* First sprite sets the total. */
2016-08-15 20:33:08 +02:00
_cursor . total_offs = offs ;
_cursor . total_size = size ;
2024-04-24 22:26:07 +02:00
first = false ;
2016-08-15 20:33:08 +02:00
} else {
2024-04-24 22:26:07 +02:00
/* Additional sprites expand the total. */
2021-01-08 11:16:18 +01:00
int right = std : : max ( _cursor . total_offs . x + _cursor . total_size . x , offs . x + size . x ) ;
int bottom = std : : max ( _cursor . total_offs . y + _cursor . total_size . y , offs . y + size . y ) ;
2016-08-15 20:33:08 +02:00
if ( offs . x < _cursor . total_offs . x ) _cursor . total_offs . x = offs . x ;
if ( offs . y < _cursor . total_offs . y ) _cursor . total_offs . y = offs . y ;
_cursor . total_size . x = right - _cursor . total_offs . x ;
_cursor . total_size . y = bottom - _cursor . total_offs . y ;
}
}
2004-08-09 19:04:08 +02:00
2016-08-15 20:33:08 +02:00
_cursor . dirty = true ;
2010-08-05 21:23:19 +02:00
}
/**
* Switch cursor to different sprite .
* @ param cursor Sprite to draw for the cursor .
* @ param pal Palette to use for recolouring .
*/
static void SetCursorSprite ( CursorID cursor , PaletteID pal )
{
2024-04-24 22:26:07 +02:00
if ( _cursor . sprites . size ( ) = = 1 & & _cursor . sprites [ 0 ] . image . sprite = = cursor & & _cursor . sprites [ 0 ] . image . pal = = pal ) return ;
2010-08-05 21:23:19 +02:00
2024-04-24 22:26:07 +02:00
_cursor . sprites . clear ( ) ;
_cursor . sprites . emplace_back ( cursor , pal , 0 , 0 ) ;
2010-08-05 21:23:19 +02:00
2016-08-15 20:33:08 +02:00
UpdateCursorSize ( ) ;
2004-08-09 19:04:08 +02:00
}
2007-03-07 12:47:46 +01:00
static void SwitchAnimatedCursor ( )
2004-08-09 19:04:08 +02:00
{
2007-02-24 16:05:14 +01:00
const AnimCursor * cur = _cursor . animate_cur ;
2005-06-06 15:47:06 +02:00
2019-04-10 23:07:06 +02:00
if ( cur = = nullptr | | cur - > sprite = = AnimCursor : : LAST ) cur = _cursor . animate_list ;
2004-08-09 19:04:08 +02:00
2024-04-24 22:26:07 +02:00
assert ( ! _cursor . sprites . empty ( ) ) ;
SetCursorSprite ( cur - > sprite , _cursor . sprites [ 0 ] . image . pal ) ;
2004-08-09 19:04:08 +02:00
2007-02-24 16:05:14 +01:00
_cursor . animate_timeout = cur - > display_time ;
_cursor . animate_cur = cur + 1 ;
2004-08-09 19:04:08 +02:00
}
2007-03-07 12:47:46 +01:00
void CursorTick ( )
2005-01-22 21:23:18 +01:00
{
2010-07-24 12:14:39 +02:00
if ( _cursor . animate_timeout ! = 0 & & - - _cursor . animate_timeout = = 0 ) {
2004-08-09 19:04:08 +02:00
SwitchAnimatedCursor ( ) ;
2010-07-24 12:14:39 +02:00
}
2004-08-09 19:04:08 +02:00
}
2016-08-15 20:32:48 +02:00
/**
* Set or unset the ZZZ cursor .
* @ param busy Whether to show the ZZZ cursor .
*/
void SetMouseCursorBusy ( bool busy )
{
2024-04-24 22:26:07 +02:00
assert ( ! _cursor . sprites . empty ( ) ) ;
2016-08-15 20:32:48 +02:00
if ( busy ) {
2024-04-24 22:26:07 +02:00
if ( _cursor . sprites [ 0 ] . image . sprite = = SPR_CURSOR_MOUSE ) SetMouseCursor ( SPR_CURSOR_ZZZ , PAL_NONE ) ;
2016-08-15 20:32:48 +02:00
} else {
2024-04-24 22:26:07 +02:00
if ( _cursor . sprites [ 0 ] . image . sprite = = SPR_CURSOR_ZZZ ) SetMouseCursor ( SPR_CURSOR_MOUSE , PAL_NONE ) ;
2016-08-15 20:32:48 +02:00
}
}
2010-08-05 21:23:19 +02:00
/**
* Assign a single non - animated sprite to the cursor .
* @ param sprite Sprite to draw for the cursor .
* @ param pal Palette to use for recolouring .
* @ see SetAnimatedMouseCursor
*/
2010-01-21 02:38:13 +01:00
void SetMouseCursor ( CursorID sprite , PaletteID pal )
2004-08-09 19:04:08 +02:00
{
2007-03-01 02:24:44 +01:00
/* Turn off animation */
2004-08-09 19:04:08 +02:00
_cursor . animate_timeout = 0 ;
2007-03-01 02:24:44 +01:00
/* Set cursor */
2007-01-14 20:57:49 +01:00
SetCursorSprite ( sprite , pal ) ;
2004-08-09 19:04:08 +02:00
}
2010-08-05 21:23:19 +02:00
/**
* Assign an animation to the cursor .
* @ param table Array of animation states .
* @ see SetMouseCursor
*/
2007-02-24 16:05:14 +01:00
void SetAnimatedMouseCursor ( const AnimCursor * table )
2004-08-09 19:04:08 +02:00
{
2024-04-24 22:26:07 +02:00
assert ( ! _cursor . sprites . empty ( ) ) ;
2004-08-09 19:04:08 +02:00
_cursor . animate_list = table ;
2019-04-10 23:07:06 +02:00
_cursor . animate_cur = nullptr ;
2024-04-24 22:26:07 +02:00
_cursor . sprites [ 0 ] . image . pal = PAL_NONE ;
2004-08-09 19:04:08 +02:00
SwitchAnimatedCursor ( ) ;
}
2020-12-06 20:18:19 +01:00
/**
2023-06-03 23:09:41 +02:00
* Update cursor position based on a relative change .
*
2020-12-06 20:18:19 +01:00
* @ param delta_x How much change in the X position .
* @ param delta_y How much change in the Y position .
*/
void CursorVars : : UpdateCursorPositionRelative ( int delta_x , int delta_y )
{
2023-06-03 23:09:41 +02:00
assert ( this - > fix_at ) ;
2020-12-06 20:18:19 +01:00
2023-06-03 23:09:41 +02:00
this - > delta . x = delta_x ;
this - > delta . y = delta_y ;
2020-12-06 20:18:19 +01:00
}
2015-02-23 00:06:45 +01:00
/**
* Update cursor position on mouse movement .
* @ param x New X position .
* @ param y New Y position .
* @ return true , if the OS cursor position should be warped back to this - > pos .
*/
2023-06-03 23:10:16 +02:00
bool CursorVars : : UpdateCursorPosition ( int x , int y )
2015-02-23 00:06:45 +01:00
{
2023-06-03 23:10:16 +02:00
this - > delta . x = x - this - > pos . x ;
this - > delta . y = y - this - > pos . y ;
2015-02-23 00:06:45 +01:00
if ( this - > fix_at ) {
2023-06-03 23:10:16 +02:00
return this - > delta . x ! = 0 | | this - > delta . y ! = 0 ;
2015-02-23 00:06:45 +01:00
} else if ( this - > pos . x ! = x | | this - > pos . y ! = y ) {
this - > dirty = true ;
this - > pos . x = x ;
this - > pos . y = y ;
}
2023-06-03 23:10:16 +02:00
return false ;
2015-02-23 00:06:45 +01:00
}
2007-07-29 17:48:43 +02:00
bool ChangeResInGame ( int width , int height )
2004-09-10 21:02:27 +02:00
{
2014-04-28 23:06:51 +02:00
return ( _screen . width = = width & & _screen . height = = height ) | | VideoDriver : : GetInstance ( ) - > ChangeResolution ( width , height ) ;
2004-08-09 19:04:08 +02:00
}
2004-09-21 22:56:49 +02:00
2008-01-01 15:20:48 +01:00
bool ToggleFullScreen ( bool fs )
2006-12-03 18:42:56 +01:00
{
2014-04-28 23:06:51 +02:00
bool result = VideoDriver : : GetInstance ( ) - > ToggleFullscreen ( fs ) ;
2019-04-12 18:46:49 +02:00
if ( _fullscreen ! = fs & & _resolutions . empty ( ) ) {
2021-06-12 09:10:17 +02:00
Debug ( driver , 0 , " Could not find a suitable fullscreen resolution " ) ;
2006-12-03 18:42:56 +01:00
}
2008-01-01 15:20:48 +01:00
return result ;
2006-12-03 18:42:56 +01:00
}
2005-05-16 18:19:32 +02:00
2019-04-12 18:46:49 +02:00
void SortResolutions ( )
2004-09-21 22:56:49 +02:00
{
2019-04-12 18:46:49 +02:00
std : : sort ( _resolutions . begin ( ) , _resolutions . end ( ) ) ;
2024-01-14 23:25:54 +01:00
/* Remove any duplicates from the list. */
auto last = std : : unique ( _resolutions . begin ( ) , _resolutions . end ( ) ) ;
_resolutions . erase ( last , _resolutions . end ( ) ) ;
2004-09-21 22:56:49 +02:00
}
2021-01-08 22:15:06 +01:00
/**
* Resolve GUI zoom level , if auto - suggestion is requested .
*/
void UpdateGUIZoom ( )
{
/* Determine real GUI zoom to use. */
2021-04-20 12:49:20 +02:00
if ( _gui_scale_cfg = = - 1 ) {
_gui_scale = VideoDriver : : GetInstance ( ) - > GetSuggestedUIScale ( ) ;
2021-01-08 22:15:06 +01:00
} else {
2021-04-20 12:49:20 +02:00
_gui_scale = Clamp ( _gui_scale_cfg , MIN_INTERFACE_SCALE , MAX_INTERFACE_SCALE ) ;
2021-01-08 22:15:06 +01:00
}
2024-04-04 19:51:46 +02:00
int8_t new_zoom = ScaleGUITrad ( 1 ) < = 1 ? ZOOM_LVL_NORMAL : ScaleGUITrad ( 1 ) > = 4 ? ZOOM_LVL_IN_4X : ZOOM_LVL_IN_2X ;
2023-04-17 00:14:03 +02:00
/* Font glyphs should not be clamped to min/max zoom. */
_font_zoom = static_cast < ZoomLevel > ( new_zoom ) ;
2021-04-20 12:49:20 +02:00
/* Ensure the gui_zoom is clamped between min/max. */
new_zoom = Clamp ( new_zoom , _settings_client . gui . zoom_min , _settings_client . gui . zoom_max ) ;
_gui_zoom = static_cast < ZoomLevel > ( new_zoom ) ;
2021-01-08 22:15:06 +01:00
}
2021-02-28 15:41:03 +01:00
2022-09-07 22:17:10 +02:00
/**
* Resolve GUI zoom level and adjust GUI to new zoom , if auto - suggestion is requested .
2021-04-20 12:49:20 +02:00
* @ param automatic Set if the change is occuring due to OS DPI scaling being changed .
2022-09-07 22:17:10 +02:00
* @ returns true when the zoom level has changed , caller must call ReInitAllWindows ( true )
* after resizing the application ' s window / buffer .
*/
2021-04-20 12:49:20 +02:00
bool AdjustGUIZoom ( bool automatic )
2022-09-07 22:17:10 +02:00
{
2023-04-17 00:14:03 +02:00
ZoomLevel old_gui_zoom = _gui_zoom ;
ZoomLevel old_font_zoom = _font_zoom ;
2021-04-20 12:49:20 +02:00
int old_scale = _gui_scale ;
2022-09-07 22:17:10 +02:00
UpdateGUIZoom ( ) ;
2023-12-11 13:30:20 +01:00
if ( old_scale = = _gui_scale & & old_gui_zoom = = _gui_zoom ) return false ;
2021-04-20 12:49:20 +02:00
2023-12-11 22:03:54 +01:00
/* Update cursors if sprite zoom level has changed. */
2023-04-17 00:14:03 +02:00
if ( old_gui_zoom ! = _gui_zoom ) {
2021-04-20 12:49:20 +02:00
VideoDriver : : GetInstance ( ) - > ClearSystemSprites ( ) ;
UpdateCursorSize ( ) ;
2023-12-11 22:03:54 +01:00
}
if ( old_font_zoom ! = _font_zoom ) {
2023-04-17 00:14:03 +02:00
GfxClearFontSpriteCache ( ) ;
2021-04-20 12:49:20 +02:00
}
2023-12-13 20:51:44 +01:00
ClearFontCache ( ) ;
LoadStringWidthTable ( ) ;
2021-04-20 12:49:20 +02:00
2023-12-20 23:10:05 +01:00
SetupWidgetDimensions ( ) ;
2022-09-07 22:17:10 +02:00
UpdateAllVirtCoords ( ) ;
/* Adjust all window sizes to match the new zoom level, so that they don't appear
to move around when the application is moved to a screen with different DPI . */
2023-04-17 00:14:03 +02:00
auto zoom_shift = old_gui_zoom - _gui_zoom ;
2022-09-07 22:17:10 +02:00
for ( Window * w : Window : : Iterate ( ) ) {
2021-04-20 12:49:20 +02:00
if ( automatic ) {
w - > left = ( w - > left * _gui_scale ) / old_scale ;
w - > top = ( w - > top * _gui_scale ) / old_scale ;
}
2022-09-07 22:17:10 +02:00
if ( w - > viewport ! = nullptr ) {
2023-12-22 16:23:42 +01:00
w - > viewport - > zoom = static_cast < ZoomLevel > ( Clamp ( w - > viewport - > zoom - zoom_shift , _settings_client . gui . zoom_min , _settings_client . gui . zoom_max ) ) ;
2022-09-07 22:17:10 +02:00
}
}
return true ;
}
2021-02-28 15:41:03 +01:00
void ChangeGameSpeed ( bool enable_fast_forward )
{
if ( enable_fast_forward ) {
_game_speed = _settings_client . gui . fast_forward_speed_limit ;
} else {
_game_speed = 100 ;
}
}