Fix #7266: Reorder reinitialization of caches when changing font zoom level. (#7273)
[openttd-github.git] / src / gfx.cpp
blob7a6b827b7ad1d9954b0124006e492ca37f019217
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 = NULL, SpriteID sprite_id = SPR_CURSOR_MOUSE);
56 static void GfxMainBlitter(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub = NULL, SpriteID sprite_id = SPR_CURSOR_MOUSE, ZoomLevel zoom = ZOOM_LVL_NORMAL);
58 static ReusableBuffer<uint8> _cursor_backup;
60 ZoomLevelByte _gui_zoom; ///< GUI Zoom level
61 ZoomLevelByte _font_zoom; ///< Font Zoom level
63 /**
64 * The rect for repaint.
66 * This rectangle defines the area which should be repaint by the video driver.
68 * @ingroup dirty
70 static Rect _invalid_rect;
71 static const byte *_colour_remap_ptr;
72 static byte _string_colourremap[3]; ///< Recoloursprite for stringdrawing. The grf loader ensures that #ST_FONT sprites only use colours 0 to 2.
74 static const uint DIRTY_BLOCK_HEIGHT = 8;
75 static const uint DIRTY_BLOCK_WIDTH = 64;
77 static uint _dirty_bytes_per_line = 0;
78 static byte *_dirty_blocks = NULL;
79 extern uint _dirty_block_colour;
81 void GfxScroll(int left, int top, int width, int height, int xo, int yo)
83 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
85 if (xo == 0 && yo == 0) return;
87 if (_cursor.visible) UndrawMouseCursor();
89 #ifdef ENABLE_NETWORK
90 if (_networking) NetworkUndrawChatMessage();
91 #endif /* ENABLE_NETWORK */
93 blitter->ScrollBuffer(_screen.dst_ptr, left, top, width, height, xo, yo);
94 /* This part of the screen is now dirty. */
95 VideoDriver::GetInstance()->MakeDirty(left, top, width, height);
99 /**
100 * Applies a certain FillRectMode-operation to a rectangle [left, right] x [top, bottom] on the screen.
102 * @pre dpi->zoom == ZOOM_LVL_NORMAL, right >= left, bottom >= top
103 * @param left Minimum X (inclusive)
104 * @param top Minimum Y (inclusive)
105 * @param right Maximum X (inclusive)
106 * @param bottom Maximum Y (inclusive)
107 * @param colour A 8 bit palette index (FILLRECT_OPAQUE and FILLRECT_CHECKER) or a recolour spritenumber (FILLRECT_RECOLOUR)
108 * @param mode
109 * FILLRECT_OPAQUE: Fill the rectangle with the specified colour
110 * FILLRECT_CHECKER: Like FILLRECT_OPAQUE, but only draw every second pixel (used to grey out things)
111 * FILLRECT_RECOLOUR: Apply a recolour sprite to every pixel in the rectangle currently on screen
113 void GfxFillRect(int left, int top, int right, int bottom, int colour, FillRectMode mode)
115 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
116 const DrawPixelInfo *dpi = _cur_dpi;
117 void *dst;
118 const int otop = top;
119 const int oleft = left;
121 if (dpi->zoom != ZOOM_LVL_NORMAL) return;
122 if (left > right || top > bottom) return;
123 if (right < dpi->left || left >= dpi->left + dpi->width) return;
124 if (bottom < dpi->top || top >= dpi->top + dpi->height) return;
126 if ( (left -= dpi->left) < 0) left = 0;
127 right = right - dpi->left + 1;
128 if (right > dpi->width) right = dpi->width;
129 right -= left;
130 assert(right > 0);
132 if ( (top -= dpi->top) < 0) top = 0;
133 bottom = bottom - dpi->top + 1;
134 if (bottom > dpi->height) bottom = dpi->height;
135 bottom -= top;
136 assert(bottom > 0);
138 dst = blitter->MoveTo(dpi->dst_ptr, left, top);
140 switch (mode) {
141 default: // FILLRECT_OPAQUE
142 blitter->DrawRect(dst, right, bottom, (uint8)colour);
143 break;
145 case FILLRECT_RECOLOUR:
146 blitter->DrawColourMappingRect(dst, right, bottom, GB(colour, 0, PALETTE_WIDTH));
147 break;
149 case FILLRECT_CHECKER: {
150 byte bo = (oleft - left + dpi->left + otop - top + dpi->top) & 1;
151 do {
152 for (int i = (bo ^= 1); i < right; i += 2) blitter->SetPixel(dst, i, 0, (uint8)colour);
153 dst = blitter->MoveTo(dst, 0, 1);
154 } while (--bottom > 0);
155 break;
161 * Check line clipping by using a linear equation and draw the visible part of
162 * the line given by x/y and x2/y2.
163 * @param video Destination pointer to draw into.
164 * @param x X coordinate of first point.
165 * @param y Y coordinate of first point.
166 * @param x2 X coordinate of second point.
167 * @param y2 Y coordinate of second point.
168 * @param screen_width With of the screen to check clipping against.
169 * @param screen_height Height of the screen to check clipping against.
170 * @param colour Colour of the line.
171 * @param width Width of the line.
172 * @param dash Length of dashes for dashed lines. 0 means solid line.
174 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)
176 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
178 assert(width > 0);
180 if (y2 == y || x2 == x) {
181 /* Special case: horizontal/vertical line. All checks already done in GfxPreprocessLine. */
182 blitter->DrawLine(video, x, y, x2, y2, screen_width, screen_height, colour, width, dash);
183 return;
186 int grade_y = y2 - y;
187 int grade_x = x2 - x;
189 /* Clipping rectangle. Slightly extended so we can ignore the width of the line. */
190 int extra = (int)CeilDiv(3 * width, 4); // not less then "width * sqrt(2) / 2"
191 Rect clip = { -extra, -extra, screen_width - 1 + extra, screen_height - 1 + extra };
193 /* prevent integer overflows. */
194 int margin = 1;
195 while (INT_MAX / abs(grade_y) < max(abs(clip.left - x), abs(clip.right - x))) {
196 grade_y /= 2;
197 grade_x /= 2;
198 margin *= 2; // account for rounding errors
201 /* Imagine that the line is infinitely long and it intersects with
202 * infinitely long left and right edges of the clipping rectangle.
203 * If both intersection points are outside the clipping rectangle
204 * and both on the same side of it, we don't need to draw anything. */
205 int left_isec_y = y + (clip.left - x) * grade_y / grade_x;
206 int right_isec_y = y + (clip.right - x) * grade_y / grade_x;
207 if ((left_isec_y > clip.bottom + margin && right_isec_y > clip.bottom + margin) ||
208 (left_isec_y < clip.top - margin && right_isec_y < clip.top - margin)) {
209 return;
212 /* It is possible to use the line equation to further reduce the amount of
213 * work the blitter has to do by shortening the effective line segment.
214 * However, in order to get that right and prevent the flickering effects
215 * of rounding errors so much additional code has to be run here that in
216 * the general case the effect is not noticable. */
218 blitter->DrawLine(video, x, y, x2, y2, screen_width, screen_height, colour, width, dash);
222 * Align parameters of a line to the given DPI and check simple clipping.
223 * @param dpi Screen parameters to align with.
224 * @param x X coordinate of first point.
225 * @param y Y coordinate of first point.
226 * @param x2 X coordinate of second point.
227 * @param y2 Y coordinate of second point.
228 * @param width Width of the line.
229 * @return True if the line is likely to be visible, false if it's certainly
230 * invisible.
232 static inline bool GfxPreprocessLine(DrawPixelInfo *dpi, int &x, int &y, int &x2, int &y2, int width)
234 x -= dpi->left;
235 x2 -= dpi->left;
236 y -= dpi->top;
237 y2 -= dpi->top;
239 /* Check simple clipping */
240 if (x + width / 2 < 0 && x2 + width / 2 < 0 ) return false;
241 if (y + width / 2 < 0 && y2 + width / 2 < 0 ) return false;
242 if (x - width / 2 > dpi->width && x2 - width / 2 > dpi->width ) return false;
243 if (y - width / 2 > dpi->height && y2 - width / 2 > dpi->height) return false;
244 return true;
247 void GfxDrawLine(int x, int y, int x2, int y2, int colour, int width, int dash)
249 DrawPixelInfo *dpi = _cur_dpi;
250 if (GfxPreprocessLine(dpi, x, y, x2, y2, width)) {
251 GfxDoDrawLine(dpi->dst_ptr, x, y, x2, y2, dpi->width, dpi->height, colour, width, dash);
255 void GfxDrawLineUnscaled(int x, int y, int x2, int y2, int colour)
257 DrawPixelInfo *dpi = _cur_dpi;
258 if (GfxPreprocessLine(dpi, x, y, x2, y2, 1)) {
259 GfxDoDrawLine(dpi->dst_ptr,
260 UnScaleByZoom(x, dpi->zoom), UnScaleByZoom(y, dpi->zoom),
261 UnScaleByZoom(x2, dpi->zoom), UnScaleByZoom(y2, dpi->zoom),
262 UnScaleByZoom(dpi->width, dpi->zoom), UnScaleByZoom(dpi->height, dpi->zoom), colour, 1);
267 * Draws the projection of a parallelepiped.
268 * This can be used to draw boxes in world coordinates.
270 * @param x Screen X-coordinate of top front corner.
271 * @param y Screen Y-coordinate of top front corner.
272 * @param dx1 Screen X-length of first edge.
273 * @param dy1 Screen Y-length of first edge.
274 * @param dx2 Screen X-length of second edge.
275 * @param dy2 Screen Y-length of second edge.
276 * @param dx3 Screen X-length of third edge.
277 * @param dy3 Screen Y-length of third edge.
279 void DrawBox(int x, int y, int dx1, int dy1, int dx2, int dy2, int dx3, int dy3)
281 /* ....
282 * .. ....
283 * .. ....
284 * .. ^
285 * <--__(dx1,dy1) /(dx2,dy2)
286 * : --__ / :
287 * : --__ / :
288 * : *(x,y) :
289 * : | :
290 * : | ..
291 * .... |(dx3,dy3)
292 * .... | ..
293 * ....V.
296 static const byte colour = PC_WHITE;
298 GfxDrawLineUnscaled(x, y, x + dx1, y + dy1, colour);
299 GfxDrawLineUnscaled(x, y, x + dx2, y + dy2, colour);
300 GfxDrawLineUnscaled(x, y, x + dx3, y + dy3, colour);
302 GfxDrawLineUnscaled(x + dx1, y + dy1, x + dx1 + dx2, y + dy1 + dy2, colour);
303 GfxDrawLineUnscaled(x + dx1, y + dy1, x + dx1 + dx3, y + dy1 + dy3, colour);
304 GfxDrawLineUnscaled(x + dx2, y + dy2, x + dx2 + dx1, y + dy2 + dy1, colour);
305 GfxDrawLineUnscaled(x + dx2, y + dy2, x + dx2 + dx3, y + dy2 + dy3, colour);
306 GfxDrawLineUnscaled(x + dx3, y + dy3, x + dx3 + dx1, y + dy3 + dy1, colour);
307 GfxDrawLineUnscaled(x + dx3, y + dy3, x + dx3 + dx2, y + dy3 + dy2, colour);
311 * Set the colour remap to be for the given colour.
312 * @param colour the new colour of the remap.
314 static void SetColourRemap(TextColour colour)
316 if (colour == TC_INVALID) return;
318 /* Black strings have no shading ever; the shading is black, so it
319 * would be invisible at best, but it actually makes it illegible. */
320 bool no_shade = (colour & TC_NO_SHADE) != 0 || colour == TC_BLACK;
321 bool raw_colour = (colour & TC_IS_PALETTE_COLOUR) != 0;
322 colour &= ~(TC_NO_SHADE | TC_IS_PALETTE_COLOUR);
324 _string_colourremap[1] = raw_colour ? (byte)colour : _string_colourmap[colour];
325 _string_colourremap[2] = no_shade ? 0 : 1;
326 _colour_remap_ptr = _string_colourremap;
330 * Drawing routine for drawing a laid out line of text.
331 * @param line String to draw.
332 * @param y The top most position to draw on.
333 * @param left The left most position to draw on.
334 * @param right The right most position to draw on.
335 * @param align The alignment of the string when drawing left-to-right. In the
336 * case a right-to-left language is chosen this is inverted so it
337 * will be drawn in the right direction.
338 * @param underline Whether to underline what has been drawn or not.
339 * @param truncation Whether to perform string truncation or not.
341 * @return In case of left or center alignment the right most pixel we have drawn to.
342 * In case of right alignment the left most pixel we have drawn to.
344 static int DrawLayoutLine(const ParagraphLayouter::Line *line, int y, int left, int right, StringAlignment align, bool underline, bool truncation)
346 if (line->CountRuns() == 0) return 0;
348 int w = line->GetWidth();
349 int h = line->GetLeading();
352 * The following is needed for truncation.
353 * Depending on the text direction, we either remove bits at the rear
354 * or the front. For this we shift the entire area to draw so it fits
355 * within the left/right bounds and the side we do not truncate it on.
356 * Then we determine the truncation location, i.e. glyphs that fall
357 * outside of the range min_x - max_x will not be drawn; they are thus
358 * the truncated glyphs.
360 * At a later step we insert the dots.
363 int max_w = right - left + 1; // The maximum width.
365 int offset_x = 0; // The offset we need for positioning the glyphs
366 int min_x = left; // The minimum x position to draw normal glyphs on.
367 int max_x = right; // The maximum x position to draw normal glyphs on.
369 truncation &= max_w < w; // Whether we need to do truncation.
370 int dot_width = 0; // Cache for the width of the dot.
371 const Sprite *dot_sprite = NULL; // Cache for the sprite of the dot.
373 if (truncation) {
375 * Assumption may be made that all fonts of a run are of the same size.
376 * In any case, we'll use these dots for the abbreviation, so even if
377 * another size would be chosen it won't have truncated too little for
378 * the truncation dots.
380 FontCache *fc = ((const Font*)line->GetVisualRun(0)->GetFont())->fc;
381 GlyphID dot_glyph = fc->MapCharToGlyph('.');
382 dot_width = fc->GetGlyphWidth(dot_glyph);
383 dot_sprite = fc->GetGlyph(dot_glyph);
385 if (_current_text_dir == TD_RTL) {
386 min_x += 3 * dot_width;
387 offset_x = w - 3 * dot_width - max_w;
388 } else {
389 max_x -= 3 * dot_width;
392 w = max_w;
395 /* In case we have a RTL language we swap the alignment. */
396 if (!(align & SA_FORCE) && _current_text_dir == TD_RTL && (align & SA_HOR_MASK) != SA_HOR_CENTER) align ^= SA_RIGHT;
398 /* right is the right most position to draw on. In this case we want to do
399 * calculations with the width of the string. In comparison right can be
400 * seen as lastof(todraw) and width as lengthof(todraw). They differ by 1.
401 * So most +1/-1 additions are to move from lengthof to 'indices'.
403 switch (align & SA_HOR_MASK) {
404 case SA_LEFT:
405 /* right + 1 = left + w */
406 right = left + w - 1;
407 break;
409 case SA_HOR_CENTER:
410 left = RoundDivSU(right + 1 + left - w, 2);
411 /* right + 1 = left + w */
412 right = left + w - 1;
413 break;
415 case SA_RIGHT:
416 left = right + 1 - w;
417 break;
419 default:
420 NOT_REACHED();
423 TextColour colour = TC_BLACK;
424 bool draw_shadow = false;
425 for (int run_index = 0; run_index < line->CountRuns(); run_index++) {
426 const ParagraphLayouter::VisualRun *run = line->GetVisualRun(run_index);
427 const Font *f = (const Font*)run->GetFont();
429 FontCache *fc = f->fc;
430 colour = f->colour;
431 SetColourRemap(colour);
433 DrawPixelInfo *dpi = _cur_dpi;
434 int dpi_left = dpi->left;
435 int dpi_right = dpi->left + dpi->width - 1;
437 draw_shadow = fc->GetDrawGlyphShadow() && (colour & TC_NO_SHADE) == 0 && colour != TC_BLACK;
439 for (int i = 0; i < run->GetGlyphCount(); i++) {
440 GlyphID glyph = run->GetGlyphs()[i];
442 /* Not a valid glyph (empty) */
443 if (glyph == 0xFFFF) continue;
445 int begin_x = (int)run->GetPositions()[i * 2] + left - offset_x;
446 int end_x = (int)run->GetPositions()[i * 2 + 2] + left - offset_x - 1;
447 int top = (int)run->GetPositions()[i * 2 + 1] + y;
449 /* Truncated away. */
450 if (truncation && (begin_x < min_x || end_x > max_x)) continue;
452 const Sprite *sprite = fc->GetGlyph(glyph);
453 /* Check clipping (the "+ 1" is for the shadow). */
454 if (begin_x + sprite->x_offs > dpi_right || begin_x + sprite->x_offs + sprite->width /* - 1 + 1 */ < dpi_left) continue;
456 if (draw_shadow && (glyph & SPRITE_GLYPH) == 0) {
457 SetColourRemap(TC_BLACK);
458 GfxMainBlitter(sprite, begin_x + 1, top + 1, BM_COLOUR_REMAP);
459 SetColourRemap(colour);
461 GfxMainBlitter(sprite, begin_x, top, BM_COLOUR_REMAP);
465 if (truncation) {
466 int x = (_current_text_dir == TD_RTL) ? left : (right - 3 * dot_width);
467 for (int i = 0; i < 3; i++, x += dot_width) {
468 if (draw_shadow) {
469 SetColourRemap(TC_BLACK);
470 GfxMainBlitter(dot_sprite, x + 1, y + 1, BM_COLOUR_REMAP);
471 SetColourRemap(colour);
473 GfxMainBlitter(dot_sprite, x, y, BM_COLOUR_REMAP);
477 if (underline) {
478 GfxFillRect(left, y + h, right, y + h, _string_colourremap[1]);
481 return (align & SA_HOR_MASK) == SA_RIGHT ? left : right;
485 * Draw string, possibly truncated to make it fit in its allocated space
487 * @param left The left most position to draw on.
488 * @param right The right most position to draw on.
489 * @param top The top most position to draw on.
490 * @param str String to draw.
491 * @param colour Colour used for drawing the string, see DoDrawString() for details
492 * @param align The alignment of the string when drawing left-to-right. In the
493 * case a right-to-left language is chosen this is inverted so it
494 * will be drawn in the right direction.
495 * @param underline Whether to underline what has been drawn or not.
496 * @param fontsize The size of the initial characters.
497 * @return In case of left or center alignment the right most pixel we have drawn to.
498 * In case of right alignment the left most pixel we have drawn to.
500 int DrawString(int left, int right, int top, const char *str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
502 /* The string may contain control chars to change the font, just use the biggest font for clipping. */
503 int max_height = max(max(FONT_HEIGHT_SMALL, FONT_HEIGHT_NORMAL), max(FONT_HEIGHT_LARGE, FONT_HEIGHT_MONO));
505 /* Funny glyphs may extent outside the usual bounds, so relax the clipping somewhat. */
506 int extra = max_height / 2;
508 if (_cur_dpi->top + _cur_dpi->height + extra < top || _cur_dpi->top > top + max_height + extra ||
509 _cur_dpi->left + _cur_dpi->width + extra < left || _cur_dpi->left > right + extra) {
510 return 0;
513 Layouter layout(str, INT32_MAX, colour, fontsize);
514 if (layout.Length() == 0) return 0;
516 return DrawLayoutLine(*layout.Begin(), top, left, right, align, underline, true);
520 * Draw string, possibly truncated to make it fit in its allocated space
522 * @param left The left most position to draw on.
523 * @param right The right most position to draw on.
524 * @param top The top most position to draw on.
525 * @param str String to draw.
526 * @param colour Colour used for drawing the string, see DoDrawString() for details
527 * @param align The alignment of the string when drawing left-to-right. In the
528 * case a right-to-left language is chosen this is inverted so it
529 * will be drawn in the right direction.
530 * @param underline Whether to underline what has been drawn or not.
531 * @param fontsize The size of the initial characters.
532 * @return In case of left or center alignment the right most pixel we have drawn to.
533 * In case of right alignment the left most pixel we have drawn to.
535 int DrawString(int left, int right, int top, StringID str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
537 char buffer[DRAW_STRING_BUFFER];
538 GetString(buffer, str, lastof(buffer));
539 return DrawString(left, right, top, buffer, colour, align, underline, fontsize);
543 * Calculates height of string (in pixels). The string is changed to a multiline string if needed.
544 * @param str string to check
545 * @param maxw maximum string width
546 * @return height of pixels of string when it is drawn
548 int GetStringHeight(const char *str, int maxw, FontSize fontsize)
550 Layouter layout(str, maxw, TC_FROMSTRING, fontsize);
551 return layout.GetBounds().height;
555 * Calculates height of string (in pixels). The string is changed to a multiline string if needed.
556 * @param str string to check
557 * @param maxw maximum string width
558 * @return height of pixels of string when it is drawn
560 int GetStringHeight(StringID str, int maxw)
562 char buffer[DRAW_STRING_BUFFER];
563 GetString(buffer, str, lastof(buffer));
564 return GetStringHeight(buffer, maxw);
568 * Calculates number of lines of string. The string is changed to a multiline string if needed.
569 * @param str string to check
570 * @param maxw maximum string width
571 * @return number of lines of string when it is drawn
573 int GetStringLineCount(StringID str, int maxw)
575 char buffer[DRAW_STRING_BUFFER];
576 GetString(buffer, str, lastof(buffer));
578 Layouter layout(buffer, maxw);
579 return layout.Length();
583 * Calculate string bounding box for multi-line strings.
584 * @param str String to check.
585 * @param suggestion Suggested bounding box.
586 * @return Bounding box for the multi-line string, may be bigger than \a suggestion.
588 Dimension GetStringMultiLineBoundingBox(StringID str, const Dimension &suggestion)
590 Dimension box = {suggestion.width, (uint)GetStringHeight(str, suggestion.width)};
591 return box;
595 * Calculate string bounding box for multi-line strings.
596 * @param str String to check.
597 * @param suggestion Suggested bounding box.
598 * @return Bounding box for the multi-line string, may be bigger than \a suggestion.
600 Dimension GetStringMultiLineBoundingBox(const char *str, const Dimension &suggestion)
602 Dimension box = {suggestion.width, (uint)GetStringHeight(str, suggestion.width)};
603 return box;
607 * Draw string, possibly over multiple lines.
609 * @param left The left most position to draw on.
610 * @param right The right most position to draw on.
611 * @param top The top most position to draw on.
612 * @param bottom The bottom most position to draw on.
613 * @param str String to draw.
614 * @param colour Colour used for drawing the string, see DoDrawString() for details
615 * @param align The horizontal and vertical alignment of the string.
616 * @param underline Whether to underline all strings
617 * @param fontsize The size of the initial characters.
619 * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written.
621 int DrawStringMultiLine(int left, int right, int top, int bottom, const char *str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
623 int maxw = right - left + 1;
624 int maxh = bottom - top + 1;
626 /* It makes no sense to even try if it can't be drawn anyway, or
627 * do we really want to support fonts of 0 or less pixels high? */
628 if (maxh <= 0) return top;
630 Layouter layout(str, maxw, colour, fontsize);
631 int total_height = layout.GetBounds().height;
632 int y;
633 switch (align & SA_VERT_MASK) {
634 case SA_TOP:
635 y = top;
636 break;
638 case SA_VERT_CENTER:
639 y = RoundDivSU(bottom + top - total_height, 2);
640 break;
642 case SA_BOTTOM:
643 y = bottom - total_height;
644 break;
646 default: NOT_REACHED();
649 int last_line = top;
650 int first_line = bottom;
652 for (const ParagraphLayouter::Line **iter = layout.Begin(); iter != layout.End(); iter++) {
653 const ParagraphLayouter::Line *line = *iter;
655 int line_height = line->GetLeading();
656 if (y >= top && y < bottom) {
657 last_line = y + line_height;
658 if (first_line > y) first_line = y;
660 DrawLayoutLine(line, y, left, right, align, underline, false);
662 y += line_height;
665 return ((align & SA_VERT_MASK) == SA_BOTTOM) ? first_line : last_line;
669 * Draw string, possibly over multiple lines.
671 * @param left The left most position to draw on.
672 * @param right The right most position to draw on.
673 * @param top The top most position to draw on.
674 * @param bottom The bottom most position to draw on.
675 * @param str String to draw.
676 * @param colour Colour used for drawing the string, see DoDrawString() for details
677 * @param align The horizontal and vertical alignment of the string.
678 * @param underline Whether to underline all strings
679 * @param fontsize The size of the initial characters.
681 * @return If \a align is #SA_BOTTOM, the top to where we have written, else the bottom to where we have written.
683 int DrawStringMultiLine(int left, int right, int top, int bottom, StringID str, TextColour colour, StringAlignment align, bool underline, FontSize fontsize)
685 char buffer[DRAW_STRING_BUFFER];
686 GetString(buffer, str, lastof(buffer));
687 return DrawStringMultiLine(left, right, top, bottom, buffer, colour, align, underline, fontsize);
691 * Return the string dimension in pixels. The height and width are returned
692 * in a single Dimension value. TINYFONT, BIGFONT modifiers are only
693 * supported as the first character of the string. The returned dimensions
694 * are therefore a rough estimation correct for all the current strings
695 * but not every possible combination
696 * @param str string to calculate pixel-width
697 * @param start_fontsize Fontsize to start the text with
698 * @return string width and height in pixels
700 Dimension GetStringBoundingBox(const char *str, FontSize start_fontsize)
702 Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize);
703 return layout.GetBounds();
707 * Get bounding box of a string. Uses parameters set by #SetDParam if needed.
708 * Has the same restrictions as #GetStringBoundingBox(const char *str, FontSize start_fontsize).
709 * @param strid String to examine.
710 * @return Width and height of the bounding box for the string in pixels.
712 Dimension GetStringBoundingBox(StringID strid)
714 char buffer[DRAW_STRING_BUFFER];
716 GetString(buffer, strid, lastof(buffer));
717 return GetStringBoundingBox(buffer);
721 * Get the leading corner of a character in a single-line string relative
722 * to the start of the string.
723 * @param str String containing the character.
724 * @param ch Pointer to the character in the string.
725 * @param start_fontsize Font size to start the text with.
726 * @return Upper left corner of the glyph associated with the character.
728 Point GetCharPosInString(const char *str, const char *ch, FontSize start_fontsize)
730 Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize);
731 return layout.GetCharPosition(ch);
735 * Get the character from a string that is drawn at a specific position.
736 * @param str String to test.
737 * @param x Position relative to the start of the string.
738 * @param start_fontsize Font size to start the text with.
739 * @return Pointer to the character at the position or NULL if there is no character at the position.
741 const char *GetCharAtPosition(const char *str, int x, FontSize start_fontsize)
743 if (x < 0) return NULL;
745 Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize);
746 return layout.GetCharAtPosition(x);
750 * Draw single character horizontally centered around (x,y)
751 * @param c Character (glyph) to draw
752 * @param x X position to draw character
753 * @param y Y position to draw character
754 * @param colour Colour to use, see DoDrawString() for details
756 void DrawCharCentered(WChar c, int x, int y, TextColour colour)
758 SetColourRemap(colour);
759 GfxMainBlitter(GetGlyph(FS_NORMAL, c), x - GetCharacterWidth(FS_NORMAL, c) / 2, y, BM_COLOUR_REMAP);
763 * Get the size of a sprite.
764 * @param sprid Sprite to examine.
765 * @param[out] offset Optionally returns the sprite position offset.
766 * @return Sprite size in pixels.
767 * @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.
769 Dimension GetSpriteSize(SpriteID sprid, Point *offset, ZoomLevel zoom)
771 const Sprite *sprite = GetSprite(sprid, ST_NORMAL);
773 if (offset != NULL) {
774 offset->x = UnScaleByZoom(sprite->x_offs, zoom);
775 offset->y = UnScaleByZoom(sprite->y_offs, zoom);
778 Dimension d;
779 d.width = max<int>(0, UnScaleByZoom(sprite->x_offs + sprite->width, zoom));
780 d.height = max<int>(0, UnScaleByZoom(sprite->y_offs + sprite->height, zoom));
781 return d;
785 * Helper function to get the blitter mode for different types of palettes.
786 * @param pal The palette to get the blitter mode for.
787 * @return The blitter mode associated with the palette.
789 static BlitterMode GetBlitterMode(PaletteID pal)
791 switch (pal) {
792 case PAL_NONE: return BM_NORMAL;
793 case PALETTE_CRASH: return BM_CRASH_REMAP;
794 case PALETTE_ALL_BLACK: return BM_BLACK_REMAP;
795 default: return BM_COLOUR_REMAP;
800 * Draw a sprite in a viewport.
801 * @param img Image number to draw
802 * @param pal Palette to use.
803 * @param x Left coordinate of image in viewport, scaled by zoom
804 * @param y Top coordinate of image in viewport, scaled by zoom
805 * @param sub If available, draw only specified part of the sprite
807 void DrawSpriteViewport(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub)
809 SpriteID real_sprite = GB(img, 0, SPRITE_WIDTH);
810 if (HasBit(img, PALETTE_MODIFIER_TRANSPARENT)) {
811 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
812 GfxMainBlitterViewport(GetSprite(real_sprite, ST_NORMAL), x, y, BM_TRANSPARENT, sub, real_sprite);
813 } else if (pal != PAL_NONE) {
814 if (HasBit(pal, PALETTE_TEXT_RECOLOUR)) {
815 SetColourRemap((TextColour)GB(pal, 0, PALETTE_WIDTH));
816 } else {
817 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
819 GfxMainBlitterViewport(GetSprite(real_sprite, ST_NORMAL), x, y, GetBlitterMode(pal), sub, real_sprite);
820 } else {
821 GfxMainBlitterViewport(GetSprite(real_sprite, ST_NORMAL), x, y, BM_NORMAL, sub, real_sprite);
826 * Draw a sprite, not in a viewport
827 * @param img Image number to draw
828 * @param pal Palette to use.
829 * @param x Left coordinate of image in pixels
830 * @param y Top coordinate of image in pixels
831 * @param sub If available, draw only specified part of the sprite
832 * @param zoom Zoom level of sprite
834 void DrawSprite(SpriteID img, PaletteID pal, int x, int y, const SubSprite *sub, ZoomLevel zoom)
836 SpriteID real_sprite = GB(img, 0, SPRITE_WIDTH);
837 if (HasBit(img, PALETTE_MODIFIER_TRANSPARENT)) {
838 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
839 GfxMainBlitter(GetSprite(real_sprite, ST_NORMAL), x, y, BM_TRANSPARENT, sub, real_sprite, zoom);
840 } else if (pal != PAL_NONE) {
841 if (HasBit(pal, PALETTE_TEXT_RECOLOUR)) {
842 SetColourRemap((TextColour)GB(pal, 0, PALETTE_WIDTH));
843 } else {
844 _colour_remap_ptr = GetNonSprite(GB(pal, 0, PALETTE_WIDTH), ST_RECOLOUR) + 1;
846 GfxMainBlitter(GetSprite(real_sprite, ST_NORMAL), x, y, GetBlitterMode(pal), sub, real_sprite, zoom);
847 } else {
848 GfxMainBlitter(GetSprite(real_sprite, ST_NORMAL), x, y, BM_NORMAL, sub, real_sprite, zoom);
853 * The code for setting up the blitter mode and sprite information before finally drawing the sprite.
854 * @param sprite The sprite to draw.
855 * @param x The X location to draw.
856 * @param y The Y location to draw.
857 * @param mode The settings for the blitter to pass.
858 * @param sub Whether to only draw a sub set of the sprite.
859 * @param zoom The zoom level at which to draw the sprites.
860 * @tparam ZOOM_BASE The factor required to get the sub sprite information into the right size.
861 * @tparam SCALED_XY Whether the X and Y are scaled or unscaled.
863 template <int ZOOM_BASE, bool SCALED_XY>
864 static void GfxBlitter(const Sprite * const sprite, int x, int y, BlitterMode mode, const SubSprite * const sub, SpriteID sprite_id, ZoomLevel zoom)
866 const DrawPixelInfo *dpi = _cur_dpi;
867 Blitter::BlitterParams bp;
869 if (SCALED_XY) {
870 /* Scale it */
871 x = ScaleByZoom(x, zoom);
872 y = ScaleByZoom(y, zoom);
875 /* Move to the correct offset */
876 x += sprite->x_offs;
877 y += sprite->y_offs;
879 if (sub == NULL) {
880 /* No clipping. */
881 bp.skip_left = 0;
882 bp.skip_top = 0;
883 bp.width = UnScaleByZoom(sprite->width, zoom);
884 bp.height = UnScaleByZoom(sprite->height, zoom);
885 } else {
886 /* Amount of pixels to clip from the source sprite */
887 int clip_left = max(0, -sprite->x_offs + sub->left * ZOOM_BASE );
888 int clip_top = max(0, -sprite->y_offs + sub->top * ZOOM_BASE );
889 int clip_right = max(0, sprite->width - (-sprite->x_offs + (sub->right + 1) * ZOOM_BASE));
890 int clip_bottom = max(0, sprite->height - (-sprite->y_offs + (sub->bottom + 1) * ZOOM_BASE));
892 if (clip_left + clip_right >= sprite->width) return;
893 if (clip_top + clip_bottom >= sprite->height) return;
895 bp.skip_left = UnScaleByZoomLower(clip_left, zoom);
896 bp.skip_top = UnScaleByZoomLower(clip_top, zoom);
897 bp.width = UnScaleByZoom(sprite->width - clip_left - clip_right, zoom);
898 bp.height = UnScaleByZoom(sprite->height - clip_top - clip_bottom, zoom);
900 x += ScaleByZoom(bp.skip_left, zoom);
901 y += ScaleByZoom(bp.skip_top, zoom);
904 /* Copy the main data directly from the sprite */
905 bp.sprite = sprite->data;
906 bp.sprite_width = sprite->width;
907 bp.sprite_height = sprite->height;
908 bp.top = 0;
909 bp.left = 0;
911 bp.dst = dpi->dst_ptr;
912 bp.pitch = dpi->pitch;
913 bp.remap = _colour_remap_ptr;
915 assert(sprite->width > 0);
916 assert(sprite->height > 0);
918 if (bp.width <= 0) return;
919 if (bp.height <= 0) return;
921 y -= SCALED_XY ? ScaleByZoom(dpi->top, zoom) : dpi->top;
922 int y_unscaled = UnScaleByZoom(y, zoom);
923 /* Check for top overflow */
924 if (y < 0) {
925 bp.height -= -y_unscaled;
926 if (bp.height <= 0) return;
927 bp.skip_top += -y_unscaled;
928 y = 0;
929 } else {
930 bp.top = y_unscaled;
933 /* Check for bottom overflow */
934 y += SCALED_XY ? ScaleByZoom(bp.height - dpi->height, zoom) : ScaleByZoom(bp.height, zoom) - dpi->height;
935 if (y > 0) {
936 bp.height -= UnScaleByZoom(y, zoom);
937 if (bp.height <= 0) return;
940 x -= SCALED_XY ? ScaleByZoom(dpi->left, zoom) : dpi->left;
941 int x_unscaled = UnScaleByZoom(x, zoom);
942 /* Check for left overflow */
943 if (x < 0) {
944 bp.width -= -x_unscaled;
945 if (bp.width <= 0) return;
946 bp.skip_left += -x_unscaled;
947 x = 0;
948 } else {
949 bp.left = x_unscaled;
952 /* Check for right overflow */
953 x += SCALED_XY ? ScaleByZoom(bp.width - dpi->width, zoom) : ScaleByZoom(bp.width, zoom) - dpi->width;
954 if (x > 0) {
955 bp.width -= UnScaleByZoom(x, zoom);
956 if (bp.width <= 0) return;
959 assert(bp.skip_left + bp.width <= UnScaleByZoom(sprite->width, zoom));
960 assert(bp.skip_top + bp.height <= UnScaleByZoom(sprite->height, zoom));
962 /* We do not want to catch the mouse. However we also use that spritenumber for unknown (text) sprites. */
963 if (_newgrf_debug_sprite_picker.mode == SPM_REDRAW && sprite_id != SPR_CURSOR_MOUSE) {
964 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
965 void *topleft = blitter->MoveTo(bp.dst, bp.left, bp.top);
966 void *bottomright = blitter->MoveTo(topleft, bp.width - 1, bp.height - 1);
968 void *clicked = _newgrf_debug_sprite_picker.clicked_pixel;
970 if (topleft <= clicked && clicked <= bottomright) {
971 uint offset = (((size_t)clicked - (size_t)topleft) / (blitter->GetScreenDepth() / 8)) % bp.pitch;
972 if (offset < (uint)bp.width) {
973 _newgrf_debug_sprite_picker.sprites.Include(sprite_id);
978 BlitterFactory::GetCurrentBlitter()->Draw(&bp, mode, zoom);
981 static void GfxMainBlitterViewport(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub, SpriteID sprite_id)
983 GfxBlitter<ZOOM_LVL_BASE, false>(sprite, x, y, mode, sub, sprite_id, _cur_dpi->zoom);
986 static void GfxMainBlitter(const Sprite *sprite, int x, int y, BlitterMode mode, const SubSprite *sub, SpriteID sprite_id, ZoomLevel zoom)
988 GfxBlitter<1, true>(sprite, x, y, mode, sub, sprite_id, zoom);
991 void DoPaletteAnimations();
993 void GfxInitPalettes()
995 memcpy(&_cur_palette, &_palette, sizeof(_cur_palette));
996 DoPaletteAnimations();
999 #define EXTR(p, q) (((uint16)(palette_animation_counter * (p)) * (q)) >> 16)
1000 #define EXTR2(p, q) (((uint16)(~palette_animation_counter * (p)) * (q)) >> 16)
1002 void DoPaletteAnimations()
1004 /* Animation counter for the palette animation. */
1005 static int palette_animation_counter = 0;
1006 palette_animation_counter += 8;
1008 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
1009 const Colour *s;
1010 const ExtraPaletteValues *ev = &_extra_palette_values;
1011 Colour old_val[PALETTE_ANIM_SIZE];
1012 const uint old_tc = palette_animation_counter;
1013 uint i;
1014 uint j;
1016 if (blitter != NULL && blitter->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_NONE) {
1017 palette_animation_counter = 0;
1020 Colour *palette_pos = &_cur_palette.palette[PALETTE_ANIM_START]; // Points to where animations are taking place on the palette
1021 /* Makes a copy of the current animation palette in old_val,
1022 * so the work on the current palette could be compared, see if there has been any changes */
1023 memcpy(old_val, palette_pos, sizeof(old_val));
1025 /* Fizzy Drink bubbles animation */
1026 s = ev->fizzy_drink;
1027 j = EXTR2(512, EPV_CYCLES_FIZZY_DRINK);
1028 for (i = 0; i != EPV_CYCLES_FIZZY_DRINK; i++) {
1029 *palette_pos++ = s[j];
1030 j++;
1031 if (j == EPV_CYCLES_FIZZY_DRINK) j = 0;
1034 /* Oil refinery fire animation */
1035 s = ev->oil_refinery;
1036 j = EXTR2(512, EPV_CYCLES_OIL_REFINERY);
1037 for (i = 0; i != EPV_CYCLES_OIL_REFINERY; i++) {
1038 *palette_pos++ = s[j];
1039 j++;
1040 if (j == EPV_CYCLES_OIL_REFINERY) j = 0;
1043 /* Radio tower blinking */
1045 byte i = (palette_animation_counter >> 1) & 0x7F;
1046 byte v;
1048 if (i < 0x3f) {
1049 v = 255;
1050 } else if (i < 0x4A || i >= 0x75) {
1051 v = 128;
1052 } else {
1053 v = 20;
1055 palette_pos->r = v;
1056 palette_pos->g = 0;
1057 palette_pos->b = 0;
1058 palette_pos++;
1060 i ^= 0x40;
1061 if (i < 0x3f) {
1062 v = 255;
1063 } else if (i < 0x4A || i >= 0x75) {
1064 v = 128;
1065 } else {
1066 v = 20;
1068 palette_pos->r = v;
1069 palette_pos->g = 0;
1070 palette_pos->b = 0;
1071 palette_pos++;
1074 /* Handle lighthouse and stadium animation */
1075 s = ev->lighthouse;
1076 j = EXTR(256, EPV_CYCLES_LIGHTHOUSE);
1077 for (i = 0; i != EPV_CYCLES_LIGHTHOUSE; i++) {
1078 *palette_pos++ = s[j];
1079 j++;
1080 if (j == EPV_CYCLES_LIGHTHOUSE) j = 0;
1083 /* Dark blue water */
1084 s = (_settings_game.game_creation.landscape == LT_TOYLAND) ? ev->dark_water_toyland : ev->dark_water;
1085 j = EXTR(320, EPV_CYCLES_DARK_WATER);
1086 for (i = 0; i != EPV_CYCLES_DARK_WATER; i++) {
1087 *palette_pos++ = s[j];
1088 j++;
1089 if (j == EPV_CYCLES_DARK_WATER) j = 0;
1092 /* Glittery water */
1093 s = (_settings_game.game_creation.landscape == LT_TOYLAND) ? ev->glitter_water_toyland : ev->glitter_water;
1094 j = EXTR(128, EPV_CYCLES_GLITTER_WATER);
1095 for (i = 0; i != EPV_CYCLES_GLITTER_WATER / 3; i++) {
1096 *palette_pos++ = s[j];
1097 j += 3;
1098 if (j >= EPV_CYCLES_GLITTER_WATER) j -= EPV_CYCLES_GLITTER_WATER;
1101 if (blitter != NULL && blitter->UsePaletteAnimation() == Blitter::PALETTE_ANIMATION_NONE) {
1102 palette_animation_counter = old_tc;
1103 } else {
1104 if (memcmp(old_val, &_cur_palette.palette[PALETTE_ANIM_START], sizeof(old_val)) != 0 && _cur_palette.count_dirty == 0) {
1105 /* Did we changed anything on the palette? Seems so. Mark it as dirty */
1106 _cur_palette.first_dirty = PALETTE_ANIM_START;
1107 _cur_palette.count_dirty = PALETTE_ANIM_SIZE;
1113 * Determine a contrasty text colour for a coloured background.
1114 * @param background Background colour.
1115 * @param threshold Background colour brightness threshold below which the background is considered dark and TC_WHITE is returned, range: 0 - 255, default 128.
1116 * @return TC_BLACK or TC_WHITE depending on what gives a better contrast.
1118 TextColour GetContrastColour(uint8 background, uint8 threshold)
1120 Colour c = _cur_palette.palette[background];
1121 /* Compute brightness according to http://www.w3.org/TR/AERT#color-contrast.
1122 * The following formula computes 1000 * brightness^2, with brightness being in range 0 to 255. */
1123 uint sq1000_brightness = c.r * c.r * 299 + c.g * c.g * 587 + c.b * c.b * 114;
1124 /* Compare with threshold brightness which defaults to 128 (50%) */
1125 return sq1000_brightness < ((uint) threshold) * ((uint) threshold) * 1000 ? TC_WHITE : TC_BLACK;
1129 * Initialize _stringwidth_table cache
1130 * @param monospace Whether to load the monospace cache or the normal fonts.
1132 void LoadStringWidthTable(bool monospace)
1134 ClearFontCache();
1136 for (FontSize fs = monospace ? FS_MONO : FS_BEGIN; fs < (monospace ? FS_END : FS_MONO); fs++) {
1137 for (uint i = 0; i != 224; i++) {
1138 _stringwidth_table[fs][i] = GetGlyphWidth(fs, i + 32);
1142 ReInitAllWindows();
1146 * Return width of character glyph.
1147 * @param size Font of the character
1148 * @param key Character code glyph
1149 * @return Width of the character glyph
1151 byte GetCharacterWidth(FontSize size, WChar key)
1153 /* Use _stringwidth_table cache if possible */
1154 if (key >= 32 && key < 256) return _stringwidth_table[size][key - 32];
1156 return GetGlyphWidth(size, key);
1160 * Return the maximum width of single digit.
1161 * @param size Font of the digit
1162 * @return Width of the digit.
1164 byte GetDigitWidth(FontSize size)
1166 byte width = 0;
1167 for (char c = '0'; c <= '9'; c++) {
1168 width = max(GetCharacterWidth(size, c), width);
1170 return width;
1174 * Determine the broadest digits for guessing the maximum width of a n-digit number.
1175 * @param[out] front Broadest digit, which is not 0. (Use this digit as first digit for numbers with more than one digit.)
1176 * @param[out] next Broadest digit, including 0. (Use this digit for all digits, except the first one; or for numbers with only one digit.)
1177 * @param size Font of the digit
1179 void GetBroadestDigit(uint *front, uint *next, FontSize size)
1181 int width = -1;
1182 for (char c = '9'; c >= '0'; c--) {
1183 int w = GetCharacterWidth(size, c);
1184 if (w > width) {
1185 width = w;
1186 *next = c - '0';
1187 if (c != '0') *front = c - '0';
1192 void ScreenSizeChanged()
1194 _dirty_bytes_per_line = CeilDiv(_screen.width, DIRTY_BLOCK_WIDTH);
1195 _dirty_blocks = ReallocT<byte>(_dirty_blocks, _dirty_bytes_per_line * CeilDiv(_screen.height, DIRTY_BLOCK_HEIGHT));
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 == NULL) 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 /* Don't draw the mouse cursor if the screen is not ready */
1221 if (_screen.dst_ptr == NULL) return;
1223 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
1225 /* Redraw mouse cursor but only when it's inside the window */
1226 if (!_cursor.in_window) return;
1228 /* Don't draw the mouse cursor if it's already drawn */
1229 if (_cursor.visible) {
1230 if (!_cursor.dirty) return;
1231 UndrawMouseCursor();
1234 /* Determine visible area */
1235 int left = _cursor.pos.x + _cursor.total_offs.x;
1236 int width = _cursor.total_size.x;
1237 if (left < 0) {
1238 width += left;
1239 left = 0;
1241 if (left + width > _screen.width) {
1242 width = _screen.width - left;
1244 if (width <= 0) return;
1246 int top = _cursor.pos.y + _cursor.total_offs.y;
1247 int height = _cursor.total_size.y;
1248 if (top < 0) {
1249 height += top;
1250 top = 0;
1252 if (top + height > _screen.height) {
1253 height = _screen.height - top;
1255 if (height <= 0) return;
1257 _cursor.draw_pos.x = left;
1258 _cursor.draw_pos.y = top;
1259 _cursor.draw_size.x = width;
1260 _cursor.draw_size.y = height;
1262 uint8 *buffer = _cursor_backup.Allocate(blitter->BufferSize(_cursor.draw_size.x, _cursor.draw_size.y));
1264 /* Make backup of stuff below cursor */
1265 blitter->CopyToBuffer(blitter->MoveTo(_screen.dst_ptr, _cursor.draw_pos.x, _cursor.draw_pos.y), buffer, _cursor.draw_size.x, _cursor.draw_size.y);
1267 /* Draw cursor on screen */
1268 _cur_dpi = &_screen;
1269 for (uint i = 0; i < _cursor.sprite_count; ++i) {
1270 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);
1273 VideoDriver::GetInstance()->MakeDirty(_cursor.draw_pos.x, _cursor.draw_pos.y, _cursor.draw_size.x, _cursor.draw_size.y);
1275 _cursor.visible = true;
1276 _cursor.dirty = false;
1279 void RedrawScreenRect(int left, int top, int right, int bottom)
1281 assert(right <= _screen.width && bottom <= _screen.height);
1282 if (_cursor.visible) {
1283 if (right > _cursor.draw_pos.x &&
1284 left < _cursor.draw_pos.x + _cursor.draw_size.x &&
1285 bottom > _cursor.draw_pos.y &&
1286 top < _cursor.draw_pos.y + _cursor.draw_size.y) {
1287 UndrawMouseCursor();
1291 #ifdef ENABLE_NETWORK
1292 if (_networking) NetworkUndrawChatMessage();
1293 #endif /* ENABLE_NETWORK */
1295 DrawOverlappedWindowForAll(left, top, right, bottom);
1297 VideoDriver::GetInstance()->MakeDirty(left, top, right - left, bottom - top);
1301 * Repaints the rectangle blocks which are marked as 'dirty'.
1303 * @see SetDirtyBlocks
1305 void DrawDirtyBlocks()
1307 byte *b = _dirty_blocks;
1308 const int w = Align(_screen.width, DIRTY_BLOCK_WIDTH);
1309 const int h = Align(_screen.height, DIRTY_BLOCK_HEIGHT);
1310 int x;
1311 int y;
1313 if (HasModalProgress()) {
1314 /* We are generating the world, so release our rights to the map and
1315 * painting while we are waiting a bit. */
1316 _modal_progress_paint_mutex->EndCritical();
1317 _modal_progress_work_mutex->EndCritical();
1319 /* Wait a while and update _realtime_tick so we are given the rights */
1320 if (!IsFirstModalProgressLoop()) CSleep(MODAL_PROGRESS_REDRAW_TIMEOUT);
1321 _realtime_tick += MODAL_PROGRESS_REDRAW_TIMEOUT;
1323 /* Modal progress thread may need blitter access while we are waiting for it. */
1324 VideoDriver::GetInstance()->ReleaseBlitterLock();
1325 _modal_progress_paint_mutex->BeginCritical();
1326 VideoDriver::GetInstance()->AcquireBlitterLock();
1327 _modal_progress_work_mutex->BeginCritical();
1329 /* When we ended with the modal progress, do not draw the blocks.
1330 * Simply let the next run do so, otherwise we would be loading
1331 * the new state (and possibly change the blitter) when we hold
1332 * the drawing lock, which we must not do. */
1333 if (_switch_mode != SM_NONE && !HasModalProgress()) return;
1336 y = 0;
1337 do {
1338 x = 0;
1339 do {
1340 if (*b != 0) {
1341 int left;
1342 int top;
1343 int right = x + DIRTY_BLOCK_WIDTH;
1344 int bottom = y;
1345 byte *p = b;
1346 int h2;
1348 /* First try coalescing downwards */
1349 do {
1350 *p = 0;
1351 p += _dirty_bytes_per_line;
1352 bottom += DIRTY_BLOCK_HEIGHT;
1353 } while (bottom != h && *p != 0);
1355 /* Try coalescing to the right too. */
1356 h2 = (bottom - y) / DIRTY_BLOCK_HEIGHT;
1357 assert(h2 > 0);
1358 p = b;
1360 while (right != w) {
1361 byte *p2 = ++p;
1362 int h = h2;
1363 /* Check if a full line of dirty flags is set. */
1364 do {
1365 if (!*p2) goto no_more_coalesc;
1366 p2 += _dirty_bytes_per_line;
1367 } while (--h != 0);
1369 /* Wohoo, can combine it one step to the right!
1370 * Do that, and clear the bits. */
1371 right += DIRTY_BLOCK_WIDTH;
1373 h = h2;
1374 p2 = p;
1375 do {
1376 *p2 = 0;
1377 p2 += _dirty_bytes_per_line;
1378 } while (--h != 0);
1380 no_more_coalesc:
1382 left = x;
1383 top = y;
1385 if (left < _invalid_rect.left ) left = _invalid_rect.left;
1386 if (top < _invalid_rect.top ) top = _invalid_rect.top;
1387 if (right > _invalid_rect.right ) right = _invalid_rect.right;
1388 if (bottom > _invalid_rect.bottom) bottom = _invalid_rect.bottom;
1390 if (left < right && top < bottom) {
1391 RedrawScreenRect(left, top, right, bottom);
1395 } while (b++, (x += DIRTY_BLOCK_WIDTH) != w);
1396 } while (b += -(int)(w / DIRTY_BLOCK_WIDTH) + _dirty_bytes_per_line, (y += DIRTY_BLOCK_HEIGHT) != h);
1398 ++_dirty_block_colour;
1399 _invalid_rect.left = w;
1400 _invalid_rect.top = h;
1401 _invalid_rect.right = 0;
1402 _invalid_rect.bottom = 0;
1406 * This function extends the internal _invalid_rect rectangle as it
1407 * now contains the rectangle defined by the given parameters. Note
1408 * the point (0,0) is top left.
1410 * @param left The left edge of the rectangle
1411 * @param top The top edge of the rectangle
1412 * @param right The right edge of the rectangle
1413 * @param bottom The bottom edge of the rectangle
1414 * @see DrawDirtyBlocks
1416 * @todo The name of the function should be called like @c AddDirtyBlock as
1417 * it neither set a dirty rect nor add several dirty rects although
1418 * the function name is in plural. (Progman)
1420 void SetDirtyBlocks(int left, int top, int right, int bottom)
1422 byte *b;
1423 int width;
1424 int height;
1426 if (left < 0) left = 0;
1427 if (top < 0) top = 0;
1428 if (right > _screen.width) right = _screen.width;
1429 if (bottom > _screen.height) bottom = _screen.height;
1431 if (left >= right || top >= bottom) return;
1433 if (left < _invalid_rect.left ) _invalid_rect.left = left;
1434 if (top < _invalid_rect.top ) _invalid_rect.top = top;
1435 if (right > _invalid_rect.right ) _invalid_rect.right = right;
1436 if (bottom > _invalid_rect.bottom) _invalid_rect.bottom = bottom;
1438 left /= DIRTY_BLOCK_WIDTH;
1439 top /= DIRTY_BLOCK_HEIGHT;
1441 b = _dirty_blocks + top * _dirty_bytes_per_line + left;
1443 width = ((right - 1) / DIRTY_BLOCK_WIDTH) - left + 1;
1444 height = ((bottom - 1) / DIRTY_BLOCK_HEIGHT) - top + 1;
1446 assert(width > 0 && height > 0);
1448 do {
1449 int i = width;
1451 do b[--i] = 0xFF; while (i != 0);
1453 b += _dirty_bytes_per_line;
1454 } while (--height != 0);
1458 * This function mark the whole screen as dirty. This results in repainting
1459 * the whole screen. Use this with care as this function will break the
1460 * idea about marking only parts of the screen as 'dirty'.
1461 * @ingroup dirty
1463 void MarkWholeScreenDirty()
1465 SetDirtyBlocks(0, 0, _screen.width, _screen.height);
1469 * Set up a clipping area for only drawing into a certain area. To do this,
1470 * Fill a DrawPixelInfo object with the supplied relative rectangle, backup
1471 * the original (calling) _cur_dpi and assign the just returned DrawPixelInfo
1472 * _cur_dpi. When you are done, give restore _cur_dpi's original value
1473 * @param *n the DrawPixelInfo that will be the clipping rectangle box allowed
1474 * for drawing
1475 * @param left,top,width,height the relative coordinates of the clipping
1476 * rectangle relative to the current _cur_dpi. This will most likely be the
1477 * offset from the calling window coordinates
1478 * @return return false if the requested rectangle is not possible with the
1479 * current dpi pointer. Only continue of the return value is true, or you'll
1480 * get some nasty results
1482 bool FillDrawPixelInfo(DrawPixelInfo *n, int left, int top, int width, int height)
1484 Blitter *blitter = BlitterFactory::GetCurrentBlitter();
1485 const DrawPixelInfo *o = _cur_dpi;
1487 n->zoom = ZOOM_LVL_NORMAL;
1489 assert(width > 0);
1490 assert(height > 0);
1492 if ((left -= o->left) < 0) {
1493 width += left;
1494 if (width <= 0) return false;
1495 n->left = -left;
1496 left = 0;
1497 } else {
1498 n->left = 0;
1501 if (width > o->width - left) {
1502 width = o->width - left;
1503 if (width <= 0) return false;
1505 n->width = width;
1507 if ((top -= o->top) < 0) {
1508 height += top;
1509 if (height <= 0) return false;
1510 n->top = -top;
1511 top = 0;
1512 } else {
1513 n->top = 0;
1516 n->dst_ptr = blitter->MoveTo(o->dst_ptr, left, top);
1517 n->pitch = o->pitch;
1519 if (height > o->height - top) {
1520 height = o->height - top;
1521 if (height <= 0) return false;
1523 n->height = height;
1525 return true;
1529 * Update cursor dimension.
1530 * Called when changing cursor sprite resp. reloading grfs.
1532 void UpdateCursorSize()
1534 /* Ignore setting any cursor before the sprites are loaded. */
1535 if (GetMaxSpriteID() == 0) return;
1537 assert_compile(lengthof(_cursor.sprite_seq) == lengthof(_cursor.sprite_pos));
1538 assert(_cursor.sprite_count <= lengthof(_cursor.sprite_seq));
1539 for (uint i = 0; i < _cursor.sprite_count; ++i) {
1540 const Sprite *p = GetSprite(GB(_cursor.sprite_seq[i].sprite, 0, SPRITE_WIDTH), ST_NORMAL);
1541 Point offs, size;
1542 offs.x = UnScaleGUI(p->x_offs) + _cursor.sprite_pos[i].x;
1543 offs.y = UnScaleGUI(p->y_offs) + _cursor.sprite_pos[i].y;
1544 size.x = UnScaleGUI(p->width);
1545 size.y = UnScaleGUI(p->height);
1547 if (i == 0) {
1548 _cursor.total_offs = offs;
1549 _cursor.total_size = size;
1550 } else {
1551 int right = max(_cursor.total_offs.x + _cursor.total_size.x, offs.x + size.x);
1552 int bottom = max(_cursor.total_offs.y + _cursor.total_size.y, offs.y + size.y);
1553 if (offs.x < _cursor.total_offs.x) _cursor.total_offs.x = offs.x;
1554 if (offs.y < _cursor.total_offs.y) _cursor.total_offs.y = offs.y;
1555 _cursor.total_size.x = right - _cursor.total_offs.x;
1556 _cursor.total_size.y = bottom - _cursor.total_offs.y;
1560 _cursor.dirty = true;
1564 * Switch cursor to different sprite.
1565 * @param cursor Sprite to draw for the cursor.
1566 * @param pal Palette to use for recolouring.
1568 static void SetCursorSprite(CursorID cursor, PaletteID pal)
1570 if (_cursor.sprite_count == 1 && _cursor.sprite_seq[0].sprite == cursor && _cursor.sprite_seq[0].pal == pal) return;
1572 _cursor.sprite_count = 1;
1573 _cursor.sprite_seq[0].sprite = cursor;
1574 _cursor.sprite_seq[0].pal = pal;
1575 _cursor.sprite_pos[0].x = 0;
1576 _cursor.sprite_pos[0].y = 0;
1578 UpdateCursorSize();
1581 static void SwitchAnimatedCursor()
1583 const AnimCursor *cur = _cursor.animate_cur;
1585 if (cur == NULL || cur->sprite == AnimCursor::LAST) cur = _cursor.animate_list;
1587 SetCursorSprite(cur->sprite, _cursor.sprite_seq[0].pal);
1589 _cursor.animate_timeout = cur->display_time;
1590 _cursor.animate_cur = cur + 1;
1593 void CursorTick()
1595 if (_cursor.animate_timeout != 0 && --_cursor.animate_timeout == 0) {
1596 SwitchAnimatedCursor();
1601 * Set or unset the ZZZ cursor.
1602 * @param busy Whether to show the ZZZ cursor.
1604 void SetMouseCursorBusy(bool busy)
1606 if (busy) {
1607 if (_cursor.sprite_seq[0].sprite == SPR_CURSOR_MOUSE) SetMouseCursor(SPR_CURSOR_ZZZ, PAL_NONE);
1608 } else {
1609 if (_cursor.sprite_seq[0].sprite == SPR_CURSOR_ZZZ) SetMouseCursor(SPR_CURSOR_MOUSE, PAL_NONE);
1614 * Assign a single non-animated sprite to the cursor.
1615 * @param sprite Sprite to draw for the cursor.
1616 * @param pal Palette to use for recolouring.
1617 * @see SetAnimatedMouseCursor
1619 void SetMouseCursor(CursorID sprite, PaletteID pal)
1621 /* Turn off animation */
1622 _cursor.animate_timeout = 0;
1623 /* Set cursor */
1624 SetCursorSprite(sprite, pal);
1628 * Assign an animation to the cursor.
1629 * @param table Array of animation states.
1630 * @see SetMouseCursor
1632 void SetAnimatedMouseCursor(const AnimCursor *table)
1634 _cursor.animate_list = table;
1635 _cursor.animate_cur = NULL;
1636 _cursor.sprite_seq[0].pal = PAL_NONE;
1637 SwitchAnimatedCursor();
1641 * Update cursor position on mouse movement.
1642 * @param x New X position.
1643 * @param y New Y position.
1644 * @param queued_warp True, if the OS queues mouse warps after pending mouse movement events.
1645 * False, if the warp applies instantaneous.
1646 * @return true, if the OS cursor position should be warped back to this->pos.
1648 bool CursorVars::UpdateCursorPosition(int x, int y, bool queued_warp)
1650 /* Detecting relative mouse movement is somewhat tricky.
1651 * - There may be multiple mouse move events in the video driver queue (esp. when OpenTTD lags a bit).
1652 * - When we request warping the mouse position (return true), a mouse move event is appended at the end of the queue.
1654 * So, when this->fix_at is active, we use the following strategy:
1655 * - The first movement triggers the warp to reset the mouse position.
1656 * - Subsequent events have to compute movement relative to the previous event.
1657 * - The relative movement is finished, when we receive the event matching the warp.
1660 if (x == this->pos.x && y == this->pos.y) {
1661 /* Warp finished. */
1662 this->queued_warp = false;
1665 this->delta.x = x - (this->queued_warp ? this->last_position.x : this->pos.x);
1666 this->delta.y = y - (this->queued_warp ? this->last_position.y : this->pos.y);
1668 this->last_position.x = x;
1669 this->last_position.y = y;
1671 bool need_warp = false;
1672 if (this->fix_at) {
1673 if (this->delta.x != 0 || this->delta.y != 0) {
1674 /* Trigger warp.
1675 * Note: We also trigger warping again, if there is already a pending warp.
1676 * This makes it more tolerant about the OS or other software inbetween
1677 * botchering the warp. */
1678 this->queued_warp = queued_warp;
1679 need_warp = true;
1681 } else if (this->pos.x != x || this->pos.y != y) {
1682 this->queued_warp = false; // Cancel warping, we are no longer confining the position.
1683 this->dirty = true;
1684 this->pos.x = x;
1685 this->pos.y = y;
1687 return need_warp;
1690 bool ChangeResInGame(int width, int height)
1692 return (_screen.width == width && _screen.height == height) || VideoDriver::GetInstance()->ChangeResolution(width, height);
1695 bool ToggleFullScreen(bool fs)
1697 bool result = VideoDriver::GetInstance()->ToggleFullscreen(fs);
1698 if (_fullscreen != fs && _num_resolutions == 0) {
1699 DEBUG(driver, 0, "Could not find a suitable fullscreen resolution");
1701 return result;
1704 static int CDECL compare_res(const Dimension *pa, const Dimension *pb)
1706 int x = pa->width - pb->width;
1707 if (x != 0) return x;
1708 return pa->height - pb->height;
1711 void SortResolutions(int count)
1713 QSortT(_resolutions, count, &compare_res);