Fix #8316: Make sort industries by production and transported with a cargo filter...
[openttd-github.git] / src / gfx.cpp
blob9ccb036d25ed15bafaf979152f7098101cbe0dea
1 /*
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/>.
6 */
8 /** @file gfx.cpp Handling of drawing text and other gfx related stuff. */
10 #include "stdafx.h"
11 #include "gfx_layout.h"
12 #include "progress.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_func.h"
21 #include "newgrf_debug.h"
22 #include "thread.h"
24 #include "table/palettes.h"
25 #include "table/string_colours.h"
26 #include "table/sprites.h"
27 #include "table/control_codes.h"
29 #include "safeguards.h"
31 byte _dirkeys; ///< 1 = left, 2 = up, 4 = right, 8 = down
32 bool _fullscreen;
33 byte _support8bpp;
34 CursorVars _cursor;
35 bool _ctrl_pressed; ///< Is Ctrl pressed?
36 bool _shift_pressed; ///< Is Shift pressed?
37 uint16 _game_speed = 100; ///< Current game-speed; 100 is 1x, 0 is infinite.
38 bool _left_button_down; ///< Is left mouse button pressed?
39 bool _left_button_clicked; ///< Is left mouse button clicked?
40 bool _right_button_down; ///< Is right mouse button pressed?
41 bool _right_button_clicked; ///< Is right mouse button clicked?
42 DrawPixelInfo _screen;
43 bool _screen_disable_anim = false; ///< Disable palette animation (important for 32bpp-anim blitter during giant screenshot)
44 std::atomic<bool> _exit_game;
45 GameMode _game_mode;
46 SwitchMode _switch_mode; ///< The next mainloop command.
47 PauseMode _pause_mode;
48 Palette _cur_palette;
50 static byte _stringwidth_table[FS_END][224]; ///< Cache containing width of often used characters. @see GetCharacterWidth()
51 DrawPixelInfo *_cur_dpi;
52 byte _colour_gradient[COLOUR_END][8];
54 static std::recursive_mutex _palette_mutex; ///< To coordinate access to _cur_palette.
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_NORMAL);
59 static ReusableBuffer<uint8> _cursor_backup;
61 ZoomLevel _gui_zoom; ///< GUI Zoom level
62 ZoomLevel _font_zoom; ///< Font Zoom level
64 int8 _gui_zoom_cfg; ///< GUI zoom level in config.
65 int8 _font_zoom_cfg; ///< Font zoom level in config.
68 /**
69 * The rect for repaint.
71 * This rectangle defines the area which should be repaint by the video driver.
73 * @ingroup dirty
75 static Rect _invalid_rect;
76 static const byte *_colour_remap_ptr;
77 static byte _string_colourremap[3]; ///< Recoloursprite for stringdrawing. The grf loader ensures that #ST_FONT sprites only use colours 0 to 2.
79 static const uint DIRTY_BLOCK_HEIGHT = 8;
80 static const uint DIRTY_BLOCK_WIDTH = 64;
82 static uint _dirty_bytes_per_line = 0;
83 static byte *_dirty_blocks = nullptr;
84 extern uint _dirty_block_colour;
86 void GfxScroll(int left, int top, int width, int height, int xo, int yo)
88 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
90 if (xo == 0 && yo == 0) return;
92 if (_cursor.visible) UndrawMouseCursor();
94 if (_networking) NetworkUndrawChatMessage();
96 blitter->ScrollBuffer(_screen.dst_ptr, left, top, width, height, xo, yo);
97 /* This part of the screen is now dirty. */
98 VideoDriver::GetInstance()->MakeDirty(left, top, width, height);
103 * Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
105 * @pre dpi->zoom == ZOOM_LVL_NORMAL, right >= left, bottom >= top
106 * @param left Minimum X (inclusive)
107 * @param top Minimum Y (inclusive)
108 * @param right Maximum X (inclusive)
109 * @param bottom Maximum Y (inclusive)
110 * @param colour A 8 bit palette index (FILLRECT_OPAQUE and FILLRECT_CHECKER) or a recolour spritenumber (FILLRECT_RECOLOUR)
111 * @param mode
112 * FILLRECT_OPAQUE: Fill the rectangle with the specified colour
113 * FILLRECT_CHECKER: Like FILLRECT_OPAQUE, but only draw every second pixel (used to grey out things)
114 * FILLRECT_RECOLOUR: Apply a recolour sprite to every pixel in the rectangle currently on screen
116 void GfxFillRect(int left, int top, int right, int bottom, int colour, FillRectMode mode)
118 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
119 const DrawPixelInfo *dpi = _cur_dpi;
120 void *dst;
121 const int otop = top;
122 const int oleft = left;
124 if (dpi->zoom != ZOOM_LVL_NORMAL) return;
125 if (left > right || top > bottom) return;
126 if (right < dpi->left || left >= dpi->left + dpi->width) return;
127 if (bottom < dpi->top || top >= dpi->top + dpi->height) return;
129 if ( (left -= dpi->left) < 0) left = 0;
130 right = right - dpi->left + 1;
131 if (right > dpi->width) right = dpi->width;
132 right -= left;
133 assert(right > 0);
135 if ( (top -= dpi->top) < 0) top = 0;
136 bottom = bottom - dpi->top + 1;
137 if (bottom > dpi->height) bottom = dpi->height;
138 bottom -= top;
139 assert(bottom > 0);
141 dst = blitter->MoveTo(dpi->dst_ptr, left, top);
143 switch (mode) {
144 default: // FILLRECT_OPAQUE
145 blitter->DrawRect(dst, right, bottom, (uint8)colour);
146 break;
148 case FILLRECT_RECOLOUR:
149 blitter->DrawColourMappingRect(dst, right, bottom, GB(colour, 0, PALETTE_WIDTH));
150 break;
152 case FILLRECT_CHECKER: {
153 byte bo = (oleft - left + dpi->left + otop - top + dpi->top) & 1;
154 do {
155 for (int i = (bo ^= 1); i < right; i += 2) blitter->SetPixel(dst, i, 0, (uint8)colour);
156 dst = blitter->MoveTo(dst, 0, 1);
157 } while (--bottom > 0);
158 break;
163 typedef std::pair<Point, Point> LineSegment;
166 * Make line segments from a polygon defined by points, translated by an offset.
167 * Entirely horizontal lines (start and end at same Y coordinate) are skipped, as they are irrelevant to scanline conversion algorithms.
168 * Generated line segments always have the lowest Y coordinate point first, i.e. original direction is lost.
169 * @param shape The polygon to convert.
170 * @param offset Offset vector subtracted from all coordinates in the shape.
171 * @return Vector of undirected line segments.
173 static std::vector<LineSegment> MakePolygonSegments(const std::vector<Point> &shape, Point offset)
175 std::vector<LineSegment> segments;
176 if (shape.size() < 3) return segments; // fewer than 3 will always result in an empty polygon
177 segments.reserve(shape.size());
179 /* Connect first and last point by having initial previous point be the last */
180 Point prev = shape.back();
181 prev.x -= offset.x;
182 prev.y -= offset.y;
183 for (Point pt : shape) {
184 pt.x -= offset.x;
185 pt.y -= offset.y;
186 /* Create segments for all non-horizontal lines in the polygon.
187 * The segments always have lowest Y coordinate first. */
188 if (prev.y > pt.y) {
189 segments.emplace_back(pt, prev);
190 } else if (prev.y < pt.y) {
191 segments.emplace_back(prev, pt);
193 prev = pt;
196 return segments;
200 * Fill a polygon with colour.
201 * The odd-even winding rule is used, i.e. self-intersecting polygons will have holes in them.
202 * Left and top edges are inclusive, right and bottom edges are exclusive.
203 * @note For rectangles the GfxFillRect function will be faster.
204 * @pre dpi->zoom == ZOOM_LVL_NORMAL
205 * @param shape List of points on the polygon.
206 * @param colour An 8 bit palette index (FILLRECT_OPAQUE and FILLRECT_CHECKER) or a recolour spritenumber (FILLRECT_RECOLOUR).
207 * @param mode
208 * FILLRECT_OPAQUE: Fill the polygon with the specified colour.
209 * FILLRECT_CHECKER: Fill every other pixel with the specified colour, in a checkerboard pattern.
210 * FILLRECT_RECOLOUR: Apply a recolour sprite to every pixel in the polygon.
212 void GfxFillPolygon(const std::vector<Point> &shape, int colour, FillRectMode mode)
214 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
215 const DrawPixelInfo *dpi = _cur_dpi;
216 if (dpi->zoom != ZOOM_LVL_NORMAL) return;
218 std::vector<LineSegment> segments = MakePolygonSegments(shape, Point{ dpi->left, dpi->top });
220 /* Remove segments appearing entirely above or below the clipping area. */
221 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());
223 /* Check that this wasn't an empty shape (all points on a horizontal line or outside clipping.) */
224 if (segments.empty()) return;
226 /* Sort the segments by first point Y coordinate. */
227 std::sort(segments.begin(), segments.end(), [](const LineSegment &a, const LineSegment &b) { return a.first.y < b.first.y; });
229 /* Segments intersecting current scanline. */
230 std::vector<LineSegment> active;
231 /* Intersection points with a scanline.
232 * Kept outside loop to avoid repeated re-allocations. */
233 std::vector<int> intersections;
234 /* Normal, reasonable polygons don't have many intersections per scanline. */
235 active.reserve(4);
236 intersections.reserve(4);
238 /* Scan through the segments and paint each scanline. */
239 int y = segments.front().first.y;
240 std::vector<LineSegment>::iterator nextseg = segments.begin();
241 while (!active.empty() || nextseg != segments.end()) {
242 /* Clean up segments that have ended. */
243 active.erase(std::remove_if(active.begin(), active.end(), [y](const LineSegment &s) { return s.second.y == y; }), active.end());
245 /* Activate all segments starting on this scanline. */
246 while (nextseg != segments.end() && nextseg->first.y == y) {
247 active.push_back(*nextseg);
248 ++nextseg;
251 /* Check clipping. */
252 if (y < 0) {
253 ++y;
254 continue;
256 if (y >= dpi->height) return;
258 /* Intersect scanline with all active segments. */
259 intersections.clear();
260 for (const LineSegment &s : active) {
261 const int sdx = s.second.x - s.first.x;
262 const int sdy = s.second.y - s.first.y;
263 const int ldy = y - s.first.y;
264 const int x = s.first.x + sdx * ldy / sdy;
265 intersections.push_back(x);
268 /* Fill between pairs of intersections. */
269 std::sort(intersections.begin(), intersections.end());
270 for (size_t i = 1; i < intersections.size(); i += 2) {
271 /* Check clipping. */
272 const int x1 = std::max(0, intersections[i - 1]);
273 const int x2 = std::min(intersections[i], dpi->width);
274 if (x2 < 0) continue;
275 if (x1 >= dpi->width) continue;
277 /* Fill line y from x1 to x2. */
278 void *dst = blitter->MoveTo(dpi->dst_ptr, x1, y);
279 switch (mode) {
280 default: // FILLRECT_OPAQUE
281 blitter->DrawRect(dst, x2 - x1, 1, (uint8)colour);
282 break;
283 case FILLRECT_RECOLOUR:
284 blitter->DrawColourMappingRect(dst, x2 - x1, 1, GB(colour, 0, PALETTE_WIDTH));
285 break;
286 case FILLRECT_CHECKER:
287 /* Fill every other pixel, offset such that the sum of filled pixels' X and Y coordinates is odd.
288 * This creates a checkerboard effect. */
289 for (int x = (x1 + y) & 1; x < x2 - x1; x += 2) {
290 blitter->SetPixel(dst, x, 0, (uint8)colour);
292 break;
296 /* Next line */
297 ++y;
302 * Check line clipping by using a linear equation and draw the visible part of
303 * the line given by x/y and x2/y2.
304 * @param video Destination pointer to draw into.
305 * @param x X coordinate of first point.
306 * @param y Y coordinate of first point.
307 * @param x2 X coordinate of second point.
308 * @param y2 Y coordinate of second point.
309 * @param screen_width With of the screen to check clipping against.
310 * @param screen_height Height of the screen to check clipping against.
311 * @param colour Colour of the line.
312 * @param width Width of the line.
313 * @param dash Length of dashes for dashed lines. 0 means solid line.
315 static inline void GfxDoDrawLine(void *video, int x, int y, int x2, int y2, int screen_width, int screen_height, uint8 colour, int width, int dash = 0)
317 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
319 assert(width > 0);
321 if (y2 == y || x2 == x) {
322 /* Special case: horizontal/vertical line. All checks already done in GfxPreprocessLine. */
323 blitter->DrawLine(video, x, y, x2, y2, screen_width, screen_height, colour, width, dash);
324 return;
327 int grade_y = y2 - y;
328 int grade_x = x2 - x;
330 /* Clipping rectangle. Slightly extended so we can ignore the width of the line. */
331 int extra = (int)CeilDiv(3 * width, 4); // not less then "width * sqrt(2) / 2"
332 Rect clip = { -extra, -extra, screen_width - 1 + extra, screen_height - 1 + extra };
334 /* prevent integer overflows. */
335 int margin = 1;
336 while (INT_MAX / abs(grade_y) < std::max(abs(clip.left - x), abs(clip.right - x))) {
337 grade_y /= 2;
338 grade_x /= 2;
339 margin *= 2; // account for rounding errors
342 /* Imagine that the line is infinitely long and it intersects with
343 * infinitely long left and right edges of the clipping rectangle.
344 * If both intersection points are outside the clipping rectangle
345 * and both on the same side of it, we don't need to draw anything. */
346 int left_isec_y = y + (clip.left - x) * grade_y / grade_x;
347 int right_isec_y = y + (clip.right - x) * grade_y / grade_x;
348 if ((left_isec_y > clip.bottom + margin && right_isec_y > clip.bottom + margin) ||
349 (left_isec_y < clip.top - margin && right_isec_y < clip.top - margin)) {
350 return;
353 /* It is possible to use the line equation to further reduce the amount of
354 * work the blitter has to do by shortening the effective line segment.
355 * However, in order to get that right and prevent the flickering effects
356 * of rounding errors so much additional code has to be run here that in
357 * the general case the effect is not noticeable. */
359 blitter->DrawLine(video, x, y, x2, y2, screen_width, screen_height, colour, width, dash);
363 * Align parameters of a line to the given DPI and check simple clipping.
364 * @param dpi Screen parameters to align with.
365 * @param x X coordinate of first point.
366 * @param y Y coordinate of first point.
367 * @param x2 X coordinate of second point.
368 * @param y2 Y coordinate of second point.
369 * @param width Width of the line.
370 * @return True if the line is likely to be visible, false if it's certainly
371 * invisible.
373 static inline bool GfxPreprocessLine(DrawPixelInfo *dpi, int &x, int &y, int &x2, int &y2, int width)
375 x -= dpi->left;
376 x2 -= dpi->left;
377 y -= dpi->top;
378 y2 -= dpi->top;
380 /* Check simple clipping */
381 if (x + width / 2 < 0 && x2 + width / 2 < 0 ) return false;
382 if (y + width / 2 < 0 && y2 + width / 2 < 0 ) return false;
383 if (x - width / 2 > dpi->width && x2 - width / 2 > dpi->width ) return false;
384 if (y - width / 2 > dpi->height && y2 - width / 2 > dpi->height) return false;
385 return true;
388 void GfxDrawLine(int x, int y, int x2, int y2, int colour, int width, int dash)
390 DrawPixelInfo *dpi = _cur_dpi;
391 if (GfxPreprocessLine(dpi, x, y, x2, y2, width)) {
392 GfxDoDrawLine(dpi->dst_ptr, x, y, x2, y2, dpi->width, dpi->height, colour, width, dash);
396 void GfxDrawLineUnscaled(int x, int y, int x2, int y2, int colour)
398 DrawPixelInfo *dpi = _cur_dpi;
399 if (GfxPreprocessLine(dpi, x, y, x2, y2, 1)) {
400 GfxDoDrawLine(dpi->dst_ptr,
401 UnScaleByZoom(x, dpi->zoom), UnScaleByZoom(y, dpi->zoom),
402 UnScaleByZoom(x2, dpi->zoom), UnScaleByZoom(y2, dpi->zoom),
403 UnScaleByZoom(dpi->width, dpi->zoom), UnScaleByZoom(dpi->height, dpi->zoom), colour, 1);
408 * Draws the projection of a parallelepiped.
409 * This can be used to draw boxes in world coordinates.
411 * @param x Screen X-coordinate of top front corner.
412 * @param y Screen Y-coordinate of top front corner.
413 * @param dx1 Screen X-length of first edge.
414 * @param dy1 Screen Y-length of first edge.
415 * @param dx2 Screen X-length of second edge.
416 * @param dy2 Screen Y-length of second edge.
417 * @param dx3 Screen X-length of third edge.
418 * @param dy3 Screen Y-length of third edge.
420 void DrawBox(int x, int y, int dx1, int dy1, int dx2, int dy2, int dx3, int dy3)
422 /* ....
423 * .. ....
424 * .. ....
425 * .. ^
426 * <--__(dx1,dy1) /(dx2,dy2)
427 * : --__ / :
428 * : --__ / :
429 * : *(x,y) :
430 * : | :
431 * : | ..
432 * .... |(dx3,dy3)
433 * .... | ..
434 * ....V.
437 static const byte colour = PC_WHITE;
439 GfxDrawLineUnscaled(x, y, x + dx1, y + dy1, colour);
440 GfxDrawLineUnscaled(x, y, x + dx2, y + dy2, colour);
441 GfxDrawLineUnscaled(x, y, x + dx3, y + dy3, colour);
443 GfxDrawLineUnscaled(x + dx1, y + dy1, x + dx1 + dx2, y + dy1 + dy2, colour);
444 GfxDrawLineUnscaled(x + dx1, y + dy1, x + dx1 + dx3, y + dy1 + dy3, colour);
445 GfxDrawLineUnscaled(x + dx2, y + dy2, x + dx2 + dx1, y + dy2 + dy1, colour);
446 GfxDrawLineUnscaled(x + dx2, y + dy2, x + dx2 + dx3, y + dy2 + dy3, colour);
447 GfxDrawLineUnscaled(x + dx3, y + dy3, x + dx3 + dx1, y + dy3 + dy1, colour);
448 GfxDrawLineUnscaled(x + dx3, y + dy3, x + dx3 + dx2, y + dy3 + dy2, colour);
452 * Set the colour remap to be for the given colour.
453 * @param colour the new colour of the remap.
455 static void SetColourRemap(TextColour colour)
457 if (colour == TC_INVALID) return;
459 /* Black strings have no shading ever; the shading is black, so it
460 * would be invisible at best, but it actually makes it illegible. */
461 bool no_shade = (colour & TC_NO_SHADE) != 0 || colour == TC_BLACK;
462 bool raw_colour = (colour & TC_IS_PALETTE_COLOUR) != 0;
463 colour &= ~(TC_NO_SHADE | TC_IS_PALETTE_COLOUR | TC_FORCED);
465 _string_colourremap[1] = raw_colour ? (byte)colour : _string_colourmap[colour];
466 _string_colourremap[2] = no_shade ? 0 : 1;
467 _colour_remap_ptr = _string_colourremap;
471 * Drawing routine for drawing a laid out line of text.
472 * @param line String to draw.
473 * @param y The top most position to draw on.
474 * @param left The left most position to draw on.
475 * @param right The right most position to draw on.
476 * @param align The alignment of the string when drawing left-to-right. In the
477 * case a right-to-left language is chosen this is inverted so it
478 * will be drawn in the right direction.
479 * @param underline Whether to underline what has been drawn or not.
480 * @param truncation Whether to perform string truncation or not.
482 * @return In case of left or center alignment the right most pixel we have drawn to.
483 * In case of right alignment the left most pixel we have drawn to.
485 static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left, int right, StringAlignment align, bool underline, bool truncation)
487 if (line.CountRuns() == 0) return 0;
489 int w = line.GetWidth();
490 int h = line.GetLeading();
493 * The following is needed for truncation.
494 * Depending on the text direction, we either remove bits at the rear
495 * or the front. For this we shift the entire area to draw so it fits
496 * within the left/right bounds and the side we do not truncate it on.
497 * Then we determine the truncation location, i.e. glyphs that fall
498 * outside of the range min_x - max_x will not be drawn; they are thus
499 * the truncated glyphs.
501 * At a later step we insert the dots.
504 int max_w = right - left + 1; // The maximum width.
506 int offset_x = 0; // The offset we need for positioning the glyphs
507 int min_x = left; // The minimum x position to draw normal glyphs on.
508 int max_x = right; // The maximum x position to draw normal glyphs on.
510 truncation &= max_w < w; // Whether we need to do truncation.
511 int dot_width = 0; // Cache for the width of the dot.
512 const Sprite *dot_sprite = nullptr; // Cache for the sprite of the dot.
514 if (truncation) {
516 * Assumption may be made that all fonts of a run are of the same size.
517 * In any case, we'll use these dots for the abbreviation, so even if
518 * another size would be chosen it won't have truncated too little for
519 * the truncation dots.
521 FontCache *fc = ((const Font*)line.GetVisualRun(0).GetFont())->fc;
522 GlyphID dot_glyph = fc->MapCharToGlyph('.');
523 dot_width = fc->GetGlyphWidth(dot_glyph);
524 dot_sprite = fc->GetGlyph(dot_glyph);
526 if (_current_text_dir == TD_RTL) {
527 min_x += 3 * dot_width;
528 offset_x = w - 3 * dot_width - max_w;
529 } else {
530 max_x -= 3 * dot_width;
533 w = max_w;
536 /* In case we have a RTL language we swap the alignment. */
537 if (!(align & SA_FORCE) && _current_text_dir == TD_RTL && (align & SA_HOR_MASK) != SA_HOR_CENTER) align ^= SA_RIGHT;
539 /* right is the right most position to draw on. In this case we want to do
540 * calculations with the width of the string. In comparison right can be
541 * seen as lastof(todraw) and width as lengthof(todraw). They differ by 1.
542 * So most +1/-1 additions are to move from lengthof to 'indices'.
544 switch (align & SA_HOR_MASK) {
545 case SA_LEFT:
546 /* right + 1 = left + w */
547 right = left + w - 1;
548 break;
550 case SA_HOR_CENTER:
551 left = RoundDivSU(right + 1 + left - w, 2);
552 /* right + 1 = left + w */
553 right = left + w - 1;
554 break;
556 case SA_RIGHT:
557 left = right + 1 - w;
558 break;
560 default:
561 NOT_REACHED();
564 TextColour colour = TC_BLACK;
565 bool draw_shadow = false;
566 for (int run_index = 0; run_index < line.CountRuns(); run_index++) {
567 const ParagraphLayouter::VisualRun &run = line.GetVisualRun(run_index);
568 const Font *f = (const Font*)run.GetFont();
570 FontCache *fc = f->fc;
571 colour = f->colour;
572 SetColourRemap(colour);
574 DrawPixelInfo *dpi = _cur_dpi;
575 int dpi_left = dpi->left;
576 int dpi_right = dpi->left + dpi->width - 1;
578 draw_shadow = fc->GetDrawGlyphShadow() && (colour & TC_NO_SHADE) == 0 && colour != TC_BLACK;
580 for (int i = 0; i < run.GetGlyphCount(); i++) {
581 GlyphID glyph = run.GetGlyphs()[i];
583 /* Not a valid glyph (empty) */
584 if (glyph == 0xFFFF) continue;
586 int begin_x = (int)run.GetPositions()[i * 2] + left - offset_x;
587 int end_x = (int)run.GetPositions()[i * 2 + 2] + left - offset_x - 1;
588 int top = (int)run.GetPositions()[i * 2 + 1] + y;
590 /* Truncated away. */
591 if (truncation && (begin_x < min_x || end_x > max_x)) continue;
593 const Sprite *sprite = fc->GetGlyph(glyph);
594 /* Check clipping (the "+ 1" is for the shadow). */
595 if (begin_x + sprite->x_offs > dpi_right || begin_x + sprite->x_offs + sprite->width /* - 1 + 1 */ < dpi_left) continue;
597 if (draw_shadow && (glyph & SPRITE_GLYPH) == 0) {
598 SetColourRemap(TC_BLACK);
599 GfxMainBlitter(sprite, begin_x + 1, top + 1, BM_COLOUR_REMAP);
600 SetColourRemap(colour);
602 GfxMainBlitter(sprite, begin_x, top, BM_COLOUR_REMAP);
606 if (truncation) {
607 int x = (_current_text_dir == TD_RTL) ? left : (right - 3 * dot_width);
608 for (int i = 0; i < 3; i++, x += dot_width) {
609 if (draw_shadow) {
610 SetColourRemap(TC_BLACK);
611 GfxMainBlitter(dot_sprite, x + 1, y + 1, BM_COLOUR_REMAP);
612 SetColourRemap(colour);
614 GfxMainBlitter(dot_sprite, x, y, BM_COLOUR_REMAP);
618 if (underline) {
619 GfxFillRect(left, y + h, right, y + h, _string_colourremap[1]);
622 return (align & SA_HOR_MASK) == SA_RIGHT ? left : right;
626 * Draw string, possibly truncated to make it fit in its allocated space
628 * @param left The left most position to draw on.
629 * @param right The right most position to draw on.
630 * @param top The top most position to draw on.
631 * @param str String to draw.
632 * @param colour Colour used for drawing the string, for details see _string_colourmap in
633 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
634 * @param align The alignment of the string when drawing left-to-right. In the
635 * case a right-to-left language is chosen this is inverted so it
636 * will be drawn in the right direction.
637 * @param underline Whether to underline what has been drawn or not.
638 * @param fontsize The size of the initial characters.
639 * @return In case of left or center alignment the right most pixel we have drawn to.
640 * In case of right alignment the left most pixel we have drawn to.
642 int DrawString(int left, int right, int top, const char *str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
644 /* The string may contain control chars to change the font, just use the biggest font for clipping. */
645 int max_height = std::max({FONT_HEIGHT_SMALL, FONT_HEIGHT_NORMAL, FONT_HEIGHT_LARGE, FONT_HEIGHT_MONO});
647 /* Funny glyphs may extent outside the usual bounds, so relax the clipping somewhat. */
648 int extra = max_height / 2;
650 if (_cur_dpi->top + _cur_dpi->height + extra < top || _cur_dpi->top > top + max_height + extra ||
651 _cur_dpi->left + _cur_dpi->width + extra < left || _cur_dpi->left > right + extra) {
652 return 0;
655 Layouter layout(str, INT32_MAX, colour, fontsize);
656 if (layout.size() == 0) return 0;
658 return DrawLayoutLine(*layout.front(), top, left, right, align, underline, true);
662 * Draw string, possibly truncated to make it fit in its allocated space
664 * @param left The left most position to draw on.
665 * @param right The right most position to draw on.
666 * @param top The top most position to draw on.
667 * @param str String to draw.
668 * @param colour Colour used for drawing the string, for details see _string_colourmap in
669 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
670 * @param align The alignment of the string when drawing left-to-right. In the
671 * case a right-to-left language is chosen this is inverted so it
672 * will be drawn in the right direction.
673 * @param underline Whether to underline what has been drawn or not.
674 * @param fontsize The size of the initial characters.
675 * @return In case of left or center alignment the right most pixel we have drawn to.
676 * In case of right alignment the left most pixel we have drawn to.
678 int DrawString(int left, int right, int top, const std::string &str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
680 return DrawString(left, right, top, str.c_str(), colour, align, underline, fontsize);
684 * Draw string, possibly truncated to make it fit in its allocated space
686 * @param left The left most position to draw on.
687 * @param right The right most position to draw on.
688 * @param top The top most position to draw on.
689 * @param str String to draw.
690 * @param colour Colour used for drawing the string, for details see _string_colourmap in
691 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
692 * @param align The alignment of the string when drawing left-to-right. In the
693 * case a right-to-left language is chosen this is inverted so it
694 * will be drawn in the right direction.
695 * @param underline Whether to underline what has been drawn or not.
696 * @param fontsize The size of the initial characters.
697 * @return In case of left or center alignment the right most pixel we have drawn to.
698 * In case of right alignment the left most pixel we have drawn to.
700 int DrawString(int left, int right, int top, StringID str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
702 char buffer[DRAW_STRING_BUFFER];
703 GetString(buffer, str, lastof(buffer));
704 return DrawString(left, right, top, buffer, colour, align, underline, fontsize);
708 * Calculates height of string (in pixels). The string is changed to a multiline string if needed.
709 * @param str string to check
710 * @param maxw maximum string width
711 * @return height of pixels of string when it is drawn
713 int GetStringHeight(const char *str, int maxw, FontSize fontsize)
715 Layouter layout(str, maxw, TC_FROMSTRING, fontsize);
716 return layout.GetBounds().height;
720 * Calculates height of string (in pixels). The string is changed to a multiline string if needed.
721 * @param str string to check
722 * @param maxw maximum string width
723 * @return height of pixels of string when it is drawn
725 int GetStringHeight(StringID str, int maxw)
727 char buffer[DRAW_STRING_BUFFER];
728 GetString(buffer, str, lastof(buffer));
729 return GetStringHeight(buffer, maxw);
733 * Calculates number of lines of string. The string is changed to a multiline string if needed.
734 * @param str string to check
735 * @param maxw maximum string width
736 * @return number of lines of string when it is drawn
738 int GetStringLineCount(StringID str, int maxw)
740 char buffer[DRAW_STRING_BUFFER];
741 GetString(buffer, str, lastof(buffer));
743 Layouter layout(buffer, maxw);
744 return (uint)layout.size();
748 * Calculate string bounding box for multi-line strings.
749 * @param str String to check.
750 * @param suggestion Suggested bounding box.
751 * @return Bounding box for the multi-line string, may be bigger than \a suggestion.
753 Dimension GetStringMultiLineBoundingBox(StringID str, const Dimension &suggestion)
755 Dimension box = {suggestion.width, (uint)GetStringHeight(str, suggestion.width)};
756 return box;
760 * Calculate string bounding box for multi-line strings.
761 * @param str String to check.
762 * @param suggestion Suggested bounding box.
763 * @return Bounding box for the multi-line string, may be bigger than \a suggestion.
765 Dimension GetStringMultiLineBoundingBox(const char *str, const Dimension &suggestion)
767 Dimension box = {suggestion.width, (uint)GetStringHeight(str, suggestion.width)};
768 return box;
772 * Draw string, possibly over multiple lines.
774 * @param left The left most position to draw on.
775 * @param right The right most position to draw on.
776 * @param top The top most position to draw on.
777 * @param bottom The bottom most position to draw on.
778 * @param str String to draw.
779 * @param colour Colour used for drawing the string, for details see _string_colourmap in
780 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
781 * @param align The horizontal and vertical alignment of the string.
782 * @param underline Whether to underline all strings
783 * @param fontsize The size of the initial characters.
785 * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written.
787 int DrawStringMultiLine(int left, int right, int top, int bottom, const char *str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
789 int maxw = right - left + 1;
790 int maxh = bottom - top + 1;
792 /* It makes no sense to even try if it can't be drawn anyway, or
793 * do we really want to support fonts of 0 or less pixels high? */
794 if (maxh <= 0) return top;
796 Layouter layout(str, maxw, colour, fontsize);
797 int total_height = layout.GetBounds().height;
798 int y;
799 switch (align & SA_VERT_MASK) {
800 case SA_TOP:
801 y = top;
802 break;
804 case SA_VERT_CENTER:
805 y = RoundDivSU(bottom + top - total_height, 2);
806 break;
808 case SA_BOTTOM:
809 y = bottom - total_height;
810 break;
812 default: NOT_REACHED();
815 int last_line = top;
816 int first_line = bottom;
818 for (const auto &line : layout) {
820 int line_height = line->GetLeading();
821 if (y >= top && y < bottom) {
822 last_line = y + line_height;
823 if (first_line > y) first_line = y;
825 DrawLayoutLine(*line, y, left, right, align, underline, false);
827 y += line_height;
830 return ((align & SA_VERT_MASK) == SA_BOTTOM) ? first_line : last_line;
835 * Draw string, possibly over multiple lines.
837 * @param left The left most position to draw on.
838 * @param right The right most position to draw on.
839 * @param top The top most position to draw on.
840 * @param bottom The bottom most position to draw on.
841 * @param str String to draw.
842 * @param colour Colour used for drawing the string, for details see _string_colourmap in
843 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
844 * @param align The horizontal and vertical alignment of the string.
845 * @param underline Whether to underline all strings
846 * @param fontsize The size of the initial characters.
848 * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written.
850 int DrawStringMultiLine(int left, int right, int top, int bottom, const std::string &str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
852 return DrawStringMultiLine(left, right, top, bottom, str.c_str(), colour, align, underline, fontsize);
856 * Draw string, possibly over multiple lines.
858 * @param left The left most position to draw on.
859 * @param right The right most position to draw on.
860 * @param top The top most position to draw on.
861 * @param bottom The bottom most position to draw on.
862 * @param str String to draw.
863 * @param colour Colour used for drawing the string, for details see _string_colourmap in
864 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
865 * @param align The horizontal and vertical alignment of the string.
866 * @param underline Whether to underline all strings
867 * @param fontsize The size of the initial characters.
869 * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written.
871 int DrawStringMultiLine(int left, int right, int top, int bottom, StringID str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
873 char buffer[DRAW_STRING_BUFFER];
874 GetString(buffer, str, lastof(buffer));
875 return DrawStringMultiLine(left, right, top, bottom, buffer, colour, align, underline, fontsize);
879 * Return the string dimension in pixels. The height and width are returned
880 * in a single Dimension value. TINYFONT, BIGFONT modifiers are only
881 * supported as the first character of the string. The returned dimensions
882 * are therefore a rough estimation correct for all the current strings
883 * but not every possible combination
884 * @param str string to calculate pixel-width
885 * @param start_fontsize Fontsize to start the text with
886 * @return string width and height in pixels
888 Dimension GetStringBoundingBox(const char *str, FontSize start_fontsize)
890 Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize);
891 return layout.GetBounds();
895 * Return the string dimension in pixels. The height and width are returned
896 * in a single Dimension value. TINYFONT, BIGFONT modifiers are only
897 * supported as the first character of the string. The returned dimensions
898 * are therefore a rough estimation correct for all the current strings
899 * but not every possible combination
900 * @param str string to calculate pixel-width
901 * @param start_fontsize Fontsize to start the text with
902 * @return string width and height in pixels
904 Dimension GetStringBoundingBox(const std::string &str, FontSize start_fontsize)
906 return GetStringBoundingBox(str.c_str(), start_fontsize);
910 * Get bounding box of a string. Uses parameters set by #SetDParam if needed.
911 * Has the same restrictions as #GetStringBoundingBox(const char *str, FontSize start_fontsize).
912 * @param strid String to examine.
913 * @return Width and height of the bounding box for the string in pixels.
915 Dimension GetStringBoundingBox(StringID strid)
917 char buffer[DRAW_STRING_BUFFER];
919 GetString(buffer, strid, lastof(buffer));
920 return GetStringBoundingBox(buffer);
924 * Get the leading corner of a character in a single-line string relative
925 * to the start of the string.
926 * @param str String containing the character.
927 * @param ch Pointer to the character in the string.
928 * @param start_fontsize Font size to start the text with.
929 * @return Upper left corner of the glyph associated with the character.
931 Point GetCharPosInString(const char *str, const char *ch, FontSize start_fontsize)
933 Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize);
934 return layout.GetCharPosition(ch);
938 * Get the character from a string that is drawn at a specific position.
939 * @param str String to test.
940 * @param x Position relative to the start of the string.
941 * @param start_fontsize Font size to start the text with.
942 * @return Pointer to the character at the position or nullptr if there is no character at the position.
944 const char *GetCharAtPosition(const char *str, int x, FontSize start_fontsize)
946 if (x < 0) return nullptr;
948 Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize);
949 return layout.GetCharAtPosition(x);
953 * Draw single character horizontally centered around (x,y)
954 * @param c Character (glyph) to draw
955 * @param r Rectangle to draw character within
956 * @param colour Colour to use, for details see _string_colourmap in
957 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
959 void DrawCharCentered(WChar c, const Rect &r, TextColour colour)
961 SetColourRemap(colour);
962 GfxMainBlitter(GetGlyph(FS_NORMAL, c),
963 CenterBounds(r.left, r.right, GetCharacterWidth(FS_NORMAL, c)),
964 CenterBounds(r.top, r.bottom, FONT_HEIGHT_NORMAL),
965 BM_COLOUR_REMAP);
969 * Get the size of a sprite.
970 * @param sprid Sprite to examine.
971 * @param[out] offset Optionally returns the sprite position offset.
972 * @param zoom The zoom level applicable to the sprite.
973 * @return Sprite size in pixels.
974 * @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.
976 Dimension GetSpriteSize(SpriteID sprid, Point *offset, ZoomLevel zoom)
978 const Sprite *sprite = GetSprite(sprid, ST_NORMAL);
980 if (offset != nullptr) {
981 offset->x = UnScaleByZoom(sprite->x_offs, zoom);
982 offset->y = UnScaleByZoom(sprite->y_offs, zoom);
985 Dimension d;
986 d.width = std::max<int>(0, UnScaleByZoom(sprite->x_offs + sprite->width, zoom));
987 d.height = std::max<int>(0, UnScaleByZoom(sprite->y_offs + sprite->height, zoom));
988 return d;
992 * Helper function to get the blitter mode for different types of palettes.
993 * @param pal The palette to get the blitter mode for.
994 * @return The blitter mode associated with the palette.
996 static BlitterMode GetBlitterMode(PaletteID pal)
998 switch (pal) {
999 case PAL_NONE: return BM_NORMAL;
1000 case PALETTE_CRASH: return BM_CRASH_REMAP;
1001 case PALETTE_ALL_BLACK: return BM_BLACK_REMAP;
1002 default: return BM_COLOUR_REMAP;
1007 * Draw a sprite in a viewport.
1008 * @param img Image number to draw
1009 * @param pal Palette to use.
1010 * @param x Left coordinate of image in viewport, scaled by zoom
1011 * @param y Top coordinate of image in viewport, scaled by zoom
1012 * @param sub If available, draw only specified part of the sprite
1014 void DrawSpriteViewport(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub)
1016 SpriteID real_sprite = GB(img, 0, SPRITE_WIDTH);
1017 if (HasBit(img, PALETTE_MODIFIER_TRANSPARENT)) {
1018 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
1019 GfxMainBlitterViewport(GetSprite(real_sprite, ST_NORMAL), x, y, BM_TRANSPARENT, sub, real_sprite);
1020 } else if (pal != PAL_NONE) {
1021 if (HasBit(pal, PALETTE_TEXT_RECOLOUR)) {
1022 SetColourRemap((TextColour)GB(pal, 0, PALETTE_WIDTH));
1023 } else {
1024 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
1026 GfxMainBlitterViewport(GetSprite(real_sprite, ST_NORMAL), x, y, GetBlitterMode(pal), sub, real_sprite);
1027 } else {
1028 GfxMainBlitterViewport(GetSprite(real_sprite, ST_NORMAL), x, y, BM_NORMAL, sub, real_sprite);
1033 * Draw a sprite, not in a viewport
1034 * @param img Image number to draw
1035 * @param pal Palette to use.
1036 * @param x Left coordinate of image in pixels
1037 * @param y Top coordinate of image in pixels
1038 * @param sub If available, draw only specified part of the sprite
1039 * @param zoom Zoom level of sprite
1041 void DrawSprite(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub, ZoomLevel zoom)
1043 SpriteID real_sprite = GB(img, 0, SPRITE_WIDTH);
1044 if (HasBit(img, PALETTE_MODIFIER_TRANSPARENT)) {
1045 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
1046 GfxMainBlitter(GetSprite(real_sprite, ST_NORMAL), x, y, BM_TRANSPARENT, sub, real_sprite, zoom);
1047 } else if (pal != PAL_NONE) {
1048 if (HasBit(pal, PALETTE_TEXT_RECOLOUR)) {
1049 SetColourRemap((TextColour)GB(pal, 0, PALETTE_WIDTH));
1050 } else {
1051 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
1053 GfxMainBlitter(GetSprite(real_sprite, ST_NORMAL), x, y, GetBlitterMode(pal), sub, real_sprite, zoom);
1054 } else {
1055 GfxMainBlitter(GetSprite(real_sprite, ST_NORMAL), x, y, BM_NORMAL, sub, real_sprite, zoom);
1060 * The code for setting up the blitter mode and sprite information before finally drawing the sprite.
1061 * @param sprite The sprite to draw.
1062 * @param x The X location to draw.
1063 * @param y The Y location to draw.
1064 * @param mode The settings for the blitter to pass.
1065 * @param sub Whether to only draw a sub set of the sprite.
1066 * @param zoom The zoom level at which to draw the sprites.
1067 * @tparam ZOOM_BASE The factor required to get the sub sprite information into the right size.
1068 * @tparam SCALED_XY Whether the X and Y are scaled or unscaled.
1070 template <int ZOOM_BASE, bool SCALED_XY>
1071 static void GfxBlitter(const Sprite * const sprite, int x, int y, BlitterMode mode, const SubSprite * const sub, SpriteID sprite_id, ZoomLevel zoom)
1073 const DrawPixelInfo *dpi = _cur_dpi;
1074 Blitter::BlitterParams bp;
1076 if (SCALED_XY) {
1077 /* Scale it */
1078 x = ScaleByZoom(x, zoom);
1079 y = ScaleByZoom(y, zoom);
1082 /* Move to the correct offset */
1083 x += sprite->x_offs;
1084 y += sprite->y_offs;
1086 if (sub == nullptr) {
1087 /* No clipping. */
1088 bp.skip_left = 0;
1089 bp.skip_top = 0;
1090 bp.width = UnScaleByZoom(sprite->width, zoom);
1091 bp.height = UnScaleByZoom(sprite->height, zoom);
1092 } else {
1093 /* Amount of pixels to clip from the source sprite */
1094 int clip_left = std::max(0, -sprite->x_offs + sub->left * ZOOM_BASE );
1095 int clip_top = std::max(0, -sprite->y_offs + sub->top * ZOOM_BASE );
1096 int clip_right = std::max(0, sprite->width - (-sprite->x_offs + (sub->right + 1) * ZOOM_BASE));
1097 int clip_bottom = std::max(0, sprite->height - (-sprite->y_offs + (sub->bottom + 1) * ZOOM_BASE));
1099 if (clip_left + clip_right >= sprite->width) return;
1100 if (clip_top + clip_bottom >= sprite->height) return;
1102 bp.skip_left = UnScaleByZoomLower(clip_left, zoom);
1103 bp.skip_top = UnScaleByZoomLower(clip_top, zoom);
1104 bp.width = UnScaleByZoom(sprite->width - clip_left - clip_right, zoom);
1105 bp.height = UnScaleByZoom(sprite->height - clip_top - clip_bottom, zoom);
1107 x += ScaleByZoom(bp.skip_left, zoom);
1108 y += ScaleByZoom(bp.skip_top, zoom);
1111 /* Copy the main data directly from the sprite */
1112 bp.sprite = sprite->data;
1113 bp.sprite_width = sprite->width;
1114 bp.sprite_height = sprite->height;
1115 bp.top = 0;
1116 bp.left = 0;
1118 bp.dst = dpi->dst_ptr;
1119 bp.pitch = dpi->pitch;
1120 bp.remap = _colour_remap_ptr;
1122 assert(sprite->width > 0);
1123 assert(sprite->height > 0);
1125 if (bp.width <= 0) return;
1126 if (bp.height <= 0) return;
1128 y -= SCALED_XY ? ScaleByZoom(dpi->top, zoom) : dpi->top;
1129 int y_unscaled = UnScaleByZoom(y, zoom);
1130 /* Check for top overflow */
1131 if (y < 0) {
1132 bp.height -= -y_unscaled;
1133 if (bp.height <= 0) return;
1134 bp.skip_top += -y_unscaled;
1135 y = 0;
1136 } else {
1137 bp.top = y_unscaled;
1140 /* Check for bottom overflow */
1141 y += SCALED_XY ? ScaleByZoom(bp.height - dpi->height, zoom) : ScaleByZoom(bp.height, zoom) - dpi->height;
1142 if (y > 0) {
1143 bp.height -= UnScaleByZoom(y, zoom);
1144 if (bp.height <= 0) return;
1147 x -= SCALED_XY ? ScaleByZoom(dpi->left, zoom) : dpi->left;
1148 int x_unscaled = UnScaleByZoom(x, zoom);
1149 /* Check for left overflow */
1150 if (x < 0) {
1151 bp.width -= -x_unscaled;
1152 if (bp.width <= 0) return;
1153 bp.skip_left += -x_unscaled;
1154 x = 0;
1155 } else {
1156 bp.left = x_unscaled;
1159 /* Check for right overflow */
1160 x += SCALED_XY ? ScaleByZoom(bp.width - dpi->width, zoom) : ScaleByZoom(bp.width, zoom) - dpi->width;
1161 if (x > 0) {
1162 bp.width -= UnScaleByZoom(x, zoom);
1163 if (bp.width <= 0) return;
1166 assert(bp.skip_left + bp.width <= UnScaleByZoom(sprite->width, zoom));
1167 assert(bp.skip_top + bp.height <= UnScaleByZoom(sprite->height, zoom));
1169 /* We do not want to catch the mouse. However we also use that spritenumber for unknown (text) sprites. */
1170 if (_newgrf_debug_sprite_picker.mode == SPM_REDRAW && sprite_id != SPR_CURSOR_MOUSE) {
1171 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
1172 void *topleft = blitter->MoveTo(bp.dst, bp.left, bp.top);
1173 void *bottomright = blitter->MoveTo(topleft, bp.width - 1, bp.height - 1);
1175 void *clicked = _newgrf_debug_sprite_picker.clicked_pixel;
1177 if (topleft <= clicked && clicked <= bottomright) {
1178 uint offset = (((size_t)clicked - (size_t)topleft) / (blitter->GetScreenDepth() / 8)) % bp.pitch;
1179 if (offset < (uint)bp.width) {
1180 include(_newgrf_debug_sprite_picker.sprites, sprite_id);
1185 BlitterFactory::GetCurrentBlitter()->Draw(&bp, mode, zoom);
1188 static void GfxMainBlitterViewport(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub, SpriteID sprite_id)
1190 GfxBlitter<ZOOM_LVL_BASE, false>(sprite, x, y, mode, sub, sprite_id, _cur_dpi->zoom);
1193 static void GfxMainBlitter(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub, SpriteID sprite_id, ZoomLevel zoom)
1195 GfxBlitter<1, true>(sprite, x, y, mode, sub, sprite_id, zoom);
1198 void DoPaletteAnimations();
1200 void GfxInitPalettes()
1202 std::lock_guard<std::recursive_mutex> lock(_palette_mutex);
1203 memcpy(&_cur_palette, &_palette, sizeof(_cur_palette));
1204 DoPaletteAnimations();
1208 * Copy the current palette if the palette was updated.
1209 * Used by video-driver to get a current up-to-date version of the palette,
1210 * to avoid two threads accessing the same piece of memory (with a good chance
1211 * one is already updating the palette while the other is drawing based on it).
1212 * @param local_palette The location to copy the palette to.
1213 * @param force_copy Whether to ignore if there is an update for the palette.
1214 * @return True iff a copy was done.
1216 bool CopyPalette(Palette &local_palette, bool force_copy)
1218 std::lock_guard<std::recursive_mutex> lock(_palette_mutex);
1220 if (!force_copy && _cur_palette.count_dirty == 0) return false;
1222 local_palette = _cur_palette;
1223 _cur_palette.count_dirty = 0;
1225 if (force_copy) {
1226 local_palette.first_dirty = 0;
1227 local_palette.count_dirty = 256;
1230 return true;
1233 #define EXTR(p, q) (((uint16)(palette_animation_counter * (p)) * (q)) >> 16)
1234 #define EXTR2(p, q) (((uint16)(~palette_animation_counter * (p)) * (q)) >> 16)
1236 void DoPaletteAnimations()
1238 std::lock_guard<std::recursive_mutex> lock(_palette_mutex);
1240 /* Animation counter for the palette animation. */
1241 static int palette_animation_counter = 0;
1242 palette_animation_counter += 8;
1244 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
1245 const Colour *s;
1246 const ExtraPaletteValues *ev = &_extra_palette_values;
1247 Colour old_val[PALETTE_ANIM_SIZE];
1248 const uint old_tc = palette_animation_counter;
1249 uint i;
1250 uint j;
1252 if (blitter != nullptr && blitter->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_NONE) {
1253 palette_animation_counter = 0;
1256 Colour *palette_pos = &_cur_palette.palette[PALETTE_ANIM_START]; // Points to where animations are taking place on the palette
1257 /* Makes a copy of the current animation palette in old_val,
1258 * so the work on the current palette could be compared, see if there has been any changes */
1259 memcpy(old_val, palette_pos, sizeof(old_val));
1261 /* Fizzy Drink bubbles animation */
1262 s = ev->fizzy_drink;
1263 j = EXTR2(512, EPV_CYCLES_FIZZY_DRINK);
1264 for (i = 0; i != EPV_CYCLES_FIZZY_DRINK; i++) {
1265 *palette_pos++ = s[j];
1266 j++;
1267 if (j == EPV_CYCLES_FIZZY_DRINK) j = 0;
1270 /* Oil refinery fire animation */
1271 s = ev->oil_refinery;
1272 j = EXTR2(512, EPV_CYCLES_OIL_REFINERY);
1273 for (i = 0; i != EPV_CYCLES_OIL_REFINERY; i++) {
1274 *palette_pos++ = s[j];
1275 j++;
1276 if (j == EPV_CYCLES_OIL_REFINERY) j = 0;
1279 /* Radio tower blinking */
1281 byte i = (palette_animation_counter >> 1) & 0x7F;
1282 byte v;
1284 if (i < 0x3f) {
1285 v = 255;
1286 } else if (i < 0x4A || i >= 0x75) {
1287 v = 128;
1288 } else {
1289 v = 20;
1291 palette_pos->r = v;
1292 palette_pos->g = 0;
1293 palette_pos->b = 0;
1294 palette_pos++;
1296 i ^= 0x40;
1297 if (i < 0x3f) {
1298 v = 255;
1299 } else if (i < 0x4A || i >= 0x75) {
1300 v = 128;
1301 } else {
1302 v = 20;
1304 palette_pos->r = v;
1305 palette_pos->g = 0;
1306 palette_pos->b = 0;
1307 palette_pos++;
1310 /* Handle lighthouse and stadium animation */
1311 s = ev->lighthouse;
1312 j = EXTR(256, EPV_CYCLES_LIGHTHOUSE);
1313 for (i = 0; i != EPV_CYCLES_LIGHTHOUSE; i++) {
1314 *palette_pos++ = s[j];
1315 j++;
1316 if (j == EPV_CYCLES_LIGHTHOUSE) j = 0;
1319 /* Dark blue water */
1320 s = (_settings_game.game_creation.landscape == LT_TOYLAND) ? ev->dark_water_toyland : ev->dark_water;
1321 j = EXTR(320, EPV_CYCLES_DARK_WATER);
1322 for (i = 0; i != EPV_CYCLES_DARK_WATER; i++) {
1323 *palette_pos++ = s[j];
1324 j++;
1325 if (j == EPV_CYCLES_DARK_WATER) j = 0;
1328 /* Glittery water */
1329 s = (_settings_game.game_creation.landscape == LT_TOYLAND) ? ev->glitter_water_toyland : ev->glitter_water;
1330 j = EXTR(128, EPV_CYCLES_GLITTER_WATER);
1331 for (i = 0; i != EPV_CYCLES_GLITTER_WATER / 3; i++) {
1332 *palette_pos++ = s[j];
1333 j += 3;
1334 if (j >= EPV_CYCLES_GLITTER_WATER) j -= EPV_CYCLES_GLITTER_WATER;
1337 if (blitter != nullptr && blitter->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_NONE) {
1338 palette_animation_counter = old_tc;
1339 } else {
1340 if (memcmp(old_val, &_cur_palette.palette[PALETTE_ANIM_START], sizeof(old_val)) != 0 && _cur_palette.count_dirty == 0) {
1341 /* Did we changed anything on the palette? Seems so. Mark it as dirty */
1342 _cur_palette.first_dirty = PALETTE_ANIM_START;
1343 _cur_palette.count_dirty = PALETTE_ANIM_SIZE;
1349 * Determine a contrasty text colour for a coloured background.
1350 * @param background Background colour.
1351 * @param threshold Background colour brightness threshold below which the background is considered dark and TC_WHITE is returned, range: 0 - 255, default 128.
1352 * @return TC_BLACK or TC_WHITE depending on what gives a better contrast.
1354 TextColour GetContrastColour(uint8 background, uint8 threshold)
1356 Colour c = _cur_palette.palette[background];
1357 /* Compute brightness according to http://www.w3.org/TR/AERT#color-contrast.
1358 * The following formula computes 1000 * brightness^2, with brightness being in range 0 to 255. */
1359 uint sq1000_brightness = c.r * c.r * 299 + c.g * c.g * 587 + c.b * c.b * 114;
1360 /* Compare with threshold brightness which defaults to 128 (50%) */
1361 return sq1000_brightness < ((uint) threshold) * ((uint) threshold) * 1000 ? TC_WHITE : TC_BLACK;
1365 * Initialize _stringwidth_table cache
1366 * @param monospace Whether to load the monospace cache or the normal fonts.
1368 void LoadStringWidthTable(bool monospace)
1370 ClearFontCache();
1372 for (FontSize fs = monospace ? FS_MONO : FS_BEGIN; fs < (monospace ? FS_END : FS_MONO); fs++) {
1373 for (uint i = 0; i != 224; i++) {
1374 _stringwidth_table[fs][i] = GetGlyphWidth(fs, i + 32);
1378 ReInitAllWindows(false);
1382 * Return width of character glyph.
1383 * @param size Font of the character
1384 * @param key Character code glyph
1385 * @return Width of the character glyph
1387 byte GetCharacterWidth(FontSize size, WChar key)
1389 /* Use _stringwidth_table cache if possible */
1390 if (key >= 32 && key < 256) return _stringwidth_table[size][key - 32];
1392 return GetGlyphWidth(size, key);
1396 * Return the maximum width of single digit.
1397 * @param size Font of the digit
1398 * @return Width of the digit.
1400 byte GetDigitWidth(FontSize size)
1402 byte width = 0;
1403 for (char c = '0'; c <= '9'; c++) {
1404 width = std::max(GetCharacterWidth(size, c), width);
1406 return width;
1410 * Determine the broadest digits for guessing the maximum width of a n-digit number.
1411 * @param[out] front Broadest digit, which is not 0. (Use this digit as first digit for numbers with more than one digit.)
1412 * @param[out] next Broadest digit, including 0. (Use this digit for all digits, except the first one; or for numbers with only one digit.)
1413 * @param size Font of the digit
1415 void GetBroadestDigit(uint *front, uint *next, FontSize size)
1417 int width = -1;
1418 for (char c = '9'; c >= '0'; c--) {
1419 int w = GetCharacterWidth(size, c);
1420 if (w > width) {
1421 width = w;
1422 *next = c - '0';
1423 if (c != '0') *front = c - '0';
1428 void ScreenSizeChanged()
1430 _dirty_bytes_per_line = CeilDiv(_screen.width, DIRTY_BLOCK_WIDTH);
1431 _dirty_blocks = ReallocT<byte>(_dirty_blocks, _dirty_bytes_per_line * CeilDiv(_screen.height, DIRTY_BLOCK_HEIGHT));
1433 /* check the dirty rect */
1434 if (_invalid_rect.right >= _screen.width) _invalid_rect.right = _screen.width;
1435 if (_invalid_rect.bottom >= _screen.height) _invalid_rect.bottom = _screen.height;
1437 /* screen size changed and the old bitmap is invalid now, so we don't want to undraw it */
1438 _cursor.visible = false;
1441 void UndrawMouseCursor()
1443 /* Don't undraw mouse cursor if it is handled by the video driver. */
1444 if (VideoDriver::GetInstance()->UseSystemCursor()) return;
1446 /* Don't undraw the mouse cursor if the screen is not ready */
1447 if (_screen.dst_ptr == nullptr) return;
1449 if (_cursor.visible) {
1450 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
1451 _cursor.visible = false;
1452 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);
1453 VideoDriver::GetInstance()->MakeDirty(_cursor.draw_pos.x, _cursor.draw_pos.y, _cursor.draw_size.x, _cursor.draw_size.y);
1457 void DrawMouseCursor()
1459 /* Don't draw mouse cursor if it is handled by the video driver. */
1460 if (VideoDriver::GetInstance()->UseSystemCursor()) return;
1462 /* Don't draw the mouse cursor if the screen is not ready */
1463 if (_screen.dst_ptr == nullptr) return;
1465 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
1467 /* Redraw mouse cursor but only when it's inside the window */
1468 if (!_cursor.in_window) return;
1470 /* Don't draw the mouse cursor if it's already drawn */
1471 if (_cursor.visible) {
1472 if (!_cursor.dirty) return;
1473 UndrawMouseCursor();
1476 /* Determine visible area */
1477 int left = _cursor.pos.x + _cursor.total_offs.x;
1478 int width = _cursor.total_size.x;
1479 if (left < 0) {
1480 width += left;
1481 left = 0;
1483 if (left + width > _screen.width) {
1484 width = _screen.width - left;
1486 if (width <= 0) return;
1488 int top = _cursor.pos.y + _cursor.total_offs.y;
1489 int height = _cursor.total_size.y;
1490 if (top < 0) {
1491 height += top;
1492 top = 0;
1494 if (top + height > _screen.height) {
1495 height = _screen.height - top;
1497 if (height <= 0) return;
1499 _cursor.draw_pos.x = left;
1500 _cursor.draw_pos.y = top;
1501 _cursor.draw_size.x = width;
1502 _cursor.draw_size.y = height;
1504 uint8 *buffer = _cursor_backup.Allocate(blitter->BufferSize(_cursor.draw_size.x, _cursor.draw_size.y));
1506 /* Make backup of stuff below cursor */
1507 blitter->CopyToBuffer(blitter->MoveTo(_screen.dst_ptr, _cursor.draw_pos.x, _cursor.draw_pos.y), buffer, _cursor.draw_size.x, _cursor.draw_size.y);
1509 /* Draw cursor on screen */
1510 _cur_dpi = &_screen;
1511 for (uint i = 0; i < _cursor.sprite_count; ++i) {
1512 DrawSprite(_cursor.sprite_seq[i].sprite, _cursor.sprite_seq[i].pal, _cursor.pos.x + _cursor.sprite_pos[i].x, _cursor.pos.y + _cursor.sprite_pos[i].y);
1515 VideoDriver::GetInstance()->MakeDirty(_cursor.draw_pos.x, _cursor.draw_pos.y, _cursor.draw_size.x, _cursor.draw_size.y);
1517 _cursor.visible = true;
1518 _cursor.dirty = false;
1522 * Repaints a specific rectangle of the screen.
1524 * @param left,top,right,bottom The area of the screen that needs repainting
1525 * @pre The rectangle should have been previously marked dirty with \c AddDirtyBlock.
1526 * @see AddDirtyBlock
1527 * @see DrawDirtyBlocks
1528 * @ingroup dirty
1531 void RedrawScreenRect(int left, int top, int right, int bottom)
1533 assert(right <= _screen.width && bottom <= _screen.height);
1534 if (_cursor.visible) {
1535 if (right > _cursor.draw_pos.x &&
1536 left < _cursor.draw_pos.x + _cursor.draw_size.x &&
1537 bottom > _cursor.draw_pos.y &&
1538 top < _cursor.draw_pos.y + _cursor.draw_size.y) {
1539 UndrawMouseCursor();
1543 if (_networking) NetworkUndrawChatMessage();
1545 DrawOverlappedWindowForAll(left, top, right, bottom);
1547 VideoDriver::GetInstance()->MakeDirty(left, top, right - left, bottom - top);
1551 * Repaints the rectangle blocks which are marked as 'dirty'.
1553 * @see AddDirtyBlock
1555 * @ingroup dirty
1557 void DrawDirtyBlocks()
1559 byte *b = _dirty_blocks;
1560 const int w = Align(_screen.width, DIRTY_BLOCK_WIDTH);
1561 const int h = Align(_screen.height, DIRTY_BLOCK_HEIGHT);
1562 int x;
1563 int y;
1565 y = 0;
1566 do {
1567 x = 0;
1568 do {
1569 if (*b != 0) {
1570 int left;
1571 int top;
1572 int right = x + DIRTY_BLOCK_WIDTH;
1573 int bottom = y;
1574 byte *p = b;
1575 int h2;
1577 /* First try coalescing downwards */
1578 do {
1579 *p = 0;
1580 p += _dirty_bytes_per_line;
1581 bottom += DIRTY_BLOCK_HEIGHT;
1582 } while (bottom != h && *p != 0);
1584 /* Try coalescing to the right too. */
1585 h2 = (bottom - y) / DIRTY_BLOCK_HEIGHT;
1586 assert(h2 > 0);
1587 p = b;
1589 while (right != w) {
1590 byte *p2 = ++p;
1591 int h = h2;
1592 /* Check if a full line of dirty flags is set. */
1593 do {
1594 if (!*p2) goto no_more_coalesc;
1595 p2 += _dirty_bytes_per_line;
1596 } while (--h != 0);
1598 /* Wohoo, can combine it one step to the right!
1599 * Do that, and clear the bits. */
1600 right += DIRTY_BLOCK_WIDTH;
1602 h = h2;
1603 p2 = p;
1604 do {
1605 *p2 = 0;
1606 p2 += _dirty_bytes_per_line;
1607 } while (--h != 0);
1609 no_more_coalesc:
1611 left = x;
1612 top = y;
1614 if (left < _invalid_rect.left ) left = _invalid_rect.left;
1615 if (top < _invalid_rect.top ) top = _invalid_rect.top;
1616 if (right > _invalid_rect.right ) right = _invalid_rect.right;
1617 if (bottom > _invalid_rect.bottom) bottom = _invalid_rect.bottom;
1619 if (left < right && top < bottom) {
1620 RedrawScreenRect(left, top, right, bottom);
1624 } while (b++, (x += DIRTY_BLOCK_WIDTH) != w);
1625 } while (b += -(int)(w / DIRTY_BLOCK_WIDTH) + _dirty_bytes_per_line, (y += DIRTY_BLOCK_HEIGHT) != h);
1627 ++_dirty_block_colour;
1628 _invalid_rect.left = w;
1629 _invalid_rect.top = h;
1630 _invalid_rect.right = 0;
1631 _invalid_rect.bottom = 0;
1635 * Extend the internal _invalid_rect rectangle to contain the rectangle
1636 * defined by the given parameters. Note the point (0,0) is top left.
1638 * @param left The left edge of the rectangle
1639 * @param top The top edge of the rectangle
1640 * @param right The right edge of the rectangle
1641 * @param bottom The bottom edge of the rectangle
1642 * @see DrawDirtyBlocks
1643 * @ingroup dirty
1646 void AddDirtyBlock(int left, int top, int right, int bottom)
1648 byte *b;
1649 int width;
1650 int height;
1652 if (left < 0) left = 0;
1653 if (top < 0) top = 0;
1654 if (right > _screen.width) right = _screen.width;
1655 if (bottom > _screen.height) bottom = _screen.height;
1657 if (left >= right || top >= bottom) return;
1659 if (left < _invalid_rect.left ) _invalid_rect.left = left;
1660 if (top < _invalid_rect.top ) _invalid_rect.top = top;
1661 if (right > _invalid_rect.right ) _invalid_rect.right = right;
1662 if (bottom > _invalid_rect.bottom) _invalid_rect.bottom = bottom;
1664 left /= DIRTY_BLOCK_WIDTH;
1665 top /= DIRTY_BLOCK_HEIGHT;
1667 b = _dirty_blocks + top * _dirty_bytes_per_line + left;
1669 width = ((right - 1) / DIRTY_BLOCK_WIDTH) - left + 1;
1670 height = ((bottom - 1) / DIRTY_BLOCK_HEIGHT) - top + 1;
1672 assert(width > 0 && height > 0);
1674 do {
1675 int i = width;
1677 do b[--i] = 0xFF; while (i != 0);
1679 b += _dirty_bytes_per_line;
1680 } while (--height != 0);
1684 * This function mark the whole screen as dirty. This results in repainting
1685 * the whole screen. Use this with care as this function will break the
1686 * idea about marking only parts of the screen as 'dirty'.
1687 * @ingroup dirty
1689 void MarkWholeScreenDirty()
1691 AddDirtyBlock(0, 0, _screen.width, _screen.height);
1695 * Set up a clipping area for only drawing into a certain area. To do this,
1696 * Fill a DrawPixelInfo object with the supplied relative rectangle, backup
1697 * the original (calling) _cur_dpi and assign the just returned DrawPixelInfo
1698 * _cur_dpi. When you are done, give restore _cur_dpi's original value
1699 * @param *n the DrawPixelInfo that will be the clipping rectangle box allowed
1700 * for drawing
1701 * @param left,top,width,height the relative coordinates of the clipping
1702 * rectangle relative to the current _cur_dpi. This will most likely be the
1703 * offset from the calling window coordinates
1704 * @return return false if the requested rectangle is not possible with the
1705 * current dpi pointer. Only continue of the return value is true, or you'll
1706 * get some nasty results
1708 bool FillDrawPixelInfo(DrawPixelInfo *n, int left, int top, int width, int height)
1710 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
1711 const DrawPixelInfo *o = _cur_dpi;
1713 n->zoom = ZOOM_LVL_NORMAL;
1715 assert(width > 0);
1716 assert(height > 0);
1718 if ((left -= o->left) < 0) {
1719 width += left;
1720 if (width <= 0) return false;
1721 n->left = -left;
1722 left = 0;
1723 } else {
1724 n->left = 0;
1727 if (width > o->width - left) {
1728 width = o->width - left;
1729 if (width <= 0) return false;
1731 n->width = width;
1733 if ((top -= o->top) < 0) {
1734 height += top;
1735 if (height <= 0) return false;
1736 n->top = -top;
1737 top = 0;
1738 } else {
1739 n->top = 0;
1742 n->dst_ptr = blitter->MoveTo(o->dst_ptr, left, top);
1743 n->pitch = o->pitch;
1745 if (height > o->height - top) {
1746 height = o->height - top;
1747 if (height <= 0) return false;
1749 n->height = height;
1751 return true;
1755 * Update cursor dimension.
1756 * Called when changing cursor sprite resp. reloading grfs.
1758 void UpdateCursorSize()
1760 /* Ignore setting any cursor before the sprites are loaded. */
1761 if (GetMaxSpriteID() == 0) return;
1763 static_assert(lengthof(_cursor.sprite_seq) == lengthof(_cursor.sprite_pos));
1764 assert(_cursor.sprite_count <= lengthof(_cursor.sprite_seq));
1765 for (uint i = 0; i < _cursor.sprite_count; ++i) {
1766 const Sprite *p = GetSprite(GB(_cursor.sprite_seq[i].sprite, 0, SPRITE_WIDTH), ST_NORMAL);
1767 Point offs, size;
1768 offs.x = UnScaleGUI(p->x_offs) + _cursor.sprite_pos[i].x;
1769 offs.y = UnScaleGUI(p->y_offs) + _cursor.sprite_pos[i].y;
1770 size.x = UnScaleGUI(p->width);
1771 size.y = UnScaleGUI(p->height);
1773 if (i == 0) {
1774 _cursor.total_offs = offs;
1775 _cursor.total_size = size;
1776 } else {
1777 int right = std::max(_cursor.total_offs.x + _cursor.total_size.x, offs.x + size.x);
1778 int bottom = std::max(_cursor.total_offs.y + _cursor.total_size.y, offs.y + size.y);
1779 if (offs.x < _cursor.total_offs.x) _cursor.total_offs.x = offs.x;
1780 if (offs.y < _cursor.total_offs.y) _cursor.total_offs.y = offs.y;
1781 _cursor.total_size.x = right - _cursor.total_offs.x;
1782 _cursor.total_size.y = bottom - _cursor.total_offs.y;
1786 _cursor.dirty = true;
1790 * Switch cursor to different sprite.
1791 * @param cursor Sprite to draw for the cursor.
1792 * @param pal Palette to use for recolouring.
1794 static void SetCursorSprite(CursorID cursor, PaletteID pal)
1796 if (_cursor.sprite_count == 1 && _cursor.sprite_seq[0].sprite == cursor && _cursor.sprite_seq[0].pal == pal) return;
1798 _cursor.sprite_count = 1;
1799 _cursor.sprite_seq[0].sprite = cursor;
1800 _cursor.sprite_seq[0].pal = pal;
1801 _cursor.sprite_pos[0].x = 0;
1802 _cursor.sprite_pos[0].y = 0;
1804 UpdateCursorSize();
1807 static void SwitchAnimatedCursor()
1809 const AnimCursor *cur = _cursor.animate_cur;
1811 if (cur == nullptr || cur->sprite == AnimCursor::LAST) cur = _cursor.animate_list;
1813 SetCursorSprite(cur->sprite, _cursor.sprite_seq[0].pal);
1815 _cursor.animate_timeout = cur->display_time;
1816 _cursor.animate_cur = cur + 1;
1819 void CursorTick()
1821 if (_cursor.animate_timeout != 0 && --_cursor.animate_timeout == 0) {
1822 SwitchAnimatedCursor();
1827 * Set or unset the ZZZ cursor.
1828 * @param busy Whether to show the ZZZ cursor.
1830 void SetMouseCursorBusy(bool busy)
1832 if (busy) {
1833 if (_cursor.sprite_seq[0].sprite == SPR_CURSOR_MOUSE) SetMouseCursor(SPR_CURSOR_ZZZ, PAL_NONE);
1834 } else {
1835 if (_cursor.sprite_seq[0].sprite == SPR_CURSOR_ZZZ) SetMouseCursor(SPR_CURSOR_MOUSE, PAL_NONE);
1840 * Assign a single non-animated sprite to the cursor.
1841 * @param sprite Sprite to draw for the cursor.
1842 * @param pal Palette to use for recolouring.
1843 * @see SetAnimatedMouseCursor
1845 void SetMouseCursor(CursorID sprite, PaletteID pal)
1847 /* Turn off animation */
1848 _cursor.animate_timeout = 0;
1849 /* Set cursor */
1850 SetCursorSprite(sprite, pal);
1854 * Assign an animation to the cursor.
1855 * @param table Array of animation states.
1856 * @see SetMouseCursor
1858 void SetAnimatedMouseCursor(const AnimCursor *table)
1860 _cursor.animate_list = table;
1861 _cursor.animate_cur = nullptr;
1862 _cursor.sprite_seq[0].pal = PAL_NONE;
1863 SwitchAnimatedCursor();
1867 * Update cursor position on mouse movement for relative modes.
1868 * @param delta_x How much change in the X position.
1869 * @param delta_y How much change in the Y position.
1871 void CursorVars::UpdateCursorPositionRelative(int delta_x, int delta_y)
1873 if (this->fix_at) {
1874 this->delta.x = delta_x;
1875 this->delta.y = delta_y;
1876 } else {
1877 int last_position_x = this->pos.x;
1878 int last_position_y = this->pos.y;
1880 this->pos.x = Clamp(this->pos.x + delta_x, 0, _cur_resolution.width - 1);
1881 this->pos.y = Clamp(this->pos.y + delta_y, 0, _cur_resolution.height - 1);
1883 this->delta.x = last_position_x - this->pos.x;
1884 this->delta.y = last_position_y - this->pos.y;
1886 this->dirty = true;
1891 * Update cursor position on mouse movement.
1892 * @param x New X position.
1893 * @param y New Y position.
1894 * @param queued_warp True, if the OS queues mouse warps after pending mouse movement events.
1895 * False, if the warp applies instantaneous.
1896 * @return true, if the OS cursor position should be warped back to this->pos.
1898 bool CursorVars::UpdateCursorPosition(int x, int y, bool queued_warp)
1900 /* Detecting relative mouse movement is somewhat tricky.
1901 * - There may be multiple mouse move events in the video driver queue (esp. when OpenTTD lags a bit).
1902 * - When we request warping the mouse position (return true), a mouse move event is appended at the end of the queue.
1904 * So, when this->fix_at is active, we use the following strategy:
1905 * - The first movement triggers the warp to reset the mouse position.
1906 * - Subsequent events have to compute movement relative to the previous event.
1907 * - The relative movement is finished, when we receive the event matching the warp.
1910 if (x == this->pos.x && y == this->pos.y) {
1911 /* Warp finished. */
1912 this->queued_warp = false;
1915 this->delta.x = x - (this->queued_warp ? this->last_position.x : this->pos.x);
1916 this->delta.y = y - (this->queued_warp ? this->last_position.y : this->pos.y);
1918 this->last_position.x = x;
1919 this->last_position.y = y;
1921 bool need_warp = false;
1922 if (this->fix_at) {
1923 if (this->delta.x != 0 || this->delta.y != 0) {
1924 /* Trigger warp.
1925 * Note: We also trigger warping again, if there is already a pending warp.
1926 * This makes it more tolerant about the OS or other software in between
1927 * botchering the warp. */
1928 this->queued_warp = queued_warp;
1929 need_warp = true;
1931 } else if (this->pos.x != x || this->pos.y != y) {
1932 this->queued_warp = false; // Cancel warping, we are no longer confining the position.
1933 this->dirty = true;
1934 this->pos.x = x;
1935 this->pos.y = y;
1937 return need_warp;
1940 bool ChangeResInGame(int width, int height)
1942 return (_screen.width == width && _screen.height == height) || VideoDriver::GetInstance()->ChangeResolution(width, height);
1945 bool ToggleFullScreen(bool fs)
1947 bool result = VideoDriver::GetInstance()->ToggleFullscreen(fs);
1948 if (_fullscreen != fs && _resolutions.empty()) {
1949 Debug(driver, 0, "Could not find a suitable fullscreen resolution");
1951 return result;
1954 void SortResolutions()
1956 std::sort(_resolutions.begin(), _resolutions.end());
1960 * Resolve GUI zoom level, if auto-suggestion is requested.
1962 void UpdateGUIZoom()
1964 /* Determine real GUI zoom to use. */
1965 if (_gui_zoom_cfg == ZOOM_LVL_CFG_AUTO) {
1966 _gui_zoom = static_cast<ZoomLevel>(Clamp(VideoDriver::GetInstance()->GetSuggestedUIZoom(), _settings_client.gui.zoom_min, _settings_client.gui.zoom_max));
1967 } else {
1968 /* Ensure the gui_zoom is clamped between min/max. Change the
1969 * _gui_zoom_cfg if it isn't, as this is used to visually show the
1970 * selection in the Game Options. */
1971 _gui_zoom_cfg = Clamp(_gui_zoom_cfg, _settings_client.gui.zoom_min, _settings_client.gui.zoom_max);
1972 _gui_zoom = static_cast<ZoomLevel>(_gui_zoom_cfg);
1975 /* Determine real font zoom to use. */
1976 if (_font_zoom_cfg == ZOOM_LVL_CFG_AUTO) {
1977 _font_zoom = static_cast<ZoomLevel>(VideoDriver::GetInstance()->GetSuggestedUIZoom());
1978 } else {
1979 _font_zoom = static_cast<ZoomLevel>(_font_zoom_cfg);
1983 void ChangeGameSpeed(bool enable_fast_forward)
1985 if (enable_fast_forward) {
1986 _game_speed = _settings_client.gui.fast_forward_speed_limit;
1987 } else {
1988 _game_speed = 100;