Make it possible to stop road vehicles from slowing down in curves so "diagonal"...
[openttd-joker.git] / src / gfx.cpp
blobd97ce1b43daba529f4f8638ffd73a713fdbe67d1
1 /* $Id$ */
3 /*
4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 */
10 /** @file gfx.cpp Handling of drawing text and other gfx related stuff. */
12 #include "stdafx.h"
13 #include "gfx_layout.h"
14 #include "progress.h"
15 #include "zoom_func.h"
16 #include "blitter/factory.hpp"
17 #include "video/video_driver.hpp"
18 #include "strings_func.h"
19 #include "settings_type.h"
20 #include "network/network.h"
21 #include "network/network_func.h"
22 #include "window_func.h"
23 #include "newgrf_debug.h"
25 #include "table/palettes.h"
26 #include "table/string_colours.h"
27 #include "table/sprites.h"
28 #include "table/control_codes.h"
30 #include "safeguards.h"
32 byte _dirkeys; ///< 1 = left, 2 = up, 4 = right, 8 = down
33 bool _fullscreen;
34 byte _support8bpp;
35 CursorVars _cursor;
36 bool _ctrl_pressed; ///< Is Ctrl pressed?
37 bool _shift_pressed; ///< Is Shift pressed?
38 byte _fast_forward;
39 bool _left_button_down; ///< Is left mouse button pressed?
40 bool _left_button_clicked; ///< Is left mouse button clicked?
41 bool _right_button_down; ///< Is right mouse button pressed?
42 bool _right_button_clicked; ///< Is right mouse button clicked?
43 DrawPixelInfo _screen;
44 bool _screen_disable_anim = false; ///< Disable palette animation (important for 32bpp-anim blitter during giant screenshot)
45 bool _exit_game;
46 GameMode _game_mode;
47 SwitchMode _switch_mode; ///< The next mainloop command.
48 PauseModeByte _pause_mode;
49 Palette _cur_palette;
51 static byte _stringwidth_table[FS_END][224]; ///< Cache containing width of often used characters. @see GetCharacterWidth()
52 DrawPixelInfo *_cur_dpi;
53 byte _colour_gradient[COLOUR_END][8];
55 static void GfxMainBlitterViewport(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub = nullptr, SpriteID sprite_id = SPR_CURSOR_MOUSE);
56 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);
58 static ReusableBuffer<uint8> _cursor_backup;
60 ZoomLevelByte _gui_zoom; ///< GUI 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 #ifdef ENABLE_NETWORK
89 if (_networking) NetworkUndrawChatMessage();
90 #endif /* ENABLE_NETWORK */
92 blitter->ScrollBuffer(_screen.dst_ptr, left, top, width, height, xo, yo);
93 /* This part of the screen is now dirty. */
94 VideoDriver::GetInstance()->MakeDirty(left, top, width, height);
98 /**
99 * Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
101 * @pre dpi->zoom == ZOOM_LVL_NORMAL, right >= left, bottom >= top
102 * @param left Minimum X (inclusive)
103 * @param top Minimum Y (inclusive)
104 * @param right Maximum X (inclusive)
105 * @param bottom Maximum Y (inclusive)
106 * @param colour A 8 bit palette index (FILLRECT_OPAQUE and FILLRECT_CHECKER) or a recolour spritenumber (FILLRECT_RECOLOUR)
107 * @param mode
108 * FILLRECT_OPAQUE: Fill the rectangle with the specified colour
109 * FILLRECT_CHECKER: Like FILLRECT_OPAQUE, but only draw every second pixel (used to grey out things)
110 * FILLRECT_RECOLOUR: Apply a recolour sprite to every pixel in the rectangle currently on screen
112 void GfxFillRect(int left, int top, int right, int bottom, int colour, FillRectMode mode)
114 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
115 const DrawPixelInfo *dpi = _cur_dpi;
116 void *dst;
117 const int otop = top;
118 const int oleft = left;
120 if (dpi->zoom != ZOOM_LVL_NORMAL) return;
121 if (left > right || top > bottom) return;
122 if (right < dpi->left || left >= dpi->left + dpi->width) return;
123 if (bottom < dpi->top || top >= dpi->top + dpi->height) return;
125 if ( (left -= dpi->left) < 0) left = 0;
126 right = right - dpi->left + 1;
127 if (right > dpi->width) right = dpi->width;
128 right -= left;
129 assert(right > 0);
131 if ( (top -= dpi->top) < 0) top = 0;
132 bottom = bottom - dpi->top + 1;
133 if (bottom > dpi->height) bottom = dpi->height;
134 bottom -= top;
135 assert(bottom > 0);
137 dst = blitter->MoveTo(dpi->dst_ptr, left, top);
139 switch (mode) {
140 default: // FILLRECT_OPAQUE
141 blitter->DrawRect(dst, right, bottom, (uint8)colour);
142 break;
144 case FILLRECT_RECOLOUR:
145 blitter->DrawColourMappingRect(dst, right, bottom, GB(colour, 0, PALETTE_WIDTH));
146 break;
148 case FILLRECT_CHECKER: {
149 byte bo = (oleft - left + dpi->left + otop - top + dpi->top) & 1;
150 do {
151 for (int i = (bo ^= 1); i < right; i += 2) blitter->SetPixel(dst, i, 0, (uint8)colour);
152 dst = blitter->MoveTo(dst, 0, 1);
153 } while (--bottom > 0);
154 break;
160 * Check line clipping by using a linear equation and draw the visible part of
161 * the line given by x/y and x2/y2.
162 * @param video Destination pointer to draw into.
163 * @param x X coordinate of first point.
164 * @param y Y coordinate of first point.
165 * @param x2 X coordinate of second point.
166 * @param y2 Y coordinate of second point.
167 * @param screen_width With of the screen to check clipping against.
168 * @param screen_height Height of the screen to check clipping against.
169 * @param colour Colour of the line.
170 * @param width Width of the line.
171 * @param dash Length of dashes for dashed lines. 0 means solid line.
173 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)
175 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
177 assert(width > 0);
179 if (y2 == y || x2 == x) {
180 /* Special case: horizontal/vertical line. All checks already done in GfxPreprocessLine. */
181 blitter->DrawLine(video, x, y, x2, y2, screen_width, screen_height, colour, width, dash);
182 return;
185 int grade_y = y2 - y;
186 int grade_x = x2 - x;
188 /* Clipping rectangle. Slightly extended so we can ignore the width of the line. */
189 int extra = (int)CeilDiv(3 * width, 4); // not less then "width * sqrt(2) / 2"
190 Rect clip = { -extra, -extra, screen_width - 1 + extra, screen_height - 1 + extra };
192 /* prevent integer overflows. */
193 int margin = 1;
194 while (INT_MAX / abs(grade_y) < max(abs(clip.left - x), abs(clip.right - x))) {
195 grade_y /= 2;
196 grade_x /= 2;
197 margin *= 2; // account for rounding errors
200 /* Imagine that the line is infinitely long and it intersects with
201 * infinitely long left and right edges of the clipping rectangle.
202 * If both intersection points are outside the clipping rectangle
203 * and both on the same side of it, we don't need to draw anything. */
204 int left_isec_y = y + (clip.left - x) * grade_y / grade_x;
205 int right_isec_y = y + (clip.right - x) * grade_y / grade_x;
206 if ((left_isec_y > clip.bottom + margin && right_isec_y > clip.bottom + margin) ||
207 (left_isec_y < clip.top - margin && right_isec_y < clip.top - margin)) {
208 return;
211 /* It is possible to use the line equation to further reduce the amount of
212 * work the blitter has to do by shortening the effective line segment.
213 * However, in order to get that right and prevent the flickering effects
214 * of rounding errors so much additional code has to be run here that in
215 * the general case the effect is not noticable. */
217 blitter->DrawLine(video, x, y, x2, y2, screen_width, screen_height, colour, width, dash);
221 * Align parameters of a line to the given DPI and check simple clipping.
222 * @param dpi Screen parameters to align with.
223 * @param x X coordinate of first point.
224 * @param y Y coordinate of first point.
225 * @param x2 X coordinate of second point.
226 * @param y2 Y coordinate of second point.
227 * @param width Width of the line.
228 * @return True if the line is likely to be visible, false if it's certainly
229 * invisible.
231 static inline bool GfxPreprocessLine(DrawPixelInfo *dpi, int &x, int &y, int &x2, int &y2, int width)
233 x -= dpi->left;
234 x2 -= dpi->left;
235 y -= dpi->top;
236 y2 -= dpi->top;
238 /* Check simple clipping */
239 if (x + width / 2 < 0 && x2 + width / 2 < 0 ) return false;
240 if (y + width / 2 < 0 && y2 + width / 2 < 0 ) return false;
241 if (x - width / 2 > dpi->width && x2 - width / 2 > dpi->width ) return false;
242 if (y - width / 2 > dpi->height && y2 - width / 2 > dpi->height) return false;
243 return true;
246 void GfxDrawLine(int x, int y, int x2, int y2, int colour, int width, int dash)
248 DrawPixelInfo *dpi = _cur_dpi;
249 if (GfxPreprocessLine(dpi, x, y, x2, y2, width)) {
250 GfxDoDrawLine(dpi->dst_ptr, x, y, x2, y2, dpi->width, dpi->height, colour, width, dash);
254 void GfxDrawLineUnscaled(int x, int y, int x2, int y2, int colour)
256 DrawPixelInfo *dpi = _cur_dpi;
257 if (GfxPreprocessLine(dpi, x, y, x2, y2, 1)) {
258 GfxDoDrawLine(dpi->dst_ptr,
259 UnScaleByZoom(x, dpi->zoom), UnScaleByZoom(y, dpi->zoom),
260 UnScaleByZoom(x2, dpi->zoom), UnScaleByZoom(y2, dpi->zoom),
261 UnScaleByZoom(dpi->width, dpi->zoom), UnScaleByZoom(dpi->height, dpi->zoom), colour, 1);
266 * Draws the projection of a parallelepiped.
267 * This can be used to draw boxes in world coordinates.
269 * @param x Screen X-coordinate of top front corner.
270 * @param y Screen Y-coordinate of top front corner.
271 * @param dx1 Screen X-length of first edge.
272 * @param dy1 Screen Y-length of first edge.
273 * @param dx2 Screen X-length of second edge.
274 * @param dy2 Screen Y-length of second edge.
275 * @param dx3 Screen X-length of third edge.
276 * @param dy3 Screen Y-length of third edge.
278 void DrawBox(int x, int y, int dx1, int dy1, int dx2, int dy2, int dx3, int dy3)
280 /* ....
281 * .. ....
282 * .. ....
283 * .. ^
284 * <--__(dx1,dy1) /(dx2,dy2)
285 * : --__ / :
286 * : --__ / :
287 * : *(x,y) :
288 * : | :
289 * : | ..
290 * .... |(dx3,dy3)
291 * .... | ..
292 * ....V.
295 static const byte colour = PC_WHITE;
297 GfxDrawLineUnscaled(x, y, x + dx1, y + dy1, colour);
298 GfxDrawLineUnscaled(x, y, x + dx2, y + dy2, colour);
299 GfxDrawLineUnscaled(x, y, x + dx3, y + dy3, colour);
301 GfxDrawLineUnscaled(x + dx1, y + dy1, x + dx1 + dx2, y + dy1 + dy2, colour);
302 GfxDrawLineUnscaled(x + dx1, y + dy1, x + dx1 + dx3, y + dy1 + dy3, colour);
303 GfxDrawLineUnscaled(x + dx2, y + dy2, x + dx2 + dx1, y + dy2 + dy1, colour);
304 GfxDrawLineUnscaled(x + dx2, y + dy2, x + dx2 + dx3, y + dy2 + dy3, colour);
305 GfxDrawLineUnscaled(x + dx3, y + dy3, x + dx3 + dx1, y + dy3 + dy1, colour);
306 GfxDrawLineUnscaled(x + dx3, y + dy3, x + dx3 + dx2, y + dy3 + dy2, colour);
310 * Set the colour remap to be for the given colour.
311 * @param colour the new colour of the remap.
313 static void SetColourRemap(TextColour colour)
315 if (colour == TC_INVALID) return;
317 /* Black strings have no shading ever; the shading is black, so it
318 * would be invisible at best, but it actually makes it illegible. */
319 bool no_shade = (colour & TC_NO_SHADE) != 0 || colour == TC_BLACK;
320 bool raw_colour = (colour & TC_IS_PALETTE_COLOUR) != 0;
321 colour &= ~(TC_NO_SHADE | TC_IS_PALETTE_COLOUR);
323 _string_colourremap[1] = raw_colour ? (byte)colour : _string_colourmap[colour];
324 _string_colourremap[2] = no_shade ? 0 : 1;
325 _colour_remap_ptr = _string_colourremap;
329 * Drawing routine for drawing a laid out line of text.
330 * @param line String to draw.
331 * @param y The top most position to draw on.
332 * @param left The left most position to draw on.
333 * @param right The right most position to draw on.
334 * @param align The alignment of the string when drawing left-to-right. In the
335 * case a right-to-left language is chosen this is inverted so it
336 * will be drawn in the right direction.
337 * @param underline Whether to underline what has been drawn or not.
338 * @param truncation Whether to perform string truncation or not.
340 * @return In case of left or center alignment the right most pixel we have drawn to.
341 * In case of right alignment the left most pixel we have drawn to.
343 static int DrawLayoutLine(const ParagraphLayouter::Line *line, int y, int left, int right, StringAlignment align, bool underline, bool truncation)
345 if (line->CountRuns() == 0) return 0;
347 int w = line->GetWidth();
348 int h = line->GetLeading();
351 * The following is needed for truncation.
352 * Depending on the text direction, we either remove bits at the rear
353 * or the front. For this we shift the entire area to draw so it fits
354 * within the left/right bounds and the side we do not truncate it on.
355 * Then we determine the truncation location, i.e. glyphs that fall
356 * outside of the range min_x - max_x will not be drawn; they are thus
357 * the truncated glyphs.
359 * At a later step we insert the dots.
362 int max_w = right - left + 1; // The maximum width.
364 int offset_x = 0; // The offset we need for positioning the glyphs
365 int min_x = left; // The minimum x position to draw normal glyphs on.
366 int max_x = right; // The maximum x position to draw normal glyphs on.
368 truncation &= max_w < w; // Whether we need to do truncation.
369 int dot_width = 0; // Cache for the width of the dot.
370 const Sprite *dot_sprite = nullptr; // Cache for the sprite of the dot.
372 if (truncation) {
374 * Assumption may be made that all fonts of a run are of the same size.
375 * In any case, we'll use these dots for the abbreviation, so even if
376 * another size would be chosen it won't have truncated too little for
377 * the truncation dots.
379 FontCache *fc = ((const Font*)line->GetVisualRun(0)->GetFont())->fc;
380 GlyphID dot_glyph = fc->MapCharToGlyph('.');
381 dot_width = fc->GetGlyphWidth(dot_glyph);
382 dot_sprite = fc->GetGlyph(dot_glyph);
384 if (_current_text_dir == TD_RTL) {
385 min_x += 3 * dot_width;
386 offset_x = w - 3 * dot_width - max_w;
387 } else {
388 max_x -= 3 * dot_width;
391 w = max_w;
394 /* In case we have a RTL language we swap the alignment. */
395 if (!(align & SA_FORCE) && _current_text_dir == TD_RTL && (align & SA_HOR_MASK) != SA_HOR_CENTER) align ^= SA_RIGHT;
397 /* right is the right most position to draw on. In this case we want to do
398 * calculations with the width of the string. In comparison right can be
399 * seen as lastof(todraw) and width as lengthof(todraw). They differ by 1.
400 * So most +1/-1 additions are to move from lengthof to 'indices'.
402 switch (align & SA_HOR_MASK) {
403 case SA_LEFT:
404 /* right + 1 = left + w */
405 right = left + w - 1;
406 break;
408 case SA_HOR_CENTER:
409 left = RoundDivSU(right + 1 + left - w, 2);
410 /* right + 1 = left + w */
411 right = left + w - 1;
412 break;
414 case SA_RIGHT:
415 left = right + 1 - w;
416 break;
418 default:
419 NOT_REACHED();
422 TextColour colour = TC_BLACK;
423 bool draw_shadow = false;
424 for (int run_index = 0; run_index < line->CountRuns(); run_index++) {
425 const ParagraphLayouter::VisualRun *run = line->GetVisualRun(run_index);
426 const Font *f = (const Font*)run->GetFont();
428 FontCache *fc = f->fc;
429 colour = f->colour;
430 SetColourRemap(colour);
432 DrawPixelInfo *dpi = _cur_dpi;
433 int dpi_left = dpi->left;
434 int dpi_right = dpi->left + dpi->width - 1;
436 draw_shadow = fc->GetDrawGlyphShadow() && (colour & TC_NO_SHADE) == 0 && colour != TC_BLACK;
438 for (int i = 0; i < run->GetGlyphCount(); i++) {
439 GlyphID glyph = run->GetGlyphs()[i];
441 /* Not a valid glyph (empty) */
442 if (glyph == 0xFFFF) continue;
444 int begin_x = (int)run->GetPositions()[i * 2] + left - offset_x;
445 int end_x = (int)run->GetPositions()[i * 2 + 2] + left - offset_x - 1;
446 int top = (int)run->GetPositions()[i * 2 + 1] + y;
448 /* Truncated away. */
449 if (truncation && (begin_x < min_x || end_x > max_x)) continue;
451 const Sprite *sprite = fc->GetGlyph(glyph);
452 /* Check clipping (the "+ 1" is for the shadow). */
453 if (begin_x + sprite->x_offs > dpi_right || begin_x + sprite->x_offs + sprite->width /* - 1 + 1 */ < dpi_left) continue;
455 if (draw_shadow && (glyph & SPRITE_GLYPH) == 0) {
456 SetColourRemap(TC_BLACK);
457 GfxMainBlitter(sprite, begin_x + 1, top + 1, BM_COLOUR_REMAP);
458 SetColourRemap(colour);
460 GfxMainBlitter(sprite, begin_x, top, BM_COLOUR_REMAP);
464 if (truncation) {
465 int x = (_current_text_dir == TD_RTL) ? left : (right - 3 * dot_width);
466 for (int i = 0; i < 3; i++, x += dot_width) {
467 if (draw_shadow) {
468 SetColourRemap(TC_BLACK);
469 GfxMainBlitter(dot_sprite, x + 1, y + 1, BM_COLOUR_REMAP);
470 SetColourRemap(colour);
472 GfxMainBlitter(dot_sprite, x, y, BM_COLOUR_REMAP);
476 if (underline) {
477 GfxFillRect(left, y + h, right, y + h, _string_colourremap[1]);
480 return (align & SA_HOR_MASK) == SA_RIGHT ? left : right;
484 * Draw string, possibly truncated to make it fit in its allocated space
486 * @param left The left most position to draw on.
487 * @param right The right most position to draw on.
488 * @param top The top most position to draw on.
489 * @param str String to draw.
490 * @param colour Colour used for drawing the string, see DoDrawString() for details
491 * @param align The alignment of the string when drawing left-to-right. In the
492 * case a right-to-left language is chosen this is inverted so it
493 * will be drawn in the right direction.
494 * @param underline Whether to underline what has been drawn or not.
495 * @param fontsize The size of the initial characters.
496 * @return In case of left or center alignment the right most pixel we have drawn to.
497 * In case of right alignment the left most pixel we have drawn to.
499 int DrawString(int left, int right, int top, const char *str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
501 /* The string may contain control chars to change the font, just use the biggest font for clipping. */
502 int max_height = max(max(FONT_HEIGHT_SMALL, FONT_HEIGHT_NORMAL), max(FONT_HEIGHT_LARGE, FONT_HEIGHT_MONO));
504 /* Funny glyphs may extent outside the usual bounds, so relax the clipping somewhat. */
505 int extra = max_height / 2;
507 if (_cur_dpi->top + _cur_dpi->height + extra < top || _cur_dpi->top > top + max_height + extra ||
508 _cur_dpi->left + _cur_dpi->width + extra < left || _cur_dpi->left > right + extra) {
509 return 0;
512 Layouter layout(str, INT32_MAX, colour, fontsize);
513 if (layout.Length() == 0) return 0;
515 return DrawLayoutLine(*layout.Begin(), top, left, right, align, underline, true);
519 * Draw string, possibly truncated to make it fit in its allocated space
521 * @param left The left most position to draw on.
522 * @param right The right most position to draw on.
523 * @param top The top most position to draw on.
524 * @param str String to draw.
525 * @param colour Colour used for drawing the string, see DoDrawString() for details
526 * @param align The alignment of the string when drawing left-to-right. In the
527 * case a right-to-left language is chosen this is inverted so it
528 * will be drawn in the right direction.
529 * @param underline Whether to underline what has been drawn or not.
530 * @param fontsize The size of the initial characters.
531 * @return In case of left or center alignment the right most pixel we have drawn to.
532 * In case of right alignment the left most pixel we have drawn to.
534 int DrawString(int left, int right, int top, StringID str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
536 char buffer[DRAW_STRING_BUFFER];
537 GetString(buffer, str, lastof(buffer));
538 return DrawString(left, right, top, buffer, colour, align, underline, fontsize);
542 * Calculates height of string (in pixels). The string is changed to a multiline string if needed.
543 * @param str string to check
544 * @param maxw maximum string width
545 * @return height of pixels of string when it is drawn
547 int GetStringHeight(const char *str, int maxw, FontSize fontsize)
549 Layouter layout(str, maxw, TC_FROMSTRING, fontsize);
550 return layout.GetBounds().height;
554 * Calculates height of string (in pixels). The string is changed to a multiline string if needed.
555 * @param str string to check
556 * @param maxw maximum string width
557 * @return height of pixels of string when it is drawn
559 int GetStringHeight(StringID str, int maxw)
561 char buffer[DRAW_STRING_BUFFER];
562 GetString(buffer, str, lastof(buffer));
563 return GetStringHeight(buffer, maxw);
567 * Calculates number of lines of string. The string is changed to a multiline string if needed.
568 * @param str string to check
569 * @param maxw maximum string width
570 * @return number of lines of string when it is drawn
572 int GetStringLineCount(StringID str, int maxw)
574 char buffer[DRAW_STRING_BUFFER];
575 GetString(buffer, str, lastof(buffer));
577 Layouter layout(buffer, maxw);
578 return layout.Length();
582 * Calculate string bounding box for multi-line strings.
583 * @param str String to check.
584 * @param suggestion Suggested bounding box.
585 * @return Bounding box for the multi-line string, may be bigger than \a suggestion.
587 Dimension GetStringMultiLineBoundingBox(StringID str, const Dimension &suggestion)
589 Dimension box = {suggestion.width, (uint)GetStringHeight(str, suggestion.width)};
590 return box;
594 * Calculate string bounding box for multi-line strings.
595 * @param str String to check.
596 * @param suggestion Suggested bounding box.
597 * @return Bounding box for the multi-line string, may be bigger than \a suggestion.
599 Dimension GetStringMultiLineBoundingBox(const char *str, const Dimension &suggestion)
601 Dimension box = {suggestion.width, (uint)GetStringHeight(str, suggestion.width)};
602 return box;
606 * Draw string, possibly over multiple lines.
608 * @param left The left most position to draw on.
609 * @param right The right most position to draw on.
610 * @param top The top most position to draw on.
611 * @param bottom The bottom most position to draw on.
612 * @param str String to draw.
613 * @param colour Colour used for drawing the string, see DoDrawString() for details
614 * @param align The horizontal and vertical alignment of the string.
615 * @param underline Whether to underline all strings
616 * @param fontsize The size of the initial characters.
618 * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written.
620 int DrawStringMultiLine(int left, int right, int top, int bottom, const char *str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
622 int maxw = right - left + 1;
623 int maxh = bottom - top + 1;
625 /* It makes no sense to even try if it can't be drawn anyway, or
626 * do we really want to support fonts of 0 or less pixels high? */
627 if (maxh <= 0) return top;
629 Layouter layout(str, maxw, colour, fontsize);
630 int total_height = layout.GetBounds().height;
631 int y;
632 switch (align & SA_VERT_MASK) {
633 case SA_TOP:
634 y = top;
635 break;
637 case SA_VERT_CENTER:
638 y = RoundDivSU(bottom + top - total_height, 2);
639 break;
641 case SA_BOTTOM:
642 y = bottom - total_height;
643 break;
645 default: NOT_REACHED();
648 int last_line = top;
649 int first_line = bottom;
651 for (const ParagraphLayouter::Line **iter = layout.Begin(); iter != layout.End(); iter++) {
652 const ParagraphLayouter::Line *line = *iter;
654 int line_height = line->GetLeading();
655 if (y >= top && y < bottom) {
656 last_line = y + line_height;
657 if (first_line > y) first_line = y;
659 DrawLayoutLine(line, y, left, right, align, underline, false);
661 y += line_height;
664 return ((align & SA_VERT_MASK) == SA_BOTTOM) ? first_line : last_line;
668 * Draw string, possibly over multiple lines.
670 * @param left The left most position to draw on.
671 * @param right The right most position to draw on.
672 * @param top The top most position to draw on.
673 * @param bottom The bottom most position to draw on.
674 * @param str String to draw.
675 * @param colour Colour used for drawing the string, see DoDrawString() for details
676 * @param align The horizontal and vertical alignment of the string.
677 * @param underline Whether to underline all strings
678 * @param fontsize The size of the initial characters.
680 * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written.
682 int DrawStringMultiLine(int left, int right, int top, int bottom, StringID str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
684 char buffer[DRAW_STRING_BUFFER];
685 GetString(buffer, str, lastof(buffer));
686 return DrawStringMultiLine(left, right, top, bottom, buffer, colour, align, underline, fontsize);
690 * Return the string dimension in pixels. The height and width are returned
691 * in a single Dimension value. TINYFONT, BIGFONT modifiers are only
692 * supported as the first character of the string. The returned dimensions
693 * are therefore a rough estimation correct for all the current strings
694 * but not every possible combination
695 * @param str string to calculate pixel-width
696 * @param start_fontsize Fontsize to start the text with
697 * @return string width and height in pixels
699 Dimension GetStringBoundingBox(const char *str, FontSize start_fontsize)
701 Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize);
702 return layout.GetBounds();
706 * Get bounding box of a string. Uses parameters set by #DParam if needed.
707 * Has the same restrictions as #GetStringBoundingBox(const char *str).
708 * @param strid String to examine.
709 * @return Width and height of the bounding box for the string in pixels.
711 Dimension GetStringBoundingBox(StringID strid)
713 char buffer[DRAW_STRING_BUFFER];
715 GetString(buffer, strid, lastof(buffer));
716 return GetStringBoundingBox(buffer);
720 * Get the leading corner of a character in a single-line string relative
721 * to the start of the string.
722 * @param str String containing the character.
723 * @param ch Pointer to the character in the string.
724 * @param start_fontsize Font size to start the text with.
725 * @return Upper left corner of the glyph associated with the character.
727 Point GetCharPosInString(const char *str, const char *ch, FontSize start_fontsize)
729 Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize);
730 return layout.GetCharPosition(ch);
734 * Get the character from a string that is drawn at a specific position.
735 * @param str String to test.
736 * @param x Position relative to the start of the string.
737 * @param start_fontsize Font size to start the text with.
738 * @return Pointer to the character at the position or nullptr if there is no character at the position.
740 const char *GetCharAtPosition(const char *str, int x, FontSize start_fontsize)
742 if (x < 0) return nullptr;
744 Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize);
745 return layout.GetCharAtPosition(x);
749 * Draw single character horizontally centered around (x,y)
750 * @param c Character (glyph) to draw
751 * @param x X position to draw character
752 * @param y Y position to draw character
753 * @param colour Colour to use, see DoDrawString() for details
755 void DrawCharCentered(WChar c, int x, int y, TextColour colour)
757 SetColourRemap(colour);
758 GfxMainBlitter(GetGlyph(FS_NORMAL, c), x - GetCharacterWidth(FS_NORMAL, c) / 2, y, BM_COLOUR_REMAP);
762 * Get the size of a sprite.
763 * @param sprid Sprite to examine.
764 * @param [out] offset Optionally returns the sprite position offset.
765 * @return Sprite size in pixels.
766 * @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.
768 Dimension GetSpriteSize(SpriteID sprid, Point *offset, ZoomLevel zoom)
770 const Sprite *sprite = GetSprite(sprid, ST_NORMAL);
772 if (offset != nullptr) {
773 offset->x = UnScaleByZoom(sprite->x_offs, zoom);
774 offset->y = UnScaleByZoom(sprite->y_offs, zoom);
777 Dimension d;
778 d.width = max<int>(0, UnScaleByZoom(sprite->x_offs + sprite->width, zoom));
779 d.height = max<int>(0, UnScaleByZoom(sprite->y_offs + sprite->height, zoom));
780 return d;
784 * Helper function to get the blitter mode for different types of palettes.
785 * @param pal The palette to get the blitter mode for.
786 * @return The blitter mode associated with the palette.
788 static BlitterMode GetBlitterMode(PaletteID pal)
790 switch (pal) {
791 case PAL_NONE: return BM_NORMAL;
792 case PALETTE_CRASH: return BM_CRASH_REMAP;
793 case PALETTE_ALL_BLACK: return BM_BLACK_REMAP;
794 default: return BM_COLOUR_REMAP;
799 * Draw a sprite in a viewport.
800 * @param img Image number to draw
801 * @param pal Palette to use.
802 * @param x Left coordinate of image in viewport, scaled by zoom
803 * @param y Top coordinate of image in viewport, scaled by zoom
804 * @param sub If available, draw only specified part of the sprite
806 void DrawSpriteViewport(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub)
808 SpriteID real_sprite = GB(img, 0, SPRITE_WIDTH);
809 if (HasBit(img, PALETTE_MODIFIER_TRANSPARENT)) {
810 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
811 GfxMainBlitterViewport(GetSprite(real_sprite, ST_NORMAL), x, y, BM_TRANSPARENT, sub, real_sprite);
812 } else if (pal != PAL_NONE) {
813 if (HasBit(pal, PALETTE_TEXT_RECOLOUR)) {
814 SetColourRemap((TextColour)GB(pal, 0, PALETTE_WIDTH));
815 } else {
816 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
818 GfxMainBlitterViewport(GetSprite(real_sprite, ST_NORMAL), x, y, GetBlitterMode(pal), sub, real_sprite);
819 } else {
820 GfxMainBlitterViewport(GetSprite(real_sprite, ST_NORMAL), x, y, BM_NORMAL, sub, real_sprite);
825 * Draw a sprite, not in a viewport
826 * @param img Image number to draw
827 * @param pal Palette to use.
828 * @param x Left coordinate of image in pixels
829 * @param y Top coordinate of image in pixels
830 * @param sub If available, draw only specified part of the sprite
831 * @param zoom Zoom level of sprite
833 void DrawSprite(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub, ZoomLevel zoom)
835 SpriteID real_sprite = GB(img, 0, SPRITE_WIDTH);
836 if (HasBit(img, PALETTE_MODIFIER_TRANSPARENT)) {
837 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
838 GfxMainBlitter(GetSprite(real_sprite, ST_NORMAL), x, y, BM_TRANSPARENT, sub, real_sprite, zoom);
839 } else if (pal != PAL_NONE) {
840 if (HasBit(pal, PALETTE_TEXT_RECOLOUR)) {
841 SetColourRemap((TextColour)GB(pal, 0, PALETTE_WIDTH));
842 } else {
843 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
845 GfxMainBlitter(GetSprite(real_sprite, ST_NORMAL), x, y, GetBlitterMode(pal), sub, real_sprite, zoom);
846 } else {
847 GfxMainBlitter(GetSprite(real_sprite, ST_NORMAL), x, y, BM_NORMAL, sub, real_sprite, zoom);
852 * The code for setting up the blitter mode and sprite information before finally drawing the sprite.
853 * @param sprite The sprite to draw.
854 * @param x The X location to draw.
855 * @param y The Y location to draw.
856 * @param mode The settings for the blitter to pass.
857 * @param sub Whether to only draw a sub set of the sprite.
858 * @param zoom The zoom level at which to draw the sprites.
859 * @tparam ZOOM_BASE The factor required to get the sub sprite information into the right size.
860 * @tparam SCALED_XY Whether the X and Y are scaled or unscaled.
862 template <int ZOOM_BASE, bool SCALED_XY>
863 static void GfxBlitter(const Sprite * const sprite, int x, int y, BlitterMode mode, const SubSprite * const sub, SpriteID sprite_id, ZoomLevel zoom)
865 const DrawPixelInfo *dpi = _cur_dpi;
866 Blitter::BlitterParams bp;
868 if (SCALED_XY) {
869 /* Scale it */
870 x = ScaleByZoom(x, zoom);
871 y = ScaleByZoom(y, zoom);
874 /* Move to the correct offset */
875 x += sprite->x_offs;
876 y += sprite->y_offs;
878 if (sub == nullptr) {
879 /* No clipping. */
880 bp.skip_left = 0;
881 bp.skip_top = 0;
882 bp.width = UnScaleByZoom(sprite->width, zoom);
883 bp.height = UnScaleByZoom(sprite->height, zoom);
884 } else {
885 /* Amount of pixels to clip from the source sprite */
886 int clip_left = max(0, -sprite->x_offs + sub->left * ZOOM_BASE );
887 int clip_top = max(0, -sprite->y_offs + sub->top * ZOOM_BASE );
888 int clip_right = max(0, sprite->width - (-sprite->x_offs + (sub->right + 1) * ZOOM_BASE));
889 int clip_bottom = max(0, sprite->height - (-sprite->y_offs + (sub->bottom + 1) * ZOOM_BASE));
891 if (clip_left + clip_right >= sprite->width) return;
892 if (clip_top + clip_bottom >= sprite->height) return;
894 bp.skip_left = UnScaleByZoomLower(clip_left, zoom);
895 bp.skip_top = UnScaleByZoomLower(clip_top, zoom);
896 bp.width = UnScaleByZoom(sprite->width - clip_left - clip_right, zoom);
897 bp.height = UnScaleByZoom(sprite->height - clip_top - clip_bottom, zoom);
899 x += ScaleByZoom(bp.skip_left, zoom);
900 y += ScaleByZoom(bp.skip_top, zoom);
903 /* Copy the main data directly from the sprite */
904 bp.sprite = sprite->data;
905 bp.sprite_width = sprite->width;
906 bp.sprite_height = sprite->height;
907 bp.top = 0;
908 bp.left = 0;
910 bp.dst = dpi->dst_ptr;
911 bp.pitch = dpi->pitch;
912 bp.remap = _colour_remap_ptr;
914 assert(sprite->width > 0);
915 assert(sprite->height > 0);
917 if (bp.width <= 0) return;
918 if (bp.height <= 0) return;
920 y -= SCALED_XY ? ScaleByZoom(dpi->top, zoom) : dpi->top;
921 int y_unscaled = UnScaleByZoom(y, zoom);
922 /* Check for top overflow */
923 if (y < 0) {
924 bp.height -= -y_unscaled;
925 if (bp.height <= 0) return;
926 bp.skip_top += -y_unscaled;
927 y = 0;
928 } else {
929 bp.top = y_unscaled;
932 /* Check for bottom overflow */
933 y += SCALED_XY ? ScaleByZoom(bp.height - dpi->height, zoom) : ScaleByZoom(bp.height, zoom) - dpi->height;
934 if (y > 0) {
935 bp.height -= UnScaleByZoom(y, zoom);
936 if (bp.height <= 0) return;
939 x -= SCALED_XY ? ScaleByZoom(dpi->left, zoom) : dpi->left;
940 int x_unscaled = UnScaleByZoom(x, zoom);
941 /* Check for left overflow */
942 if (x < 0) {
943 bp.width -= -x_unscaled;
944 if (bp.width <= 0) return;
945 bp.skip_left += -x_unscaled;
946 x = 0;
947 } else {
948 bp.left = x_unscaled;
951 /* Check for right overflow */
952 x += SCALED_XY ? ScaleByZoom(bp.width - dpi->width, zoom) : ScaleByZoom(bp.width, zoom) - dpi->width;
953 if (x > 0) {
954 bp.width -= UnScaleByZoom(x, zoom);
955 if (bp.width <= 0) return;
958 assert(bp.skip_left + bp.width <= UnScaleByZoom(sprite->width, zoom));
959 assert(bp.skip_top + bp.height <= UnScaleByZoom(sprite->height, zoom));
961 /* We do not want to catch the mouse. However we also use that spritenumber for unknown (text) sprites. */
962 if (_newgrf_debug_sprite_picker.mode == SPM_REDRAW && sprite_id != SPR_CURSOR_MOUSE) {
963 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
964 void *topleft = blitter->MoveTo(bp.dst, bp.left, bp.top);
965 void *bottomright = blitter->MoveTo(topleft, bp.width - 1, bp.height - 1);
967 void *clicked = _newgrf_debug_sprite_picker.clicked_pixel;
969 if (topleft <= clicked && clicked <= bottomright) {
970 uint offset = (((size_t)clicked - (size_t)topleft) / (blitter->GetScreenDepth() / 8)) % bp.pitch;
971 if (offset < (uint)bp.width) {
972 _newgrf_debug_sprite_picker.sprites.Include(sprite_id);
977 BlitterFactory::GetCurrentBlitter()->Draw(&bp, mode, zoom);
980 static void GfxMainBlitterViewport(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub, SpriteID sprite_id)
982 GfxBlitter<ZOOM_LVL_BASE, false>(sprite, x, y, mode, sub, sprite_id, _cur_dpi->zoom);
985 static void GfxMainBlitter(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub, SpriteID sprite_id, ZoomLevel zoom)
987 GfxBlitter<1, true>(sprite, x, y, mode, sub, sprite_id, zoom);
990 void DoPaletteAnimations();
992 void GfxInitPalettes()
994 memcpy(&_cur_palette, &_palette, sizeof(_cur_palette));
995 DoPaletteAnimations();
998 #define EXTR(p, q) (((uint16)(palette_animation_counter * (p)) * (q)) >> 16)
999 #define EXTR2(p, q) (((uint16)(~palette_animation_counter * (p)) * (q)) >> 16)
1001 void DoPaletteAnimations()
1003 /* Animation counter for the palette animation. */
1004 static int palette_animation_counter = 0;
1005 palette_animation_counter += 8;
1007 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
1008 const Colour *s;
1009 const ExtraPaletteValues *ev = &_extra_palette_values;
1010 Colour old_val[PALETTE_ANIM_SIZE];
1011 const uint old_tc = palette_animation_counter;
1012 uint i;
1013 uint j;
1015 if (blitter != nullptr && blitter->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_NONE) {
1016 palette_animation_counter = 0;
1019 Colour *palette_pos = &_cur_palette.palette[PALETTE_ANIM_START]; // Points to where animations are taking place on the palette
1020 /* Makes a copy of the current animation palette in old_val,
1021 * so the work on the current palette could be compared, see if there has been any changes */
1022 memcpy(old_val, palette_pos, sizeof(old_val));
1024 /* Fizzy Drink bubbles animation */
1025 s = ev->fizzy_drink;
1026 j = EXTR2(512, EPV_CYCLES_FIZZY_DRINK);
1027 for (i = 0; i != EPV_CYCLES_FIZZY_DRINK; i++) {
1028 *palette_pos++ = s[j];
1029 j++;
1030 if (j == EPV_CYCLES_FIZZY_DRINK) j = 0;
1033 /* Oil refinery fire animation */
1034 s = ev->oil_refinery;
1035 j = EXTR2(512, EPV_CYCLES_OIL_REFINERY);
1036 for (i = 0; i != EPV_CYCLES_OIL_REFINERY; i++) {
1037 *palette_pos++ = s[j];
1038 j++;
1039 if (j == EPV_CYCLES_OIL_REFINERY) j = 0;
1042 /* Radio tower blinking */
1044 byte i = (palette_animation_counter >> 1) & 0x7F;
1045 byte v;
1047 if (i < 0x3f) {
1048 v = 255;
1049 } else if (i < 0x4A || i >= 0x75) {
1050 v = 128;
1051 } else {
1052 v = 20;
1054 palette_pos->r = v;
1055 palette_pos->g = 0;
1056 palette_pos->b = 0;
1057 palette_pos++;
1059 i ^= 0x40;
1060 if (i < 0x3f) {
1061 v = 255;
1062 } else if (i < 0x4A || i >= 0x75) {
1063 v = 128;
1064 } else {
1065 v = 20;
1067 palette_pos->r = v;
1068 palette_pos->g = 0;
1069 palette_pos->b = 0;
1070 palette_pos++;
1073 /* Handle lighthouse and stadium animation */
1074 s = ev->lighthouse;
1075 j = EXTR(256, EPV_CYCLES_LIGHTHOUSE);
1076 for (i = 0; i != EPV_CYCLES_LIGHTHOUSE; i++) {
1077 *palette_pos++ = s[j];
1078 j++;
1079 if (j == EPV_CYCLES_LIGHTHOUSE) j = 0;
1082 /* Dark blue water */
1083 s = (_settings_game.game_creation.landscape == LT_TOYLAND) ? ev->dark_water_toyland : ev->dark_water;
1084 j = EXTR(320, EPV_CYCLES_DARK_WATER);
1085 for (i = 0; i != EPV_CYCLES_DARK_WATER; i++) {
1086 *palette_pos++ = s[j];
1087 j++;
1088 if (j == EPV_CYCLES_DARK_WATER) j = 0;
1091 /* Glittery water */
1092 s = (_settings_game.game_creation.landscape == LT_TOYLAND) ? ev->glitter_water_toyland : ev->glitter_water;
1093 j = EXTR(128, EPV_CYCLES_GLITTER_WATER);
1094 for (i = 0; i != EPV_CYCLES_GLITTER_WATER / 3; i++) {
1095 *palette_pos++ = s[j];
1096 j += 3;
1097 if (j >= EPV_CYCLES_GLITTER_WATER) j -= EPV_CYCLES_GLITTER_WATER;
1100 if (blitter != nullptr && blitter->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_NONE) {
1101 palette_animation_counter = old_tc;
1102 } else {
1103 if (memcmp(old_val, &_cur_palette.palette[PALETTE_ANIM_START], sizeof(old_val)) != 0 && _cur_palette.count_dirty == 0) {
1104 /* Did we changed anything on the palette? Seems so. Mark it as dirty */
1105 _cur_palette.first_dirty = PALETTE_ANIM_START;
1106 _cur_palette.count_dirty = PALETTE_ANIM_SIZE;
1112 * Determine a contrasty text colour for a coloured background.
1113 * @param background Background colour.
1114 * @return TC_BLACK or TC_WHITE depending on what gives a better contrast.
1116 TextColour GetContrastColour(uint8 background, uint8 threshold)
1118 Colour c = _cur_palette.palette[background];
1119 /* Compute brightness according to http://www.w3.org/TR/AERT#color-contrast.
1120 * The following formula computes 1000 * brightness^2, with brightness being in range 0 to 255. */
1121 auto sq1000_brightness = c.r * c.r * 299 + c.g * c.g * 587 + c.b * c.b * 114;
1122 /* Compare with threshold brightness which defaults to 128 (50%) */
1123 return sq1000_brightness < (threshold * 128 * 1000) ? TC_WHITE : TC_BLACK;
1127 * Initialize _stringwidth_table cache
1128 * @param monospace Whether to load the monospace cache or the normal fonts.
1130 void LoadStringWidthTable(bool monospace)
1132 for (FontSize fs = monospace ? FS_MONO : FS_BEGIN; fs < (monospace ? FS_END : FS_MONO); fs++) {
1133 for (uint i = 0; i != 224; i++) {
1134 _stringwidth_table[fs][i] = GetGlyphWidth(fs, i + 32);
1138 ClearFontCache();
1139 ReInitAllWindows();
1143 * Return width of character glyph.
1144 * @param size Font of the character
1145 * @param key Character code glyph
1146 * @return Width of the character glyph
1148 byte GetCharacterWidth(FontSize size, WChar key)
1150 /* Use _stringwidth_table cache if possible */
1151 if (key >= 32 && key < 256) return _stringwidth_table[size][key - 32];
1153 return GetGlyphWidth(size, key);
1157 * Return the maximum width of single digit.
1158 * @param size Font of the digit
1159 * @return Width of the digit.
1161 byte GetDigitWidth(FontSize size)
1163 byte width = 0;
1164 for (char c = '0'; c <= '9'; c++) {
1165 width = max(GetCharacterWidth(size, c), width);
1167 return width;
1171 * Determine the broadest digits for guessing the maximum width of a n-digit number.
1172 * @param [out] front Broadest digit, which is not 0. (Use this digit as first digit for numbers with more than one digit.)
1173 * @param [out] next Broadest digit, including 0. (Use this digit for all digits, except the first one; or for numbers with only one digit.)
1174 * @param size Font of the digit
1176 void GetBroadestDigit(uint *front, uint *next, FontSize size)
1178 int width = -1;
1179 for (char c = '9'; c >= '0'; c--) {
1180 int w = GetCharacterWidth(size, c);
1181 if (w > width) {
1182 width = w;
1183 *next = c - '0';
1184 if (c != '0') *front = c - '0';
1189 void ScreenSizeChanged()
1191 _dirty_bytes_per_line = CeilDiv(_screen.width, DIRTY_BLOCK_WIDTH);
1192 _dirty_blocks = ReallocT<byte>(_dirty_blocks, _dirty_bytes_per_line * CeilDiv(_screen.height, DIRTY_BLOCK_HEIGHT));
1194 extern uint32 *_vp_map_line;
1195 _vp_map_line = ReallocT<uint32>(_vp_map_line, _screen.width);
1197 /* check the dirty rect */
1198 if (_invalid_rect.right >= _screen.width) _invalid_rect.right = _screen.width;
1199 if (_invalid_rect.bottom >= _screen.height) _invalid_rect.bottom = _screen.height;
1201 /* screen size changed and the old bitmap is invalid now, so we don't want to undraw it */
1202 _cursor.visible = false;
1205 void UndrawMouseCursor()
1207 /* Don't undraw the mouse cursor if the screen is not ready */
1208 if (_screen.dst_ptr == nullptr) return;
1210 if (_cursor.visible) {
1211 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
1212 _cursor.visible = false;
1213 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);
1214 VideoDriver::GetInstance()->MakeDirty(_cursor.draw_pos.x, _cursor.draw_pos.y, _cursor.draw_size.x, _cursor.draw_size.y);
1218 void DrawMouseCursor()
1220 #if defined(WINCE)
1221 /* Don't ever draw the mouse for WinCE, as we work with a stylus */
1222 return;
1223 #endif
1225 /* Don't draw the mouse cursor if the screen is not ready */
1226 if (_screen.dst_ptr == nullptr) return;
1228 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
1230 /* Redraw mouse cursor but only when it's inside the window */
1231 if (!_cursor.in_window) return;
1233 /* Don't draw the mouse cursor if it's already drawn */
1234 if (_cursor.visible) {
1235 if (!_cursor.dirty) return;
1236 UndrawMouseCursor();
1239 /* Determine visible area */
1240 int left = _cursor.pos.x + _cursor.total_offs.x;
1241 int width = _cursor.total_size.x;
1242 if (left < 0) {
1243 width += left;
1244 left = 0;
1246 if (left + width > _screen.width) {
1247 width = _screen.width - left;
1249 if (width <= 0) return;
1251 int top = _cursor.pos.y + _cursor.total_offs.y;
1252 int height = _cursor.total_size.y;
1253 if (top < 0) {
1254 height += top;
1255 top = 0;
1257 if (top + height > _screen.height) {
1258 height = _screen.height - top;
1260 if (height <= 0) return;
1262 _cursor.draw_pos.x = left;
1263 _cursor.draw_pos.y = top;
1264 _cursor.draw_size.x = width;
1265 _cursor.draw_size.y = height;
1267 uint8 *buffer = _cursor_backup.Allocate(blitter->BufferSize(_cursor.draw_size.x, _cursor.draw_size.y));
1269 /* Make backup of stuff below cursor */
1270 blitter->CopyToBuffer(blitter->MoveTo(_screen.dst_ptr, _cursor.draw_pos.x, _cursor.draw_pos.y), buffer, _cursor.draw_size.x, _cursor.draw_size.y);
1272 /* Draw cursor on screen */
1273 _cur_dpi = &_screen;
1274 for (uint i = 0; i < _cursor.sprite_count; ++i) {
1275 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);
1278 VideoDriver::GetInstance()->MakeDirty(_cursor.draw_pos.x, _cursor.draw_pos.y, _cursor.draw_size.x, _cursor.draw_size.y);
1280 _cursor.visible = true;
1281 _cursor.dirty = false;
1284 void RedrawScreenRect(int left, int top, int right, int bottom)
1286 assert(right <= _screen.width && bottom <= _screen.height);
1287 if (_cursor.visible) {
1288 if (right > _cursor.draw_pos.x &&
1289 left < _cursor.draw_pos.x + _cursor.draw_size.x &&
1290 bottom > _cursor.draw_pos.y &&
1291 top < _cursor.draw_pos.y + _cursor.draw_size.y) {
1292 UndrawMouseCursor();
1296 #ifdef ENABLE_NETWORK
1297 if (_networking) NetworkUndrawChatMessage();
1298 #endif /* ENABLE_NETWORK */
1300 DrawOverlappedWindowForAll(left, top, right, bottom);
1302 VideoDriver::GetInstance()->MakeDirty(left, top, right - left, bottom - top);
1306 * Repaints the rectangle blocks which are marked as 'dirty'.
1308 * @see SetDirtyBlocks
1310 void DrawDirtyBlocks()
1312 byte *b = _dirty_blocks;
1313 const int w = Align(_screen.width, DIRTY_BLOCK_WIDTH);
1314 const int h = Align(_screen.height, DIRTY_BLOCK_HEIGHT);
1315 int x;
1316 int y;
1318 if (HasModalProgress()) {
1319 /* We are generating the world, so release our rights to the map and
1320 * painting while we are waiting a bit. */
1321 _modal_progress_paint_mutex->EndCritical();
1322 _modal_progress_work_mutex->EndCritical();
1324 /* Wait a while and update _realtime_tick so we are given the rights */
1325 if (!IsFirstModalProgressLoop()) CSleep(MODAL_PROGRESS_REDRAW_TIMEOUT);
1326 _realtime_tick += MODAL_PROGRESS_REDRAW_TIMEOUT;
1327 _modal_progress_paint_mutex->BeginCritical();
1328 _modal_progress_work_mutex->BeginCritical();
1330 /* When we ended with the modal progress, do not draw the blocks.
1331 * Simply let the next run do so, otherwise we would be loading
1332 * the new state (and possibly change the blitter) when we hold
1333 * the drawing lock, which we must not do. */
1334 if (_switch_mode != SM_NONE && !HasModalProgress()) return;
1337 y = 0;
1338 do {
1339 x = 0;
1340 do {
1341 if (*b != 0) {
1342 int left;
1343 int top;
1344 int right = x + DIRTY_BLOCK_WIDTH;
1345 int bottom = y;
1346 byte *p = b;
1347 int h2;
1349 /* First try coalescing downwards */
1350 do {
1351 *p = 0;
1352 p += _dirty_bytes_per_line;
1353 bottom += DIRTY_BLOCK_HEIGHT;
1354 } while (bottom != h && *p != 0);
1356 /* Try coalescing to the right too. */
1357 h2 = (bottom - y) / DIRTY_BLOCK_HEIGHT;
1358 assert(h2 > 0);
1359 p = b;
1361 while (right != w) {
1362 byte *p2 = ++p;
1363 int h = h2;
1364 /* Check if a full line of dirty flags is set. */
1365 do {
1366 if (!*p2) goto no_more_coalesc;
1367 p2 += _dirty_bytes_per_line;
1368 } while (--h != 0);
1370 /* Wohoo, can combine it one step to the right!
1371 * Do that, and clear the bits. */
1372 right += DIRTY_BLOCK_WIDTH;
1374 h = h2;
1375 p2 = p;
1376 do {
1377 *p2 = 0;
1378 p2 += _dirty_bytes_per_line;
1379 } while (--h != 0);
1381 no_more_coalesc:
1383 left = x;
1384 top = y;
1386 if (left < _invalid_rect.left ) left = _invalid_rect.left;
1387 if (top < _invalid_rect.top ) top = _invalid_rect.top;
1388 if (right > _invalid_rect.right ) right = _invalid_rect.right;
1389 if (bottom > _invalid_rect.bottom) bottom = _invalid_rect.bottom;
1391 if (left < right && top < bottom) {
1392 RedrawScreenRect(left, top, right, bottom);
1396 } while (b++, (x += DIRTY_BLOCK_WIDTH) != w);
1397 } while (b += -(int)(w / DIRTY_BLOCK_WIDTH) + _dirty_bytes_per_line, (y += DIRTY_BLOCK_HEIGHT) != h);
1399 ++_dirty_block_colour;
1400 _invalid_rect.left = w;
1401 _invalid_rect.top = h;
1402 _invalid_rect.right = 0;
1403 _invalid_rect.bottom = 0;
1407 * This function extends the internal _invalid_rect rectangle as it
1408 * now contains the rectangle defined by the given parameters. Note
1409 * the point (0,0) is top left.
1411 * @param left The left edge of the rectangle
1412 * @param top The top edge of the rectangle
1413 * @param right The right edge of the rectangle
1414 * @param bottom The bottom edge of the rectangle
1415 * @see DrawDirtyBlocks
1417 * @todo The name of the function should be called like @c AddDirtyBlock as
1418 * it neither set a dirty rect nor add several dirty rects although
1419 * the function name is in plural. (Progman)
1421 void SetDirtyBlocks(int left, int top, int right, int bottom)
1423 byte *b;
1424 int width;
1425 int height;
1427 if (left < 0) left = 0;
1428 if (top < 0) top = 0;
1429 if (right > _screen.width) right = _screen.width;
1430 if (bottom > _screen.height) bottom = _screen.height;
1432 if (left >= right || top >= bottom) return;
1434 if (left < _invalid_rect.left ) _invalid_rect.left = left;
1435 if (top < _invalid_rect.top ) _invalid_rect.top = top;
1436 if (right > _invalid_rect.right ) _invalid_rect.right = right;
1437 if (bottom > _invalid_rect.bottom) _invalid_rect.bottom = bottom;
1439 left /= DIRTY_BLOCK_WIDTH;
1440 top /= DIRTY_BLOCK_HEIGHT;
1442 b = _dirty_blocks + top * _dirty_bytes_per_line + left;
1444 width = ((right - 1) / DIRTY_BLOCK_WIDTH) - left + 1;
1445 height = ((bottom - 1) / DIRTY_BLOCK_HEIGHT) - top + 1;
1447 assert(width > 0 && height > 0);
1449 do {
1450 int i = width;
1452 do b[--i] = 0xFF; while (i != 0);
1454 b += _dirty_bytes_per_line;
1455 } while (--height != 0);
1459 * This function mark the whole screen as dirty. This results in repainting
1460 * the whole screen. Use this with care as this function will break the
1461 * idea about marking only parts of the screen as 'dirty'.
1462 * @ingroup dirty
1464 void MarkWholeScreenDirty()
1466 SetDirtyBlocks(0, 0, _screen.width, _screen.height);
1470 * Set up a clipping area for only drawing into a certain area. To do this,
1471 * Fill a DrawPixelInfo object with the supplied relative rectangle, backup
1472 * the original (calling) _cur_dpi and assign the just returned DrawPixelInfo
1473 * _cur_dpi. When you are done, give restore _cur_dpi's original value
1474 * @param *n the DrawPixelInfo that will be the clipping rectangle box allowed
1475 * for drawing
1476 * @param left,top,width,height the relative coordinates of the clipping
1477 * rectangle relative to the current _cur_dpi. This will most likely be the
1478 * offset from the calling window coordinates
1479 * @return return false if the requested rectangle is not possible with the
1480 * current dpi pointer. Only continue of the return value is true, or you'll
1481 * get some nasty results
1483 bool FillDrawPixelInfo(DrawPixelInfo *n, int left, int top, int width, int height)
1485 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
1486 const DrawPixelInfo *o = _cur_dpi;
1488 n->zoom = ZOOM_LVL_NORMAL;
1490 assert(width > 0);
1491 assert(height > 0);
1493 if ((left -= o->left) < 0) {
1494 width += left;
1495 if (width <= 0) return false;
1496 n->left = -left;
1497 left = 0;
1498 } else {
1499 n->left = 0;
1502 if (width > o->width - left) {
1503 width = o->width - left;
1504 if (width <= 0) return false;
1506 n->width = width;
1508 if ((top -= o->top) < 0) {
1509 height += top;
1510 if (height <= 0) return false;
1511 n->top = -top;
1512 top = 0;
1513 } else {
1514 n->top = 0;
1517 n->dst_ptr = blitter->MoveTo(o->dst_ptr, left, top);
1518 n->pitch = o->pitch;
1520 if (height > o->height - top) {
1521 height = o->height - top;
1522 if (height <= 0) return false;
1524 n->height = height;
1526 return true;
1530 * Update cursor dimension.
1531 * Called when changing cursor sprite resp. reloading grfs.
1533 void UpdateCursorSize()
1535 /* Ignore setting any cursor before the sprites are loaded. */
1536 if (GetMaxSpriteID() == 0) return;
1538 assert_compile(lengthof(_cursor.sprite_seq) == lengthof(_cursor.sprite_pos));
1539 assert(_cursor.sprite_count <= lengthof(_cursor.sprite_seq));
1540 for (uint i = 0; i < _cursor.sprite_count; ++i) {
1541 const Sprite *p = GetSprite(GB(_cursor.sprite_seq[i].sprite, 0, SPRITE_WIDTH), ST_NORMAL);
1542 Point offs, size;
1543 offs.x = UnScaleGUI(p->x_offs) + _cursor.sprite_pos[i].x;
1544 offs.y = UnScaleGUI(p->y_offs) + _cursor.sprite_pos[i].y;
1545 size.x = UnScaleGUI(p->width);
1546 size.y = UnScaleGUI(p->height);
1548 if (i == 0) {
1549 _cursor.total_offs = offs;
1550 _cursor.total_size = size;
1551 } else {
1552 int right = max(_cursor.total_offs.x + _cursor.total_size.x, offs.x + size.x);
1553 int bottom = max(_cursor.total_offs.y + _cursor.total_size.y, offs.y + size.y);
1554 if (offs.x < _cursor.total_offs.x) _cursor.total_offs.x = offs.x;
1555 if (offs.y < _cursor.total_offs.y) _cursor.total_offs.y = offs.y;
1556 _cursor.total_size.x = right - _cursor.total_offs.x;
1557 _cursor.total_size.y = bottom - _cursor.total_offs.y;
1561 _cursor.dirty = true;
1565 * Switch cursor to different sprite.
1566 * @param cursor Sprite to draw for the cursor.
1567 * @param pal Palette to use for recolouring.
1569 static void SetCursorSprite(CursorID cursor, PaletteID pal)
1571 if (_cursor.sprite_count == 1 && _cursor.sprite_seq[0].sprite == cursor && _cursor.sprite_seq[0].pal == pal) return;
1573 _cursor.sprite_count = 1;
1574 _cursor.sprite_seq[0].sprite = cursor;
1575 _cursor.sprite_seq[0].pal = pal;
1576 _cursor.sprite_pos[0].x = 0;
1577 _cursor.sprite_pos[0].y = 0;
1579 UpdateCursorSize();
1582 static void SwitchAnimatedCursor()
1584 const AnimCursor *cur = _cursor.animate_cur;
1586 if (cur == nullptr || cur->sprite == AnimCursor::LAST) cur = _cursor.animate_list;
1588 SetCursorSprite(cur->sprite, _cursor.sprite_seq[0].pal);
1590 _cursor.animate_timeout = cur->display_time;
1591 _cursor.animate_cur = cur + 1;
1594 void CursorTick()
1596 if (_cursor.animate_timeout != 0 && --_cursor.animate_timeout == 0) {
1597 SwitchAnimatedCursor();
1602 * Set or unset the ZZZ cursor.
1603 * @param busy Whether to show the ZZZ cursor.
1605 void SetMouseCursorBusy(bool busy)
1607 if (busy) {
1608 if (_cursor.sprite_seq[0].sprite == SPR_CURSOR_MOUSE) SetMouseCursor(SPR_CURSOR_ZZZ, PAL_NONE);
1609 } else {
1610 if (_cursor.sprite_seq[0].sprite == SPR_CURSOR_ZZZ) SetMouseCursor(SPR_CURSOR_MOUSE, PAL_NONE);
1615 * Assign a single non-animated sprite to the cursor.
1616 * @param sprite Sprite to draw for the cursor.
1617 * @param pal Palette to use for recolouring.
1618 * @see SetAnimatedMouseCursor
1620 void SetMouseCursor(CursorID sprite, PaletteID pal)
1622 /* Turn off animation */
1623 _cursor.animate_timeout = 0;
1624 /* Set cursor */
1625 SetCursorSprite(sprite, pal);
1629 * Assign an animation to the cursor.
1630 * @param table Array of animation states.
1631 * @see SetMouseCursor
1633 void SetAnimatedMouseCursor(const AnimCursor *table)
1635 _cursor.animate_list = table;
1636 _cursor.animate_cur = nullptr;
1637 _cursor.sprite_seq[0].pal = PAL_NONE;
1638 SwitchAnimatedCursor();
1642 * Update cursor position on mouse movement.
1643 * @param x New X position.
1644 * @param y New Y position.
1645 * @param queued True, if the OS queues mouse warps after pending mouse movement events.
1646 * False, if the warp applies instantaneous.
1647 * @return true, if the OS cursor position should be warped back to this->pos.
1649 bool CursorVars::UpdateCursorPosition(int x, int y, bool queued_warp)
1651 /* Detecting relative mouse movement is somewhat tricky.
1652 * - There may be multiple mouse move events in the video driver queue (esp. when OpenTTD lags a bit).
1653 * - When we request warping the mouse position (return true), a mouse move event is appended at the end of the queue.
1655 * So, when this->fix_at is active, we use the following strategy:
1656 * - The first movement triggers the warp to reset the mouse position.
1657 * - Subsequent events have to compute movement relative to the previous event.
1658 * - The relative movement is finished, when we receive the event matching the warp.
1661 if (x == this->pos.x && y == this->pos.y) {
1662 /* Warp finished. */
1663 this->queued_warp = false;
1666 this->delta.x = x - (this->queued_warp ? this->last_position.x : this->pos.x);
1667 this->delta.y = y - (this->queued_warp ? this->last_position.y : this->pos.y);
1669 this->last_position.x = x;
1670 this->last_position.y = y;
1672 bool need_warp = false;
1673 if (this->fix_at) {
1674 if (this->delta.x != 0 || this->delta.y != 0) {
1675 /* Trigger warp.
1676 * Note: We also trigger warping again, if there is already a pending warp.
1677 * This makes it more tolerant about the OS or other software inbetween
1678 * botchering the warp. */
1679 this->queued_warp = queued_warp;
1680 need_warp = true;
1682 } else if (this->pos.x != x || this->pos.y != y) {
1683 this->queued_warp = false; // Cancel warping, we are no longer confining the position.
1684 this->dirty = true;
1685 this->pos.x = x;
1686 this->pos.y = y;
1688 return need_warp;
1691 bool ChangeResInGame(int width, int height)
1693 return (_screen.width == width && _screen.height == height) || VideoDriver::GetInstance()->ChangeResolution(width, height);
1696 bool ToggleFullScreen(bool fs)
1698 bool result = VideoDriver::GetInstance()->ToggleFullscreen(fs);
1699 if (_fullscreen != fs && _num_resolutions == 0) {
1700 DEBUG(driver, 0, "Could not find a suitable fullscreen resolution");
1702 return result;
1705 static int CDECL compare_res(const Dimension *pa, const Dimension *pb)
1707 int x = pa->width - pb->width;
1708 if (x != 0) return x;
1709 return pa->height - pb->height;
1712 void SortResolutions(int count)
1714 QSortT(_resolutions, count, &compare_res);