2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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/>.
8 /** @file gfx.cpp Handling of drawing text and other gfx related stuff. */
11 #include "gfx_layout.h"
13 #include "zoom_func.h"
14 #include "blitter/factory.hpp"
15 #include "video/video_driver.hpp"
16 #include "strings_func.h"
17 #include "settings_type.h"
18 #include "network/network.h"
19 #include "network/network_func.h"
20 #include "window_gui.h"
21 #include "window_func.h"
22 #include "newgrf_debug.h"
23 #include "core/backup_type.hpp"
24 #include "core/container_func.hpp"
25 #include "core/geometry_func.hpp"
26 #include "viewport_func.h"
28 #include "table/string_colours.h"
29 #include "table/sprites.h"
30 #include "table/control_codes.h"
32 #include "safeguards.h"
34 uint8_t _dirkeys
; ///< 1 = left, 2 = up, 4 = right, 8 = down
38 bool _ctrl_pressed
; ///< Is Ctrl pressed?
39 bool _shift_pressed
; ///< Is Shift pressed?
40 uint16_t _game_speed
= 100; ///< Current game-speed; 100 is 1x, 0 is infinite.
41 bool _left_button_down
; ///< Is left mouse button pressed?
42 bool _left_button_clicked
; ///< Is left mouse button clicked?
43 bool _right_button_down
; ///< Is right mouse button pressed?
44 bool _right_button_clicked
; ///< Is right mouse button clicked?
45 DrawPixelInfo _screen
;
46 bool _screen_disable_anim
= false; ///< Disable palette animation (important for 32bpp-anim blitter during giant screenshot)
47 std::atomic
<bool> _exit_game
;
49 SwitchMode _switch_mode
; ///< The next mainloop command.
50 PauseMode _pause_mode
;
51 GameSessionStats _game_session_stats
; ///< Statistics about the current session.
53 static uint8_t _stringwidth_table
[FS_END
][224]; ///< Cache containing width of often used characters. @see GetCharacterWidth()
54 DrawPixelInfo
*_cur_dpi
;
56 static void GfxMainBlitterViewport(const Sprite
*sprite
, int x
, int y
, BlitterMode mode
, const SubSprite
*sub
= nullptr, SpriteID sprite_id
= SPR_CURSOR_MOUSE
);
57 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
);
59 static ReusableBuffer
<uint8_t> _cursor_backup
;
61 ZoomLevel _gui_zoom
= ZOOM_LVL_NORMAL
; ///< GUI Zoom level
62 ZoomLevel _font_zoom
= _gui_zoom
; ///< Sprite font Zoom level (not clamped)
63 int _gui_scale
= MIN_INTERFACE_SCALE
; ///< GUI scale, 100 is 100%.
64 int _gui_scale_cfg
; ///< GUI scale in config.
67 * The rect for repaint.
69 * This rectangle defines the area which should be repaint by the video driver.
73 static Rect _invalid_rect
;
74 static const uint8_t *_colour_remap_ptr
;
75 static uint8_t _string_colourremap
[3]; ///< Recoloursprite for stringdrawing. The grf loader ensures that #SpriteType::Font sprites only use colours 0 to 2.
77 static const uint DIRTY_BLOCK_HEIGHT
= 8;
78 static const uint DIRTY_BLOCK_WIDTH
= 64;
80 static uint _dirty_bytes_per_line
= 0;
81 static uint8_t *_dirty_blocks
= nullptr;
82 extern uint _dirty_block_colour
;
84 void GfxScroll(int left
, int top
, int width
, int height
, int xo
, int yo
)
86 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
88 if (xo
== 0 && yo
== 0) return;
90 if (_cursor
.visible
) UndrawMouseCursor();
92 if (_networking
) NetworkUndrawChatMessage();
94 blitter
->ScrollBuffer(_screen
.dst_ptr
, left
, top
, width
, height
, xo
, yo
);
95 /* This part of the screen is now dirty. */
96 VideoDriver::GetInstance()->MakeDirty(left
, top
, width
, height
);
101 * Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
103 * @pre dpi->zoom == ZOOM_LVL_MIN, right >= left, bottom >= top
104 * @param left Minimum X (inclusive)
105 * @param top Minimum Y (inclusive)
106 * @param right Maximum X (inclusive)
107 * @param bottom Maximum Y (inclusive)
108 * @param colour A 8 bit palette index (FILLRECT_OPAQUE and FILLRECT_CHECKER) or a recolour spritenumber (FILLRECT_RECOLOUR)
110 * FILLRECT_OPAQUE: Fill the rectangle with the specified colour
111 * FILLRECT_CHECKER: Like FILLRECT_OPAQUE, but only draw every second pixel (used to grey out things)
112 * FILLRECT_RECOLOUR: Apply a recolour sprite to every pixel in the rectangle currently on screen
114 void GfxFillRect(int left
, int top
, int right
, int bottom
, int colour
, FillRectMode mode
)
116 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
117 const DrawPixelInfo
*dpi
= _cur_dpi
;
119 const int otop
= top
;
120 const int oleft
= left
;
122 if (dpi
->zoom
!= ZOOM_LVL_MIN
) return;
123 if (left
> right
|| top
> bottom
) return;
124 if (right
< dpi
->left
|| left
>= dpi
->left
+ dpi
->width
) return;
125 if (bottom
< dpi
->top
|| top
>= dpi
->top
+ dpi
->height
) return;
127 if ( (left
-= dpi
->left
) < 0) left
= 0;
128 right
= right
- dpi
->left
+ 1;
129 if (right
> dpi
->width
) right
= dpi
->width
;
133 if ( (top
-= dpi
->top
) < 0) top
= 0;
134 bottom
= bottom
- dpi
->top
+ 1;
135 if (bottom
> dpi
->height
) bottom
= dpi
->height
;
139 dst
= blitter
->MoveTo(dpi
->dst_ptr
, left
, top
);
142 default: // FILLRECT_OPAQUE
143 blitter
->DrawRect(dst
, right
, bottom
, (uint8_t)colour
);
146 case FILLRECT_RECOLOUR
:
147 blitter
->DrawColourMappingRect(dst
, right
, bottom
, GB(colour
, 0, PALETTE_WIDTH
));
150 case FILLRECT_CHECKER
: {
151 uint8_t bo
= (oleft
- left
+ dpi
->left
+ otop
- top
+ dpi
->top
) & 1;
153 for (int i
= (bo
^= 1); i
< right
; i
+= 2) blitter
->SetPixel(dst
, i
, 0, (uint8_t)colour
);
154 dst
= blitter
->MoveTo(dst
, 0, 1);
155 } while (--bottom
> 0);
161 typedef std::pair
<Point
, Point
> LineSegment
;
164 * Make line segments from a polygon defined by points, translated by an offset.
165 * Entirely horizontal lines (start and end at same Y coordinate) are skipped, as they are irrelevant to scanline conversion algorithms.
166 * Generated line segments always have the lowest Y coordinate point first, i.e. original direction is lost.
167 * @param shape The polygon to convert.
168 * @param offset Offset vector subtracted from all coordinates in the shape.
169 * @return Vector of undirected line segments.
171 static std::vector
<LineSegment
> MakePolygonSegments(const std::vector
<Point
> &shape
, Point offset
)
173 std::vector
<LineSegment
> segments
;
174 if (shape
.size() < 3) return segments
; // fewer than 3 will always result in an empty polygon
175 segments
.reserve(shape
.size());
177 /* Connect first and last point by having initial previous point be the last */
178 Point prev
= shape
.back();
181 for (Point pt
: shape
) {
184 /* Create segments for all non-horizontal lines in the polygon.
185 * The segments always have lowest Y coordinate first. */
187 segments
.emplace_back(pt
, prev
);
188 } else if (prev
.y
< pt
.y
) {
189 segments
.emplace_back(prev
, pt
);
198 * Fill a polygon with colour.
199 * The odd-even winding rule is used, i.e. self-intersecting polygons will have holes in them.
200 * Left and top edges are inclusive, right and bottom edges are exclusive.
201 * @note For rectangles the GfxFillRect function will be faster.
202 * @pre dpi->zoom == ZOOM_LVL_MIN
203 * @param shape List of points on the polygon.
204 * @param colour An 8 bit palette index (FILLRECT_OPAQUE and FILLRECT_CHECKER) or a recolour spritenumber (FILLRECT_RECOLOUR).
206 * FILLRECT_OPAQUE: Fill the polygon with the specified colour.
207 * FILLRECT_CHECKER: Fill every other pixel with the specified colour, in a checkerboard pattern.
208 * FILLRECT_RECOLOUR: Apply a recolour sprite to every pixel in the polygon.
210 void GfxFillPolygon(const std::vector
<Point
> &shape
, int colour
, FillRectMode mode
)
212 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
213 const DrawPixelInfo
*dpi
= _cur_dpi
;
214 if (dpi
->zoom
!= ZOOM_LVL_MIN
) return;
216 std::vector
<LineSegment
> segments
= MakePolygonSegments(shape
, Point
{ dpi
->left
, dpi
->top
});
218 /* Remove segments appearing entirely above or below the clipping area. */
219 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());
221 /* Check that this wasn't an empty shape (all points on a horizontal line or outside clipping.) */
222 if (segments
.empty()) return;
224 /* Sort the segments by first point Y coordinate. */
225 std::sort(segments
.begin(), segments
.end(), [](const LineSegment
&a
, const LineSegment
&b
) { return a
.first
.y
< b
.first
.y
; });
227 /* Segments intersecting current scanline. */
228 std::vector
<LineSegment
> active
;
229 /* Intersection points with a scanline.
230 * Kept outside loop to avoid repeated re-allocations. */
231 std::vector
<int> intersections
;
232 /* Normal, reasonable polygons don't have many intersections per scanline. */
234 intersections
.reserve(4);
236 /* Scan through the segments and paint each scanline. */
237 int y
= segments
.front().first
.y
;
238 std::vector
<LineSegment
>::iterator nextseg
= segments
.begin();
239 while (!active
.empty() || nextseg
!= segments
.end()) {
240 /* Clean up segments that have ended. */
241 active
.erase(std::remove_if(active
.begin(), active
.end(), [y
](const LineSegment
&s
) { return s
.second
.y
== y
; }), active
.end());
243 /* Activate all segments starting on this scanline. */
244 while (nextseg
!= segments
.end() && nextseg
->first
.y
== y
) {
245 active
.push_back(*nextseg
);
249 /* Check clipping. */
254 if (y
>= dpi
->height
) return;
256 /* Intersect scanline with all active segments. */
257 intersections
.clear();
258 for (const LineSegment
&s
: active
) {
259 const int sdx
= s
.second
.x
- s
.first
.x
;
260 const int sdy
= s
.second
.y
- s
.first
.y
;
261 const int ldy
= y
- s
.first
.y
;
262 const int x
= s
.first
.x
+ sdx
* ldy
/ sdy
;
263 intersections
.push_back(x
);
266 /* Fill between pairs of intersections. */
267 std::sort(intersections
.begin(), intersections
.end());
268 for (size_t i
= 1; i
< intersections
.size(); i
+= 2) {
269 /* Check clipping. */
270 const int x1
= std::max(0, intersections
[i
- 1]);
271 const int x2
= std::min(intersections
[i
], dpi
->width
);
272 if (x2
< 0) continue;
273 if (x1
>= dpi
->width
) continue;
275 /* Fill line y from x1 to x2. */
276 void *dst
= blitter
->MoveTo(dpi
->dst_ptr
, x1
, y
);
278 default: // FILLRECT_OPAQUE
279 blitter
->DrawRect(dst
, x2
- x1
, 1, (uint8_t)colour
);
281 case FILLRECT_RECOLOUR
:
282 blitter
->DrawColourMappingRect(dst
, x2
- x1
, 1, GB(colour
, 0, PALETTE_WIDTH
));
284 case FILLRECT_CHECKER
:
285 /* Fill every other pixel, offset such that the sum of filled pixels' X and Y coordinates is odd.
286 * This creates a checkerboard effect. */
287 for (int x
= (x1
+ y
) & 1; x
< x2
- x1
; x
+= 2) {
288 blitter
->SetPixel(dst
, x
, 0, (uint8_t)colour
);
300 * Check line clipping by using a linear equation and draw the visible part of
301 * the line given by x/y and x2/y2.
302 * @param video Destination pointer to draw into.
303 * @param x X coordinate of first point.
304 * @param y Y coordinate of first point.
305 * @param x2 X coordinate of second point.
306 * @param y2 Y coordinate of second point.
307 * @param screen_width With of the screen to check clipping against.
308 * @param screen_height Height of the screen to check clipping against.
309 * @param colour Colour of the line.
310 * @param width Width of the line.
311 * @param dash Length of dashes for dashed lines. 0 means solid line.
313 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)
315 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
319 if (y2
== y
|| x2
== x
) {
320 /* Special case: horizontal/vertical line. All checks already done in GfxPreprocessLine. */
321 blitter
->DrawLine(video
, x
, y
, x2
, y2
, screen_width
, screen_height
, colour
, width
, dash
);
325 int grade_y
= y2
- y
;
326 int grade_x
= x2
- x
;
328 /* Clipping rectangle. Slightly extended so we can ignore the width of the line. */
329 int extra
= (int)CeilDiv(3 * width
, 4); // not less then "width * sqrt(2) / 2"
330 Rect clip
= { -extra
, -extra
, screen_width
- 1 + extra
, screen_height
- 1 + extra
};
332 /* prevent integer overflows. */
334 while (INT_MAX
/ abs(grade_y
) < std::max(abs(clip
.left
- x
), abs(clip
.right
- x
))) {
337 margin
*= 2; // account for rounding errors
340 /* Imagine that the line is infinitely long and it intersects with
341 * infinitely long left and right edges of the clipping rectangle.
342 * If both intersection points are outside the clipping rectangle
343 * and both on the same side of it, we don't need to draw anything. */
344 int left_isec_y
= y
+ (clip
.left
- x
) * grade_y
/ grade_x
;
345 int right_isec_y
= y
+ (clip
.right
- x
) * grade_y
/ grade_x
;
346 if ((left_isec_y
> clip
.bottom
+ margin
&& right_isec_y
> clip
.bottom
+ margin
) ||
347 (left_isec_y
< clip
.top
- margin
&& right_isec_y
< clip
.top
- margin
)) {
351 /* It is possible to use the line equation to further reduce the amount of
352 * work the blitter has to do by shortening the effective line segment.
353 * However, in order to get that right and prevent the flickering effects
354 * of rounding errors so much additional code has to be run here that in
355 * the general case the effect is not noticeable. */
357 blitter
->DrawLine(video
, x
, y
, x2
, y2
, screen_width
, screen_height
, colour
, width
, dash
);
361 * Align parameters of a line to the given DPI and check simple clipping.
362 * @param dpi Screen parameters to align with.
363 * @param x X coordinate of first point.
364 * @param y Y coordinate of first point.
365 * @param x2 X coordinate of second point.
366 * @param y2 Y coordinate of second point.
367 * @param width Width of the line.
368 * @return True if the line is likely to be visible, false if it's certainly
371 static inline bool GfxPreprocessLine(DrawPixelInfo
*dpi
, int &x
, int &y
, int &x2
, int &y2
, int width
)
378 /* Check simple clipping */
379 if (x
+ width
/ 2 < 0 && x2
+ width
/ 2 < 0 ) return false;
380 if (y
+ width
/ 2 < 0 && y2
+ width
/ 2 < 0 ) return false;
381 if (x
- width
/ 2 > dpi
->width
&& x2
- width
/ 2 > dpi
->width
) return false;
382 if (y
- width
/ 2 > dpi
->height
&& y2
- width
/ 2 > dpi
->height
) return false;
386 void GfxDrawLine(int x
, int y
, int x2
, int y2
, int colour
, int width
, int dash
)
388 DrawPixelInfo
*dpi
= _cur_dpi
;
389 if (GfxPreprocessLine(dpi
, x
, y
, x2
, y2
, width
)) {
390 GfxDoDrawLine(dpi
->dst_ptr
, x
, y
, x2
, y2
, dpi
->width
, dpi
->height
, colour
, width
, dash
);
394 void GfxDrawLineUnscaled(int x
, int y
, int x2
, int y2
, int colour
)
396 DrawPixelInfo
*dpi
= _cur_dpi
;
397 if (GfxPreprocessLine(dpi
, x
, y
, x2
, y2
, 1)) {
398 GfxDoDrawLine(dpi
->dst_ptr
,
399 UnScaleByZoom(x
, dpi
->zoom
), UnScaleByZoom(y
, dpi
->zoom
),
400 UnScaleByZoom(x2
, dpi
->zoom
), UnScaleByZoom(y2
, dpi
->zoom
),
401 UnScaleByZoom(dpi
->width
, dpi
->zoom
), UnScaleByZoom(dpi
->height
, dpi
->zoom
), colour
, 1);
406 * Draws the projection of a parallelepiped.
407 * This can be used to draw boxes in world coordinates.
409 * @param x Screen X-coordinate of top front corner.
410 * @param y Screen Y-coordinate of top front corner.
411 * @param dx1 Screen X-length of first edge.
412 * @param dy1 Screen Y-length of first edge.
413 * @param dx2 Screen X-length of second edge.
414 * @param dy2 Screen Y-length of second edge.
415 * @param dx3 Screen X-length of third edge.
416 * @param dy3 Screen Y-length of third edge.
418 void DrawBox(int x
, int y
, int dx1
, int dy1
, int dx2
, int dy2
, int dx3
, int dy3
)
424 * <--__(dx1,dy1) /(dx2,dy2)
435 static const uint8_t colour
= PC_WHITE
;
437 GfxDrawLineUnscaled(x
, y
, x
+ dx1
, y
+ dy1
, colour
);
438 GfxDrawLineUnscaled(x
, y
, x
+ dx2
, y
+ dy2
, colour
);
439 GfxDrawLineUnscaled(x
, y
, x
+ dx3
, y
+ dy3
, colour
);
441 GfxDrawLineUnscaled(x
+ dx1
, y
+ dy1
, x
+ dx1
+ dx2
, y
+ dy1
+ dy2
, colour
);
442 GfxDrawLineUnscaled(x
+ dx1
, y
+ dy1
, x
+ dx1
+ dx3
, y
+ dy1
+ dy3
, colour
);
443 GfxDrawLineUnscaled(x
+ dx2
, y
+ dy2
, x
+ dx2
+ dx1
, y
+ dy2
+ dy1
, colour
);
444 GfxDrawLineUnscaled(x
+ dx2
, y
+ dy2
, x
+ dx2
+ dx3
, y
+ dy2
+ dy3
, colour
);
445 GfxDrawLineUnscaled(x
+ dx3
, y
+ dy3
, x
+ dx3
+ dx1
, y
+ dy3
+ dy1
, colour
);
446 GfxDrawLineUnscaled(x
+ dx3
, y
+ dy3
, x
+ dx3
+ dx2
, y
+ dy3
+ dy2
, colour
);
450 * Draw the outline of a Rect
451 * @param r Rect to draw.
452 * @param colour Colour of the outline.
453 * @param width Width of the outline.
454 * @param dash Length of dashes for dashed lines. 0 means solid lines.
456 void DrawRectOutline(const Rect
&r
, int colour
, int width
, int dash
)
458 GfxDrawLine(r
.left
, r
.top
, r
.right
, r
.top
, colour
, width
, dash
);
459 GfxDrawLine(r
.left
, r
.top
, r
.left
, r
.bottom
, colour
, width
, dash
);
460 GfxDrawLine(r
.right
, r
.top
, r
.right
, r
.bottom
, colour
, width
, dash
);
461 GfxDrawLine(r
.left
, r
.bottom
, r
.right
, r
.bottom
, colour
, width
, dash
);
465 * Set the colour remap to be for the given colour.
466 * @param colour the new colour of the remap.
468 static void SetColourRemap(TextColour colour
)
470 if (colour
== TC_INVALID
) return;
472 /* Black strings have no shading ever; the shading is black, so it
473 * would be invisible at best, but it actually makes it illegible. */
474 bool no_shade
= (colour
& TC_NO_SHADE
) != 0 || colour
== TC_BLACK
;
475 bool raw_colour
= (colour
& TC_IS_PALETTE_COLOUR
) != 0;
476 colour
&= ~(TC_NO_SHADE
| TC_IS_PALETTE_COLOUR
| TC_FORCED
);
478 _string_colourremap
[1] = raw_colour
? (uint8_t)colour
: _string_colourmap
[colour
];
479 _string_colourremap
[2] = no_shade
? 0 : 1;
480 _colour_remap_ptr
= _string_colourremap
;
484 * Drawing routine for drawing a laid out line of text.
485 * @param line String to draw.
486 * @param y The top most position to draw on.
487 * @param left The left most position to draw on.
488 * @param right The right most position to draw on.
489 * @param align The alignment of the string when drawing left-to-right. In the
490 * case a right-to-left language is chosen this is inverted so it
491 * will be drawn in the right direction.
492 * @param underline Whether to underline what has been drawn or not.
493 * @param truncation Whether to perform string truncation or not.
494 * @param default_colour Colour of text if not specified within string.
496 * @return In case of left or center alignment the right most pixel we have drawn to.
497 * In case of right alignment the left most pixel we have drawn to.
499 static int DrawLayoutLine(const ParagraphLayouter::Line
&line
, int y
, int left
, int right
, StringAlignment align
, bool underline
, bool truncation
, TextColour default_colour
)
501 if (line
.CountRuns() == 0) return 0;
503 int w
= line
.GetWidth();
504 int h
= line
.GetLeading();
507 * The following is needed for truncation.
508 * Depending on the text direction, we either remove bits at the rear
509 * or the front. For this we shift the entire area to draw so it fits
510 * within the left/right bounds and the side we do not truncate it on.
511 * Then we determine the truncation location, i.e. glyphs that fall
512 * outside of the range min_x - max_x will not be drawn; they are thus
513 * the truncated glyphs.
515 * At a later step we insert the dots.
518 int max_w
= right
- left
+ 1; // The maximum width.
520 int offset_x
= 0; // The offset we need for positioning the glyphs
521 int min_x
= left
; // The minimum x position to draw normal glyphs on.
522 int max_x
= right
; // The maximum x position to draw normal glyphs on.
524 truncation
&= max_w
< w
; // Whether we need to do truncation.
525 int dot_width
= 0; // Cache for the width of the dot.
526 const Sprite
*dot_sprite
= nullptr; // Cache for the sprite of the dot.
527 bool dot_has_shadow
= false; // Whether the dot's font requires shadows.
531 * Assumption may be made that all fonts of a run are of the same size.
532 * In any case, we'll use these dots for the abbreviation, so even if
533 * another size would be chosen it won't have truncated too little for
534 * the truncation dots.
536 FontCache
*fc
= line
.GetVisualRun(0).GetFont()->fc
;
537 dot_has_shadow
= fc
->GetDrawGlyphShadow();
538 GlyphID dot_glyph
= fc
->MapCharToGlyph('.');
539 dot_width
= fc
->GetGlyphWidth(dot_glyph
);
540 dot_sprite
= fc
->GetGlyph(dot_glyph
);
542 if (_current_text_dir
== TD_RTL
) {
543 min_x
+= 3 * dot_width
;
544 offset_x
= w
- 3 * dot_width
- max_w
;
546 max_x
-= 3 * dot_width
;
552 /* In case we have a RTL language we swap the alignment. */
553 if (!(align
& SA_FORCE
) && _current_text_dir
== TD_RTL
&& (align
& SA_HOR_MASK
) != SA_HOR_CENTER
) align
^= SA_RIGHT
;
555 /* right is the right most position to draw on. In this case we want to do
556 * calculations with the width of the string. In comparison right can be
557 * seen as lastof(todraw) and width as lengthof(todraw). They differ by 1.
558 * So most +1/-1 additions are to move from lengthof to 'indices'.
560 switch (align
& SA_HOR_MASK
) {
562 /* right + 1 = left + w */
563 right
= left
+ w
- 1;
567 left
= RoundDivSU(right
+ 1 + left
- w
, 2);
568 /* right + 1 = left + w */
569 right
= left
+ w
- 1;
573 left
= right
+ 1 - w
;
580 const uint shadow_offset
= ScaleGUITrad(1);
582 /* Draw shadow, then foreground */
583 for (bool do_shadow
: { true, false }) {
584 bool colour_has_shadow
= false;
585 for (int run_index
= 0; run_index
< line
.CountRuns(); run_index
++) {
586 const ParagraphLayouter::VisualRun
&run
= line
.GetVisualRun(run_index
);
587 const auto &glyphs
= run
.GetGlyphs();
588 const auto &positions
= run
.GetPositions();
589 const Font
*f
= run
.GetFont();
591 FontCache
*fc
= f
->fc
;
592 TextColour colour
= f
->colour
;
593 if (colour
== TC_INVALID
|| HasFlag(default_colour
, TC_FORCED
)) colour
= default_colour
;
594 colour_has_shadow
= (colour
& TC_NO_SHADE
) == 0 && colour
!= TC_BLACK
;
595 SetColourRemap(do_shadow
? TC_BLACK
: colour
); // the last run also sets the colour for the truncation dots
596 if (do_shadow
&& (!fc
->GetDrawGlyphShadow() || !colour_has_shadow
)) continue;
598 DrawPixelInfo
*dpi
= _cur_dpi
;
599 int dpi_left
= dpi
->left
;
600 int dpi_right
= dpi
->left
+ dpi
->width
- 1;
602 for (int i
= 0; i
< run
.GetGlyphCount(); i
++) {
603 GlyphID glyph
= glyphs
[i
];
605 /* Not a valid glyph (empty) */
606 if (glyph
== 0xFFFF) continue;
608 int begin_x
= positions
[i
].left
+ left
- offset_x
;
609 int end_x
= positions
[i
].right
+ left
- offset_x
;
610 int top
= positions
[i
].top
+ y
;
612 /* Truncated away. */
613 if (truncation
&& (begin_x
< min_x
|| end_x
> max_x
)) continue;
615 const Sprite
*sprite
= fc
->GetGlyph(glyph
);
616 /* Check clipping (the "+ 1" is for the shadow). */
617 if (begin_x
+ sprite
->x_offs
> dpi_right
|| begin_x
+ sprite
->x_offs
+ sprite
->width
/* - 1 + 1 */ < dpi_left
) continue;
619 if (do_shadow
&& (glyph
& SPRITE_GLYPH
) != 0) continue;
621 GfxMainBlitter(sprite
, begin_x
+ (do_shadow
? shadow_offset
: 0), top
+ (do_shadow
? shadow_offset
: 0), BM_COLOUR_REMAP
);
625 if (truncation
&& (!do_shadow
|| (dot_has_shadow
&& colour_has_shadow
))) {
626 int x
= (_current_text_dir
== TD_RTL
) ? left
: (right
- 3 * dot_width
);
627 for (int i
= 0; i
< 3; i
++, x
+= dot_width
) {
628 GfxMainBlitter(dot_sprite
, x
+ (do_shadow
? shadow_offset
: 0), y
+ (do_shadow
? shadow_offset
: 0), BM_COLOUR_REMAP
);
634 GfxFillRect(left
, y
+ h
, right
, y
+ h
+ WidgetDimensions::scaled
.bevel
.top
- 1, _string_colourremap
[1]);
637 return (align
& SA_HOR_MASK
) == SA_RIGHT
? left
: right
;
641 * Draw string, possibly truncated to make it fit in its allocated space
643 * @param left The left most position to draw on.
644 * @param right The right most position to draw on.
645 * @param top The top most position to draw on.
646 * @param str String to draw.
647 * @param colour Colour used for drawing the string, for details see _string_colourmap in
648 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
649 * @param align The alignment of the string when drawing left-to-right. In the
650 * case a right-to-left language is chosen this is inverted so it
651 * will be drawn in the right direction.
652 * @param underline Whether to underline what has been drawn or not.
653 * @param fontsize The size of the initial characters.
654 * @return In case of left or center alignment the right most pixel we have drawn to.
655 * In case of right alignment the left most pixel we have drawn to.
657 int DrawString(int left
, int right
, int top
, std::string_view str
, TextColour colour
, StringAlignment align
, bool underline
, FontSize fontsize
)
659 /* The string may contain control chars to change the font, just use the biggest font for clipping. */
660 int max_height
= std::max({GetCharacterHeight(FS_SMALL
), GetCharacterHeight(FS_NORMAL
), GetCharacterHeight(FS_LARGE
), GetCharacterHeight(FS_MONO
)});
662 /* Funny glyphs may extent outside the usual bounds, so relax the clipping somewhat. */
663 int extra
= max_height
/ 2;
665 if (_cur_dpi
->top
+ _cur_dpi
->height
+ extra
< top
|| _cur_dpi
->top
> top
+ max_height
+ extra
||
666 _cur_dpi
->left
+ _cur_dpi
->width
+ extra
< left
|| _cur_dpi
->left
> right
+ extra
) {
670 Layouter
layout(str
, INT32_MAX
, fontsize
);
671 if (layout
.empty()) return 0;
673 return DrawLayoutLine(*layout
.front(), top
, left
, right
, align
, underline
, true, colour
);
677 * Draw string, possibly truncated to make it fit in its allocated space
679 * @param left The left most position to draw on.
680 * @param right The right most position to draw on.
681 * @param top The top most position to draw on.
682 * @param str String to draw.
683 * @param colour Colour used for drawing the string, for details see _string_colourmap in
684 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
685 * @param align The alignment of the string when drawing left-to-right. In the
686 * case a right-to-left language is chosen this is inverted so it
687 * will be drawn in the right direction.
688 * @param underline Whether to underline what has been drawn or not.
689 * @param fontsize The size of the initial characters.
690 * @return In case of left or center alignment the right most pixel we have drawn to.
691 * In case of right alignment the left most pixel we have drawn to.
693 int DrawString(int left
, int right
, int top
, StringID str
, TextColour colour
, StringAlignment align
, bool underline
, FontSize fontsize
)
695 return DrawString(left
, right
, top
, GetString(str
), colour
, align
, underline
, fontsize
);
699 * Calculates height of string (in pixels). The string is changed to a multiline string if needed.
700 * @param str string to check
701 * @param maxw maximum string width
702 * @return height of pixels of string when it is drawn
704 int GetStringHeight(std::string_view str
, int maxw
, FontSize fontsize
)
707 Layouter
layout(str
, maxw
, fontsize
);
708 return layout
.GetBounds().height
;
712 * Calculates height of string (in pixels). The string is changed to a multiline string if needed.
713 * @param str string to check
714 * @param maxw maximum string width
715 * @return height of pixels of string when it is drawn
717 int GetStringHeight(StringID str
, int maxw
)
719 return GetStringHeight(GetString(str
), maxw
);
723 * Calculates number of lines of string. The string is changed to a multiline string if needed.
724 * @param str string to check
725 * @param maxw maximum string width
726 * @return number of lines of string when it is drawn
728 int GetStringLineCount(StringID str
, int maxw
)
730 Layouter
layout(GetString(str
), maxw
);
731 return (uint
)layout
.size();
735 * Calculate string bounding box for multi-line strings.
736 * @param str String to check.
737 * @param suggestion Suggested bounding box.
738 * @return Bounding box for the multi-line string, may be bigger than \a suggestion.
740 Dimension
GetStringMultiLineBoundingBox(StringID str
, const Dimension
&suggestion
)
742 Dimension box
= {suggestion
.width
, (uint
)GetStringHeight(str
, suggestion
.width
)};
747 * Calculate string bounding box for multi-line strings.
748 * @param str String to check.
749 * @param suggestion Suggested bounding box.
750 * @return Bounding box for the multi-line string, may be bigger than \a suggestion.
752 Dimension
GetStringMultiLineBoundingBox(std::string_view str
, const Dimension
&suggestion
)
754 Dimension box
= {suggestion
.width
, (uint
)GetStringHeight(str
, suggestion
.width
)};
759 * Draw string, possibly over multiple lines.
761 * @param left The left most position to draw on.
762 * @param right The right most position to draw on.
763 * @param top The top most position to draw on.
764 * @param bottom The bottom most position to draw on.
765 * @param str String to draw.
766 * @param colour Colour used for drawing the string, for details see _string_colourmap in
767 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
768 * @param align The horizontal and vertical alignment of the string.
769 * @param underline Whether to underline all strings
770 * @param fontsize The size of the initial characters.
772 * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written.
774 int DrawStringMultiLine(int left
, int right
, int top
, int bottom
, std::string_view str
, TextColour colour
, StringAlignment align
, bool underline
, FontSize fontsize
)
776 int maxw
= right
- left
+ 1;
777 int maxh
= bottom
- top
+ 1;
779 /* It makes no sense to even try if it can't be drawn anyway, or
780 * do we really want to support fonts of 0 or less pixels high? */
781 if (maxh
<= 0) return top
;
783 Layouter
layout(str
, maxw
, fontsize
);
784 int total_height
= layout
.GetBounds().height
;
786 switch (align
& SA_VERT_MASK
) {
792 y
= RoundDivSU(bottom
+ top
- total_height
, 2);
796 y
= bottom
- total_height
;
799 default: NOT_REACHED();
803 int first_line
= bottom
;
805 for (const auto &line
: layout
) {
807 int line_height
= line
->GetLeading();
808 if (y
>= top
&& y
+ line_height
- 1 <= bottom
) {
809 last_line
= y
+ line_height
;
810 if (first_line
> y
) first_line
= y
;
812 DrawLayoutLine(*line
, y
, left
, right
, align
, underline
, false, colour
);
817 return ((align
& SA_VERT_MASK
) == SA_BOTTOM
) ? first_line
: last_line
;
821 * Draw string, possibly over multiple lines.
823 * @param left The left most position to draw on.
824 * @param right The right most position to draw on.
825 * @param top The top most position to draw on.
826 * @param bottom The bottom most position to draw on.
827 * @param str String to draw.
828 * @param colour Colour used for drawing the string, for details see _string_colourmap in
829 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
830 * @param align The horizontal and vertical alignment of the string.
831 * @param underline Whether to underline all strings
832 * @param fontsize The size of the initial characters.
834 * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written.
836 int DrawStringMultiLine(int left
, int right
, int top
, int bottom
, StringID str
, TextColour colour
, StringAlignment align
, bool underline
, FontSize fontsize
)
838 return DrawStringMultiLine(left
, right
, top
, bottom
, GetString(str
), colour
, align
, underline
, fontsize
);
842 * Return the string dimension in pixels. The height and width are returned
843 * in a single Dimension value. TINYFONT, BIGFONT modifiers are only
844 * supported as the first character of the string. The returned dimensions
845 * are therefore a rough estimation correct for all the current strings
846 * but not every possible combination
847 * @param str string to calculate pixel-width
848 * @param start_fontsize Fontsize to start the text with
849 * @return string width and height in pixels
851 Dimension
GetStringBoundingBox(std::string_view str
, FontSize start_fontsize
)
853 Layouter
layout(str
, INT32_MAX
, start_fontsize
);
854 return layout
.GetBounds();
858 * Get bounding box of a string. Uses parameters set by #SetDParam if needed.
859 * Has the same restrictions as #GetStringBoundingBox(std::string_view str, FontSize start_fontsize).
860 * @param strid String to examine.
861 * @return Width and height of the bounding box for the string in pixels.
863 Dimension
GetStringBoundingBox(StringID strid
, FontSize start_fontsize
)
865 return GetStringBoundingBox(GetString(strid
), start_fontsize
);
869 * Get maximum width of a list of strings.
870 * @param list List of strings.
871 * @param fontsize Font size to use.
872 * @return Width of longest string within the list.
874 uint
GetStringListWidth(std::span
<const StringID
> list
, FontSize fontsize
)
877 for (auto str
: list
) {
878 width
= std::max(width
, GetStringBoundingBox(str
, fontsize
).width
);
884 * Get maximum dimension of a list of strings.
885 * @param list List of strings, terminated by INVALID_STRING_ID.
886 * @param fontsize Font size to use.
887 * @return Dimension of highest and longest string within the list.
889 Dimension
GetStringListBoundingBox(std::span
<const StringID
> list
, FontSize fontsize
)
892 for (auto str
: list
) {
893 d
= maxdim(d
, GetStringBoundingBox(str
, fontsize
));
899 * Draw single character horizontally centered around (x,y)
900 * @param c Character (glyph) to draw
901 * @param r Rectangle to draw character within
902 * @param colour Colour to use, for details see _string_colourmap in
903 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
905 void DrawCharCentered(char32_t c
, const Rect
&r
, TextColour colour
)
907 SetColourRemap(colour
);
908 GfxMainBlitter(GetGlyph(FS_NORMAL
, c
),
909 CenterBounds(r
.left
, r
.right
, GetCharacterWidth(FS_NORMAL
, c
)),
910 CenterBounds(r
.top
, r
.bottom
, GetCharacterHeight(FS_NORMAL
)),
915 * Get the size of a sprite.
916 * @param sprid Sprite to examine.
917 * @param[out] offset Optionally returns the sprite position offset.
918 * @param zoom The zoom level applicable to the sprite.
919 * @return Sprite size in pixels.
920 * @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.
922 Dimension
GetSpriteSize(SpriteID sprid
, Point
*offset
, ZoomLevel zoom
)
924 const Sprite
*sprite
= GetSprite(sprid
, SpriteType::Normal
);
926 if (offset
!= nullptr) {
927 offset
->x
= UnScaleByZoom(sprite
->x_offs
, zoom
);
928 offset
->y
= UnScaleByZoom(sprite
->y_offs
, zoom
);
932 d
.width
= std::max
<int>(0, UnScaleByZoom(sprite
->x_offs
+ sprite
->width
, zoom
));
933 d
.height
= std::max
<int>(0, UnScaleByZoom(sprite
->y_offs
+ sprite
->height
, zoom
));
938 * Helper function to get the blitter mode for different types of palettes.
939 * @param pal The palette to get the blitter mode for.
940 * @return The blitter mode associated with the palette.
942 static BlitterMode
GetBlitterMode(PaletteID pal
)
945 case PAL_NONE
: return BM_NORMAL
;
946 case PALETTE_CRASH
: return BM_CRASH_REMAP
;
947 case PALETTE_ALL_BLACK
: return BM_BLACK_REMAP
;
948 default: return BM_COLOUR_REMAP
;
953 * Draw a sprite in a viewport.
954 * @param img Image number to draw
955 * @param pal Palette to use.
956 * @param x Left coordinate of image in viewport, scaled by zoom
957 * @param y Top coordinate of image in viewport, scaled by zoom
958 * @param sub If available, draw only specified part of the sprite
960 void DrawSpriteViewport(SpriteID img
, PaletteID pal
, int x
, int y
, const SubSprite
*sub
)
962 SpriteID real_sprite
= GB(img
, 0, SPRITE_WIDTH
);
963 if (HasBit(img
, PALETTE_MODIFIER_TRANSPARENT
)) {
964 pal
= GB(pal
, 0, PALETTE_WIDTH
);
965 _colour_remap_ptr
= GetNonSprite(pal
, SpriteType::Recolour
) + 1;
966 GfxMainBlitterViewport(GetSprite(real_sprite
, SpriteType::Normal
), x
, y
, pal
== PALETTE_TO_TRANSPARENT
? BM_TRANSPARENT
: BM_TRANSPARENT_REMAP
, sub
, real_sprite
);
967 } else if (pal
!= PAL_NONE
) {
968 if (HasBit(pal
, PALETTE_TEXT_RECOLOUR
)) {
969 SetColourRemap((TextColour
)GB(pal
, 0, PALETTE_WIDTH
));
971 _colour_remap_ptr
= GetNonSprite(GB(pal
, 0, PALETTE_WIDTH
), SpriteType::Recolour
) + 1;
973 GfxMainBlitterViewport(GetSprite(real_sprite
, SpriteType::Normal
), x
, y
, GetBlitterMode(pal
), sub
, real_sprite
);
975 GfxMainBlitterViewport(GetSprite(real_sprite
, SpriteType::Normal
), x
, y
, BM_NORMAL
, sub
, real_sprite
);
980 * Draw a sprite, not in a viewport
981 * @param img Image number to draw
982 * @param pal Palette to use.
983 * @param x Left coordinate of image in pixels
984 * @param y Top coordinate of image in pixels
985 * @param sub If available, draw only specified part of the sprite
986 * @param zoom Zoom level of sprite
988 void DrawSprite(SpriteID img
, PaletteID pal
, int x
, int y
, const SubSprite
*sub
, ZoomLevel zoom
)
990 SpriteID real_sprite
= GB(img
, 0, SPRITE_WIDTH
);
991 if (HasBit(img
, PALETTE_MODIFIER_TRANSPARENT
)) {
992 pal
= GB(pal
, 0, PALETTE_WIDTH
);
993 _colour_remap_ptr
= GetNonSprite(pal
, SpriteType::Recolour
) + 1;
994 GfxMainBlitter(GetSprite(real_sprite
, SpriteType::Normal
), x
, y
, pal
== PALETTE_TO_TRANSPARENT
? BM_TRANSPARENT
: BM_TRANSPARENT_REMAP
, sub
, real_sprite
, zoom
);
995 } else if (pal
!= PAL_NONE
) {
996 if (HasBit(pal
, PALETTE_TEXT_RECOLOUR
)) {
997 SetColourRemap((TextColour
)GB(pal
, 0, PALETTE_WIDTH
));
999 _colour_remap_ptr
= GetNonSprite(GB(pal
, 0, PALETTE_WIDTH
), SpriteType::Recolour
) + 1;
1001 GfxMainBlitter(GetSprite(real_sprite
, SpriteType::Normal
), x
, y
, GetBlitterMode(pal
), sub
, real_sprite
, zoom
);
1003 GfxMainBlitter(GetSprite(real_sprite
, SpriteType::Normal
), x
, y
, BM_NORMAL
, sub
, real_sprite
, zoom
);
1008 * The code for setting up the blitter mode and sprite information before finally drawing the sprite.
1009 * @param sprite The sprite to draw.
1010 * @param x The X location to draw.
1011 * @param y The Y location to draw.
1012 * @param mode The settings for the blitter to pass.
1013 * @param sub Whether to only draw a sub set of the sprite.
1014 * @param zoom The zoom level at which to draw the sprites.
1015 * @param dst Optional parameter for a different blitting destination.
1016 * @tparam ZOOM_BASE The factor required to get the sub sprite information into the right size.
1017 * @tparam SCALED_XY Whether the X and Y are scaled or unscaled.
1019 template <int ZOOM_BASE
, bool SCALED_XY
>
1020 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)
1022 const DrawPixelInfo
*dpi
= (dst
!= nullptr) ? dst
: _cur_dpi
;
1023 Blitter::BlitterParams bp
;
1027 x
= ScaleByZoom(x
, zoom
);
1028 y
= ScaleByZoom(y
, zoom
);
1031 /* Move to the correct offset */
1032 x
+= sprite
->x_offs
;
1033 y
+= sprite
->y_offs
;
1035 if (sub
== nullptr) {
1039 bp
.width
= UnScaleByZoom(sprite
->width
, zoom
);
1040 bp
.height
= UnScaleByZoom(sprite
->height
, zoom
);
1042 /* Amount of pixels to clip from the source sprite */
1043 int clip_left
= std::max(0, -sprite
->x_offs
+ sub
->left
* ZOOM_BASE
);
1044 int clip_top
= std::max(0, -sprite
->y_offs
+ sub
->top
* ZOOM_BASE
);
1045 int clip_right
= std::max(0, sprite
->width
- (-sprite
->x_offs
+ (sub
->right
+ 1) * ZOOM_BASE
));
1046 int clip_bottom
= std::max(0, sprite
->height
- (-sprite
->y_offs
+ (sub
->bottom
+ 1) * ZOOM_BASE
));
1048 if (clip_left
+ clip_right
>= sprite
->width
) return;
1049 if (clip_top
+ clip_bottom
>= sprite
->height
) return;
1051 bp
.skip_left
= UnScaleByZoomLower(clip_left
, zoom
);
1052 bp
.skip_top
= UnScaleByZoomLower(clip_top
, zoom
);
1053 bp
.width
= UnScaleByZoom(sprite
->width
- clip_left
- clip_right
, zoom
);
1054 bp
.height
= UnScaleByZoom(sprite
->height
- clip_top
- clip_bottom
, zoom
);
1056 x
+= ScaleByZoom(bp
.skip_left
, zoom
);
1057 y
+= ScaleByZoom(bp
.skip_top
, zoom
);
1060 /* Copy the main data directly from the sprite */
1061 bp
.sprite
= sprite
->data
;
1062 bp
.sprite_width
= sprite
->width
;
1063 bp
.sprite_height
= sprite
->height
;
1067 bp
.dst
= dpi
->dst_ptr
;
1068 bp
.pitch
= dpi
->pitch
;
1069 bp
.remap
= _colour_remap_ptr
;
1071 assert(sprite
->width
> 0);
1072 assert(sprite
->height
> 0);
1074 if (bp
.width
<= 0) return;
1075 if (bp
.height
<= 0) return;
1077 y
-= SCALED_XY
? ScaleByZoom(dpi
->top
, zoom
) : dpi
->top
;
1078 int y_unscaled
= UnScaleByZoom(y
, zoom
);
1079 /* Check for top overflow */
1081 bp
.height
-= -y_unscaled
;
1082 if (bp
.height
<= 0) return;
1083 bp
.skip_top
+= -y_unscaled
;
1086 bp
.top
= y_unscaled
;
1089 /* Check for bottom overflow */
1090 y
+= SCALED_XY
? ScaleByZoom(bp
.height
- dpi
->height
, zoom
) : ScaleByZoom(bp
.height
, zoom
) - dpi
->height
;
1092 bp
.height
-= UnScaleByZoom(y
, zoom
);
1093 if (bp
.height
<= 0) return;
1096 x
-= SCALED_XY
? ScaleByZoom(dpi
->left
, zoom
) : dpi
->left
;
1097 int x_unscaled
= UnScaleByZoom(x
, zoom
);
1098 /* Check for left overflow */
1100 bp
.width
-= -x_unscaled
;
1101 if (bp
.width
<= 0) return;
1102 bp
.skip_left
+= -x_unscaled
;
1105 bp
.left
= x_unscaled
;
1108 /* Check for right overflow */
1109 x
+= SCALED_XY
? ScaleByZoom(bp
.width
- dpi
->width
, zoom
) : ScaleByZoom(bp
.width
, zoom
) - dpi
->width
;
1111 bp
.width
-= UnScaleByZoom(x
, zoom
);
1112 if (bp
.width
<= 0) return;
1115 assert(bp
.skip_left
+ bp
.width
<= UnScaleByZoom(sprite
->width
, zoom
));
1116 assert(bp
.skip_top
+ bp
.height
<= UnScaleByZoom(sprite
->height
, zoom
));
1118 /* We do not want to catch the mouse. However we also use that spritenumber for unknown (text) sprites. */
1119 if (_newgrf_debug_sprite_picker
.mode
== SPM_REDRAW
&& sprite_id
!= SPR_CURSOR_MOUSE
) {
1120 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1121 void *topleft
= blitter
->MoveTo(bp
.dst
, bp
.left
, bp
.top
);
1122 void *bottomright
= blitter
->MoveTo(topleft
, bp
.width
- 1, bp
.height
- 1);
1124 void *clicked
= _newgrf_debug_sprite_picker
.clicked_pixel
;
1126 if (topleft
<= clicked
&& clicked
<= bottomright
) {
1127 uint offset
= (((size_t)clicked
- (size_t)topleft
) / (blitter
->GetScreenDepth() / 8)) % bp
.pitch
;
1128 if (offset
< (uint
)bp
.width
) {
1129 include(_newgrf_debug_sprite_picker
.sprites
, sprite_id
);
1134 BlitterFactory::GetCurrentBlitter()->Draw(&bp
, mode
, zoom
);
1138 * Draws a sprite to a new RGBA buffer (see Colour union) instead of drawing to the screen.
1140 * @param spriteId The sprite to draw.
1141 * @param zoom The zoom level at which to draw the sprites.
1142 * @return Pixel buffer, or nullptr if an 8bpp blitter is being used.
1144 std::unique_ptr
<uint32_t[]> DrawSpriteToRgbaBuffer(SpriteID spriteId
, ZoomLevel zoom
)
1146 /* Invalid zoom level requested? */
1147 if (zoom
< _settings_client
.gui
.zoom_min
|| zoom
> _settings_client
.gui
.zoom_max
) return nullptr;
1149 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1150 if (blitter
->GetScreenDepth() != 8 && blitter
->GetScreenDepth() != 32) return nullptr;
1152 /* Gather information about the sprite to write, reserve memory */
1153 const SpriteID real_sprite
= GB(spriteId
, 0, SPRITE_WIDTH
);
1154 const Sprite
*sprite
= GetSprite(real_sprite
, SpriteType::Normal
);
1155 Dimension dim
= GetSpriteSize(real_sprite
, nullptr, zoom
);
1156 size_t dim_size
= static_cast<size_t>(dim
.width
) * dim
.height
;
1157 std::unique_ptr
<uint32_t[]> result
= std::make_unique
<uint32_t[]>(dim_size
);
1159 /* Prepare new DrawPixelInfo - Normally this would be the screen but we want to draw to another buffer here.
1160 * Normally, pitch would be scaled screen width, but in our case our "screen" is only the sprite width wide. */
1162 dpi
.dst_ptr
= result
.get();
1163 dpi
.pitch
= dim
.width
;
1166 dpi
.width
= dim
.width
;
1167 dpi
.height
= dim
.height
;
1170 dim_size
= static_cast<size_t>(dim
.width
) * dim
.height
;
1172 /* If the current blitter is a paletted blitter, we have to render to an extra buffer and resolve the palette later. */
1173 std::unique_ptr
<uint8_t[]> pal_buffer
{};
1174 if (blitter
->GetScreenDepth() == 8) {
1175 pal_buffer
= std::make_unique
<uint8_t[]>(dim_size
);
1176 dpi
.dst_ptr
= pal_buffer
.get();
1179 /* Temporarily disable screen animations while blitting - This prevents 40bpp_anim from writing to the animation buffer. */
1180 Backup
<bool> disable_anim(_screen_disable_anim
, true);
1181 GfxBlitter
<1, true>(sprite
, 0, 0, BM_NORMAL
, nullptr, real_sprite
, zoom
, &dpi
);
1182 disable_anim
.Restore();
1184 if (blitter
->GetScreenDepth() == 8) {
1185 /* Resolve palette. */
1186 uint32_t *dst
= result
.get();
1187 const uint8_t *src
= pal_buffer
.get();
1188 for (size_t i
= 0; i
< dim_size
; ++i
) {
1189 *dst
++ = _cur_palette
.palette
[*src
++].data
;
1196 static void GfxMainBlitterViewport(const Sprite
*sprite
, int x
, int y
, BlitterMode mode
, const SubSprite
*sub
, SpriteID sprite_id
)
1198 GfxBlitter
<ZOOM_BASE
, false>(sprite
, x
, y
, mode
, sub
, sprite_id
, _cur_dpi
->zoom
);
1201 static void GfxMainBlitter(const Sprite
*sprite
, int x
, int y
, BlitterMode mode
, const SubSprite
*sub
, SpriteID sprite_id
, ZoomLevel zoom
)
1203 GfxBlitter
<1, true>(sprite
, x
, y
, mode
, sub
, sprite_id
, zoom
);
1207 * Initialize _stringwidth_table cache
1208 * @param monospace Whether to load the monospace cache or the normal fonts.
1210 void LoadStringWidthTable(bool monospace
)
1214 for (FontSize fs
= monospace
? FS_MONO
: FS_BEGIN
; fs
< (monospace
? FS_END
: FS_MONO
); fs
++) {
1215 for (uint i
= 0; i
!= 224; i
++) {
1216 _stringwidth_table
[fs
][i
] = GetGlyphWidth(fs
, i
+ 32);
1222 * Return width of character glyph.
1223 * @param size Font of the character
1224 * @param key Character code glyph
1225 * @return Width of the character glyph
1227 uint8_t GetCharacterWidth(FontSize size
, char32_t key
)
1229 /* Use _stringwidth_table cache if possible */
1230 if (key
>= 32 && key
< 256) return _stringwidth_table
[size
][key
- 32];
1232 return GetGlyphWidth(size
, key
);
1236 * Return the maximum width of single digit.
1237 * @param size Font of the digit
1238 * @return Width of the digit.
1240 uint8_t GetDigitWidth(FontSize size
)
1243 for (char c
= '0'; c
<= '9'; c
++) {
1244 width
= std::max(GetCharacterWidth(size
, c
), width
);
1250 * Determine the broadest digits for guessing the maximum width of a n-digit number.
1251 * @param[out] front Broadest digit, which is not 0. (Use this digit as first digit for numbers with more than one digit.)
1252 * @param[out] next Broadest digit, including 0. (Use this digit for all digits, except the first one; or for numbers with only one digit.)
1253 * @param size Font of the digit
1255 void GetBroadestDigit(uint
*front
, uint
*next
, FontSize size
)
1258 for (char c
= '9'; c
>= '0'; c
--) {
1259 int w
= GetCharacterWidth(size
, c
);
1263 if (c
!= '0') *front
= c
- '0';
1268 void ScreenSizeChanged()
1270 _dirty_bytes_per_line
= CeilDiv(_screen
.width
, DIRTY_BLOCK_WIDTH
);
1271 _dirty_blocks
= ReallocT
<uint8_t>(_dirty_blocks
, static_cast<size_t>(_dirty_bytes_per_line
) * CeilDiv(_screen
.height
, DIRTY_BLOCK_HEIGHT
));
1273 /* check the dirty rect */
1274 if (_invalid_rect
.right
>= _screen
.width
) _invalid_rect
.right
= _screen
.width
;
1275 if (_invalid_rect
.bottom
>= _screen
.height
) _invalid_rect
.bottom
= _screen
.height
;
1277 /* screen size changed and the old bitmap is invalid now, so we don't want to undraw it */
1278 _cursor
.visible
= false;
1281 void UndrawMouseCursor()
1283 /* Don't undraw mouse cursor if it is handled by the video driver. */
1284 if (VideoDriver::GetInstance()->UseSystemCursor()) return;
1286 /* Don't undraw the mouse cursor if the screen is not ready */
1287 if (_screen
.dst_ptr
== nullptr) return;
1289 if (_cursor
.visible
) {
1290 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1291 _cursor
.visible
= false;
1292 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
);
1293 VideoDriver::GetInstance()->MakeDirty(_cursor
.draw_pos
.x
, _cursor
.draw_pos
.y
, _cursor
.draw_size
.x
, _cursor
.draw_size
.y
);
1297 void DrawMouseCursor()
1299 /* Don't draw mouse cursor if it is handled by the video driver. */
1300 if (VideoDriver::GetInstance()->UseSystemCursor()) return;
1302 /* Don't draw the mouse cursor if the screen is not ready */
1303 if (_screen
.dst_ptr
== nullptr) return;
1305 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1307 /* Redraw mouse cursor but only when it's inside the window */
1308 if (!_cursor
.in_window
) return;
1310 /* Don't draw the mouse cursor if it's already drawn */
1311 if (_cursor
.visible
) {
1312 if (!_cursor
.dirty
) return;
1313 UndrawMouseCursor();
1316 /* Determine visible area */
1317 int left
= _cursor
.pos
.x
+ _cursor
.total_offs
.x
;
1318 int width
= _cursor
.total_size
.x
;
1323 if (left
+ width
> _screen
.width
) {
1324 width
= _screen
.width
- left
;
1326 if (width
<= 0) return;
1328 int top
= _cursor
.pos
.y
+ _cursor
.total_offs
.y
;
1329 int height
= _cursor
.total_size
.y
;
1334 if (top
+ height
> _screen
.height
) {
1335 height
= _screen
.height
- top
;
1337 if (height
<= 0) return;
1339 _cursor
.draw_pos
.x
= left
;
1340 _cursor
.draw_pos
.y
= top
;
1341 _cursor
.draw_size
.x
= width
;
1342 _cursor
.draw_size
.y
= height
;
1344 uint8_t *buffer
= _cursor_backup
.Allocate(blitter
->BufferSize(_cursor
.draw_size
.x
, _cursor
.draw_size
.y
));
1346 /* Make backup of stuff below cursor */
1347 blitter
->CopyToBuffer(blitter
->MoveTo(_screen
.dst_ptr
, _cursor
.draw_pos
.x
, _cursor
.draw_pos
.y
), buffer
, _cursor
.draw_size
.x
, _cursor
.draw_size
.y
);
1349 /* Draw cursor on screen */
1350 _cur_dpi
= &_screen
;
1351 for (const auto &cs
: _cursor
.sprites
) {
1352 DrawSprite(cs
.image
.sprite
, cs
.image
.pal
, _cursor
.pos
.x
+ cs
.pos
.x
, _cursor
.pos
.y
+ cs
.pos
.y
);
1355 VideoDriver::GetInstance()->MakeDirty(_cursor
.draw_pos
.x
, _cursor
.draw_pos
.y
, _cursor
.draw_size
.x
, _cursor
.draw_size
.y
);
1357 _cursor
.visible
= true;
1358 _cursor
.dirty
= false;
1362 * Repaints a specific rectangle of the screen.
1364 * @param left,top,right,bottom The area of the screen that needs repainting
1365 * @pre The rectangle should have been previously marked dirty with \c AddDirtyBlock.
1366 * @see AddDirtyBlock
1367 * @see DrawDirtyBlocks
1371 void RedrawScreenRect(int left
, int top
, int right
, int bottom
)
1373 assert(right
<= _screen
.width
&& bottom
<= _screen
.height
);
1374 if (_cursor
.visible
) {
1375 if (right
> _cursor
.draw_pos
.x
&&
1376 left
< _cursor
.draw_pos
.x
+ _cursor
.draw_size
.x
&&
1377 bottom
> _cursor
.draw_pos
.y
&&
1378 top
< _cursor
.draw_pos
.y
+ _cursor
.draw_size
.y
) {
1379 UndrawMouseCursor();
1383 if (_networking
) NetworkUndrawChatMessage();
1385 DrawOverlappedWindowForAll(left
, top
, right
, bottom
);
1387 VideoDriver::GetInstance()->MakeDirty(left
, top
, right
- left
, bottom
- top
);
1391 * Repaints the rectangle blocks which are marked as 'dirty'.
1393 * @see AddDirtyBlock
1397 void DrawDirtyBlocks()
1399 uint8_t *b
= _dirty_blocks
;
1400 const int w
= Align(_screen
.width
, DIRTY_BLOCK_WIDTH
);
1401 const int h
= Align(_screen
.height
, DIRTY_BLOCK_HEIGHT
);
1412 int right
= x
+ DIRTY_BLOCK_WIDTH
;
1417 /* First try coalescing downwards */
1420 p
+= _dirty_bytes_per_line
;
1421 bottom
+= DIRTY_BLOCK_HEIGHT
;
1422 } while (bottom
!= h
&& *p
!= 0);
1424 /* Try coalescing to the right too. */
1425 h2
= (bottom
- y
) / DIRTY_BLOCK_HEIGHT
;
1429 while (right
!= w
) {
1432 /* Check if a full line of dirty flags is set. */
1434 if (!*p2
) goto no_more_coalesc
;
1435 p2
+= _dirty_bytes_per_line
;
1438 /* Wohoo, can combine it one step to the right!
1439 * Do that, and clear the bits. */
1440 right
+= DIRTY_BLOCK_WIDTH
;
1446 p2
+= _dirty_bytes_per_line
;
1454 if (left
< _invalid_rect
.left
) left
= _invalid_rect
.left
;
1455 if (top
< _invalid_rect
.top
) top
= _invalid_rect
.top
;
1456 if (right
> _invalid_rect
.right
) right
= _invalid_rect
.right
;
1457 if (bottom
> _invalid_rect
.bottom
) bottom
= _invalid_rect
.bottom
;
1459 if (left
< right
&& top
< bottom
) {
1460 RedrawScreenRect(left
, top
, right
, bottom
);
1464 } while (b
++, (x
+= DIRTY_BLOCK_WIDTH
) != w
);
1465 } while (b
+= -(int)(w
/ DIRTY_BLOCK_WIDTH
) + _dirty_bytes_per_line
, (y
+= DIRTY_BLOCK_HEIGHT
) != h
);
1467 ++_dirty_block_colour
;
1468 _invalid_rect
.left
= w
;
1469 _invalid_rect
.top
= h
;
1470 _invalid_rect
.right
= 0;
1471 _invalid_rect
.bottom
= 0;
1475 * Extend the internal _invalid_rect rectangle to contain the rectangle
1476 * defined by the given parameters. Note the point (0,0) is top left.
1478 * @param left The left edge of the rectangle
1479 * @param top The top edge of the rectangle
1480 * @param right The right edge of the rectangle
1481 * @param bottom The bottom edge of the rectangle
1482 * @see DrawDirtyBlocks
1486 void AddDirtyBlock(int left
, int top
, int right
, int bottom
)
1492 if (left
< 0) left
= 0;
1493 if (top
< 0) top
= 0;
1494 if (right
> _screen
.width
) right
= _screen
.width
;
1495 if (bottom
> _screen
.height
) bottom
= _screen
.height
;
1497 if (left
>= right
|| top
>= bottom
) return;
1499 if (left
< _invalid_rect
.left
) _invalid_rect
.left
= left
;
1500 if (top
< _invalid_rect
.top
) _invalid_rect
.top
= top
;
1501 if (right
> _invalid_rect
.right
) _invalid_rect
.right
= right
;
1502 if (bottom
> _invalid_rect
.bottom
) _invalid_rect
.bottom
= bottom
;
1504 left
/= DIRTY_BLOCK_WIDTH
;
1505 top
/= DIRTY_BLOCK_HEIGHT
;
1507 b
= _dirty_blocks
+ top
* _dirty_bytes_per_line
+ left
;
1509 width
= ((right
- 1) / DIRTY_BLOCK_WIDTH
) - left
+ 1;
1510 height
= ((bottom
- 1) / DIRTY_BLOCK_HEIGHT
) - top
+ 1;
1512 assert(width
> 0 && height
> 0);
1517 do b
[--i
] = 0xFF; while (i
!= 0);
1519 b
+= _dirty_bytes_per_line
;
1520 } while (--height
!= 0);
1524 * This function mark the whole screen as dirty. This results in repainting
1525 * the whole screen. Use this with care as this function will break the
1526 * idea about marking only parts of the screen as 'dirty'.
1529 void MarkWholeScreenDirty()
1531 AddDirtyBlock(0, 0, _screen
.width
, _screen
.height
);
1535 * Set up a clipping area for only drawing into a certain area. To do this,
1536 * Fill a DrawPixelInfo object with the supplied relative rectangle, backup
1537 * the original (calling) _cur_dpi and assign the just returned DrawPixelInfo
1538 * _cur_dpi. When you are done, give restore _cur_dpi's original value
1539 * @param *n the DrawPixelInfo that will be the clipping rectangle box allowed
1541 * @param left,top,width,height the relative coordinates of the clipping
1542 * rectangle relative to the current _cur_dpi. This will most likely be the
1543 * offset from the calling window coordinates
1544 * @return return false if the requested rectangle is not possible with the
1545 * current dpi pointer. Only continue of the return value is true, or you'll
1546 * get some nasty results
1548 bool FillDrawPixelInfo(DrawPixelInfo
*n
, int left
, int top
, int width
, int height
)
1550 Blitter
*blitter
= BlitterFactory::GetCurrentBlitter();
1551 const DrawPixelInfo
*o
= _cur_dpi
;
1553 n
->zoom
= ZOOM_LVL_MIN
;
1558 if ((left
-= o
->left
) < 0) {
1560 if (width
<= 0) return false;
1567 if (width
> o
->width
- left
) {
1568 width
= o
->width
- left
;
1569 if (width
<= 0) return false;
1573 if ((top
-= o
->top
) < 0) {
1575 if (height
<= 0) return false;
1582 n
->dst_ptr
= blitter
->MoveTo(o
->dst_ptr
, left
, top
);
1583 n
->pitch
= o
->pitch
;
1585 if (height
> o
->height
- top
) {
1586 height
= o
->height
- top
;
1587 if (height
<= 0) return false;
1595 * Update cursor dimension.
1596 * Called when changing cursor sprite resp. reloading grfs.
1598 void UpdateCursorSize()
1600 /* Ignore setting any cursor before the sprites are loaded. */
1601 if (GetMaxSpriteID() == 0) return;
1604 for (const auto &cs
: _cursor
.sprites
) {
1605 const Sprite
*p
= GetSprite(GB(cs
.image
.sprite
, 0, SPRITE_WIDTH
), SpriteType::Normal
);
1607 offs
.x
= UnScaleGUI(p
->x_offs
) + cs
.pos
.x
;
1608 offs
.y
= UnScaleGUI(p
->y_offs
) + cs
.pos
.y
;
1609 size
.x
= UnScaleGUI(p
->width
);
1610 size
.y
= UnScaleGUI(p
->height
);
1613 /* First sprite sets the total. */
1614 _cursor
.total_offs
= offs
;
1615 _cursor
.total_size
= size
;
1618 /* Additional sprites expand the total. */
1619 int right
= std::max(_cursor
.total_offs
.x
+ _cursor
.total_size
.x
, offs
.x
+ size
.x
);
1620 int bottom
= std::max(_cursor
.total_offs
.y
+ _cursor
.total_size
.y
, offs
.y
+ size
.y
);
1621 if (offs
.x
< _cursor
.total_offs
.x
) _cursor
.total_offs
.x
= offs
.x
;
1622 if (offs
.y
< _cursor
.total_offs
.y
) _cursor
.total_offs
.y
= offs
.y
;
1623 _cursor
.total_size
.x
= right
- _cursor
.total_offs
.x
;
1624 _cursor
.total_size
.y
= bottom
- _cursor
.total_offs
.y
;
1628 _cursor
.dirty
= true;
1632 * Switch cursor to different sprite.
1633 * @param cursor Sprite to draw for the cursor.
1634 * @param pal Palette to use for recolouring.
1636 static void SetCursorSprite(CursorID cursor
, PaletteID pal
)
1638 if (_cursor
.sprites
.size() == 1 && _cursor
.sprites
[0].image
.sprite
== cursor
&& _cursor
.sprites
[0].image
.pal
== pal
) return;
1640 _cursor
.sprites
.clear();
1641 _cursor
.sprites
.emplace_back(cursor
, pal
, 0, 0);
1646 static void SwitchAnimatedCursor()
1648 const AnimCursor
*cur
= _cursor
.animate_cur
;
1650 if (cur
== nullptr || cur
->sprite
== AnimCursor::LAST
) cur
= _cursor
.animate_list
;
1652 assert(!_cursor
.sprites
.empty());
1653 SetCursorSprite(cur
->sprite
, _cursor
.sprites
[0].image
.pal
);
1655 _cursor
.animate_timeout
= cur
->display_time
;
1656 _cursor
.animate_cur
= cur
+ 1;
1661 if (_cursor
.animate_timeout
!= 0 && --_cursor
.animate_timeout
== 0) {
1662 SwitchAnimatedCursor();
1667 * Set or unset the ZZZ cursor.
1668 * @param busy Whether to show the ZZZ cursor.
1670 void SetMouseCursorBusy(bool busy
)
1672 assert(!_cursor
.sprites
.empty());
1674 if (_cursor
.sprites
[0].image
.sprite
== SPR_CURSOR_MOUSE
) SetMouseCursor(SPR_CURSOR_ZZZ
, PAL_NONE
);
1676 if (_cursor
.sprites
[0].image
.sprite
== SPR_CURSOR_ZZZ
) SetMouseCursor(SPR_CURSOR_MOUSE
, PAL_NONE
);
1681 * Assign a single non-animated sprite to the cursor.
1682 * @param sprite Sprite to draw for the cursor.
1683 * @param pal Palette to use for recolouring.
1684 * @see SetAnimatedMouseCursor
1686 void SetMouseCursor(CursorID sprite
, PaletteID pal
)
1688 /* Turn off animation */
1689 _cursor
.animate_timeout
= 0;
1691 SetCursorSprite(sprite
, pal
);
1695 * Assign an animation to the cursor.
1696 * @param table Array of animation states.
1697 * @see SetMouseCursor
1699 void SetAnimatedMouseCursor(const AnimCursor
*table
)
1701 assert(!_cursor
.sprites
.empty());
1702 _cursor
.animate_list
= table
;
1703 _cursor
.animate_cur
= nullptr;
1704 _cursor
.sprites
[0].image
.pal
= PAL_NONE
;
1705 SwitchAnimatedCursor();
1709 * Update cursor position based on a relative change.
1711 * @param delta_x How much change in the X position.
1712 * @param delta_y How much change in the Y position.
1714 void CursorVars::UpdateCursorPositionRelative(int delta_x
, int delta_y
)
1716 assert(this->fix_at
);
1718 this->delta
.x
= delta_x
;
1719 this->delta
.y
= delta_y
;
1723 * Update cursor position on mouse movement.
1724 * @param x New X position.
1725 * @param y New Y position.
1726 * @return true, if the OS cursor position should be warped back to this->pos.
1728 bool CursorVars::UpdateCursorPosition(int x
, int y
)
1730 this->delta
.x
= x
- this->pos
.x
;
1731 this->delta
.y
= y
- this->pos
.y
;
1734 return this->delta
.x
!= 0 || this->delta
.y
!= 0;
1735 } else if (this->pos
.x
!= x
|| this->pos
.y
!= y
) {
1744 bool ChangeResInGame(int width
, int height
)
1746 return (_screen
.width
== width
&& _screen
.height
== height
) || VideoDriver::GetInstance()->ChangeResolution(width
, height
);
1749 bool ToggleFullScreen(bool fs
)
1751 bool result
= VideoDriver::GetInstance()->ToggleFullscreen(fs
);
1752 if (_fullscreen
!= fs
&& _resolutions
.empty()) {
1753 Debug(driver
, 0, "Could not find a suitable fullscreen resolution");
1758 void SortResolutions()
1760 std::sort(_resolutions
.begin(), _resolutions
.end());
1762 /* Remove any duplicates from the list. */
1763 auto last
= std::unique(_resolutions
.begin(), _resolutions
.end());
1764 _resolutions
.erase(last
, _resolutions
.end());
1768 * Resolve GUI zoom level, if auto-suggestion is requested.
1770 void UpdateGUIZoom()
1772 /* Determine real GUI zoom to use. */
1773 if (_gui_scale_cfg
== -1) {
1774 _gui_scale
= VideoDriver::GetInstance()->GetSuggestedUIScale();
1776 _gui_scale
= Clamp(_gui_scale_cfg
, MIN_INTERFACE_SCALE
, MAX_INTERFACE_SCALE
);
1779 int8_t new_zoom
= ScaleGUITrad(1) <= 1 ? ZOOM_LVL_NORMAL
: ScaleGUITrad(1) >= 4 ? ZOOM_LVL_IN_4X
: ZOOM_LVL_IN_2X
;
1780 /* Font glyphs should not be clamped to min/max zoom. */
1781 _font_zoom
= static_cast<ZoomLevel
>(new_zoom
);
1782 /* Ensure the gui_zoom is clamped between min/max. */
1783 new_zoom
= Clamp(new_zoom
, _settings_client
.gui
.zoom_min
, _settings_client
.gui
.zoom_max
);
1784 _gui_zoom
= static_cast<ZoomLevel
>(new_zoom
);
1788 * Resolve GUI zoom level and adjust GUI to new zoom, if auto-suggestion is requested.
1789 * @param automatic Set if the change is occuring due to OS DPI scaling being changed.
1790 * @returns true when the zoom level has changed, caller must call ReInitAllWindows(true)
1791 * after resizing the application's window/buffer.
1793 bool AdjustGUIZoom(bool automatic
)
1795 ZoomLevel old_gui_zoom
= _gui_zoom
;
1796 ZoomLevel old_font_zoom
= _font_zoom
;
1797 int old_scale
= _gui_scale
;
1799 if (old_scale
== _gui_scale
&& old_gui_zoom
== _gui_zoom
) return false;
1801 /* Update cursors if sprite zoom level has changed. */
1802 if (old_gui_zoom
!= _gui_zoom
) {
1803 VideoDriver::GetInstance()->ClearSystemSprites();
1806 if (old_font_zoom
!= _font_zoom
) {
1807 GfxClearFontSpriteCache();
1810 LoadStringWidthTable();
1812 SetupWidgetDimensions();
1813 UpdateAllVirtCoords();
1815 /* Adjust all window sizes to match the new zoom level, so that they don't appear
1816 to move around when the application is moved to a screen with different DPI. */
1817 auto zoom_shift
= old_gui_zoom
- _gui_zoom
;
1818 for (Window
*w
: Window::Iterate()) {
1820 w
->left
= (w
->left
* _gui_scale
) / old_scale
;
1821 w
->top
= (w
->top
* _gui_scale
) / old_scale
;
1823 if (w
->viewport
!= nullptr) {
1824 w
->viewport
->zoom
= static_cast<ZoomLevel
>(Clamp(w
->viewport
->zoom
- zoom_shift
, _settings_client
.gui
.zoom_min
, _settings_client
.gui
.zoom_max
));
1831 void ChangeGameSpeed(bool enable_fast_forward
)
1833 if (enable_fast_forward
) {
1834 _game_speed
= _settings_client
.gui
.fast_forward_speed_limit
;