Add: INR currency (#8136)
[openttd-github.git] / src / gfx.cpp
blob076837341649a29c34aedbfedc02af3f3b0ab89d
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 byte _fast_forward;
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 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 void GfxMainBlitterViewport(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub = nullptr, SpriteID sprite_id = SPR_CURSOR_MOUSE);
55 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);
57 static ReusableBuffer<uint8> _cursor_backup;
59 ZoomLevel _gui_zoom; ///< GUI Zoom level
60 ZoomLevel _font_zoom; ///< Font Zoom level
62 /**
63 * The rect for repaint.
65 * This rectangle defines the area which should be repaint by the video driver.
67 * @ingroup dirty
69 static Rect _invalid_rect;
70 static const byte *_colour_remap_ptr;
71 static byte _string_colourremap[3]; ///< Recoloursprite for stringdrawing. The grf loader ensures that #ST_FONT sprites only use colours 0 to 2.
73 static const uint DIRTY_BLOCK_HEIGHT = 8;
74 static const uint DIRTY_BLOCK_WIDTH = 64;
76 static uint _dirty_bytes_per_line = 0;
77 static byte *_dirty_blocks = nullptr;
78 extern uint _dirty_block_colour;
80 void GfxScroll(int left, int top, int width, int height, int xo, int yo)
82 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
84 if (xo == 0 && yo == 0) return;
86 if (_cursor.visible) UndrawMouseCursor();
88 if (_networking) NetworkUndrawChatMessage();
90 blitter->ScrollBuffer(_screen.dst_ptr, left, top, width, height, xo, yo);
91 /* This part of the screen is now dirty. */
92 VideoDriver::GetInstance()->MakeDirty(left, top, width, height);
96 /**
97 * Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
99 * @pre dpi->zoom == ZOOM_LVL_NORMAL, right >= left, bottom >= top
100 * @param left Minimum X (inclusive)
101 * @param top Minimum Y (inclusive)
102 * @param right Maximum X (inclusive)
103 * @param bottom Maximum Y (inclusive)
104 * @param colour A 8 bit palette index (FILLRECT_OPAQUE and FILLRECT_CHECKER) or a recolour spritenumber (FILLRECT_RECOLOUR)
105 * @param mode
106 * FILLRECT_OPAQUE: Fill the rectangle with the specified colour
107 * FILLRECT_CHECKER: Like FILLRECT_OPAQUE, but only draw every second pixel (used to grey out things)
108 * FILLRECT_RECOLOUR: Apply a recolour sprite to every pixel in the rectangle currently on screen
110 void GfxFillRect(int left, int top, int right, int bottom, int colour, FillRectMode mode)
112 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
113 const DrawPixelInfo *dpi = _cur_dpi;
114 void *dst;
115 const int otop = top;
116 const int oleft = left;
118 if (dpi->zoom != ZOOM_LVL_NORMAL) return;
119 if (left > right || top > bottom) return;
120 if (right < dpi->left || left >= dpi->left + dpi->width) return;
121 if (bottom < dpi->top || top >= dpi->top + dpi->height) return;
123 if ( (left -= dpi->left) < 0) left = 0;
124 right = right - dpi->left + 1;
125 if (right > dpi->width) right = dpi->width;
126 right -= left;
127 assert(right > 0);
129 if ( (top -= dpi->top) < 0) top = 0;
130 bottom = bottom - dpi->top + 1;
131 if (bottom > dpi->height) bottom = dpi->height;
132 bottom -= top;
133 assert(bottom > 0);
135 dst = blitter->MoveTo(dpi->dst_ptr, left, top);
137 switch (mode) {
138 default: // FILLRECT_OPAQUE
139 blitter->DrawRect(dst, right, bottom, (uint8)colour);
140 break;
142 case FILLRECT_RECOLOUR:
143 blitter->DrawColourMappingRect(dst, right, bottom, GB(colour, 0, PALETTE_WIDTH));
144 break;
146 case FILLRECT_CHECKER: {
147 byte bo = (oleft - left + dpi->left + otop - top + dpi->top) & 1;
148 do {
149 for (int i = (bo ^= 1); i < right; i += 2) blitter->SetPixel(dst, i, 0, (uint8)colour);
150 dst = blitter->MoveTo(dst, 0, 1);
151 } while (--bottom > 0);
152 break;
157 typedef std::pair<Point, Point> LineSegment;
160 * Make line segments from a polygon defined by points, translated by an offset.
161 * Entirely horizontal lines (start and end at same Y coordinate) are skipped, as they are irrelevant to scanline conversion algorithms.
162 * Generated line segments always have the lowest Y coordinate point first, i.e. original direction is lost.
163 * @param shape The polygon to convert.
164 * @param offset Offset vector subtracted from all coordinates in the shape.
165 * @return Vector of undirected line segments.
167 static std::vector<LineSegment> MakePolygonSegments(const std::vector<Point> &shape, Point offset)
169 std::vector<LineSegment> segments;
170 if (shape.size() < 3) return segments; // fewer than 3 will always result in an empty polygon
171 segments.reserve(shape.size());
173 /* Connect first and last point by having initial previous point be the last */
174 Point prev = shape.back();
175 prev.x -= offset.x;
176 prev.y -= offset.y;
177 for (Point pt : shape) {
178 pt.x -= offset.x;
179 pt.y -= offset.y;
180 /* Create segments for all non-horizontal lines in the polygon.
181 * The segments always have lowest Y coordinate first. */
182 if (prev.y > pt.y) {
183 segments.emplace_back(pt, prev);
184 } else if (prev.y < pt.y) {
185 segments.emplace_back(prev, pt);
187 prev = pt;
190 return segments;
194 * Fill a polygon with colour.
195 * The odd-even winding rule is used, i.e. self-intersecting polygons will have holes in them.
196 * Left and top edges are inclusive, right and bottom edges are exclusive.
197 * @note For rectangles the GfxFillRect function will be faster.
198 * @pre dpi->zoom == ZOOM_LVL_NORMAL
199 * @param shape List of points on the polygon.
200 * @param colour An 8 bit palette index (FILLRECT_OPAQUE and FILLRECT_CHECKER) or a recolour spritenumber (FILLRECT_RECOLOUR).
201 * @param mode
202 * FILLRECT_OPAQUE: Fill the polygon with the specified colour.
203 * FILLRECT_CHECKER: Fill every other pixel with the specified colour, in a checkerboard pattern.
204 * FILLRECT_RECOLOUR: Apply a recolour sprite to every pixel in the polygon.
206 void GfxFillPolygon(const std::vector<Point> &shape, int colour, FillRectMode mode)
208 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
209 const DrawPixelInfo *dpi = _cur_dpi;
210 if (dpi->zoom != ZOOM_LVL_NORMAL) return;
212 std::vector<LineSegment> segments = MakePolygonSegments(shape, Point{ dpi->left, dpi->top });
214 /* Remove segments appearing entirely above or below the clipping area. */
215 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());
217 /* Check that this wasn't an empty shape (all points on a horizontal line or outside clipping.) */
218 if (segments.empty()) return;
220 /* Sort the segments by first point Y coordinate. */
221 std::sort(segments.begin(), segments.end(), [](const LineSegment &a, const LineSegment &b) { return a.first.y < b.first.y; });
223 /* Segments intersecting current scanline. */
224 std::vector<LineSegment> active;
225 /* Intersection points with a scanline.
226 * Kept outside loop to avoid repeated re-allocations. */
227 std::vector<int> intersections;
228 /* Normal, reasonable polygons don't have many intersections per scanline. */
229 active.reserve(4);
230 intersections.reserve(4);
232 /* Scan through the segments and paint each scanline. */
233 int y = segments.front().first.y;
234 std::vector<LineSegment>::iterator nextseg = segments.begin();
235 while (!active.empty() || nextseg != segments.end()) {
236 /* Clean up segments that have ended. */
237 active.erase(std::remove_if(active.begin(), active.end(), [y](const LineSegment &s) { return s.second.y == y; }), active.end());
239 /* Activate all segments starting on this scanline. */
240 while (nextseg != segments.end() && nextseg->first.y == y) {
241 active.push_back(*nextseg);
242 ++nextseg;
245 /* Check clipping. */
246 if (y < 0) {
247 ++y;
248 continue;
250 if (y >= dpi->height) return;
252 /* Intersect scanline with all active segments. */
253 intersections.clear();
254 for (const LineSegment &s : active) {
255 const int sdx = s.second.x - s.first.x;
256 const int sdy = s.second.y - s.first.y;
257 const int ldy = y - s.first.y;
258 const int x = s.first.x + sdx * ldy / sdy;
259 intersections.push_back(x);
262 /* Fill between pairs of intersections. */
263 std::sort(intersections.begin(), intersections.end());
264 for (size_t i = 1; i < intersections.size(); i += 2) {
265 /* Check clipping. */
266 const int x1 = max(0, intersections[i - 1]);
267 const int x2 = min(intersections[i], dpi->width);
268 if (x2 < 0) continue;
269 if (x1 >= dpi->width) continue;
271 /* Fill line y from x1 to x2. */
272 void *dst = blitter->MoveTo(dpi->dst_ptr, x1, y);
273 switch (mode) {
274 default: // FILLRECT_OPAQUE
275 blitter->DrawRect(dst, x2 - x1, 1, (uint8)colour);
276 break;
277 case FILLRECT_RECOLOUR:
278 blitter->DrawColourMappingRect(dst, x2 - x1, 1, GB(colour, 0, PALETTE_WIDTH));
279 break;
280 case FILLRECT_CHECKER:
281 /* Fill every other pixel, offset such that the sum of filled pixels' X and Y coordinates is odd.
282 * This creates a checkerboard effect. */
283 for (int x = (x1 + y) & 1; x < x2 - x1; x += 2) {
284 blitter->SetPixel(dst, x, 0, (uint8)colour);
286 break;
290 /* Next line */
291 ++y;
296 * Check line clipping by using a linear equation and draw the visible part of
297 * the line given by x/y and x2/y2.
298 * @param video Destination pointer to draw into.
299 * @param x X coordinate of first point.
300 * @param y Y coordinate of first point.
301 * @param x2 X coordinate of second point.
302 * @param y2 Y coordinate of second point.
303 * @param screen_width With of the screen to check clipping against.
304 * @param screen_height Height of the screen to check clipping against.
305 * @param colour Colour of the line.
306 * @param width Width of the line.
307 * @param dash Length of dashes for dashed lines. 0 means solid line.
309 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)
311 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
313 assert(width > 0);
315 if (y2 == y || x2 == x) {
316 /* Special case: horizontal/vertical line. All checks already done in GfxPreprocessLine. */
317 blitter->DrawLine(video, x, y, x2, y2, screen_width, screen_height, colour, width, dash);
318 return;
321 int grade_y = y2 - y;
322 int grade_x = x2 - x;
324 /* Clipping rectangle. Slightly extended so we can ignore the width of the line. */
325 int extra = (int)CeilDiv(3 * width, 4); // not less then "width * sqrt(2) / 2"
326 Rect clip = { -extra, -extra, screen_width - 1 + extra, screen_height - 1 + extra };
328 /* prevent integer overflows. */
329 int margin = 1;
330 while (INT_MAX / abs(grade_y) < max(abs(clip.left - x), abs(clip.right - x))) {
331 grade_y /= 2;
332 grade_x /= 2;
333 margin *= 2; // account for rounding errors
336 /* Imagine that the line is infinitely long and it intersects with
337 * infinitely long left and right edges of the clipping rectangle.
338 * If both intersection points are outside the clipping rectangle
339 * and both on the same side of it, we don't need to draw anything. */
340 int left_isec_y = y + (clip.left - x) * grade_y / grade_x;
341 int right_isec_y = y + (clip.right - x) * grade_y / grade_x;
342 if ((left_isec_y > clip.bottom + margin && right_isec_y > clip.bottom + margin) ||
343 (left_isec_y < clip.top - margin && right_isec_y < clip.top - margin)) {
344 return;
347 /* It is possible to use the line equation to further reduce the amount of
348 * work the blitter has to do by shortening the effective line segment.
349 * However, in order to get that right and prevent the flickering effects
350 * of rounding errors so much additional code has to be run here that in
351 * the general case the effect is not noticeable. */
353 blitter->DrawLine(video, x, y, x2, y2, screen_width, screen_height, colour, width, dash);
357 * Align parameters of a line to the given DPI and check simple clipping.
358 * @param dpi Screen parameters to align with.
359 * @param x X coordinate of first point.
360 * @param y Y coordinate of first point.
361 * @param x2 X coordinate of second point.
362 * @param y2 Y coordinate of second point.
363 * @param width Width of the line.
364 * @return True if the line is likely to be visible, false if it's certainly
365 * invisible.
367 static inline bool GfxPreprocessLine(DrawPixelInfo *dpi, int &x, int &y, int &x2, int &y2, int width)
369 x -= dpi->left;
370 x2 -= dpi->left;
371 y -= dpi->top;
372 y2 -= dpi->top;
374 /* Check simple clipping */
375 if (x + width / 2 < 0 && x2 + width / 2 < 0 ) return false;
376 if (y + width / 2 < 0 && y2 + width / 2 < 0 ) return false;
377 if (x - width / 2 > dpi->width && x2 - width / 2 > dpi->width ) return false;
378 if (y - width / 2 > dpi->height && y2 - width / 2 > dpi->height) return false;
379 return true;
382 void GfxDrawLine(int x, int y, int x2, int y2, int colour, int width, int dash)
384 DrawPixelInfo *dpi = _cur_dpi;
385 if (GfxPreprocessLine(dpi, x, y, x2, y2, width)) {
386 GfxDoDrawLine(dpi->dst_ptr, x, y, x2, y2, dpi->width, dpi->height, colour, width, dash);
390 void GfxDrawLineUnscaled(int x, int y, int x2, int y2, int colour)
392 DrawPixelInfo *dpi = _cur_dpi;
393 if (GfxPreprocessLine(dpi, x, y, x2, y2, 1)) {
394 GfxDoDrawLine(dpi->dst_ptr,
395 UnScaleByZoom(x, dpi->zoom), UnScaleByZoom(y, dpi->zoom),
396 UnScaleByZoom(x2, dpi->zoom), UnScaleByZoom(y2, dpi->zoom),
397 UnScaleByZoom(dpi->width, dpi->zoom), UnScaleByZoom(dpi->height, dpi->zoom), colour, 1);
402 * Draws the projection of a parallelepiped.
403 * This can be used to draw boxes in world coordinates.
405 * @param x Screen X-coordinate of top front corner.
406 * @param y Screen Y-coordinate of top front corner.
407 * @param dx1 Screen X-length of first edge.
408 * @param dy1 Screen Y-length of first edge.
409 * @param dx2 Screen X-length of second edge.
410 * @param dy2 Screen Y-length of second edge.
411 * @param dx3 Screen X-length of third edge.
412 * @param dy3 Screen Y-length of third edge.
414 void DrawBox(int x, int y, int dx1, int dy1, int dx2, int dy2, int dx3, int dy3)
416 /* ....
417 * .. ....
418 * .. ....
419 * .. ^
420 * <--__(dx1,dy1) /(dx2,dy2)
421 * : --__ / :
422 * : --__ / :
423 * : *(x,y) :
424 * : | :
425 * : | ..
426 * .... |(dx3,dy3)
427 * .... | ..
428 * ....V.
431 static const byte colour = PC_WHITE;
433 GfxDrawLineUnscaled(x, y, x + dx1, y + dy1, colour);
434 GfxDrawLineUnscaled(x, y, x + dx2, y + dy2, colour);
435 GfxDrawLineUnscaled(x, y, x + dx3, y + dy3, colour);
437 GfxDrawLineUnscaled(x + dx1, y + dy1, x + dx1 + dx2, y + dy1 + dy2, colour);
438 GfxDrawLineUnscaled(x + dx1, y + dy1, x + dx1 + dx3, y + dy1 + dy3, colour);
439 GfxDrawLineUnscaled(x + dx2, y + dy2, x + dx2 + dx1, y + dy2 + dy1, colour);
440 GfxDrawLineUnscaled(x + dx2, y + dy2, x + dx2 + dx3, y + dy2 + dy3, colour);
441 GfxDrawLineUnscaled(x + dx3, y + dy3, x + dx3 + dx1, y + dy3 + dy1, colour);
442 GfxDrawLineUnscaled(x + dx3, y + dy3, x + dx3 + dx2, y + dy3 + dy2, colour);
446 * Set the colour remap to be for the given colour.
447 * @param colour the new colour of the remap.
449 static void SetColourRemap(TextColour colour)
451 if (colour == TC_INVALID) return;
453 /* Black strings have no shading ever; the shading is black, so it
454 * would be invisible at best, but it actually makes it illegible. */
455 bool no_shade = (colour & TC_NO_SHADE) != 0 || colour == TC_BLACK;
456 bool raw_colour = (colour & TC_IS_PALETTE_COLOUR) != 0;
457 colour &= ~(TC_NO_SHADE | TC_IS_PALETTE_COLOUR | TC_FORCED);
459 _string_colourremap[1] = raw_colour ? (byte)colour : _string_colourmap[colour];
460 _string_colourremap[2] = no_shade ? 0 : 1;
461 _colour_remap_ptr = _string_colourremap;
465 * Drawing routine for drawing a laid out line of text.
466 * @param line String to draw.
467 * @param y The top most position to draw on.
468 * @param left The left most position to draw on.
469 * @param right The right most position to draw on.
470 * @param align The alignment of the string when drawing left-to-right. In the
471 * case a right-to-left language is chosen this is inverted so it
472 * will be drawn in the right direction.
473 * @param underline Whether to underline what has been drawn or not.
474 * @param truncation Whether to perform string truncation or not.
476 * @return In case of left or center alignment the right most pixel we have drawn to.
477 * In case of right alignment the left most pixel we have drawn to.
479 static int DrawLayoutLine(const ParagraphLayouter::Line &line, int y, int left, int right, StringAlignment align, bool underline, bool truncation)
481 if (line.CountRuns() == 0) return 0;
483 int w = line.GetWidth();
484 int h = line.GetLeading();
487 * The following is needed for truncation.
488 * Depending on the text direction, we either remove bits at the rear
489 * or the front. For this we shift the entire area to draw so it fits
490 * within the left/right bounds and the side we do not truncate it on.
491 * Then we determine the truncation location, i.e. glyphs that fall
492 * outside of the range min_x - max_x will not be drawn; they are thus
493 * the truncated glyphs.
495 * At a later step we insert the dots.
498 int max_w = right - left + 1; // The maximum width.
500 int offset_x = 0; // The offset we need for positioning the glyphs
501 int min_x = left; // The minimum x position to draw normal glyphs on.
502 int max_x = right; // The maximum x position to draw normal glyphs on.
504 truncation &= max_w < w; // Whether we need to do truncation.
505 int dot_width = 0; // Cache for the width of the dot.
506 const Sprite *dot_sprite = nullptr; // Cache for the sprite of the dot.
508 if (truncation) {
510 * Assumption may be made that all fonts of a run are of the same size.
511 * In any case, we'll use these dots for the abbreviation, so even if
512 * another size would be chosen it won't have truncated too little for
513 * the truncation dots.
515 FontCache *fc = ((const Font*)line.GetVisualRun(0).GetFont())->fc;
516 GlyphID dot_glyph = fc->MapCharToGlyph('.');
517 dot_width = fc->GetGlyphWidth(dot_glyph);
518 dot_sprite = fc->GetGlyph(dot_glyph);
520 if (_current_text_dir == TD_RTL) {
521 min_x += 3 * dot_width;
522 offset_x = w - 3 * dot_width - max_w;
523 } else {
524 max_x -= 3 * dot_width;
527 w = max_w;
530 /* In case we have a RTL language we swap the alignment. */
531 if (!(align & SA_FORCE) && _current_text_dir == TD_RTL && (align & SA_HOR_MASK) != SA_HOR_CENTER) align ^= SA_RIGHT;
533 /* right is the right most position to draw on. In this case we want to do
534 * calculations with the width of the string. In comparison right can be
535 * seen as lastof(todraw) and width as lengthof(todraw). They differ by 1.
536 * So most +1/-1 additions are to move from lengthof to 'indices'.
538 switch (align & SA_HOR_MASK) {
539 case SA_LEFT:
540 /* right + 1 = left + w */
541 right = left + w - 1;
542 break;
544 case SA_HOR_CENTER:
545 left = RoundDivSU(right + 1 + left - w, 2);
546 /* right + 1 = left + w */
547 right = left + w - 1;
548 break;
550 case SA_RIGHT:
551 left = right + 1 - w;
552 break;
554 default:
555 NOT_REACHED();
558 TextColour colour = TC_BLACK;
559 bool draw_shadow = false;
560 for (int run_index = 0; run_index < line.CountRuns(); run_index++) {
561 const ParagraphLayouter::VisualRun &run = line.GetVisualRun(run_index);
562 const Font *f = (const Font*)run.GetFont();
564 FontCache *fc = f->fc;
565 colour = f->colour;
566 SetColourRemap(colour);
568 DrawPixelInfo *dpi = _cur_dpi;
569 int dpi_left = dpi->left;
570 int dpi_right = dpi->left + dpi->width - 1;
572 draw_shadow = fc->GetDrawGlyphShadow() && (colour & TC_NO_SHADE) == 0 && colour != TC_BLACK;
574 for (int i = 0; i < run.GetGlyphCount(); i++) {
575 GlyphID glyph = run.GetGlyphs()[i];
577 /* Not a valid glyph (empty) */
578 if (glyph == 0xFFFF) continue;
580 int begin_x = (int)run.GetPositions()[i * 2] + left - offset_x;
581 int end_x = (int)run.GetPositions()[i * 2 + 2] + left - offset_x - 1;
582 int top = (int)run.GetPositions()[i * 2 + 1] + y;
584 /* Truncated away. */
585 if (truncation && (begin_x < min_x || end_x > max_x)) continue;
587 const Sprite *sprite = fc->GetGlyph(glyph);
588 /* Check clipping (the "+ 1" is for the shadow). */
589 if (begin_x + sprite->x_offs > dpi_right || begin_x + sprite->x_offs + sprite->width /* - 1 + 1 */ < dpi_left) continue;
591 if (draw_shadow && (glyph & SPRITE_GLYPH) == 0) {
592 SetColourRemap(TC_BLACK);
593 GfxMainBlitter(sprite, begin_x + 1, top + 1, BM_COLOUR_REMAP);
594 SetColourRemap(colour);
596 GfxMainBlitter(sprite, begin_x, top, BM_COLOUR_REMAP);
600 if (truncation) {
601 int x = (_current_text_dir == TD_RTL) ? left : (right - 3 * dot_width);
602 for (int i = 0; i < 3; i++, x += dot_width) {
603 if (draw_shadow) {
604 SetColourRemap(TC_BLACK);
605 GfxMainBlitter(dot_sprite, x + 1, y + 1, BM_COLOUR_REMAP);
606 SetColourRemap(colour);
608 GfxMainBlitter(dot_sprite, x, y, BM_COLOUR_REMAP);
612 if (underline) {
613 GfxFillRect(left, y + h, right, y + h, _string_colourremap[1]);
616 return (align & SA_HOR_MASK) == SA_RIGHT ? left : right;
620 * Draw string, possibly truncated to make it fit in its allocated space
622 * @param left The left most position to draw on.
623 * @param right The right most position to draw on.
624 * @param top The top most position to draw on.
625 * @param str String to draw.
626 * @param colour Colour used for drawing the string, for details see _string_colourmap in
627 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
628 * @param align The alignment of the string when drawing left-to-right. In the
629 * case a right-to-left language is chosen this is inverted so it
630 * will be drawn in the right direction.
631 * @param underline Whether to underline what has been drawn or not.
632 * @param fontsize The size of the initial characters.
633 * @return In case of left or center alignment the right most pixel we have drawn to.
634 * In case of right alignment the left most pixel we have drawn to.
636 int DrawString(int left, int right, int top, const char *str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
638 /* The string may contain control chars to change the font, just use the biggest font for clipping. */
639 int max_height = max(max(FONT_HEIGHT_SMALL, FONT_HEIGHT_NORMAL), max(FONT_HEIGHT_LARGE, FONT_HEIGHT_MONO));
641 /* Funny glyphs may extent outside the usual bounds, so relax the clipping somewhat. */
642 int extra = max_height / 2;
644 if (_cur_dpi->top + _cur_dpi->height + extra < top || _cur_dpi->top > top + max_height + extra ||
645 _cur_dpi->left + _cur_dpi->width + extra < left || _cur_dpi->left > right + extra) {
646 return 0;
649 Layouter layout(str, INT32_MAX, colour, fontsize);
650 if (layout.size() == 0) return 0;
652 return DrawLayoutLine(*layout.front(), top, left, right, align, underline, true);
656 * Draw string, possibly truncated to make it fit in its allocated space
658 * @param left The left most position to draw on.
659 * @param right The right most position to draw on.
660 * @param top The top most position to draw on.
661 * @param str String to draw.
662 * @param colour Colour used for drawing the string, for details see _string_colourmap in
663 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
664 * @param align The alignment of the string when drawing left-to-right. In the
665 * case a right-to-left language is chosen this is inverted so it
666 * will be drawn in the right direction.
667 * @param underline Whether to underline what has been drawn or not.
668 * @param fontsize The size of the initial characters.
669 * @return In case of left or center alignment the right most pixel we have drawn to.
670 * In case of right alignment the left most pixel we have drawn to.
672 int DrawString(int left, int right, int top, StringID str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
674 char buffer[DRAW_STRING_BUFFER];
675 GetString(buffer, str, lastof(buffer));
676 return DrawString(left, right, top, buffer, colour, align, underline, fontsize);
680 * Calculates height of string (in pixels). The string is changed to a multiline string if needed.
681 * @param str string to check
682 * @param maxw maximum string width
683 * @return height of pixels of string when it is drawn
685 int GetStringHeight(const char *str, int maxw, FontSize fontsize)
687 Layouter layout(str, maxw, TC_FROMSTRING, fontsize);
688 return layout.GetBounds().height;
692 * Calculates height of string (in pixels). The string is changed to a multiline string if needed.
693 * @param str string to check
694 * @param maxw maximum string width
695 * @return height of pixels of string when it is drawn
697 int GetStringHeight(StringID str, int maxw)
699 char buffer[DRAW_STRING_BUFFER];
700 GetString(buffer, str, lastof(buffer));
701 return GetStringHeight(buffer, maxw);
705 * Calculates number of lines of string. The string is changed to a multiline string if needed.
706 * @param str string to check
707 * @param maxw maximum string width
708 * @return number of lines of string when it is drawn
710 int GetStringLineCount(StringID str, int maxw)
712 char buffer[DRAW_STRING_BUFFER];
713 GetString(buffer, str, lastof(buffer));
715 Layouter layout(buffer, maxw);
716 return (uint)layout.size();
720 * Calculate string bounding box for multi-line strings.
721 * @param str String to check.
722 * @param suggestion Suggested bounding box.
723 * @return Bounding box for the multi-line string, may be bigger than \a suggestion.
725 Dimension GetStringMultiLineBoundingBox(StringID str, const Dimension &suggestion)
727 Dimension box = {suggestion.width, (uint)GetStringHeight(str, suggestion.width)};
728 return box;
732 * Calculate string bounding box for multi-line strings.
733 * @param str String to check.
734 * @param suggestion Suggested bounding box.
735 * @return Bounding box for the multi-line string, may be bigger than \a suggestion.
737 Dimension GetStringMultiLineBoundingBox(const char *str, const Dimension &suggestion)
739 Dimension box = {suggestion.width, (uint)GetStringHeight(str, suggestion.width)};
740 return box;
744 * Draw string, possibly over multiple lines.
746 * @param left The left most position to draw on.
747 * @param right The right most position to draw on.
748 * @param top The top most position to draw on.
749 * @param bottom The bottom most position to draw on.
750 * @param str String to draw.
751 * @param colour Colour used for drawing the string, for details see _string_colourmap in
752 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
753 * @param align The horizontal and vertical alignment of the string.
754 * @param underline Whether to underline all strings
755 * @param fontsize The size of the initial characters.
757 * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written.
759 int DrawStringMultiLine(int left, int right, int top, int bottom, const char *str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
761 int maxw = right - left + 1;
762 int maxh = bottom - top + 1;
764 /* It makes no sense to even try if it can't be drawn anyway, or
765 * do we really want to support fonts of 0 or less pixels high? */
766 if (maxh <= 0) return top;
768 Layouter layout(str, maxw, colour, fontsize);
769 int total_height = layout.GetBounds().height;
770 int y;
771 switch (align & SA_VERT_MASK) {
772 case SA_TOP:
773 y = top;
774 break;
776 case SA_VERT_CENTER:
777 y = RoundDivSU(bottom + top - total_height, 2);
778 break;
780 case SA_BOTTOM:
781 y = bottom - total_height;
782 break;
784 default: NOT_REACHED();
787 int last_line = top;
788 int first_line = bottom;
790 for (const auto &line : layout) {
792 int line_height = line->GetLeading();
793 if (y >= top && y < bottom) {
794 last_line = y + line_height;
795 if (first_line > y) first_line = y;
797 DrawLayoutLine(*line, y, left, right, align, underline, false);
799 y += line_height;
802 return ((align & SA_VERT_MASK) == SA_BOTTOM) ? first_line : last_line;
806 * Draw string, possibly over multiple lines.
808 * @param left The left most position to draw on.
809 * @param right The right most position to draw on.
810 * @param top The top most position to draw on.
811 * @param bottom The bottom most position to draw on.
812 * @param str String to draw.
813 * @param colour Colour used for drawing the string, for details see _string_colourmap in
814 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
815 * @param align The horizontal and vertical alignment of the string.
816 * @param underline Whether to underline all strings
817 * @param fontsize The size of the initial characters.
819 * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written.
821 int DrawStringMultiLine(int left, int right, int top, int bottom, StringID str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
823 char buffer[DRAW_STRING_BUFFER];
824 GetString(buffer, str, lastof(buffer));
825 return DrawStringMultiLine(left, right, top, bottom, buffer, colour, align, underline, fontsize);
829 * Return the string dimension in pixels. The height and width are returned
830 * in a single Dimension value. TINYFONT, BIGFONT modifiers are only
831 * supported as the first character of the string. The returned dimensions
832 * are therefore a rough estimation correct for all the current strings
833 * but not every possible combination
834 * @param str string to calculate pixel-width
835 * @param start_fontsize Fontsize to start the text with
836 * @return string width and height in pixels
838 Dimension GetStringBoundingBox(const char *str, FontSize start_fontsize)
840 Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize);
841 return layout.GetBounds();
845 * Get bounding box of a string. Uses parameters set by #SetDParam if needed.
846 * Has the same restrictions as #GetStringBoundingBox(const char *str, FontSize start_fontsize).
847 * @param strid String to examine.
848 * @return Width and height of the bounding box for the string in pixels.
850 Dimension GetStringBoundingBox(StringID strid)
852 char buffer[DRAW_STRING_BUFFER];
854 GetString(buffer, strid, lastof(buffer));
855 return GetStringBoundingBox(buffer);
859 * Get the leading corner of a character in a single-line string relative
860 * to the start of the string.
861 * @param str String containing the character.
862 * @param ch Pointer to the character in the string.
863 * @param start_fontsize Font size to start the text with.
864 * @return Upper left corner of the glyph associated with the character.
866 Point GetCharPosInString(const char *str, const char *ch, FontSize start_fontsize)
868 Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize);
869 return layout.GetCharPosition(ch);
873 * Get the character from a string that is drawn at a specific position.
874 * @param str String to test.
875 * @param x Position relative to the start of the string.
876 * @param start_fontsize Font size to start the text with.
877 * @return Pointer to the character at the position or nullptr if there is no character at the position.
879 const char *GetCharAtPosition(const char *str, int x, FontSize start_fontsize)
881 if (x < 0) return nullptr;
883 Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize);
884 return layout.GetCharAtPosition(x);
888 * Draw single character horizontally centered around (x,y)
889 * @param c Character (glyph) to draw
890 * @param x X position to draw character
891 * @param y Y position to draw character
892 * @param colour Colour to use, for details see _string_colourmap in
893 * table/palettes.h or docs/ottd-colourtext-palette.png or the enum TextColour in gfx_type.h
895 void DrawCharCentered(WChar c, int x, int y, TextColour colour)
897 SetColourRemap(colour);
898 GfxMainBlitter(GetGlyph(FS_NORMAL, c), x - GetCharacterWidth(FS_NORMAL, c) / 2, y, BM_COLOUR_REMAP);
902 * Get the size of a sprite.
903 * @param sprid Sprite to examine.
904 * @param[out] offset Optionally returns the sprite position offset.
905 * @return Sprite size in pixels.
906 * @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.
908 Dimension GetSpriteSize(SpriteID sprid, Point *offset, ZoomLevel zoom)
910 const Sprite *sprite = GetSprite(sprid, ST_NORMAL);
912 if (offset != nullptr) {
913 offset->x = UnScaleByZoom(sprite->x_offs, zoom);
914 offset->y = UnScaleByZoom(sprite->y_offs, zoom);
917 Dimension d;
918 d.width = max<int>(0, UnScaleByZoom(sprite->x_offs + sprite->width, zoom));
919 d.height = max<int>(0, UnScaleByZoom(sprite->y_offs + sprite->height, zoom));
920 return d;
924 * Helper function to get the blitter mode for different types of palettes.
925 * @param pal The palette to get the blitter mode for.
926 * @return The blitter mode associated with the palette.
928 static BlitterMode GetBlitterMode(PaletteID pal)
930 switch (pal) {
931 case PAL_NONE: return BM_NORMAL;
932 case PALETTE_CRASH: return BM_CRASH_REMAP;
933 case PALETTE_ALL_BLACK: return BM_BLACK_REMAP;
934 default: return BM_COLOUR_REMAP;
939 * Draw a sprite in a viewport.
940 * @param img Image number to draw
941 * @param pal Palette to use.
942 * @param x Left coordinate of image in viewport, scaled by zoom
943 * @param y Top coordinate of image in viewport, scaled by zoom
944 * @param sub If available, draw only specified part of the sprite
946 void DrawSpriteViewport(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub)
948 SpriteID real_sprite = GB(img, 0, SPRITE_WIDTH);
949 if (HasBit(img, PALETTE_MODIFIER_TRANSPARENT)) {
950 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
951 GfxMainBlitterViewport(GetSprite(real_sprite, ST_NORMAL), x, y, BM_TRANSPARENT, sub, real_sprite);
952 } else if (pal != PAL_NONE) {
953 if (HasBit(pal, PALETTE_TEXT_RECOLOUR)) {
954 SetColourRemap((TextColour)GB(pal, 0, PALETTE_WIDTH));
955 } else {
956 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
958 GfxMainBlitterViewport(GetSprite(real_sprite, ST_NORMAL), x, y, GetBlitterMode(pal), sub, real_sprite);
959 } else {
960 GfxMainBlitterViewport(GetSprite(real_sprite, ST_NORMAL), x, y, BM_NORMAL, sub, real_sprite);
965 * Draw a sprite, not in a viewport
966 * @param img Image number to draw
967 * @param pal Palette to use.
968 * @param x Left coordinate of image in pixels
969 * @param y Top coordinate of image in pixels
970 * @param sub If available, draw only specified part of the sprite
971 * @param zoom Zoom level of sprite
973 void DrawSprite(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub, ZoomLevel zoom)
975 SpriteID real_sprite = GB(img, 0, SPRITE_WIDTH);
976 if (HasBit(img, PALETTE_MODIFIER_TRANSPARENT)) {
977 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
978 GfxMainBlitter(GetSprite(real_sprite, ST_NORMAL), x, y, BM_TRANSPARENT, sub, real_sprite, zoom);
979 } else if (pal != PAL_NONE) {
980 if (HasBit(pal, PALETTE_TEXT_RECOLOUR)) {
981 SetColourRemap((TextColour)GB(pal, 0, PALETTE_WIDTH));
982 } else {
983 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
985 GfxMainBlitter(GetSprite(real_sprite, ST_NORMAL), x, y, GetBlitterMode(pal), sub, real_sprite, zoom);
986 } else {
987 GfxMainBlitter(GetSprite(real_sprite, ST_NORMAL), x, y, BM_NORMAL, sub, real_sprite, zoom);
992 * The code for setting up the blitter mode and sprite information before finally drawing the sprite.
993 * @param sprite The sprite to draw.
994 * @param x The X location to draw.
995 * @param y The Y location to draw.
996 * @param mode The settings for the blitter to pass.
997 * @param sub Whether to only draw a sub set of the sprite.
998 * @param zoom The zoom level at which to draw the sprites.
999 * @tparam ZOOM_BASE The factor required to get the sub sprite information into the right size.
1000 * @tparam SCALED_XY Whether the X and Y are scaled or unscaled.
1002 template <int ZOOM_BASE, bool SCALED_XY>
1003 static void GfxBlitter(const Sprite * const sprite, int x, int y, BlitterMode mode, const SubSprite * const sub, SpriteID sprite_id, ZoomLevel zoom)
1005 const DrawPixelInfo *dpi = _cur_dpi;
1006 Blitter::BlitterParams bp;
1008 if (SCALED_XY) {
1009 /* Scale it */
1010 x = ScaleByZoom(x, zoom);
1011 y = ScaleByZoom(y, zoom);
1014 /* Move to the correct offset */
1015 x += sprite->x_offs;
1016 y += sprite->y_offs;
1018 if (sub == nullptr) {
1019 /* No clipping. */
1020 bp.skip_left = 0;
1021 bp.skip_top = 0;
1022 bp.width = UnScaleByZoom(sprite->width, zoom);
1023 bp.height = UnScaleByZoom(sprite->height, zoom);
1024 } else {
1025 /* Amount of pixels to clip from the source sprite */
1026 int clip_left = max(0, -sprite->x_offs + sub->left * ZOOM_BASE );
1027 int clip_top = max(0, -sprite->y_offs + sub->top * ZOOM_BASE );
1028 int clip_right = max(0, sprite->width - (-sprite->x_offs + (sub->right + 1) * ZOOM_BASE));
1029 int clip_bottom = max(0, sprite->height - (-sprite->y_offs + (sub->bottom + 1) * ZOOM_BASE));
1031 if (clip_left + clip_right >= sprite->width) return;
1032 if (clip_top + clip_bottom >= sprite->height) return;
1034 bp.skip_left = UnScaleByZoomLower(clip_left, zoom);
1035 bp.skip_top = UnScaleByZoomLower(clip_top, zoom);
1036 bp.width = UnScaleByZoom(sprite->width - clip_left - clip_right, zoom);
1037 bp.height = UnScaleByZoom(sprite->height - clip_top - clip_bottom, zoom);
1039 x += ScaleByZoom(bp.skip_left, zoom);
1040 y += ScaleByZoom(bp.skip_top, zoom);
1043 /* Copy the main data directly from the sprite */
1044 bp.sprite = sprite->data;
1045 bp.sprite_width = sprite->width;
1046 bp.sprite_height = sprite->height;
1047 bp.top = 0;
1048 bp.left = 0;
1050 bp.dst = dpi->dst_ptr;
1051 bp.pitch = dpi->pitch;
1052 bp.remap = _colour_remap_ptr;
1054 assert(sprite->width > 0);
1055 assert(sprite->height > 0);
1057 if (bp.width <= 0) return;
1058 if (bp.height <= 0) return;
1060 y -= SCALED_XY ? ScaleByZoom(dpi->top, zoom) : dpi->top;
1061 int y_unscaled = UnScaleByZoom(y, zoom);
1062 /* Check for top overflow */
1063 if (y < 0) {
1064 bp.height -= -y_unscaled;
1065 if (bp.height <= 0) return;
1066 bp.skip_top += -y_unscaled;
1067 y = 0;
1068 } else {
1069 bp.top = y_unscaled;
1072 /* Check for bottom overflow */
1073 y += SCALED_XY ? ScaleByZoom(bp.height - dpi->height, zoom) : ScaleByZoom(bp.height, zoom) - dpi->height;
1074 if (y > 0) {
1075 bp.height -= UnScaleByZoom(y, zoom);
1076 if (bp.height <= 0) return;
1079 x -= SCALED_XY ? ScaleByZoom(dpi->left, zoom) : dpi->left;
1080 int x_unscaled = UnScaleByZoom(x, zoom);
1081 /* Check for left overflow */
1082 if (x < 0) {
1083 bp.width -= -x_unscaled;
1084 if (bp.width <= 0) return;
1085 bp.skip_left += -x_unscaled;
1086 x = 0;
1087 } else {
1088 bp.left = x_unscaled;
1091 /* Check for right overflow */
1092 x += SCALED_XY ? ScaleByZoom(bp.width - dpi->width, zoom) : ScaleByZoom(bp.width, zoom) - dpi->width;
1093 if (x > 0) {
1094 bp.width -= UnScaleByZoom(x, zoom);
1095 if (bp.width <= 0) return;
1098 assert(bp.skip_left + bp.width <= UnScaleByZoom(sprite->width, zoom));
1099 assert(bp.skip_top + bp.height <= UnScaleByZoom(sprite->height, zoom));
1101 /* We do not want to catch the mouse. However we also use that spritenumber for unknown (text) sprites. */
1102 if (_newgrf_debug_sprite_picker.mode == SPM_REDRAW && sprite_id != SPR_CURSOR_MOUSE) {
1103 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
1104 void *topleft = blitter->MoveTo(bp.dst, bp.left, bp.top);
1105 void *bottomright = blitter->MoveTo(topleft, bp.width - 1, bp.height - 1);
1107 void *clicked = _newgrf_debug_sprite_picker.clicked_pixel;
1109 if (topleft <= clicked && clicked <= bottomright) {
1110 uint offset = (((size_t)clicked - (size_t)topleft) / (blitter->GetScreenDepth() / 8)) % bp.pitch;
1111 if (offset < (uint)bp.width) {
1112 include(_newgrf_debug_sprite_picker.sprites, sprite_id);
1117 BlitterFactory::GetCurrentBlitter()->Draw(&bp, mode, zoom);
1120 static void GfxMainBlitterViewport(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub, SpriteID sprite_id)
1122 GfxBlitter<ZOOM_LVL_BASE, false>(sprite, x, y, mode, sub, sprite_id, _cur_dpi->zoom);
1125 static void GfxMainBlitter(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub, SpriteID sprite_id, ZoomLevel zoom)
1127 GfxBlitter<1, true>(sprite, x, y, mode, sub, sprite_id, zoom);
1130 void DoPaletteAnimations();
1132 void GfxInitPalettes()
1134 memcpy(&_cur_palette, &_palette, sizeof(_cur_palette));
1135 DoPaletteAnimations();
1138 #define EXTR(p, q) (((uint16)(palette_animation_counter * (p)) * (q)) >> 16)
1139 #define EXTR2(p, q) (((uint16)(~palette_animation_counter * (p)) * (q)) >> 16)
1141 void DoPaletteAnimations()
1143 /* Animation counter for the palette animation. */
1144 static int palette_animation_counter = 0;
1145 palette_animation_counter += 8;
1147 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
1148 const Colour *s;
1149 const ExtraPaletteValues *ev = &_extra_palette_values;
1150 Colour old_val[PALETTE_ANIM_SIZE];
1151 const uint old_tc = palette_animation_counter;
1152 uint i;
1153 uint j;
1155 if (blitter != nullptr && blitter->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_NONE) {
1156 palette_animation_counter = 0;
1159 Colour *palette_pos = &_cur_palette.palette[PALETTE_ANIM_START]; // Points to where animations are taking place on the palette
1160 /* Makes a copy of the current animation palette in old_val,
1161 * so the work on the current palette could be compared, see if there has been any changes */
1162 memcpy(old_val, palette_pos, sizeof(old_val));
1164 /* Fizzy Drink bubbles animation */
1165 s = ev->fizzy_drink;
1166 j = EXTR2(512, EPV_CYCLES_FIZZY_DRINK);
1167 for (i = 0; i != EPV_CYCLES_FIZZY_DRINK; i++) {
1168 *palette_pos++ = s[j];
1169 j++;
1170 if (j == EPV_CYCLES_FIZZY_DRINK) j = 0;
1173 /* Oil refinery fire animation */
1174 s = ev->oil_refinery;
1175 j = EXTR2(512, EPV_CYCLES_OIL_REFINERY);
1176 for (i = 0; i != EPV_CYCLES_OIL_REFINERY; i++) {
1177 *palette_pos++ = s[j];
1178 j++;
1179 if (j == EPV_CYCLES_OIL_REFINERY) j = 0;
1182 /* Radio tower blinking */
1184 byte i = (palette_animation_counter >> 1) & 0x7F;
1185 byte v;
1187 if (i < 0x3f) {
1188 v = 255;
1189 } else if (i < 0x4A || i >= 0x75) {
1190 v = 128;
1191 } else {
1192 v = 20;
1194 palette_pos->r = v;
1195 palette_pos->g = 0;
1196 palette_pos->b = 0;
1197 palette_pos++;
1199 i ^= 0x40;
1200 if (i < 0x3f) {
1201 v = 255;
1202 } else if (i < 0x4A || i >= 0x75) {
1203 v = 128;
1204 } else {
1205 v = 20;
1207 palette_pos->r = v;
1208 palette_pos->g = 0;
1209 palette_pos->b = 0;
1210 palette_pos++;
1213 /* Handle lighthouse and stadium animation */
1214 s = ev->lighthouse;
1215 j = EXTR(256, EPV_CYCLES_LIGHTHOUSE);
1216 for (i = 0; i != EPV_CYCLES_LIGHTHOUSE; i++) {
1217 *palette_pos++ = s[j];
1218 j++;
1219 if (j == EPV_CYCLES_LIGHTHOUSE) j = 0;
1222 /* Dark blue water */
1223 s = (_settings_game.game_creation.landscape == LT_TOYLAND) ? ev->dark_water_toyland : ev->dark_water;
1224 j = EXTR(320, EPV_CYCLES_DARK_WATER);
1225 for (i = 0; i != EPV_CYCLES_DARK_WATER; i++) {
1226 *palette_pos++ = s[j];
1227 j++;
1228 if (j == EPV_CYCLES_DARK_WATER) j = 0;
1231 /* Glittery water */
1232 s = (_settings_game.game_creation.landscape == LT_TOYLAND) ? ev->glitter_water_toyland : ev->glitter_water;
1233 j = EXTR(128, EPV_CYCLES_GLITTER_WATER);
1234 for (i = 0; i != EPV_CYCLES_GLITTER_WATER / 3; i++) {
1235 *palette_pos++ = s[j];
1236 j += 3;
1237 if (j >= EPV_CYCLES_GLITTER_WATER) j -= EPV_CYCLES_GLITTER_WATER;
1240 if (blitter != nullptr && blitter->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_NONE) {
1241 palette_animation_counter = old_tc;
1242 } else {
1243 if (memcmp(old_val, &_cur_palette.palette[PALETTE_ANIM_START], sizeof(old_val)) != 0 && _cur_palette.count_dirty == 0) {
1244 /* Did we changed anything on the palette? Seems so. Mark it as dirty */
1245 _cur_palette.first_dirty = PALETTE_ANIM_START;
1246 _cur_palette.count_dirty = PALETTE_ANIM_SIZE;
1252 * Determine a contrasty text colour for a coloured background.
1253 * @param background Background colour.
1254 * @param threshold Background colour brightness threshold below which the background is considered dark and TC_WHITE is returned, range: 0 - 255, default 128.
1255 * @return TC_BLACK or TC_WHITE depending on what gives a better contrast.
1257 TextColour GetContrastColour(uint8 background, uint8 threshold)
1259 Colour c = _cur_palette.palette[background];
1260 /* Compute brightness according to http://www.w3.org/TR/AERT#color-contrast.
1261 * The following formula computes 1000 * brightness^2, with brightness being in range 0 to 255. */
1262 uint sq1000_brightness = c.r * c.r * 299 + c.g * c.g * 587 + c.b * c.b * 114;
1263 /* Compare with threshold brightness which defaults to 128 (50%) */
1264 return sq1000_brightness < ((uint) threshold) * ((uint) threshold) * 1000 ? TC_WHITE : TC_BLACK;
1268 * Initialize _stringwidth_table cache
1269 * @param monospace Whether to load the monospace cache or the normal fonts.
1271 void LoadStringWidthTable(bool monospace)
1273 ClearFontCache();
1275 for (FontSize fs = monospace ? FS_MONO : FS_BEGIN; fs < (monospace ? FS_END : FS_MONO); fs++) {
1276 for (uint i = 0; i != 224; i++) {
1277 _stringwidth_table[fs][i] = GetGlyphWidth(fs, i + 32);
1281 ReInitAllWindows();
1285 * Return width of character glyph.
1286 * @param size Font of the character
1287 * @param key Character code glyph
1288 * @return Width of the character glyph
1290 byte GetCharacterWidth(FontSize size, WChar key)
1292 /* Use _stringwidth_table cache if possible */
1293 if (key >= 32 && key < 256) return _stringwidth_table[size][key - 32];
1295 return GetGlyphWidth(size, key);
1299 * Return the maximum width of single digit.
1300 * @param size Font of the digit
1301 * @return Width of the digit.
1303 byte GetDigitWidth(FontSize size)
1305 byte width = 0;
1306 for (char c = '0'; c <= '9'; c++) {
1307 width = max(GetCharacterWidth(size, c), width);
1309 return width;
1313 * Determine the broadest digits for guessing the maximum width of a n-digit number.
1314 * @param[out] front Broadest digit, which is not 0. (Use this digit as first digit for numbers with more than one digit.)
1315 * @param[out] next Broadest digit, including 0. (Use this digit for all digits, except the first one; or for numbers with only one digit.)
1316 * @param size Font of the digit
1318 void GetBroadestDigit(uint *front, uint *next, FontSize size)
1320 int width = -1;
1321 for (char c = '9'; c >= '0'; c--) {
1322 int w = GetCharacterWidth(size, c);
1323 if (w > width) {
1324 width = w;
1325 *next = c - '0';
1326 if (c != '0') *front = c - '0';
1331 void ScreenSizeChanged()
1333 _dirty_bytes_per_line = CeilDiv(_screen.width, DIRTY_BLOCK_WIDTH);
1334 _dirty_blocks = ReallocT<byte>(_dirty_blocks, _dirty_bytes_per_line * CeilDiv(_screen.height, DIRTY_BLOCK_HEIGHT));
1336 /* check the dirty rect */
1337 if (_invalid_rect.right >= _screen.width) _invalid_rect.right = _screen.width;
1338 if (_invalid_rect.bottom >= _screen.height) _invalid_rect.bottom = _screen.height;
1340 /* screen size changed and the old bitmap is invalid now, so we don't want to undraw it */
1341 _cursor.visible = false;
1344 void UndrawMouseCursor()
1346 /* Don't undraw the mouse cursor if the screen is not ready */
1347 if (_screen.dst_ptr == nullptr) return;
1349 if (_cursor.visible) {
1350 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
1351 _cursor.visible = false;
1352 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);
1353 VideoDriver::GetInstance()->MakeDirty(_cursor.draw_pos.x, _cursor.draw_pos.y, _cursor.draw_size.x, _cursor.draw_size.y);
1357 void DrawMouseCursor()
1359 /* Don't draw the mouse cursor if the screen is not ready */
1360 if (_screen.dst_ptr == nullptr) return;
1362 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
1364 /* Redraw mouse cursor but only when it's inside the window */
1365 if (!_cursor.in_window) return;
1367 /* Don't draw the mouse cursor if it's already drawn */
1368 if (_cursor.visible) {
1369 if (!_cursor.dirty) return;
1370 UndrawMouseCursor();
1373 /* Determine visible area */
1374 int left = _cursor.pos.x + _cursor.total_offs.x;
1375 int width = _cursor.total_size.x;
1376 if (left < 0) {
1377 width += left;
1378 left = 0;
1380 if (left + width > _screen.width) {
1381 width = _screen.width - left;
1383 if (width <= 0) return;
1385 int top = _cursor.pos.y + _cursor.total_offs.y;
1386 int height = _cursor.total_size.y;
1387 if (top < 0) {
1388 height += top;
1389 top = 0;
1391 if (top + height > _screen.height) {
1392 height = _screen.height - top;
1394 if (height <= 0) return;
1396 _cursor.draw_pos.x = left;
1397 _cursor.draw_pos.y = top;
1398 _cursor.draw_size.x = width;
1399 _cursor.draw_size.y = height;
1401 uint8 *buffer = _cursor_backup.Allocate(blitter->BufferSize(_cursor.draw_size.x, _cursor.draw_size.y));
1403 /* Make backup of stuff below cursor */
1404 blitter->CopyToBuffer(blitter->MoveTo(_screen.dst_ptr, _cursor.draw_pos.x, _cursor.draw_pos.y), buffer, _cursor.draw_size.x, _cursor.draw_size.y);
1406 /* Draw cursor on screen */
1407 _cur_dpi = &_screen;
1408 for (uint i = 0; i < _cursor.sprite_count; ++i) {
1409 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);
1412 VideoDriver::GetInstance()->MakeDirty(_cursor.draw_pos.x, _cursor.draw_pos.y, _cursor.draw_size.x, _cursor.draw_size.y);
1414 _cursor.visible = true;
1415 _cursor.dirty = false;
1418 void RedrawScreenRect(int left, int top, int right, int bottom)
1420 assert(right <= _screen.width && bottom <= _screen.height);
1421 if (_cursor.visible) {
1422 if (right > _cursor.draw_pos.x &&
1423 left < _cursor.draw_pos.x + _cursor.draw_size.x &&
1424 bottom > _cursor.draw_pos.y &&
1425 top < _cursor.draw_pos.y + _cursor.draw_size.y) {
1426 UndrawMouseCursor();
1430 if (_networking) NetworkUndrawChatMessage();
1432 DrawOverlappedWindowForAll(left, top, right, bottom);
1434 VideoDriver::GetInstance()->MakeDirty(left, top, right - left, bottom - top);
1438 * Repaints the rectangle blocks which are marked as 'dirty'.
1440 * @see SetDirtyBlocks
1442 void DrawDirtyBlocks()
1444 byte *b = _dirty_blocks;
1445 const int w = Align(_screen.width, DIRTY_BLOCK_WIDTH);
1446 const int h = Align(_screen.height, DIRTY_BLOCK_HEIGHT);
1447 int x;
1448 int y;
1450 if (HasModalProgress()) {
1451 /* We are generating the world, so release our rights to the map and
1452 * painting while we are waiting a bit. */
1453 _modal_progress_paint_mutex.unlock();
1454 _modal_progress_work_mutex.unlock();
1456 /* Wait a while and update _realtime_tick so we are given the rights */
1457 if (!IsFirstModalProgressLoop()) CSleep(MODAL_PROGRESS_REDRAW_TIMEOUT);
1458 _realtime_tick += MODAL_PROGRESS_REDRAW_TIMEOUT;
1460 /* Modal progress thread may need blitter access while we are waiting for it. */
1461 VideoDriver::GetInstance()->ReleaseBlitterLock();
1462 _modal_progress_paint_mutex.lock();
1463 VideoDriver::GetInstance()->AcquireBlitterLock();
1464 _modal_progress_work_mutex.lock();
1466 /* When we ended with the modal progress, do not draw the blocks.
1467 * Simply let the next run do so, otherwise we would be loading
1468 * the new state (and possibly change the blitter) when we hold
1469 * the drawing lock, which we must not do. */
1470 if (_switch_mode != SM_NONE && !HasModalProgress()) return;
1473 y = 0;
1474 do {
1475 x = 0;
1476 do {
1477 if (*b != 0) {
1478 int left;
1479 int top;
1480 int right = x + DIRTY_BLOCK_WIDTH;
1481 int bottom = y;
1482 byte *p = b;
1483 int h2;
1485 /* First try coalescing downwards */
1486 do {
1487 *p = 0;
1488 p += _dirty_bytes_per_line;
1489 bottom += DIRTY_BLOCK_HEIGHT;
1490 } while (bottom != h && *p != 0);
1492 /* Try coalescing to the right too. */
1493 h2 = (bottom - y) / DIRTY_BLOCK_HEIGHT;
1494 assert(h2 > 0);
1495 p = b;
1497 while (right != w) {
1498 byte *p2 = ++p;
1499 int h = h2;
1500 /* Check if a full line of dirty flags is set. */
1501 do {
1502 if (!*p2) goto no_more_coalesc;
1503 p2 += _dirty_bytes_per_line;
1504 } while (--h != 0);
1506 /* Wohoo, can combine it one step to the right!
1507 * Do that, and clear the bits. */
1508 right += DIRTY_BLOCK_WIDTH;
1510 h = h2;
1511 p2 = p;
1512 do {
1513 *p2 = 0;
1514 p2 += _dirty_bytes_per_line;
1515 } while (--h != 0);
1517 no_more_coalesc:
1519 left = x;
1520 top = y;
1522 if (left < _invalid_rect.left ) left = _invalid_rect.left;
1523 if (top < _invalid_rect.top ) top = _invalid_rect.top;
1524 if (right > _invalid_rect.right ) right = _invalid_rect.right;
1525 if (bottom > _invalid_rect.bottom) bottom = _invalid_rect.bottom;
1527 if (left < right && top < bottom) {
1528 RedrawScreenRect(left, top, right, bottom);
1532 } while (b++, (x += DIRTY_BLOCK_WIDTH) != w);
1533 } while (b += -(int)(w / DIRTY_BLOCK_WIDTH) + _dirty_bytes_per_line, (y += DIRTY_BLOCK_HEIGHT) != h);
1535 ++_dirty_block_colour;
1536 _invalid_rect.left = w;
1537 _invalid_rect.top = h;
1538 _invalid_rect.right = 0;
1539 _invalid_rect.bottom = 0;
1543 * This function extends the internal _invalid_rect rectangle as it
1544 * now contains the rectangle defined by the given parameters. Note
1545 * the point (0,0) is top left.
1547 * @param left The left edge of the rectangle
1548 * @param top The top edge of the rectangle
1549 * @param right The right edge of the rectangle
1550 * @param bottom The bottom edge of the rectangle
1551 * @see DrawDirtyBlocks
1553 * @todo The name of the function should be called like @c AddDirtyBlock as
1554 * it neither set a dirty rect nor add several dirty rects although
1555 * the function name is in plural. (Progman)
1557 void SetDirtyBlocks(int left, int top, int right, int bottom)
1559 byte *b;
1560 int width;
1561 int height;
1563 if (left < 0) left = 0;
1564 if (top < 0) top = 0;
1565 if (right > _screen.width) right = _screen.width;
1566 if (bottom > _screen.height) bottom = _screen.height;
1568 if (left >= right || top >= bottom) return;
1570 if (left < _invalid_rect.left ) _invalid_rect.left = left;
1571 if (top < _invalid_rect.top ) _invalid_rect.top = top;
1572 if (right > _invalid_rect.right ) _invalid_rect.right = right;
1573 if (bottom > _invalid_rect.bottom) _invalid_rect.bottom = bottom;
1575 left /= DIRTY_BLOCK_WIDTH;
1576 top /= DIRTY_BLOCK_HEIGHT;
1578 b = _dirty_blocks + top * _dirty_bytes_per_line + left;
1580 width = ((right - 1) / DIRTY_BLOCK_WIDTH) - left + 1;
1581 height = ((bottom - 1) / DIRTY_BLOCK_HEIGHT) - top + 1;
1583 assert(width > 0 && height > 0);
1585 do {
1586 int i = width;
1588 do b[--i] = 0xFF; while (i != 0);
1590 b += _dirty_bytes_per_line;
1591 } while (--height != 0);
1595 * This function mark the whole screen as dirty. This results in repainting
1596 * the whole screen. Use this with care as this function will break the
1597 * idea about marking only parts of the screen as 'dirty'.
1598 * @ingroup dirty
1600 void MarkWholeScreenDirty()
1602 SetDirtyBlocks(0, 0, _screen.width, _screen.height);
1606 * Set up a clipping area for only drawing into a certain area. To do this,
1607 * Fill a DrawPixelInfo object with the supplied relative rectangle, backup
1608 * the original (calling) _cur_dpi and assign the just returned DrawPixelInfo
1609 * _cur_dpi. When you are done, give restore _cur_dpi's original value
1610 * @param *n the DrawPixelInfo that will be the clipping rectangle box allowed
1611 * for drawing
1612 * @param left,top,width,height the relative coordinates of the clipping
1613 * rectangle relative to the current _cur_dpi. This will most likely be the
1614 * offset from the calling window coordinates
1615 * @return return false if the requested rectangle is not possible with the
1616 * current dpi pointer. Only continue of the return value is true, or you'll
1617 * get some nasty results
1619 bool FillDrawPixelInfo(DrawPixelInfo *n, int left, int top, int width, int height)
1621 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
1622 const DrawPixelInfo *o = _cur_dpi;
1624 n->zoom = ZOOM_LVL_NORMAL;
1626 assert(width > 0);
1627 assert(height > 0);
1629 if ((left -= o->left) < 0) {
1630 width += left;
1631 if (width <= 0) return false;
1632 n->left = -left;
1633 left = 0;
1634 } else {
1635 n->left = 0;
1638 if (width > o->width - left) {
1639 width = o->width - left;
1640 if (width <= 0) return false;
1642 n->width = width;
1644 if ((top -= o->top) < 0) {
1645 height += top;
1646 if (height <= 0) return false;
1647 n->top = -top;
1648 top = 0;
1649 } else {
1650 n->top = 0;
1653 n->dst_ptr = blitter->MoveTo(o->dst_ptr, left, top);
1654 n->pitch = o->pitch;
1656 if (height > o->height - top) {
1657 height = o->height - top;
1658 if (height <= 0) return false;
1660 n->height = height;
1662 return true;
1666 * Update cursor dimension.
1667 * Called when changing cursor sprite resp. reloading grfs.
1669 void UpdateCursorSize()
1671 /* Ignore setting any cursor before the sprites are loaded. */
1672 if (GetMaxSpriteID() == 0) return;
1674 assert_compile(lengthof(_cursor.sprite_seq) == lengthof(_cursor.sprite_pos));
1675 assert(_cursor.sprite_count <= lengthof(_cursor.sprite_seq));
1676 for (uint i = 0; i < _cursor.sprite_count; ++i) {
1677 const Sprite *p = GetSprite(GB(_cursor.sprite_seq[i].sprite, 0, SPRITE_WIDTH), ST_NORMAL);
1678 Point offs, size;
1679 offs.x = UnScaleGUI(p->x_offs) + _cursor.sprite_pos[i].x;
1680 offs.y = UnScaleGUI(p->y_offs) + _cursor.sprite_pos[i].y;
1681 size.x = UnScaleGUI(p->width);
1682 size.y = UnScaleGUI(p->height);
1684 if (i == 0) {
1685 _cursor.total_offs = offs;
1686 _cursor.total_size = size;
1687 } else {
1688 int right = max(_cursor.total_offs.x + _cursor.total_size.x, offs.x + size.x);
1689 int bottom = max(_cursor.total_offs.y + _cursor.total_size.y, offs.y + size.y);
1690 if (offs.x < _cursor.total_offs.x) _cursor.total_offs.x = offs.x;
1691 if (offs.y < _cursor.total_offs.y) _cursor.total_offs.y = offs.y;
1692 _cursor.total_size.x = right - _cursor.total_offs.x;
1693 _cursor.total_size.y = bottom - _cursor.total_offs.y;
1697 _cursor.dirty = true;
1701 * Switch cursor to different sprite.
1702 * @param cursor Sprite to draw for the cursor.
1703 * @param pal Palette to use for recolouring.
1705 static void SetCursorSprite(CursorID cursor, PaletteID pal)
1707 if (_cursor.sprite_count == 1 && _cursor.sprite_seq[0].sprite == cursor && _cursor.sprite_seq[0].pal == pal) return;
1709 _cursor.sprite_count = 1;
1710 _cursor.sprite_seq[0].sprite = cursor;
1711 _cursor.sprite_seq[0].pal = pal;
1712 _cursor.sprite_pos[0].x = 0;
1713 _cursor.sprite_pos[0].y = 0;
1715 UpdateCursorSize();
1718 static void SwitchAnimatedCursor()
1720 const AnimCursor *cur = _cursor.animate_cur;
1722 if (cur == nullptr || cur->sprite == AnimCursor::LAST) cur = _cursor.animate_list;
1724 SetCursorSprite(cur->sprite, _cursor.sprite_seq[0].pal);
1726 _cursor.animate_timeout = cur->display_time;
1727 _cursor.animate_cur = cur + 1;
1730 void CursorTick()
1732 if (_cursor.animate_timeout != 0 && --_cursor.animate_timeout == 0) {
1733 SwitchAnimatedCursor();
1738 * Set or unset the ZZZ cursor.
1739 * @param busy Whether to show the ZZZ cursor.
1741 void SetMouseCursorBusy(bool busy)
1743 if (busy) {
1744 if (_cursor.sprite_seq[0].sprite == SPR_CURSOR_MOUSE) SetMouseCursor(SPR_CURSOR_ZZZ, PAL_NONE);
1745 } else {
1746 if (_cursor.sprite_seq[0].sprite == SPR_CURSOR_ZZZ) SetMouseCursor(SPR_CURSOR_MOUSE, PAL_NONE);
1751 * Assign a single non-animated sprite to the cursor.
1752 * @param sprite Sprite to draw for the cursor.
1753 * @param pal Palette to use for recolouring.
1754 * @see SetAnimatedMouseCursor
1756 void SetMouseCursor(CursorID sprite, PaletteID pal)
1758 /* Turn off animation */
1759 _cursor.animate_timeout = 0;
1760 /* Set cursor */
1761 SetCursorSprite(sprite, pal);
1765 * Assign an animation to the cursor.
1766 * @param table Array of animation states.
1767 * @see SetMouseCursor
1769 void SetAnimatedMouseCursor(const AnimCursor *table)
1771 _cursor.animate_list = table;
1772 _cursor.animate_cur = nullptr;
1773 _cursor.sprite_seq[0].pal = PAL_NONE;
1774 SwitchAnimatedCursor();
1778 * Update cursor position on mouse movement.
1779 * @param x New X position.
1780 * @param y New Y position.
1781 * @param queued_warp True, if the OS queues mouse warps after pending mouse movement events.
1782 * False, if the warp applies instantaneous.
1783 * @return true, if the OS cursor position should be warped back to this->pos.
1785 bool CursorVars::UpdateCursorPosition(int x, int y, bool queued_warp)
1787 /* Detecting relative mouse movement is somewhat tricky.
1788 * - There may be multiple mouse move events in the video driver queue (esp. when OpenTTD lags a bit).
1789 * - When we request warping the mouse position (return true), a mouse move event is appended at the end of the queue.
1791 * So, when this->fix_at is active, we use the following strategy:
1792 * - The first movement triggers the warp to reset the mouse position.
1793 * - Subsequent events have to compute movement relative to the previous event.
1794 * - The relative movement is finished, when we receive the event matching the warp.
1797 if (x == this->pos.x && y == this->pos.y) {
1798 /* Warp finished. */
1799 this->queued_warp = false;
1802 this->delta.x = x - (this->queued_warp ? this->last_position.x : this->pos.x);
1803 this->delta.y = y - (this->queued_warp ? this->last_position.y : this->pos.y);
1805 this->last_position.x = x;
1806 this->last_position.y = y;
1808 bool need_warp = false;
1809 if (this->fix_at) {
1810 if (this->delta.x != 0 || this->delta.y != 0) {
1811 /* Trigger warp.
1812 * Note: We also trigger warping again, if there is already a pending warp.
1813 * This makes it more tolerant about the OS or other software in between
1814 * botchering the warp. */
1815 this->queued_warp = queued_warp;
1816 need_warp = true;
1818 } else if (this->pos.x != x || this->pos.y != y) {
1819 this->queued_warp = false; // Cancel warping, we are no longer confining the position.
1820 this->dirty = true;
1821 this->pos.x = x;
1822 this->pos.y = y;
1824 return need_warp;
1827 bool ChangeResInGame(int width, int height)
1829 return (_screen.width == width && _screen.height == height) || VideoDriver::GetInstance()->ChangeResolution(width, height);
1832 bool ToggleFullScreen(bool fs)
1834 bool result = VideoDriver::GetInstance()->ToggleFullscreen(fs);
1835 if (_fullscreen != fs && _resolutions.empty()) {
1836 DEBUG(driver, 0, "Could not find a suitable fullscreen resolution");
1838 return result;
1841 void SortResolutions()
1843 std::sort(_resolutions.begin(), _resolutions.end());